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