52348fe5d15c39ba16c70fb7c0e3c3235ebca2f0
[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 as err:
43             logging.error("Probably algorithm '{alg}' is not defined: {err}".
44                           format(alg=plot["algorithm"], err=repr(err)))
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     plot_title = plot.get("title", "")
63     logging.info("    Creating the data set for the {0} '{1}'.".
64                  format(plot.get("type", ""), plot_title))
65     data = input_data.filter_data(plot)
66     if data is None:
67         logging.error("No data.")
68         return
69
70     # Prepare the data for the plot
71     y_vals = dict()
72     for job in data:
73         for build in job:
74             for test in build:
75                 if y_vals.get(test["parent"], None) is None:
76                     y_vals[test["parent"]] = list()
77                 try:
78                     # TODO: Remove when definitely no NDRPDRDISC tests are used:
79                     if test["type"] in ("NDR", "PDR"):
80                         y_vals[test["parent"]].\
81                             append(test["throughput"]["value"])
82                     elif test["type"] in ("NDRPDR", ):
83                         if "-pdr" in plot_title.lower():
84                             y_vals[test["parent"]].\
85                                 append(test["throughput"]["PDR"]["LOWER"])
86                         elif "-ndr" in plot_title.lower():
87                             y_vals[test["parent"]]. \
88                                 append(test["throughput"]["NDR"]["LOWER"])
89                         else:
90                             continue
91                     else:
92                         continue
93                 except (KeyError, TypeError):
94                     y_vals[test["parent"]].append(None)
95
96     # Add None to the lists with missing data
97     max_len = 0
98     for val in y_vals.values():
99         if len(val) > max_len:
100             max_len = len(val)
101     for key, val in y_vals.items():
102         if len(val) < max_len:
103             val.extend([None for _ in range(max_len - len(val))])
104
105     # Add plot traces
106     traces = list()
107     df = pd.DataFrame(y_vals)
108     df.head()
109     for i, col in enumerate(df.columns):
110         name = "{0}. {1}".format(i + 1, col.lower().replace('-ndrpdrdisc', '').
111                                  replace('-ndrpdr', ''))
112         traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
113                                y=df[col],
114                                name=name,
115                                **plot["traces"]))
116
117     try:
118         # Create plot
119         plpl = plgo.Figure(data=traces, layout=plot["layout"])
120
121         # Export Plot
122         logging.info("    Writing file '{0}{1}'.".
123                      format(plot["output-file"], plot["output-file-type"]))
124         ploff.plot(plpl,
125                    show_link=False, auto_open=False,
126                    filename='{0}{1}'.format(plot["output-file"],
127                                             plot["output-file-type"]))
128     except PlotlyError as err:
129         logging.error("   Finished with error: {}".
130                       format(str(err).replace("\n", " ")))
131         return
132
133     logging.info("  Done.")
134
135
136 def plot_latency_box(plot, input_data):
137     """Generate the plot(s) with algorithm: plot_latency_box
138     specified in the specification file.
139
140     :param plot: Plot to generate.
141     :param input_data: Data to process.
142     :type plot: pandas.Series
143     :type input_data: InputData
144     """
145
146     logging.info("  Generating the plot {0} ...".
147                  format(plot.get("title", "")))
148
149     # Transform the data
150     plot_title = plot.get("title", "")
151     logging.info("    Creating the data set for the {0} '{1}'.".
152                  format(plot.get("type", ""), plot_title))
153     data = input_data.filter_data(plot)
154     if data is None:
155         logging.error("No data.")
156         return
157
158     # Prepare the data for the plot
159     y_tmp_vals = dict()
160     for job in data:
161         for build in job:
162             for test in build:
163                 if y_tmp_vals.get(test["parent"], None) is None:
164                     y_tmp_vals[test["parent"]] = [
165                         list(),  # direction1, min
166                         list(),  # direction1, avg
167                         list(),  # direction1, max
168                         list(),  # direction2, min
169                         list(),  # direction2, avg
170                         list()   # direction2, max
171                     ]
172                 try:
173                     # TODO: Remove when definitely no NDRPDRDISC tests are used:
174                     if test["type"] in ("NDR", "PDR"):
175                         y_tmp_vals[test["parent"]][0].append(
176                             test["latency"]["direction1"]["50"]["min"])
177                         y_tmp_vals[test["parent"]][1].append(
178                             test["latency"]["direction1"]["50"]["avg"])
179                         y_tmp_vals[test["parent"]][2].append(
180                             test["latency"]["direction1"]["50"]["max"])
181                         y_tmp_vals[test["parent"]][3].append(
182                             test["latency"]["direction2"]["50"]["min"])
183                         y_tmp_vals[test["parent"]][4].append(
184                             test["latency"]["direction2"]["50"]["avg"])
185                         y_tmp_vals[test["parent"]][5].append(
186                             test["latency"]["direction2"]["50"]["max"])
187                     elif test["type"] in ("NDRPDR", ):
188                         if "-pdr" in plot_title.lower():
189                             ttype = "PDR"
190                         elif "-ndr" in plot_title.lower():
191                             ttype = "NDR"
192                         else:
193                             continue
194                         y_tmp_vals[test["parent"]][0].append(
195                             test["latency"][ttype]["direction1"]["min"])
196                         y_tmp_vals[test["parent"]][1].append(
197                             test["latency"][ttype]["direction1"]["avg"])
198                         y_tmp_vals[test["parent"]][2].append(
199                             test["latency"][ttype]["direction1"]["max"])
200                         y_tmp_vals[test["parent"]][3].append(
201                             test["latency"][ttype]["direction2"]["min"])
202                         y_tmp_vals[test["parent"]][4].append(
203                             test["latency"][ttype]["direction2"]["avg"])
204                         y_tmp_vals[test["parent"]][5].append(
205                             test["latency"][ttype]["direction2"]["max"])
206                     else:
207                         continue
208                 except (KeyError, TypeError):
209                     pass
210
211     y_vals = dict()
212     for key, values in y_tmp_vals.items():
213         y_vals[key] = list()
214         for val in values:
215             if val:
216                 average = mean(val)
217             else:
218                 average = None
219             y_vals[key].append(average)
220             y_vals[key].append(average)  # Twice for plot.ly
221
222     # Add plot traces
223     traces = list()
224     try:
225         df = pd.DataFrame(y_vals)
226         df.head()
227     except ValueError as err:
228         logging.error("   Finished with error: {}".
229                       format(str(err).replace("\n", " ")))
230         return
231
232     for i, col in enumerate(df.columns):
233         name = "{0}. {1}".format(i + 1, col.lower().replace('-ndrpdrdisc', '').
234                                  replace('-ndrpdr', ''))
235         traces.append(plgo.Box(x=['TGint1-to-SUT1-to-SUT2-to-TGint2',
236                                   'TGint1-to-SUT1-to-SUT2-to-TGint2',
237                                   'TGint1-to-SUT1-to-SUT2-to-TGint2',
238                                   'TGint1-to-SUT1-to-SUT2-to-TGint2',
239                                   'TGint1-to-SUT1-to-SUT2-to-TGint2',
240                                   'TGint1-to-SUT1-to-SUT2-to-TGint2',
241                                   'TGint2-to-SUT2-to-SUT1-to-TGint1',
242                                   'TGint2-to-SUT2-to-SUT1-to-TGint1',
243                                   'TGint2-to-SUT2-to-SUT1-to-TGint1',
244                                   'TGint2-to-SUT2-to-SUT1-to-TGint1',
245                                   'TGint2-to-SUT2-to-SUT1-to-TGint1',
246                                   'TGint2-to-SUT2-to-SUT1-to-TGint1'],
247                                y=df[col],
248                                name=name,
249                                **plot["traces"]))
250
251     try:
252         # Create plot
253         logging.info("    Writing file '{0}{1}'.".
254                      format(plot["output-file"], plot["output-file-type"]))
255         plpl = plgo.Figure(data=traces, layout=plot["layout"])
256
257         # Export Plot
258         ploff.plot(plpl,
259                    show_link=False, auto_open=False,
260                    filename='{0}{1}'.format(plot["output-file"],
261                                             plot["output-file-type"]))
262     except PlotlyError as err:
263         logging.error("   Finished with error: {}".
264                       format(str(err).replace("\n", " ")))
265         return
266
267     logging.info("  Done.")
268
269
270 def plot_throughput_speedup_analysis(plot, input_data):
271     """Generate the plot(s) with algorithm: plot_throughput_speedup_analysis
272     specified in the specification file.
273
274     :param plot: Plot to generate.
275     :param input_data: Data to process.
276     :type plot: pandas.Series
277     :type input_data: InputData
278     """
279
280     logging.info("  Generating the plot {0} ...".
281                  format(plot.get("title", "")))
282
283     # Transform the data
284     plot_title = plot.get("title", "")
285     logging.info("    Creating the data set for the {0} '{1}'.".
286                  format(plot.get("type", ""), plot_title))
287     data = input_data.filter_data(plot)
288     if data is None:
289         logging.error("No data.")
290         return
291
292     throughput = dict()
293     for job in data:
294         for build in job:
295             for test in build:
296                 if throughput.get(test["parent"], None) is None:
297                     throughput[test["parent"]] = {"1": list(),
298                                                   "2": list(),
299                                                   "4": list()}
300                 try:
301                     # TODO: Remove when definitely no NDRPDRDISC tests are used:
302                     if test["type"] in ("NDR", "PDR"):
303                         if "1T1C" in test["tags"]:
304                             throughput[test["parent"]]["1"].\
305                                 append(test["throughput"]["value"])
306                         elif "2T2C" in test["tags"]:
307                             throughput[test["parent"]]["2"]. \
308                                 append(test["throughput"]["value"])
309                         elif "4T4C" in test["tags"]:
310                             throughput[test["parent"]]["4"]. \
311                                 append(test["throughput"]["value"])
312                     elif test["type"] in ("NDRPDR", ):
313                         if "-pdr" in plot_title.lower():
314                             ttype = "PDR"
315                         elif "-ndr" in plot_title.lower():
316                             ttype = "NDR"
317                         else:
318                             continue
319                         if "1T1C" in test["tags"]:
320                             throughput[test["parent"]]["1"].\
321                                 append(test["throughput"][ttype]["LOWER"])
322                         elif "2T2C" in test["tags"]:
323                             throughput[test["parent"]]["2"]. \
324                                 append(test["throughput"][ttype]["LOWER"])
325                         elif "4T4C" in test["tags"]:
326                             throughput[test["parent"]]["4"]. \
327                                 append(test["throughput"][ttype]["LOWER"])
328                 except (KeyError, TypeError):
329                     pass
330
331     if not throughput:
332         logging.warning("No data for the plot '{}'".
333                         format(plot.get("title", "")))
334         return
335
336     for test_name, test_vals in throughput.items():
337         for key, test_val in test_vals.items():
338             if test_val:
339                 throughput[test_name][key] = sum(test_val) / len(test_val)
340
341     names = ['1 core', '2 cores', '4 cores']
342     x_vals = list()
343     y_vals_1 = list()
344     y_vals_2 = list()
345     y_vals_4 = list()
346
347     for test_name, test_vals in throughput.items():
348         if test_vals["1"]:
349             x_vals.append("-".join(test_name.split('-')[1:-1]))
350             y_vals_1.append(1)
351             if test_vals["2"]:
352                 y_vals_2.append(
353                     round(float(test_vals["2"]) / float(test_vals["1"]), 2))
354             else:
355                 y_vals_2.append(None)
356             if test_vals["4"]:
357                 y_vals_4.append(
358                     round(float(test_vals["4"]) / float(test_vals["1"]), 2))
359             else:
360                 y_vals_4.append(None)
361
362     y_vals = [y_vals_1, y_vals_2, y_vals_4]
363
364     y_vals_zipped = zip(names, y_vals)
365     traces = list()
366     for val in y_vals_zipped:
367         traces.append(plgo.Bar(x=x_vals,
368                                y=val[1],
369                                name=val[0]))
370
371     try:
372         # Create plot
373         logging.info("    Writing file '{0}{1}'.".
374                      format(plot["output-file"], plot["output-file-type"]))
375         plpl = plgo.Figure(data=traces, layout=plot["layout"])
376
377         # Export Plot
378         ploff.plot(plpl,
379                    show_link=False, auto_open=False,
380                    filename='{0}{1}'.format(plot["output-file"],
381                                             plot["output-file-type"]))
382     except PlotlyError as err:
383         logging.error("   Finished with error: {}".
384                       format(str(err).replace("\n", " ")))
385         return
386
387     logging.info("  Done.")
388
389
390 def plot_http_server_performance_box(plot, input_data):
391     """Generate the plot(s) with algorithm: plot_http_server_performance_box
392     specified in the specification file.
393
394     :param plot: Plot to generate.
395     :param input_data: Data to process.
396     :type plot: pandas.Series
397     :type input_data: InputData
398     """
399
400     logging.info("  Generating the plot {0} ...".
401                  format(plot.get("title", "")))
402
403     # Transform the data
404     logging.info("    Creating the data set for the {0} '{1}'.".
405                  format(plot.get("type", ""), plot.get("title", "")))
406     data = input_data.filter_data(plot)
407     if data is None:
408         logging.error("No data.")
409         return
410
411     # Prepare the data for the plot
412     y_vals = dict()
413     for job in data:
414         for build in job:
415             for test in build:
416                 if y_vals.get(test["name"], None) is None:
417                     y_vals[test["name"]] = list()
418                 try:
419                     y_vals[test["name"]].append(test["result"])
420                 except (KeyError, TypeError):
421                     y_vals[test["name"]].append(None)
422
423     # Add None to the lists with missing data
424     max_len = 0
425     for val in y_vals.values():
426         if len(val) > max_len:
427             max_len = len(val)
428     for key, val in y_vals.items():
429         if len(val) < max_len:
430             val.extend([None for _ in range(max_len - len(val))])
431
432     # Add plot traces
433     traces = list()
434     df = pd.DataFrame(y_vals)
435     df.head()
436     for i, col in enumerate(df.columns):
437         name = "{0}. {1}".format(i + 1, col.lower().replace('-cps', '').
438                                  replace('-rps', ''))
439         traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
440                                y=df[col],
441                                name=name,
442                                **plot["traces"]))
443     try:
444         # Create plot
445         plpl = plgo.Figure(data=traces, layout=plot["layout"])
446
447         # Export Plot
448         logging.info("    Writing file '{0}{1}'.".
449                      format(plot["output-file"], plot["output-file-type"]))
450         ploff.plot(plpl,
451                    show_link=False, auto_open=False,
452                    filename='{0}{1}'.format(plot["output-file"],
453                                             plot["output-file-type"]))
454     except PlotlyError as err:
455         logging.error("   Finished with error: {}".
456                       format(str(err).replace("\n", " ")))
457         return
458
459     logging.info("  Done.")