CSIT-1104: Trending: Speed-up plots generation
[csit.git] / resources / tools / presentation / generator_plots.py
1 # Copyright (c) 2018 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Algorithms to generate plots.
15 """
16
17
18 import logging
19 import pandas as pd
20 import plotly.offline as ploff
21 import plotly.graph_objs as plgo
22
23 from plotly.exceptions import PlotlyError
24
25 from utils import mean
26
27
28 def generate_plots(spec, data):
29     """Generate all plots specified in the specification file.
30
31     :param spec: Specification read from the specification file.
32     :param data: Data to process.
33     :type spec: Specification
34     :type data: InputData
35     """
36
37     logging.info("Generating the plots ...")
38     for index, plot in enumerate(spec.plots):
39         try:
40             logging.info("  Plot nr {0}:".format(index + 1))
41             eval(plot["algorithm"])(plot, data)
42         except NameError:
43             logging.error("The algorithm '{0}' is not defined.".
44                           format(plot["algorithm"]))
45     logging.info("Done.")
46
47
48 def plot_performance_box(plot, input_data):
49     """Generate the plot(s) with algorithm: plot_performance_box
50     specified in the specification file.
51
52     :param plot: Plot to generate.
53     :param input_data: Data to process.
54     :type plot: pandas.Series
55     :type input_data: InputData
56     """
57
58     logging.info("  Generating the plot {0} ...".
59                  format(plot.get("title", "")))
60
61     # Transform the data
62     logging.info("    Creating the data set for the {0} '{1}'.".
63                  format(plot.get("type", ""), plot.get("title", "")))
64     data = input_data.filter_data(plot)
65     if data is None:
66         logging.error("No data.")
67         return
68
69     # Prepare the data for the plot
70     y_vals = dict()
71     for job in data:
72         for build in job:
73             for test in build:
74                 if y_vals.get(test["parent"], None) is None:
75                     y_vals[test["parent"]] = list()
76                 try:
77                     y_vals[test["parent"]].append(test["throughput"]["value"])
78                 except (KeyError, TypeError):
79                     y_vals[test["parent"]].append(None)
80
81     # Add None to the lists with missing data
82     max_len = 0
83     for val in y_vals.values():
84         if len(val) > max_len:
85             max_len = len(val)
86     for key, val in y_vals.items():
87         if len(val) < max_len:
88             val.extend([None for _ in range(max_len - len(val))])
89
90     # Add plot traces
91     traces = list()
92     df = pd.DataFrame(y_vals)
93     df.head()
94     for i, col in enumerate(df.columns):
95         name = "{0}. {1}".format(i + 1, col.lower().replace('-ndrpdrdisc', ''))
96         traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
97                                y=df[col],
98                                name=name,
99                                **plot["traces"]))
100
101     try:
102         # Create plot
103         plpl = plgo.Figure(data=traces, layout=plot["layout"])
104
105         # Export Plot
106         logging.info("    Writing file '{0}{1}'.".
107                      format(plot["output-file"], plot["output-file-type"]))
108         ploff.plot(plpl,
109                    show_link=False, auto_open=False,
110                    filename='{0}{1}'.format(plot["output-file"],
111                                             plot["output-file-type"]))
112     except PlotlyError as err:
113         logging.error("   Finished with error: {}".
114                       format(str(err).replace("\n", " ")))
115         return
116
117     logging.info("  Done.")
118
119
120 def plot_latency_box(plot, input_data):
121     """Generate the plot(s) with algorithm: plot_latency_box
122     specified in the specification file.
123
124     :param plot: Plot to generate.
125     :param input_data: Data to process.
126     :type plot: pandas.Series
127     :type input_data: InputData
128     """
129
130     logging.info("  Generating the plot {0} ...".
131                  format(plot.get("title", "")))
132
133     # Transform the data
134     logging.info("    Creating the data set for the {0} '{1}'.".
135                  format(plot.get("type", ""), plot.get("title", "")))
136     data = input_data.filter_data(plot)
137     if data is None:
138         logging.error("No data.")
139         return
140
141     # Prepare the data for the plot
142     y_tmp_vals = dict()
143     for job in data:
144         for build in job:
145             for test in build:
146                 if y_tmp_vals.get(test["parent"], None) is None:
147                     y_tmp_vals[test["parent"]] = [
148                         list(),  # direction1, min
149                         list(),  # direction1, avg
150                         list(),  # direction1, max
151                         list(),  # direction2, min
152                         list(),  # direction2, avg
153                         list()   # direction2, max
154                     ]
155                 try:
156                     y_tmp_vals[test["parent"]][0].append(
157                         test["latency"]["direction1"]["50"]["min"])
158                     y_tmp_vals[test["parent"]][1].append(
159                         test["latency"]["direction1"]["50"]["avg"])
160                     y_tmp_vals[test["parent"]][2].append(
161                         test["latency"]["direction1"]["50"]["max"])
162                     y_tmp_vals[test["parent"]][3].append(
163                         test["latency"]["direction2"]["50"]["min"])
164                     y_tmp_vals[test["parent"]][4].append(
165                         test["latency"]["direction2"]["50"]["avg"])
166                     y_tmp_vals[test["parent"]][5].append(
167                         test["latency"]["direction2"]["50"]["max"])
168                 except (KeyError, TypeError):
169                     pass
170
171     y_vals = dict()
172     for key, values in y_tmp_vals.items():
173         y_vals[key] = list()
174         for val in values:
175             if val:
176                 average = mean(val)
177             else:
178                 average = None
179             y_vals[key].append(average)
180             y_vals[key].append(average)  # Twice for plot.ly
181
182     # Add plot traces
183     traces = list()
184     try:
185         df = pd.DataFrame(y_vals)
186         df.head()
187     except ValueError as err:
188         logging.error("   Finished with error: {}".
189                       format(str(err).replace("\n", " ")))
190         return
191
192     for i, col in enumerate(df.columns):
193         name = "{0}. {1}".format(i + 1, col.lower().replace('-ndrpdrdisc', ''))
194         traces.append(plgo.Box(x=['TGint1-to-SUT1-to-SUT2-to-TGint2',
195                                   'TGint1-to-SUT1-to-SUT2-to-TGint2',
196                                   'TGint1-to-SUT1-to-SUT2-to-TGint2',
197                                   'TGint1-to-SUT1-to-SUT2-to-TGint2',
198                                   'TGint1-to-SUT1-to-SUT2-to-TGint2',
199                                   'TGint1-to-SUT1-to-SUT2-to-TGint2',
200                                   'TGint2-to-SUT2-to-SUT1-to-TGint1',
201                                   'TGint2-to-SUT2-to-SUT1-to-TGint1',
202                                   'TGint2-to-SUT2-to-SUT1-to-TGint1',
203                                   'TGint2-to-SUT2-to-SUT1-to-TGint1',
204                                   'TGint2-to-SUT2-to-SUT1-to-TGint1',
205                                   'TGint2-to-SUT2-to-SUT1-to-TGint1'],
206                                y=df[col],
207                                name=name,
208                                **plot["traces"]))
209
210     try:
211         # Create plot
212         logging.info("    Writing file '{0}{1}'.".
213                      format(plot["output-file"], plot["output-file-type"]))
214         plpl = plgo.Figure(data=traces, layout=plot["layout"])
215
216         # Export Plot
217         ploff.plot(plpl,
218                    show_link=False, auto_open=False,
219                    filename='{0}{1}'.format(plot["output-file"],
220                                             plot["output-file-type"]))
221     except PlotlyError as err:
222         logging.error("   Finished with error: {}".
223                       format(str(err).replace("\n", " ")))
224         return
225
226     logging.info("  Done.")
227
228
229 def plot_throughput_speedup_analysis(plot, input_data):
230     """Generate the plot(s) with algorithm: plot_throughput_speedup_analysis
231     specified in the specification file.
232
233     :param plot: Plot to generate.
234     :param input_data: Data to process.
235     :type plot: pandas.Series
236     :type input_data: InputData
237     """
238
239     logging.info("  Generating the plot {0} ...".
240                  format(plot.get("title", "")))
241
242     # Transform the data
243     logging.info("    Creating the data set for the {0} '{1}'.".
244                  format(plot.get("type", ""), plot.get("title", "")))
245     data = input_data.filter_data(plot)
246     if data is None:
247         logging.error("No data.")
248         return
249
250     throughput = dict()
251     for job in data:
252         for build in job:
253             for test in build:
254                 if throughput.get(test["parent"], None) is None:
255                     throughput[test["parent"]] = {"1": list(),
256                                                   "2": list(),
257                                                   "4": list()}
258                 try:
259                     if "1T1C" in test["tags"]:
260                         throughput[test["parent"]]["1"].\
261                             append(test["throughput"]["value"])
262                     elif "2T2C" in test["tags"]:
263                         throughput[test["parent"]]["2"]. \
264                             append(test["throughput"]["value"])
265                     elif "4T4C" in test["tags"]:
266                         throughput[test["parent"]]["4"]. \
267                             append(test["throughput"]["value"])
268                 except (KeyError, TypeError):
269                     pass
270
271     if not throughput:
272         logging.warning("No data for the plot '{}'".
273                         format(plot.get("title", "")))
274         return
275
276     for test_name, test_vals in throughput.items():
277         for key, test_val in test_vals.items():
278             if test_val:
279                 throughput[test_name][key] = sum(test_val) / len(test_val)
280
281     names = ['1 core', '2 cores', '4 cores']
282     x_vals = list()
283     y_vals_1 = list()
284     y_vals_2 = list()
285     y_vals_4 = list()
286
287     for test_name, test_vals in throughput.items():
288         if test_vals["1"]:
289             x_vals.append("-".join(test_name.split('-')[1:-1]))
290             y_vals_1.append(1)
291             if test_vals["2"]:
292                 y_vals_2.append(
293                     round(float(test_vals["2"]) / float(test_vals["1"]), 2))
294             else:
295                 y_vals_2.append(None)
296             if test_vals["4"]:
297                 y_vals_4.append(
298                     round(float(test_vals["4"]) / float(test_vals["1"]), 2))
299             else:
300                 y_vals_4.append(None)
301
302     y_vals = [y_vals_1, y_vals_2, y_vals_4]
303
304     y_vals_zipped = zip(names, y_vals)
305     traces = list()
306     for val in y_vals_zipped:
307         traces.append(plgo.Bar(x=x_vals,
308                                y=val[1],
309                                name=val[0]))
310
311     try:
312         # Create plot
313         logging.info("    Writing file '{0}{1}'.".
314                      format(plot["output-file"], plot["output-file-type"]))
315         plpl = plgo.Figure(data=traces, layout=plot["layout"])
316
317         # Export Plot
318         ploff.plot(plpl,
319                    show_link=False, auto_open=False,
320                    filename='{0}{1}'.format(plot["output-file"],
321                                             plot["output-file-type"]))
322     except PlotlyError as err:
323         logging.error("   Finished with error: {}".
324                       format(str(err).replace("\n", " ")))
325         return
326
327     logging.info("  Done.")
328
329
330 def plot_http_server_performance_box(plot, input_data):
331     """Generate the plot(s) with algorithm: plot_http_server_performance_box
332     specified in the specification file.
333
334     :param plot: Plot to generate.
335     :param input_data: Data to process.
336     :type plot: pandas.Series
337     :type input_data: InputData
338     """
339
340     logging.info("  Generating the plot {0} ...".
341                  format(plot.get("title", "")))
342
343     # Transform the data
344     logging.info("    Creating the data set for the {0} '{1}'.".
345                  format(plot.get("type", ""), plot.get("title", "")))
346     data = input_data.filter_data(plot)
347     if data is None:
348         logging.error("No data.")
349         return
350
351     # Prepare the data for the plot
352     y_vals = dict()
353     for job in data:
354         for build in job:
355             for test in build:
356                 if y_vals.get(test["name"], None) is None:
357                     y_vals[test["name"]] = list()
358                 try:
359                     y_vals[test["name"]].append(test["result"]["value"])
360                 except (KeyError, TypeError):
361                     y_vals[test["name"]].append(None)
362
363     # Add None to the lists with missing data
364     max_len = 0
365     for val in y_vals.values():
366         if len(val) > max_len:
367             max_len = len(val)
368     for key, val in y_vals.items():
369         if len(val) < max_len:
370             val.extend([None for _ in range(max_len - len(val))])
371
372     # Add plot traces
373     traces = list()
374     df = pd.DataFrame(y_vals)
375     df.head()
376     for i, col in enumerate(df.columns):
377         name = "{0}. {1}".format(i + 1, col.lower().replace('-cps', '').
378                                  replace('-rps', ''))
379         traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
380                                y=df[col],
381                                name=name,
382                                **plot["traces"]))
383     try:
384         # Create plot
385         plpl = plgo.Figure(data=traces, layout=plot["layout"])
386
387         # Export Plot
388         logging.info("    Writing file '{0}{1}'.".
389                      format(plot["output-file"], plot["output-file-type"]))
390         ploff.plot(plpl,
391                    show_link=False, auto_open=False,
392                    filename='{0}{1}'.format(plot["output-file"],
393                                             plot["output-file-type"]))
394     except PlotlyError as err:
395         logging.error("   Finished with error: {}".
396                       format(str(err).replace("\n", " ")))
397         return
398
399     logging.info("  Done.")