1#!/usr/bin/env python
2# SPDX-License-Identifier: Apache-2.0
3#
4# Copyright (C) 2017, ARM Limited, Google, and contributors.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18
19from trace import Trace
20
21import trappy
22import numpy as np
23import matplotlib.pyplot as plt
24import pandas as pd
25
26import re
27import argparse
28
29class BinderThroughputAnalysis:
30    """
31    For deserializing and plotting results obtained from binderThroughputTest
32    """
33
34    def __init__(self, index):
35        self.latency = []
36        self.throughput = []
37        self.labels = []
38        self.index = index
39
40    def add_data(self, label, latency, throughput):
41        """
42        Append the latency and throughput measurements of one kernel
43        image to the global dataframe.
44
45        :param label: identifier of the kernel image this data is collected from
46        :type label: string
47
48        :param latency: latency measurements of a series of experiments
49        :type latency: list of floats
50
51        :param throughput: throughput measurements of a series of experiments
52        :type throughput: list of floats
53        """
54        self.latency.append(latency)
55        self.throughput.append(throughput)
56        self.labels.append(label)
57
58    def _dfg_latency_df(self):
59        np_latency = np.array(self.latency)
60        data = np.transpose(np_latency.reshape(
61            len(self.labels), len(self.latency[0])))
62        return pd.DataFrame(data, columns=self.labels, index=self.index)
63
64    def _dfg_throughput_df(self):
65        np_throughput = np.array(self.throughput)
66        data = np.transpose(np_throughput.reshape(
67            len(self.labels),len(self.throughput[0])))
68        return pd.DataFrame(data, columns=self.labels, index=self.index)
69
70    def write_to_file(self, path):
71        """
72        Write the result dataframe of combining multiple kernel images to file.
73        Example dataframe:
74              kernel1_cs_4096  kernel2_cs_4096
75        1           35.7657         35.3081
76        2           37.3145         35.7540
77        3           39.4055         39.0940
78        4           44.4658         40.3857
79        5           55.9990         51.6852
80
81        :param path: the file to write the result dataframe to
82        :type path: string
83        """
84        with open(path, 'w') as f:
85            f.write(self._dfg_latency_df().to_string())
86            f.write('\n\n')
87            f.write(self._dfg_throughput_df().to_string())
88            f.write('\n\n')
89
90    def _plot(self, xlabel, ylabel):
91        plt.xlabel(xlabel)
92        plt.ylabel(ylabel)
93        ax = plt.gca()
94        ax.set_ylim(ymin=0)
95        plt.show()
96
97    def plot_latency(self, xlabel, ylabel):
98        self._dfg_latency_df().plot(rot=0)
99        self._plot(xlabel, ylabel)
100
101    def plot_throughput(self, xlabel, ylabel):
102        self._dfg_throughput_df().plot(rot=0)
103        self._plot(xlabel, ylabel)
104
105def deserialize(stream):
106    """
107    Convert stdout from running binderThroughputTest into a list
108    of [avg_latency, iters] pair.
109    """
110    result = {}
111    lines = stream.split("\n")
112    for l in lines:
113        if "average" in l:
114            latencies = re.findall("\d+\.\d+|\d+", l)
115            result["avg_latency"] = float(latencies[0])*1000
116        if "iterations" in l:
117            result["iters"] = float(l.split()[-1])
118    return result
119
120parser = argparse.ArgumentParser(
121        description="Visualize latency and throughput across"
122    "kernel images given binderThroughputTest output.")
123
124parser.add_argument("--test", "-t", type=str,
125                    choices=["cs", "payload"],
126                    default="cs",
127                    help="cs: vary number of cs_pairs while control payload.\n"
128                    "payload: vary payload size, control number of cs_pairs.")
129
130parser.add_argument("--paths", "-p", type=str, nargs='+',
131                    help="Paths to files to read test output from.")
132
133parser.add_argument("--out_file", "-o", type=str,
134                    help="Out file to save dataframes.")
135
136parser.add_argument("--cs_pairs", type=int, nargs='?',
137                    default=[1,2,3,4,5],
138                    help="Vary client-server pairs as index.")
139
140parser.add_argument("--payloads", type=int, nargs='?',
141                    default=[0, 4096, 4096*2, 4096*4, 4096*8],
142                    help="Vary payloads as index.")
143
144if __name__ == "__main__":
145    args = parser.parse_args()
146
147    if args.test == "cs":
148        analysis = BinderThroughputAnalysis(args.cs_pairs)
149    else:
150        analysis = BinderThroughputAnalysis(args.payloads)
151
152    for path in args.paths:
153        with open(path, 'r') as f:
154            results = f.read().split("\n\n")[:-1]
155            results = list(map(deserialize, results))
156
157            latency = [r["avg_latency"] for r in results]
158            throughput = [r["iters"] for r in results]
159            analysis.add_data(path.split('/')[-1], latency, throughput)
160
161    analysis.write_to_file(args.out_file)
162    analysis.plot_latency(args.test, "latency (microseconds")
163    analysis.plot_throughput(args.test, "iterations/sec")
164