Report: Add CSC and VSC data
[csit.git] / resources / tools / presentation / generator_plots.py
1 # Copyright (c) 2019 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 re
19 import logging
20 import pandas as pd
21 import plotly.offline as ploff
22 import plotly.graph_objs as plgo
23
24 from plotly.exceptions import PlotlyError
25 from collections import OrderedDict
26 from copy import deepcopy
27
28 from utils import mean, stdev
29
30
31 COLORS = ["SkyBlue", "Olive", "Purple", "Coral", "Indigo", "Pink",
32           "Chocolate", "Brown", "Magenta", "Cyan", "Orange", "Black",
33           "Violet", "Blue", "Yellow", "BurlyWood", "CadetBlue", "Crimson",
34           "DarkBlue", "DarkCyan", "DarkGreen", "Green", "GoldenRod",
35           "LightGreen", "LightSeaGreen", "LightSkyBlue", "Maroon",
36           "MediumSeaGreen", "SeaGreen", "LightSlateGrey"]
37
38
39 def generate_plots(spec, data):
40     """Generate all plots specified in the specification file.
41
42     :param spec: Specification read from the specification file.
43     :param data: Data to process.
44     :type spec: Specification
45     :type data: InputData
46     """
47
48     logging.info("Generating the plots ...")
49     for index, plot in enumerate(spec.plots):
50         try:
51             logging.info("  Plot nr {0}: {1}".format(index + 1,
52                                                      plot.get("title", "")))
53             plot["limits"] = spec.configuration["limits"]
54             eval(plot["algorithm"])(plot, data)
55             logging.info("  Done.")
56         except NameError as err:
57             logging.error("Probably algorithm '{alg}' is not defined: {err}".
58                           format(alg=plot["algorithm"], err=repr(err)))
59     logging.info("Done.")
60
61
62 def plot_performance_box(plot, input_data):
63     """Generate the plot(s) with algorithm: plot_performance_box
64     specified in the specification file.
65
66     :param plot: Plot to generate.
67     :param input_data: Data to process.
68     :type plot: pandas.Series
69     :type input_data: InputData
70     """
71
72     # Transform the data
73     plot_title = plot.get("title", "")
74     logging.info("    Creating the data set for the {0} '{1}'.".
75                  format(plot.get("type", ""), plot_title))
76     data = input_data.filter_data(plot)
77     if data is None:
78         logging.error("No data.")
79         return
80
81     # Prepare the data for the plot
82     y_vals = dict()
83     y_tags = dict()
84     for job in data:
85         for build in job:
86             for test in build:
87                 if y_vals.get(test["parent"], None) is None:
88                     y_vals[test["parent"]] = list()
89                     y_tags[test["parent"]] = test.get("tags", None)
90                 try:
91                     if test["type"] in ("NDRPDR", ):
92                         if "-pdr" in plot_title.lower():
93                             y_vals[test["parent"]].\
94                                 append(test["throughput"]["PDR"]["LOWER"])
95                         elif "-ndr" in plot_title.lower():
96                             y_vals[test["parent"]]. \
97                                 append(test["throughput"]["NDR"]["LOWER"])
98                         else:
99                             continue
100                     else:
101                         continue
102                 except (KeyError, TypeError):
103                     y_vals[test["parent"]].append(None)
104
105     # Sort the tests
106     order = plot.get("sort", None)
107     if order and y_tags:
108         y_sorted = OrderedDict()
109         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
110         for tag in order:
111             logging.debug(tag)
112             for suite, tags in y_tags_l.items():
113                 if "not " in tag:
114                     tag = tag.split(" ")[-1]
115                     if tag.lower() in tags:
116                         continue
117                 else:
118                     if tag.lower() not in tags:
119                         continue
120                 try:
121                     y_sorted[suite] = y_vals.pop(suite)
122                     y_tags_l.pop(suite)
123                     logging.debug(suite)
124                 except KeyError as err:
125                     logging.error("Not found: {0}".format(repr(err)))
126                 finally:
127                     break
128     else:
129         y_sorted = y_vals
130
131     # Add None to the lists with missing data
132     max_len = 0
133     nr_of_samples = list()
134     for val in y_sorted.values():
135         if len(val) > max_len:
136             max_len = len(val)
137         nr_of_samples.append(len(val))
138     for key, val in y_sorted.items():
139         if len(val) < max_len:
140             val.extend([None for _ in range(max_len - len(val))])
141
142     # Add plot traces
143     traces = list()
144     df = pd.DataFrame(y_sorted)
145     df.head()
146     y_max = list()
147     for i, col in enumerate(df.columns):
148         name = "{nr}. ({samples:02d} run{plural}) {name}".\
149             format(nr=(i + 1),
150                    samples=nr_of_samples[i],
151                    plural='s' if nr_of_samples[i] > 1 else '',
152                    name=col.lower().replace('-ndrpdr', ''))
153         if len(name) > 50:
154             name_lst = name.split('-')
155             name = ""
156             split_name = True
157             for segment in name_lst:
158                 if (len(name) + len(segment) + 1) > 50 and split_name:
159                     name += "<br>    "
160                     split_name = False
161                 name += segment + '-'
162             name = name[:-1]
163
164         logging.debug(name)
165         traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
166                                y=[y / 1000000 if y else None for y in df[col]],
167                                name=name,
168                                **plot["traces"]))
169         try:
170             val_max = max(df[col])
171         except ValueError as err:
172             logging.error(repr(err))
173             continue
174         if val_max:
175             y_max.append(int(val_max / 1000000) + 1)
176
177     try:
178         # Create plot
179         layout = deepcopy(plot["layout"])
180         if layout.get("title", None):
181             layout["title"] = "<b>Packet Throughput:</b> {0}". \
182                 format(layout["title"])
183         if y_max:
184             layout["yaxis"]["range"] = [0, max(y_max)]
185         plpl = plgo.Figure(data=traces, layout=layout)
186
187         # Export Plot
188         logging.info("    Writing file '{0}{1}'.".
189                      format(plot["output-file"], plot["output-file-type"]))
190         ploff.plot(plpl, show_link=False, auto_open=False,
191                    filename='{0}{1}'.format(plot["output-file"],
192                                             plot["output-file-type"]))
193     except PlotlyError as err:
194         logging.error("   Finished with error: {}".
195                       format(repr(err).replace("\n", " ")))
196         return
197
198
199 def plot_soak_bars(plot, input_data):
200     """Generate the plot(s) with algorithm: plot_soak_bars
201     specified in the specification file.
202
203     :param plot: Plot to generate.
204     :param input_data: Data to process.
205     :type plot: pandas.Series
206     :type input_data: InputData
207     """
208
209     # Transform the data
210     plot_title = plot.get("title", "")
211     logging.info("    Creating the data set for the {0} '{1}'.".
212                  format(plot.get("type", ""), plot_title))
213     data = input_data.filter_data(plot)
214     if data is None:
215         logging.error("No data.")
216         return
217
218     # Prepare the data for the plot
219     y_vals = dict()
220     y_tags = dict()
221     for job in data:
222         for build in job:
223             for test in build:
224                 if y_vals.get(test["parent"], None) is None:
225                     y_tags[test["parent"]] = test.get("tags", None)
226                 try:
227                     if test["type"] in ("SOAK", ):
228                         y_vals[test["parent"]] = test["throughput"]
229                     else:
230                         continue
231                 except (KeyError, TypeError):
232                     y_vals[test["parent"]] = dict()
233
234     # Sort the tests
235     order = plot.get("sort", None)
236     if order and y_tags:
237         y_sorted = OrderedDict()
238         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
239         for tag in order:
240             logging.debug(tag)
241             for suite, tags in y_tags_l.items():
242                 if "not " in tag:
243                     tag = tag.split(" ")[-1]
244                     if tag.lower() in tags:
245                         continue
246                 else:
247                     if tag.lower() not in tags:
248                         continue
249                 try:
250                     y_sorted[suite] = y_vals.pop(suite)
251                     y_tags_l.pop(suite)
252                     logging.debug(suite)
253                 except KeyError as err:
254                     logging.error("Not found: {0}".format(repr(err)))
255                 finally:
256                     break
257     else:
258         y_sorted = y_vals
259
260     idx = 0
261     y_max = 0
262     traces = list()
263     for test_name, test_data in y_sorted.items():
264         idx += 1
265         name = "{nr}. {name}".\
266             format(nr=idx, name=test_name.lower().replace('-soak', ''))
267         if len(name) > 50:
268             name_lst = name.split('-')
269             name = ""
270             split_name = True
271             for segment in name_lst:
272                 if (len(name) + len(segment) + 1) > 50 and split_name:
273                     name += "<br>    "
274                     split_name = False
275                 name += segment + '-'
276             name = name[:-1]
277
278         y_val = test_data.get("LOWER", None)
279         if y_val:
280             y_val /= 1000000
281             if y_val > y_max:
282                 y_max = y_val
283
284         time = "No Information"
285         result = "No Information"
286         hovertext = ("{name}<br>"
287                      "Packet Throughput: {val:.2f}Mpps<br>"
288                      "Final Duration: {time}<br>"
289                      "Result: {result}".format(name=name,
290                                                val=y_val,
291                                                time=time,
292                                                result=result))
293         traces.append(plgo.Bar(x=[str(idx) + '.', ],
294                                y=[y_val, ],
295                                name=name,
296                                text=hovertext,
297                                hoverinfo="text"))
298     try:
299         # Create plot
300         layout = deepcopy(plot["layout"])
301         if layout.get("title", None):
302             layout["title"] = "<b>Packet Throughput:</b> {0}". \
303                 format(layout["title"])
304         if y_max:
305             layout["yaxis"]["range"] = [0, y_max + 1]
306         plpl = plgo.Figure(data=traces, layout=layout)
307         # Export Plot
308         logging.info("    Writing file '{0}{1}'.".
309                      format(plot["output-file"], plot["output-file-type"]))
310         ploff.plot(plpl, show_link=False, auto_open=False,
311                    filename='{0}{1}'.format(plot["output-file"],
312                                             plot["output-file-type"]))
313     except PlotlyError as err:
314         logging.error("   Finished with error: {}".
315                       format(repr(err).replace("\n", " ")))
316         return
317
318
319 def plot_soak_boxes(plot, input_data):
320     """Generate the plot(s) with algorithm: plot_soak_boxes
321     specified in the specification file.
322
323     :param plot: Plot to generate.
324     :param input_data: Data to process.
325     :type plot: pandas.Series
326     :type input_data: InputData
327     """
328
329     # Transform the data
330     plot_title = plot.get("title", "")
331     logging.info("    Creating the data set for the {0} '{1}'.".
332                  format(plot.get("type", ""), plot_title))
333     data = input_data.filter_data(plot)
334     if data is None:
335         logging.error("No data.")
336         return
337
338     # Prepare the data for the plot
339     y_vals = dict()
340     y_tags = dict()
341     for job in data:
342         for build in job:
343             for test in build:
344                 if y_vals.get(test["parent"], None) is None:
345                     y_tags[test["parent"]] = test.get("tags", None)
346                 try:
347                     if test["type"] in ("SOAK", ):
348                         y_vals[test["parent"]] = test["throughput"]
349                     else:
350                         continue
351                 except (KeyError, TypeError):
352                     y_vals[test["parent"]] = dict()
353
354     # Sort the tests
355     order = plot.get("sort", None)
356     if order and y_tags:
357         y_sorted = OrderedDict()
358         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
359         for tag in order:
360             logging.debug(tag)
361             for suite, tags in y_tags_l.items():
362                 if "not " in tag:
363                     tag = tag.split(" ")[-1]
364                     if tag.lower() in tags:
365                         continue
366                 else:
367                     if tag.lower() not in tags:
368                         continue
369                 try:
370                     y_sorted[suite] = y_vals.pop(suite)
371                     y_tags_l.pop(suite)
372                     logging.debug(suite)
373                 except KeyError as err:
374                     logging.error("Not found: {0}".format(repr(err)))
375                 finally:
376                     break
377     else:
378         y_sorted = y_vals
379
380     idx = 0
381     y_max = 0
382     traces = list()
383     for test_name, test_data in y_sorted.items():
384         idx += 1
385         name = "{nr}. {name}".\
386             format(nr=idx, name=test_name.lower().replace('-soak', ''))
387         if len(name) > 50:
388             name_lst = name.split('-')
389             name = ""
390             split_name = True
391             for segment in name_lst:
392                 if (len(name) + len(segment) + 1) > 50 and split_name:
393                     name += "<br>    "
394                     split_name = False
395                 name += segment + '-'
396             name = name[:-1]
397
398         y_val = test_data.get("UPPER", None)
399         if y_val:
400             y_val /= 1000000
401             if y_val > y_max:
402                 y_max = y_val
403
404         y_base = test_data.get("LOWER", None)
405         if y_base:
406             y_base /= 1000000
407
408         hovertext = ("{name}<br>"
409                      "Upper bound: {upper:.2f}Mpps<br>"
410                      "Lower bound: {lower:.2f}Mpps".format(name=name,
411                                                            upper=y_val,
412                                                            lower=y_base))
413         traces.append(plgo.Bar(x=[str(idx) + '.', ],
414                                # +0.05 to see the value in case lower == upper
415                                y=[y_val - y_base + 0.05, ],
416                                base=y_base,
417                                name=name,
418                                text=hovertext,
419                                hoverinfo="text"))
420     try:
421         # Create plot
422         layout = deepcopy(plot["layout"])
423         if layout.get("title", None):
424             layout["title"] = "<b>Soak Tests:</b> {0}". \
425                 format(layout["title"])
426         if y_max:
427             layout["yaxis"]["range"] = [0, y_max + 1]
428         plpl = plgo.Figure(data=traces, layout=layout)
429         # Export Plot
430         logging.info("    Writing file '{0}{1}'.".
431                      format(plot["output-file"], plot["output-file-type"]))
432         ploff.plot(plpl, show_link=False, auto_open=False,
433                    filename='{0}{1}'.format(plot["output-file"],
434                                             plot["output-file-type"]))
435     except PlotlyError as err:
436         logging.error("   Finished with error: {}".
437                       format(repr(err).replace("\n", " ")))
438         return
439
440
441 def plot_latency_error_bars(plot, input_data):
442     """Generate the plot(s) with algorithm: plot_latency_error_bars
443     specified in the specification file.
444
445     :param plot: Plot to generate.
446     :param input_data: Data to process.
447     :type plot: pandas.Series
448     :type input_data: InputData
449     """
450
451     # Transform the data
452     plot_title = plot.get("title", "")
453     logging.info("    Creating the data set for the {0} '{1}'.".
454                  format(plot.get("type", ""), plot_title))
455     data = input_data.filter_data(plot)
456     if data is None:
457         logging.error("No data.")
458         return
459
460     # Prepare the data for the plot
461     y_tmp_vals = dict()
462     y_tags = dict()
463     for job in data:
464         for build in job:
465             for test in build:
466                 try:
467                     logging.debug("test['latency']: {0}\n".
468                                  format(test["latency"]))
469                 except ValueError as err:
470                     logging.warning(repr(err))
471                 if y_tmp_vals.get(test["parent"], None) is None:
472                     y_tmp_vals[test["parent"]] = [
473                         list(),  # direction1, min
474                         list(),  # direction1, avg
475                         list(),  # direction1, max
476                         list(),  # direction2, min
477                         list(),  # direction2, avg
478                         list()   # direction2, max
479                     ]
480                     y_tags[test["parent"]] = test.get("tags", None)
481                 try:
482                     if test["type"] in ("NDRPDR", ):
483                         if "-pdr" in plot_title.lower():
484                             ttype = "PDR"
485                         elif "-ndr" in plot_title.lower():
486                             ttype = "NDR"
487                         else:
488                             logging.warning("Invalid test type: {0}".
489                                             format(test["type"]))
490                             continue
491                         y_tmp_vals[test["parent"]][0].append(
492                             test["latency"][ttype]["direction1"]["min"])
493                         y_tmp_vals[test["parent"]][1].append(
494                             test["latency"][ttype]["direction1"]["avg"])
495                         y_tmp_vals[test["parent"]][2].append(
496                             test["latency"][ttype]["direction1"]["max"])
497                         y_tmp_vals[test["parent"]][3].append(
498                             test["latency"][ttype]["direction2"]["min"])
499                         y_tmp_vals[test["parent"]][4].append(
500                             test["latency"][ttype]["direction2"]["avg"])
501                         y_tmp_vals[test["parent"]][5].append(
502                             test["latency"][ttype]["direction2"]["max"])
503                     else:
504                         logging.warning("Invalid test type: {0}".
505                                         format(test["type"]))
506                         continue
507                 except (KeyError, TypeError) as err:
508                     logging.warning(repr(err))
509     logging.debug("y_tmp_vals: {0}\n".format(y_tmp_vals))
510
511     # Sort the tests
512     order = plot.get("sort", None)
513     if order and y_tags:
514         y_sorted = OrderedDict()
515         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
516         for tag in order:
517             logging.debug(tag)
518             for suite, tags in y_tags_l.items():
519                 if "not " in tag:
520                     tag = tag.split(" ")[-1]
521                     if tag.lower() in tags:
522                         continue
523                 else:
524                     if tag.lower() not in tags:
525                         continue
526                 try:
527                     y_sorted[suite] = y_tmp_vals.pop(suite)
528                     y_tags_l.pop(suite)
529                     logging.debug(suite)
530                 except KeyError as err:
531                     logging.error("Not found: {0}".format(repr(err)))
532                 finally:
533                     break
534     else:
535         y_sorted = y_tmp_vals
536
537     logging.debug("y_sorted: {0}\n".format(y_sorted))
538     x_vals = list()
539     y_vals = list()
540     y_mins = list()
541     y_maxs = list()
542     nr_of_samples = list()
543     for key, val in y_sorted.items():
544         name = "-".join(key.split("-")[1:-1])
545         if len(name) > 50:
546             name_lst = name.split('-')
547             name = ""
548             split_name = True
549             for segment in name_lst:
550                 if (len(name) + len(segment) + 1) > 50 and split_name:
551                     name += "<br>"
552                     split_name = False
553                 name += segment + '-'
554             name = name[:-1]
555         x_vals.append(name)  # dir 1
556         y_vals.append(mean(val[1]) if val[1] else None)
557         y_mins.append(mean(val[0]) if val[0] else None)
558         y_maxs.append(mean(val[2]) if val[2] else None)
559         nr_of_samples.append(len(val[1]) if val[1] else 0)
560         x_vals.append(name)  # dir 2
561         y_vals.append(mean(val[4]) if val[4] else None)
562         y_mins.append(mean(val[3]) if val[3] else None)
563         y_maxs.append(mean(val[5]) if val[5] else None)
564         nr_of_samples.append(len(val[3]) if val[3] else 0)
565
566     logging.debug("x_vals :{0}\n".format(x_vals))
567     logging.debug("y_vals :{0}\n".format(y_vals))
568     logging.debug("y_mins :{0}\n".format(y_mins))
569     logging.debug("y_maxs :{0}\n".format(y_maxs))
570     logging.debug("nr_of_samples :{0}\n".format(nr_of_samples))
571     traces = list()
572     annotations = list()
573
574     for idx in range(len(x_vals)):
575         if not bool(int(idx % 2)):
576             direction = "West-East"
577         else:
578             direction = "East-West"
579         hovertext = ("No. of Runs: {nr}<br>"
580                      "Test: {test}<br>"
581                      "Direction: {dir}<br>".format(test=x_vals[idx],
582                                                    dir=direction,
583                                                    nr=nr_of_samples[idx]))
584         if isinstance(y_maxs[idx], float):
585             hovertext += "Max: {max:.2f}uSec<br>".format(max=y_maxs[idx])
586         if isinstance(y_vals[idx], float):
587             hovertext += "Mean: {avg:.2f}uSec<br>".format(avg=y_vals[idx])
588         if isinstance(y_mins[idx], float):
589             hovertext += "Min: {min:.2f}uSec".format(min=y_mins[idx])
590
591         if isinstance(y_maxs[idx], float) and isinstance(y_vals[idx], float):
592             array = [y_maxs[idx] - y_vals[idx], ]
593         else:
594             array = [None, ]
595         if isinstance(y_mins[idx], float) and isinstance(y_vals[idx], float):
596             arrayminus = [y_vals[idx] - y_mins[idx], ]
597         else:
598             arrayminus = [None, ]
599         logging.debug("y_vals[{1}] :{0}\n".format(y_vals[idx], idx))
600         logging.debug("array :{0}\n".format(array))
601         logging.debug("arrayminus :{0}\n".format(arrayminus))
602         traces.append(plgo.Scatter(
603             x=[idx, ],
604             y=[y_vals[idx], ],
605             name=x_vals[idx],
606             legendgroup=x_vals[idx],
607             showlegend=bool(int(idx % 2)),
608             mode="markers",
609             error_y=dict(
610                 type='data',
611                 symmetric=False,
612                 array=array,
613                 arrayminus=arrayminus,
614                 color=COLORS[int(idx / 2)]
615             ),
616             marker=dict(
617                 size=10,
618                 color=COLORS[int(idx / 2)],
619             ),
620             text=hovertext,
621             hoverinfo="text",
622         ))
623         annotations.append(dict(
624             x=idx,
625             y=0,
626             xref="x",
627             yref="y",
628             xanchor="center",
629             yanchor="top",
630             text="E-W" if bool(int(idx % 2)) else "W-E",
631             font=dict(
632                 size=16,
633             ),
634             align="center",
635             showarrow=False
636         ))
637
638     try:
639         # Create plot
640         logging.info("    Writing file '{0}{1}'.".
641                      format(plot["output-file"], plot["output-file-type"]))
642         layout = deepcopy(plot["layout"])
643         if layout.get("title", None):
644             layout["title"] = "<b>Packet Latency:</b> {0}".\
645                 format(layout["title"])
646         layout["annotations"] = annotations
647         plpl = plgo.Figure(data=traces, layout=layout)
648
649         # Export Plot
650         ploff.plot(plpl,
651                    show_link=False, auto_open=False,
652                    filename='{0}{1}'.format(plot["output-file"],
653                                             plot["output-file-type"]))
654     except PlotlyError as err:
655         logging.error("   Finished with error: {}".
656                       format(str(err).replace("\n", " ")))
657         return
658
659
660 def plot_throughput_speedup_analysis(plot, input_data):
661     """Generate the plot(s) with algorithm:
662     plot_throughput_speedup_analysis
663     specified in the specification file.
664
665     :param plot: Plot to generate.
666     :param input_data: Data to process.
667     :type plot: pandas.Series
668     :type input_data: InputData
669     """
670
671     # Transform the data
672     plot_title = plot.get("title", "")
673     logging.info("    Creating the data set for the {0} '{1}'.".
674                  format(plot.get("type", ""), plot_title))
675     data = input_data.filter_data(plot)
676     if data is None:
677         logging.error("No data.")
678         return
679
680     y_vals = dict()
681     y_tags = dict()
682     for job in data:
683         for build in job:
684             for test in build:
685                 if y_vals.get(test["parent"], None) is None:
686                     y_vals[test["parent"]] = {"1": list(),
687                                               "2": list(),
688                                               "4": list()}
689                     y_tags[test["parent"]] = test.get("tags", None)
690                 try:
691                     if test["type"] in ("NDRPDR",):
692                         if "-pdr" in plot_title.lower():
693                             ttype = "PDR"
694                         elif "-ndr" in plot_title.lower():
695                             ttype = "NDR"
696                         else:
697                             continue
698                         if "1C" in test["tags"]:
699                             y_vals[test["parent"]]["1"]. \
700                                 append(test["throughput"][ttype]["LOWER"])
701                         elif "2C" in test["tags"]:
702                             y_vals[test["parent"]]["2"]. \
703                                 append(test["throughput"][ttype]["LOWER"])
704                         elif "4C" in test["tags"]:
705                             y_vals[test["parent"]]["4"]. \
706                                 append(test["throughput"][ttype]["LOWER"])
707                 except (KeyError, TypeError):
708                     pass
709
710     if not y_vals:
711         logging.warning("No data for the plot '{}'".
712                         format(plot.get("title", "")))
713         return
714
715     y_1c_max = dict()
716     for test_name, test_vals in y_vals.items():
717         for key, test_val in test_vals.items():
718             if test_val:
719                 avg_val = sum(test_val) / len(test_val)
720                 y_vals[test_name][key] = (avg_val, len(test_val))
721                 ideal = avg_val / (int(key) * 1000000.0)
722                 if test_name not in y_1c_max or ideal > y_1c_max[test_name]:
723                     y_1c_max[test_name] = ideal
724
725     vals = dict()
726     y_max = list()
727     nic_limit = 0
728     lnk_limit = 0
729     pci_limit = plot["limits"]["pci"]["pci-g3-x8"]
730     for test_name, test_vals in y_vals.items():
731         try:
732             if test_vals["1"][1]:
733                 name = "-".join(test_name.split('-')[1:-1])
734                 if len(name) > 50:
735                     name_lst = name.split('-')
736                     name = ""
737                     split_name = True
738                     for segment in name_lst:
739                         if (len(name) + len(segment) + 1) > 50 and split_name:
740                             name += "<br>"
741                             split_name = False
742                         name += segment + '-'
743                     name = name[:-1]
744
745                 vals[name] = dict()
746                 y_val_1 = test_vals["1"][0] / 1000000.0
747                 y_val_2 = test_vals["2"][0] / 1000000.0 if test_vals["2"][0] \
748                     else None
749                 y_val_4 = test_vals["4"][0] / 1000000.0 if test_vals["4"][0] \
750                     else None
751
752                 vals[name]["val"] = [y_val_1, y_val_2, y_val_4]
753                 vals[name]["rel"] = [1.0, None, None]
754                 vals[name]["ideal"] = [y_1c_max[test_name],
755                                        y_1c_max[test_name] * 2,
756                                        y_1c_max[test_name] * 4]
757                 vals[name]["diff"] = [(y_val_1 - y_1c_max[test_name]) * 100 /
758                                       y_val_1, None, None]
759                 vals[name]["count"] = [test_vals["1"][1],
760                                        test_vals["2"][1],
761                                        test_vals["4"][1]]
762
763                 try:
764                     val_max = max(max(vals[name]["val"], vals[name]["ideal"]))
765                 except ValueError as err:
766                     logging.error(err)
767                     continue
768                 if val_max:
769                     y_max.append(int((val_max / 10) + 1) * 10)
770
771                 if y_val_2:
772                     vals[name]["rel"][1] = round(y_val_2 / y_val_1, 2)
773                     vals[name]["diff"][1] = \
774                         (y_val_2 - vals[name]["ideal"][1]) * 100 / y_val_2
775                 if y_val_4:
776                     vals[name]["rel"][2] = round(y_val_4 / y_val_1, 2)
777                     vals[name]["diff"][2] = \
778                         (y_val_4 - vals[name]["ideal"][2]) * 100 / y_val_4
779         except IndexError as err:
780             logging.warning("No data for '{0}'".format(test_name))
781             logging.warning(repr(err))
782
783         # Limits:
784         if "x520" in test_name:
785             limit = plot["limits"]["nic"]["x520"]
786         elif "x710" in test_name:
787             limit = plot["limits"]["nic"]["x710"]
788         elif "xxv710" in test_name:
789             limit = plot["limits"]["nic"]["xxv710"]
790         elif "xl710" in test_name:
791             limit = plot["limits"]["nic"]["xl710"]
792         elif "x553" in test_name:
793             limit = plot["limits"]["nic"]["x553"]
794         else:
795             limit = 0
796         if limit > nic_limit:
797             nic_limit = limit
798
799         mul = 2 if "ge2p" in test_name else 1
800         if "10ge" in test_name:
801             limit = plot["limits"]["link"]["10ge"] * mul
802         elif "25ge" in test_name:
803             limit = plot["limits"]["link"]["25ge"] * mul
804         elif "40ge" in test_name:
805             limit = plot["limits"]["link"]["40ge"] * mul
806         elif "100ge" in test_name:
807             limit = plot["limits"]["link"]["100ge"] * mul
808         else:
809             limit = 0
810         if limit > lnk_limit:
811             lnk_limit = limit
812
813     # Sort the tests
814     order = plot.get("sort", None)
815     if order and y_tags:
816         y_sorted = OrderedDict()
817         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
818         for tag in order:
819             for test, tags in y_tags_l.items():
820                 if tag.lower() in tags:
821                     name = "-".join(test.split('-')[1:-1])
822                     try:
823                         y_sorted[name] = vals.pop(name)
824                         y_tags_l.pop(test)
825                     except KeyError as err:
826                         logging.error("Not found: {0}".format(err))
827                     finally:
828                         break
829     else:
830         y_sorted = vals
831
832     traces = list()
833     annotations = list()
834     x_vals = [1, 2, 4]
835
836     # Limits:
837     try:
838         threshold = 1.1 * max(y_max)  # 10%
839     except ValueError as err:
840         logging.error(err)
841         return
842     nic_limit /= 1000000.0
843     if nic_limit < threshold:
844         traces.append(plgo.Scatter(
845             x=x_vals,
846             y=[nic_limit, ] * len(x_vals),
847             name="NIC: {0:.2f}Mpps".format(nic_limit),
848             showlegend=False,
849             mode="lines",
850             line=dict(
851                 dash="dot",
852                 color=COLORS[-1],
853                 width=1),
854             hoverinfo="none"
855         ))
856         annotations.append(dict(
857             x=1,
858             y=nic_limit,
859             xref="x",
860             yref="y",
861             xanchor="left",
862             yanchor="bottom",
863             text="NIC: {0:.2f}Mpps".format(nic_limit),
864             font=dict(
865                 size=14,
866                 color=COLORS[-1],
867             ),
868             align="left",
869             showarrow=False
870         ))
871         y_max.append(int((nic_limit / 10) + 1) * 10)
872
873     lnk_limit /= 1000000.0
874     if lnk_limit < threshold:
875         traces.append(plgo.Scatter(
876             x=x_vals,
877             y=[lnk_limit, ] * len(x_vals),
878             name="Link: {0:.2f}Mpps".format(lnk_limit),
879             showlegend=False,
880             mode="lines",
881             line=dict(
882                 dash="dot",
883                 color=COLORS[-2],
884                 width=1),
885             hoverinfo="none"
886         ))
887         annotations.append(dict(
888             x=1,
889             y=lnk_limit,
890             xref="x",
891             yref="y",
892             xanchor="left",
893             yanchor="bottom",
894             text="Link: {0:.2f}Mpps".format(lnk_limit),
895             font=dict(
896                 size=14,
897                 color=COLORS[-2],
898             ),
899             align="left",
900             showarrow=False
901         ))
902         y_max.append(int((lnk_limit / 10) + 1) * 10)
903
904     pci_limit /= 1000000.0
905     if pci_limit < threshold:
906         traces.append(plgo.Scatter(
907             x=x_vals,
908             y=[pci_limit, ] * len(x_vals),
909             name="PCIe: {0:.2f}Mpps".format(pci_limit),
910             showlegend=False,
911             mode="lines",
912             line=dict(
913                 dash="dot",
914                 color=COLORS[-3],
915                 width=1),
916             hoverinfo="none"
917         ))
918         annotations.append(dict(
919             x=1,
920             y=pci_limit,
921             xref="x",
922             yref="y",
923             xanchor="left",
924             yanchor="bottom",
925             text="PCIe: {0:.2f}Mpps".format(pci_limit),
926             font=dict(
927                 size=14,
928                 color=COLORS[-3],
929             ),
930             align="left",
931             showarrow=False
932         ))
933         y_max.append(int((pci_limit / 10) + 1) * 10)
934
935     # Perfect and measured:
936     cidx = 0
937     for name, val in y_sorted.iteritems():
938         hovertext = list()
939         try:
940             for idx in range(len(val["val"])):
941                 htext = ""
942                 if isinstance(val["val"][idx], float):
943                     htext += "No. of Runs: {1}<br>" \
944                              "Mean: {0:.2f}Mpps<br>".format(val["val"][idx],
945                                                             val["count"][idx])
946                 if isinstance(val["diff"][idx], float):
947                     htext += "Diff: {0:.0f}%<br>".format(round(val["diff"][idx]))
948                 if isinstance(val["rel"][idx], float):
949                     htext += "Speedup: {0:.2f}".format(val["rel"][idx])
950                 hovertext.append(htext)
951             traces.append(plgo.Scatter(x=x_vals,
952                                        y=val["val"],
953                                        name=name,
954                                        legendgroup=name,
955                                        mode="lines+markers",
956                                        line=dict(
957                                            color=COLORS[cidx],
958                                            width=2),
959                                        marker=dict(
960                                            symbol="circle",
961                                            size=10
962                                        ),
963                                        text=hovertext,
964                                        hoverinfo="text+name"
965                                        ))
966             traces.append(plgo.Scatter(x=x_vals,
967                                        y=val["ideal"],
968                                        name="{0} perfect".format(name),
969                                        legendgroup=name,
970                                        showlegend=False,
971                                        mode="lines",
972                                        line=dict(
973                                            color=COLORS[cidx],
974                                            width=2,
975                                            dash="dash"),
976                                        text=["Perfect: {0:.2f}Mpps".format(y)
977                                              for y in val["ideal"]],
978                                        hoverinfo="text"
979                                        ))
980             cidx += 1
981         except (IndexError, ValueError, KeyError) as err:
982             logging.warning("No data for '{0}'".format(name))
983             logging.warning(repr(err))
984
985     try:
986         # Create plot
987         logging.info("    Writing file '{0}{1}'.".
988                      format(plot["output-file"], plot["output-file-type"]))
989         layout = deepcopy(plot["layout"])
990         if layout.get("title", None):
991             layout["title"] = "<b>Speedup Multi-core:</b> {0}". \
992                 format(layout["title"])
993         layout["annotations"].extend(annotations)
994         plpl = plgo.Figure(data=traces, layout=layout)
995
996         # Export Plot
997         ploff.plot(plpl,
998                    show_link=False, auto_open=False,
999                    filename='{0}{1}'.format(plot["output-file"],
1000                                             plot["output-file-type"]))
1001     except PlotlyError as err:
1002         logging.error("   Finished with error: {}".
1003                       format(str(err).replace("\n", " ")))
1004         return
1005
1006
1007 def plot_http_server_performance_box(plot, input_data):
1008     """Generate the plot(s) with algorithm: plot_http_server_performance_box
1009     specified in the specification file.
1010
1011     :param plot: Plot to generate.
1012     :param input_data: Data to process.
1013     :type plot: pandas.Series
1014     :type input_data: InputData
1015     """
1016
1017     # Transform the data
1018     logging.info("    Creating the data set for the {0} '{1}'.".
1019                  format(plot.get("type", ""), plot.get("title", "")))
1020     data = input_data.filter_data(plot)
1021     if data is None:
1022         logging.error("No data.")
1023         return
1024
1025     # Prepare the data for the plot
1026     y_vals = dict()
1027     for job in data:
1028         for build in job:
1029             for test in build:
1030                 if y_vals.get(test["name"], None) is None:
1031                     y_vals[test["name"]] = list()
1032                 try:
1033                     y_vals[test["name"]].append(test["result"])
1034                 except (KeyError, TypeError):
1035                     y_vals[test["name"]].append(None)
1036
1037     # Add None to the lists with missing data
1038     max_len = 0
1039     nr_of_samples = list()
1040     for val in y_vals.values():
1041         if len(val) > max_len:
1042             max_len = len(val)
1043         nr_of_samples.append(len(val))
1044     for key, val in y_vals.items():
1045         if len(val) < max_len:
1046             val.extend([None for _ in range(max_len - len(val))])
1047
1048     # Add plot traces
1049     traces = list()
1050     df = pd.DataFrame(y_vals)
1051     df.head()
1052     for i, col in enumerate(df.columns):
1053         name = "{nr}. ({samples:02d} run{plural}) {name}".\
1054             format(nr=(i + 1),
1055                    samples=nr_of_samples[i],
1056                    plural='s' if nr_of_samples[i] > 1 else '',
1057                    name=col.lower().replace('-ndrpdr', ''))
1058         if len(name) > 50:
1059             name_lst = name.split('-')
1060             name = ""
1061             split_name = True
1062             for segment in name_lst:
1063                 if (len(name) + len(segment) + 1) > 50 and split_name:
1064                     name += "<br>    "
1065                     split_name = False
1066                 name += segment + '-'
1067             name = name[:-1]
1068
1069         traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
1070                                y=df[col],
1071                                name=name,
1072                                **plot["traces"]))
1073     try:
1074         # Create plot
1075         plpl = plgo.Figure(data=traces, layout=plot["layout"])
1076
1077         # Export Plot
1078         logging.info("    Writing file '{0}{1}'.".
1079                      format(plot["output-file"], plot["output-file-type"]))
1080         ploff.plot(plpl, show_link=False, auto_open=False,
1081                    filename='{0}{1}'.format(plot["output-file"],
1082                                             plot["output-file-type"]))
1083     except PlotlyError as err:
1084         logging.error("   Finished with error: {}".
1085                       format(str(err).replace("\n", " ")))
1086         return
1087
1088
1089 def plot_service_density_heatmap(plot, input_data):
1090     """Generate the plot(s) with algorithm: plot_service_density_heatmap
1091     specified in the specification file.
1092
1093     :param plot: Plot to generate.
1094     :param input_data: Data to process.
1095     :type plot: pandas.Series
1096     :type input_data: InputData
1097     """
1098
1099     REGEX_CN = re.compile(r'^(\d*)R(\d*)C$')
1100
1101     txt_chains = list()
1102     txt_nodes = list()
1103     vals = dict()
1104
1105     # Transform the data
1106     logging.info("    Creating the data set for the {0} '{1}'.".
1107                  format(plot.get("type", ""), plot.get("title", "")))
1108     data = input_data.filter_data(plot, continue_on_error=True)
1109     if data is None:
1110         logging.error("No data.")
1111         return
1112
1113     for job in data:
1114         for build in job:
1115             for test in build:
1116                 for tag in test['tags']:
1117                     groups = re.search(REGEX_CN, tag)
1118                     if groups:
1119                         c = str(groups.group(1))
1120                         n = str(groups.group(2))
1121                         break
1122                 else:
1123                     continue
1124                 if vals.get(c, None) is None:
1125                     vals[c] = dict()
1126                 if vals[c].get(n, None) is None:
1127                     vals[c][n] = dict(name=test["name"],
1128                                       vals=list(),
1129                                       nr=None,
1130                                       mean=None,
1131                                       stdev=None)
1132                 if plot["include-tests"] == "MRR":
1133                     result = test["result"]["receive-rate"].avg
1134                 elif plot["include-tests"] == "PDR":
1135                     result = test["throughput"]["PDR"]["LOWER"]
1136                 elif plot["include-tests"] == "NDR":
1137                     result = test["throughput"]["NDR"]["LOWER"]
1138                 else:
1139                     result = None
1140
1141                 if result:
1142                     vals[c][n]["vals"].append(result)
1143
1144     for key_c in vals.keys():
1145         txt_chains.append(key_c)
1146         for key_n in vals[key_c].keys():
1147             txt_nodes.append(key_n)
1148             if vals[key_c][key_n]["vals"]:
1149                 vals[key_c][key_n]["nr"] = len(vals[key_c][key_n]["vals"])
1150                 vals[key_c][key_n]["mean"] = \
1151                     round(mean(vals[key_c][key_n]["vals"]) / 1000000, 2)
1152                 vals[key_c][key_n]["stdev"] = \
1153                     round(stdev(vals[key_c][key_n]["vals"]) / 1000000, 2)
1154     txt_nodes = list(set(txt_nodes))
1155
1156     txt_chains = sorted(txt_chains, key=lambda chain: int(chain))
1157     txt_nodes = sorted(txt_nodes, key=lambda node: int(node))
1158
1159     chains = [i + 1 for i in range(len(txt_chains))]
1160     nodes = [i + 1 for i in range(len(txt_nodes))]
1161
1162     data = [list() for _ in range(len(chains))]
1163     for c in chains:
1164         for n in nodes:
1165             try:
1166                 val = vals[txt_chains[c - 1]][txt_nodes[n - 1]]["mean"]
1167             except (KeyError, IndexError):
1168                 val = None
1169             data[c - 1].append(val)
1170
1171     hovertext = list()
1172     annotations = list()
1173
1174     text = ("{name}<br>"
1175             "No. of Samples: {nr}<br>"
1176             "Throughput: {val}<br>"
1177             "Stdev: {stdev}")
1178
1179     for c in range(len(txt_chains)):
1180         hover_line = list()
1181         for n in range(len(txt_nodes)):
1182             if data[c][n] is not None:
1183                 annotations.append(dict(
1184                     x=n+1,
1185                     y=c+1,
1186                     xref="x",
1187                     yref="y",
1188                     xanchor="center",
1189                     yanchor="middle",
1190                     text=str(data[c][n]),
1191                     font=dict(
1192                         size=14,
1193                     ),
1194                     align="center",
1195                     showarrow=False
1196                 ))
1197                 hover_line.append(text.format(
1198                     name=vals[txt_chains[c]][txt_nodes[n]]["name"],
1199                     nr=vals[txt_chains[c]][txt_nodes[n]]["nr"],
1200                     val=data[c][n],
1201                     stdev=vals[txt_chains[c]][txt_nodes[n]]["stdev"]))
1202         hovertext.append(hover_line)
1203
1204     traces = [
1205         plgo.Heatmap(x=nodes,
1206                      y=chains,
1207                      z=data,
1208                      colorbar=dict(
1209                          title="Packet Throughput [Mpps]",
1210                          titleside="right",
1211                          titlefont=dict(
1212                             size=14
1213                          ),
1214                      ),
1215                      showscale=True,
1216                      colorscale="Reds",
1217                      text=hovertext,
1218                      hoverinfo="text")
1219     ]
1220
1221     for idx, item in enumerate(txt_nodes):
1222         annotations.append(dict(
1223             x=idx+1,
1224             y=0,
1225             xref="x",
1226             yref="y",
1227             xanchor="center",
1228             yanchor="top",
1229             text=item,
1230             font=dict(
1231                 size=16,
1232             ),
1233             align="center",
1234             showarrow=False
1235         ))
1236     for idx, item in enumerate(txt_chains):
1237         annotations.append(dict(
1238             x=0.3,
1239             y=idx+1,
1240             xref="x",
1241             yref="y",
1242             xanchor="right",
1243             yanchor="middle",
1244             text=item,
1245             font=dict(
1246                 size=16,
1247             ),
1248             align="center",
1249             showarrow=False
1250         ))
1251     # X-axis:
1252     annotations.append(dict(
1253         x=0.55,
1254         y=1.05,
1255         xref="paper",
1256         yref="paper",
1257         xanchor="center",
1258         yanchor="middle",
1259         text="<b>No. of Network Functions per Service Instance</b>",
1260         font=dict(
1261             size=16,
1262         ),
1263         align="center",
1264         showarrow=False
1265     ))
1266     # Y-axis:
1267     annotations.append(dict(
1268         x=-0.04,
1269         y=0.5,
1270         xref="paper",
1271         yref="paper",
1272         xanchor="center",
1273         yanchor="middle",
1274         text="<b>No. of Service Instances</b>",
1275         font=dict(
1276             size=16,
1277         ),
1278         align="center",
1279         textangle=270,
1280         showarrow=False
1281     ))
1282     updatemenus = list([
1283         dict(
1284             x=1.0,
1285             y=0.0,
1286             xanchor='right',
1287             yanchor='bottom',
1288             direction='up',
1289             buttons=list([
1290                 dict(
1291                     args=[{"colorscale": "Reds", "reversescale": False}],
1292                     label="Red",
1293                     method="update"
1294                 ),
1295                 dict(
1296                     args=[{"colorscale": "Blues", "reversescale": True}],
1297                     label="Blue",
1298                     method="update"
1299                 ),
1300                 dict(
1301                     args=[{"colorscale": "Greys", "reversescale": True}],
1302                     label="Grey",
1303                     method="update"
1304                 ),
1305                 dict(
1306                     args=[{"colorscale": "Greens", "reversescale": True}],
1307                     label="Green",
1308                     method="update"
1309                 ),
1310                 dict(
1311                     args=[{"colorscale": "RdBu", "reversescale": False}],
1312                     label="RedBlue",
1313                     method="update"
1314                 ),
1315                 dict(
1316                     args=[{"colorscale": "Picnic", "reversescale": False}],
1317                     label="Picnic",
1318                     method="update"
1319                 ),
1320                 dict(
1321                     args=[{"colorscale": "Rainbow", "reversescale": False}],
1322                     label="Rainbow",
1323                     method="update"
1324                 ),
1325                 dict(
1326                     args=[{"colorscale": "Portland", "reversescale": False}],
1327                     label="Portland",
1328                     method="update"
1329                 ),
1330                 dict(
1331                     args=[{"colorscale": "Jet", "reversescale": False}],
1332                     label="Jet",
1333                     method="update"
1334                 ),
1335                 dict(
1336                     args=[{"colorscale": "Hot", "reversescale": True}],
1337                     label="Hot",
1338                     method="update"
1339                 ),
1340                 dict(
1341                     args=[{"colorscale": "Blackbody", "reversescale": True}],
1342                     label="Blackbody",
1343                     method="update"
1344                 ),
1345                 dict(
1346                     args=[{"colorscale": "Earth", "reversescale": True}],
1347                     label="Earth",
1348                     method="update"
1349                 ),
1350                 dict(
1351                     args=[{"colorscale": "Electric", "reversescale": True}],
1352                     label="Electric",
1353                     method="update"
1354                 ),
1355                 dict(
1356                     args=[{"colorscale": "Viridis", "reversescale": True}],
1357                     label="Viridis",
1358                     method="update"
1359                 ),
1360                 dict(
1361                     args=[{"colorscale": "Cividis", "reversescale": True}],
1362                     label="Cividis",
1363                     method="update"
1364                 ),
1365             ])
1366         )
1367     ])
1368
1369     try:
1370         layout = deepcopy(plot["layout"])
1371     except KeyError as err:
1372         logging.error("Finished with error: No layout defined")
1373         logging.error(repr(err))
1374         return
1375
1376     layout["annotations"] = annotations
1377     layout['updatemenus'] = updatemenus
1378
1379     try:
1380         # Create plot
1381         plpl = plgo.Figure(data=traces, layout=layout)
1382
1383         # Export Plot
1384         logging.info("    Writing file '{0}{1}'.".
1385                      format(plot["output-file"], plot["output-file-type"]))
1386         ploff.plot(plpl, show_link=False, auto_open=False,
1387                    filename='{0}{1}'.format(plot["output-file"],
1388                                             plot["output-file-type"]))
1389     except PlotlyError as err:
1390         logging.error("   Finished with error: {}".
1391                       format(str(err).replace("\n", " ")))
1392         return