a628d3f1549b89917ee5ee321f059fd88a2a9c3a
[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 REGEX_NIC = re.compile(r'\d*ge\dp\d\D*\d*-')
39
40
41 def generate_plots(spec, data):
42     """Generate all plots specified in the specification file.
43
44     :param spec: Specification read from the specification file.
45     :param data: Data to process.
46     :type spec: Specification
47     :type data: InputData
48     """
49
50     logging.info("Generating the plots ...")
51     for index, plot in enumerate(spec.plots):
52         try:
53             logging.info("  Plot nr {0}: {1}".format(index + 1,
54                                                      plot.get("title", "")))
55             plot["limits"] = spec.configuration["limits"]
56             eval(plot["algorithm"])(plot, data)
57             logging.info("  Done.")
58         except NameError as err:
59             logging.error("Probably algorithm '{alg}' is not defined: {err}".
60                           format(alg=plot["algorithm"], err=repr(err)))
61     logging.info("Done.")
62
63
64 def plot_service_density_reconf_box_name(plot, input_data):
65     """Generate the plot(s) with algorithm: plot_service_density_reconf_box_name
66     specified in the specification file.
67
68     :param plot: Plot to generate.
69     :param input_data: Data to process.
70     :type plot: pandas.Series
71     :type input_data: InputData
72     """
73
74     # Transform the data
75     plot_title = plot.get("title", "")
76     logging.info("    Creating the data set for the {0} '{1}'.".
77                  format(plot.get("type", ""), plot_title))
78     data = input_data.filter_tests_by_name(
79         plot, params=["result", "parent", "tags", "type"])
80     if data is None:
81         logging.error("No data.")
82         return
83
84     # Prepare the data for the plot
85     y_vals = OrderedDict()
86     loss = dict()
87     for job in data:
88         for build in job:
89             for test in build:
90                 if y_vals.get(test["parent"], None) is None:
91                     y_vals[test["parent"]] = list()
92                     loss[test["parent"]] = list()
93                 try:
94                     y_vals[test["parent"]].append(test["result"]["time"])
95                     loss[test["parent"]].append(test["result"]["loss"])
96                 except (KeyError, TypeError):
97                     y_vals[test["parent"]].append(None)
98
99     # Add None to the lists with missing data
100     max_len = 0
101     nr_of_samples = list()
102     for val in y_vals.values():
103         if len(val) > max_len:
104             max_len = len(val)
105         nr_of_samples.append(len(val))
106     for key, val in y_vals.items():
107         if len(val) < max_len:
108             val.extend([None for _ in range(max_len - len(val))])
109
110     # Add plot traces
111     traces = list()
112     df = pd.DataFrame(y_vals)
113     df.head()
114     for i, col in enumerate(df.columns):
115         tst_name = re.sub(REGEX_NIC, "",
116                           col.lower().replace('-ndrpdr', '').
117                           replace('2n1l-', ''))
118         tst_name = "-".join(tst_name.split("-")[3:-2])
119         name = "{nr}. ({samples:02d} run{plural}, avg pkt loss: {loss:.1f}, " \
120                "stdev: {stdev:.2f}) {name}".format(
121                     nr=(i + 1),
122                     samples=nr_of_samples[i],
123                     plural='s' if nr_of_samples[i] > 1 else '',
124                     name=tst_name,
125                     loss=mean(loss[col]) / 1000000,
126                     stdev=stdev(loss[col]) / 1000000)
127
128         traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
129                                y=[y if y else None for y in df[col]],
130                                name=name,
131                                hoverinfo="x+y",
132                                boxpoints="outliers",
133                                whiskerwidth=0))
134     try:
135         # Create plot
136         layout = deepcopy(plot["layout"])
137         layout["title"] = "<b>Time Lost:</b> {0}".format(layout["title"])
138         layout["yaxis"]["title"] = "<b>Implied Time Lost [s]</b>"
139         layout["legend"]["font"]["size"] = 14
140         layout["yaxis"].pop("range")
141         plpl = plgo.Figure(data=traces, layout=layout)
142
143         # Export Plot
144         file_type = plot.get("output-file-type", ".html")
145         logging.info("    Writing file '{0}{1}'.".
146                      format(plot["output-file"], file_type))
147         ploff.plot(plpl, show_link=False, auto_open=False,
148                    filename='{0}{1}'.format(plot["output-file"], file_type))
149     except PlotlyError as err:
150         logging.error("   Finished with error: {}".
151                       format(repr(err).replace("\n", " ")))
152         return
153
154
155 def plot_performance_box_name(plot, input_data):
156     """Generate the plot(s) with algorithm: plot_performance_box_name
157     specified in the specification file.
158
159     :param plot: Plot to generate.
160     :param input_data: Data to process.
161     :type plot: pandas.Series
162     :type input_data: InputData
163     """
164
165     # Transform the data
166     plot_title = plot.get("title", "")
167     logging.info("    Creating the data set for the {0} '{1}'.".
168                  format(plot.get("type", ""), plot_title))
169     data = input_data.filter_tests_by_name(
170         plot, params=["throughput", "parent", "tags", "type"])
171     if data is None:
172         logging.error("No data.")
173         return
174
175     # Prepare the data for the plot
176     y_vals = OrderedDict()
177     for job in data:
178         for build in job:
179             for test in build:
180                 if y_vals.get(test["parent"], None) is None:
181                     y_vals[test["parent"]] = list()
182                 try:
183                     if test["type"] in ("NDRPDR", ):
184                         if "-pdr" in plot_title.lower():
185                             y_vals[test["parent"]].\
186                                 append(test["throughput"]["PDR"]["LOWER"])
187                         elif "-ndr" in plot_title.lower():
188                             y_vals[test["parent"]]. \
189                                 append(test["throughput"]["NDR"]["LOWER"])
190                         else:
191                             continue
192                     elif test["type"] in ("SOAK", ):
193                         y_vals[test["parent"]].\
194                             append(test["throughput"]["LOWER"])
195                     else:
196                         continue
197                 except (KeyError, TypeError):
198                     y_vals[test["parent"]].append(None)
199
200     # Add None to the lists with missing data
201     max_len = 0
202     nr_of_samples = list()
203     for val in y_vals.values():
204         if len(val) > max_len:
205             max_len = len(val)
206         nr_of_samples.append(len(val))
207     for key, val in y_vals.items():
208         if len(val) < max_len:
209             val.extend([None for _ in range(max_len - len(val))])
210
211     # Add plot traces
212     traces = list()
213     df = pd.DataFrame(y_vals)
214     df.head()
215     y_max = list()
216     for i, col in enumerate(df.columns):
217         tst_name = re.sub(REGEX_NIC, "",
218                           col.lower().replace('-ndrpdr', '').
219                           replace('2n1l-', ''))
220         name = "{nr}. ({samples:02d} run{plural}) {name}".\
221             format(nr=(i + 1),
222                    samples=nr_of_samples[i],
223                    plural='s' if nr_of_samples[i] > 1 else '',
224                    name=tst_name)
225
226         logging.debug(name)
227         traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
228                                y=[y / 1000000 if y else None for y in df[col]],
229                                name=name,
230                                hoverinfo="x+y",
231                                boxpoints="outliers",
232                                whiskerwidth=0))
233         try:
234             val_max = max(df[col])
235         except ValueError as err:
236             logging.error(repr(err))
237             continue
238         if val_max:
239             y_max.append(int(val_max / 1000000) + 2)
240
241     try:
242         # Create plot
243         layout = deepcopy(plot["layout"])
244         if layout.get("title", None):
245             layout["title"] = "<b>Throughput:</b> {0}". \
246                 format(layout["title"])
247         if y_max:
248             layout["yaxis"]["range"] = [0, max(y_max)]
249         plpl = plgo.Figure(data=traces, layout=layout)
250
251         # Export Plot
252         file_type = plot.get("output-file-type", ".html")
253         logging.info("    Writing file '{0}{1}'.".
254                      format(plot["output-file"], file_type))
255         ploff.plot(plpl, show_link=False, auto_open=False,
256                    filename='{0}{1}'.format(plot["output-file"], file_type))
257     except PlotlyError as err:
258         logging.error("   Finished with error: {}".
259                       format(repr(err).replace("\n", " ")))
260         return
261
262
263 def plot_latency_error_bars_name(plot, input_data):
264     """Generate the plot(s) with algorithm: plot_latency_error_bars_name
265     specified in the specification file.
266
267     :param plot: Plot to generate.
268     :param input_data: Data to process.
269     :type plot: pandas.Series
270     :type input_data: InputData
271     """
272
273     # Transform the data
274     plot_title = plot.get("title", "")
275     logging.info("    Creating the data set for the {0} '{1}'.".
276                  format(plot.get("type", ""), plot_title))
277     data = input_data.filter_tests_by_name(
278         plot, params=["latency", "parent", "tags", "type"])
279     if data is None:
280         logging.error("No data.")
281         return
282
283     # Prepare the data for the plot
284     y_tmp_vals = OrderedDict()
285     for job in data:
286         for build in job:
287             for test in build:
288                 try:
289                     logging.debug("test['latency']: {0}\n".
290                                  format(test["latency"]))
291                 except ValueError as err:
292                     logging.warning(repr(err))
293                 if y_tmp_vals.get(test["parent"], None) is None:
294                     y_tmp_vals[test["parent"]] = [
295                         list(),  # direction1, min
296                         list(),  # direction1, avg
297                         list(),  # direction1, max
298                         list(),  # direction2, min
299                         list(),  # direction2, avg
300                         list()   # direction2, max
301                     ]
302                 try:
303                     if test["type"] in ("NDRPDR", ):
304                         if "-pdr" in plot_title.lower():
305                             ttype = "PDR"
306                         elif "-ndr" in plot_title.lower():
307                             ttype = "NDR"
308                         else:
309                             logging.warning("Invalid test type: {0}".
310                                             format(test["type"]))
311                             continue
312                         y_tmp_vals[test["parent"]][0].append(
313                             test["latency"][ttype]["direction1"]["min"])
314                         y_tmp_vals[test["parent"]][1].append(
315                             test["latency"][ttype]["direction1"]["avg"])
316                         y_tmp_vals[test["parent"]][2].append(
317                             test["latency"][ttype]["direction1"]["max"])
318                         y_tmp_vals[test["parent"]][3].append(
319                             test["latency"][ttype]["direction2"]["min"])
320                         y_tmp_vals[test["parent"]][4].append(
321                             test["latency"][ttype]["direction2"]["avg"])
322                         y_tmp_vals[test["parent"]][5].append(
323                             test["latency"][ttype]["direction2"]["max"])
324                     else:
325                         logging.warning("Invalid test type: {0}".
326                                         format(test["type"]))
327                         continue
328                 except (KeyError, TypeError) as err:
329                     logging.warning(repr(err))
330
331     x_vals = list()
332     y_vals = list()
333     y_mins = list()
334     y_maxs = list()
335     nr_of_samples = list()
336     for key, val in y_tmp_vals.items():
337         name = re.sub(REGEX_NIC, "", key.replace('-ndrpdr', '').
338                       replace('2n1l-', ''))
339         x_vals.append(name)  # dir 1
340         y_vals.append(mean(val[1]) if val[1] else None)
341         y_mins.append(mean(val[0]) if val[0] else None)
342         y_maxs.append(mean(val[2]) if val[2] else None)
343         nr_of_samples.append(len(val[1]) if val[1] else 0)
344         x_vals.append(name)  # dir 2
345         y_vals.append(mean(val[4]) if val[4] else None)
346         y_mins.append(mean(val[3]) if val[3] else None)
347         y_maxs.append(mean(val[5]) if val[5] else None)
348         nr_of_samples.append(len(val[3]) if val[3] else 0)
349
350     traces = list()
351     annotations = list()
352
353     for idx in range(len(x_vals)):
354         if not bool(int(idx % 2)):
355             direction = "West-East"
356         else:
357             direction = "East-West"
358         hovertext = ("No. of Runs: {nr}<br>"
359                      "Test: {test}<br>"
360                      "Direction: {dir}<br>".format(test=x_vals[idx],
361                                                    dir=direction,
362                                                    nr=nr_of_samples[idx]))
363         if isinstance(y_maxs[idx], float):
364             hovertext += "Max: {max:.2f}uSec<br>".format(max=y_maxs[idx])
365         if isinstance(y_vals[idx], float):
366             hovertext += "Mean: {avg:.2f}uSec<br>".format(avg=y_vals[idx])
367         if isinstance(y_mins[idx], float):
368             hovertext += "Min: {min:.2f}uSec".format(min=y_mins[idx])
369
370         if isinstance(y_maxs[idx], float) and isinstance(y_vals[idx], float):
371             array = [y_maxs[idx] - y_vals[idx], ]
372         else:
373             array = [None, ]
374         if isinstance(y_mins[idx], float) and isinstance(y_vals[idx], float):
375             arrayminus = [y_vals[idx] - y_mins[idx], ]
376         else:
377             arrayminus = [None, ]
378         traces.append(plgo.Scatter(
379             x=[idx, ],
380             y=[y_vals[idx], ],
381             name=x_vals[idx],
382             legendgroup=x_vals[idx],
383             showlegend=bool(int(idx % 2)),
384             mode="markers",
385             error_y=dict(
386                 type='data',
387                 symmetric=False,
388                 array=array,
389                 arrayminus=arrayminus,
390                 color=COLORS[int(idx / 2)]
391             ),
392             marker=dict(
393                 size=10,
394                 color=COLORS[int(idx / 2)],
395             ),
396             text=hovertext,
397             hoverinfo="text",
398         ))
399         annotations.append(dict(
400             x=idx,
401             y=0,
402             xref="x",
403             yref="y",
404             xanchor="center",
405             yanchor="top",
406             text="E-W" if bool(int(idx % 2)) else "W-E",
407             font=dict(
408                 size=16,
409             ),
410             align="center",
411             showarrow=False
412         ))
413
414     try:
415         # Create plot
416         file_type = plot.get("output-file-type", ".html")
417         logging.info("    Writing file '{0}{1}'.".
418                      format(plot["output-file"], file_type))
419         layout = deepcopy(plot["layout"])
420         if layout.get("title", None):
421             layout["title"] = "<b>Latency:</b> {0}".\
422                 format(layout["title"])
423         layout["annotations"] = annotations
424         plpl = plgo.Figure(data=traces, layout=layout)
425
426         # Export Plot
427         ploff.plot(plpl,
428                    show_link=False, auto_open=False,
429                    filename='{0}{1}'.format(plot["output-file"], file_type))
430     except PlotlyError as err:
431         logging.error("   Finished with error: {}".
432                       format(str(err).replace("\n", " ")))
433         return
434
435
436 def plot_throughput_speedup_analysis_name(plot, input_data):
437     """Generate the plot(s) with algorithm:
438     plot_throughput_speedup_analysis_name
439     specified in the specification file.
440
441     :param plot: Plot to generate.
442     :param input_data: Data to process.
443     :type plot: pandas.Series
444     :type input_data: InputData
445     """
446
447     # Transform the data
448     plot_title = plot.get("title", "")
449     logging.info("    Creating the data set for the {0} '{1}'.".
450                  format(plot.get("type", ""), plot_title))
451     data = input_data.filter_tests_by_name(
452         plot, params=["throughput", "parent", "tags", "type"])
453     if data is None:
454         logging.error("No data.")
455         return
456
457     y_vals = OrderedDict()
458     for job in data:
459         for build in job:
460             for test in build:
461                 if y_vals.get(test["parent"], None) is None:
462                     y_vals[test["parent"]] = {"1": list(),
463                                               "2": list(),
464                                               "4": list()}
465                 try:
466                     if test["type"] in ("NDRPDR",):
467                         if "-pdr" in plot_title.lower():
468                             ttype = "PDR"
469                         elif "-ndr" in plot_title.lower():
470                             ttype = "NDR"
471                         else:
472                             continue
473                         if "1C" in test["tags"]:
474                             y_vals[test["parent"]]["1"]. \
475                                 append(test["throughput"][ttype]["LOWER"])
476                         elif "2C" in test["tags"]:
477                             y_vals[test["parent"]]["2"]. \
478                                 append(test["throughput"][ttype]["LOWER"])
479                         elif "4C" in test["tags"]:
480                             y_vals[test["parent"]]["4"]. \
481                                 append(test["throughput"][ttype]["LOWER"])
482                 except (KeyError, TypeError):
483                     pass
484
485     if not y_vals:
486         logging.warning("No data for the plot '{}'".
487                         format(plot.get("title", "")))
488         return
489
490     y_1c_max = dict()
491     for test_name, test_vals in y_vals.items():
492         for key, test_val in test_vals.items():
493             if test_val:
494                 avg_val = sum(test_val) / len(test_val)
495                 y_vals[test_name][key] = (avg_val, len(test_val))
496                 ideal = avg_val / (int(key) * 1000000.0)
497                 if test_name not in y_1c_max or ideal > y_1c_max[test_name]:
498                     y_1c_max[test_name] = ideal
499
500     vals = OrderedDict()
501     y_max = list()
502     nic_limit = 0
503     lnk_limit = 0
504     pci_limit = plot["limits"]["pci"]["pci-g3-x8"]
505     for test_name, test_vals in y_vals.items():
506         try:
507             if test_vals["1"][1]:
508                 name = re.sub(REGEX_NIC, "", test_name.replace('-ndrpdr', '').
509                               replace('2n1l-', ''))
510                 vals[name] = OrderedDict()
511                 y_val_1 = test_vals["1"][0] / 1000000.0
512                 y_val_2 = test_vals["2"][0] / 1000000.0 if test_vals["2"][0] \
513                     else None
514                 y_val_4 = test_vals["4"][0] / 1000000.0 if test_vals["4"][0] \
515                     else None
516
517                 vals[name]["val"] = [y_val_1, y_val_2, y_val_4]
518                 vals[name]["rel"] = [1.0, None, None]
519                 vals[name]["ideal"] = [y_1c_max[test_name],
520                                        y_1c_max[test_name] * 2,
521                                        y_1c_max[test_name] * 4]
522                 vals[name]["diff"] = [(y_val_1 - y_1c_max[test_name]) * 100 /
523                                       y_val_1, None, None]
524                 vals[name]["count"] = [test_vals["1"][1],
525                                        test_vals["2"][1],
526                                        test_vals["4"][1]]
527
528                 try:
529                     val_max = max(vals[name]["val"])
530                 except ValueError as err:
531                     logging.error(repr(err))
532                     continue
533                 if val_max:
534                     y_max.append(val_max)
535
536                 if y_val_2:
537                     vals[name]["rel"][1] = round(y_val_2 / y_val_1, 2)
538                     vals[name]["diff"][1] = \
539                         (y_val_2 - vals[name]["ideal"][1]) * 100 / y_val_2
540                 if y_val_4:
541                     vals[name]["rel"][2] = round(y_val_4 / y_val_1, 2)
542                     vals[name]["diff"][2] = \
543                         (y_val_4 - vals[name]["ideal"][2]) * 100 / y_val_4
544         except IndexError as err:
545             logging.warning("No data for '{0}'".format(test_name))
546             logging.warning(repr(err))
547
548         # Limits:
549         if "x520" in test_name:
550             limit = plot["limits"]["nic"]["x520"]
551         elif "x710" in test_name:
552             limit = plot["limits"]["nic"]["x710"]
553         elif "xxv710" in test_name:
554             limit = plot["limits"]["nic"]["xxv710"]
555         elif "xl710" in test_name:
556             limit = plot["limits"]["nic"]["xl710"]
557         elif "x553" in test_name:
558             limit = plot["limits"]["nic"]["x553"]
559         else:
560             limit = 0
561         if limit > nic_limit:
562             nic_limit = limit
563
564         mul = 2 if "ge2p" in test_name else 1
565         if "10ge" in test_name:
566             limit = plot["limits"]["link"]["10ge"] * mul
567         elif "25ge" in test_name:
568             limit = plot["limits"]["link"]["25ge"] * mul
569         elif "40ge" in test_name:
570             limit = plot["limits"]["link"]["40ge"] * mul
571         elif "100ge" in test_name:
572             limit = plot["limits"]["link"]["100ge"] * mul
573         else:
574             limit = 0
575         if limit > lnk_limit:
576             lnk_limit = limit
577
578     traces = list()
579     annotations = list()
580     x_vals = [1, 2, 4]
581
582     # Limits:
583     try:
584         threshold = 1.1 * max(y_max)  # 10%
585     except ValueError as err:
586         logging.error(err)
587         return
588     nic_limit /= 1000000.0
589     traces.append(plgo.Scatter(
590         x=x_vals,
591         y=[nic_limit, ] * len(x_vals),
592         name="NIC: {0:.2f}Mpps".format(nic_limit),
593         showlegend=False,
594         mode="lines",
595         line=dict(
596             dash="dot",
597             color=COLORS[-1],
598             width=1),
599         hoverinfo="none"
600     ))
601     annotations.append(dict(
602         x=1,
603         y=nic_limit,
604         xref="x",
605         yref="y",
606         xanchor="left",
607         yanchor="bottom",
608         text="NIC: {0:.2f}Mpps".format(nic_limit),
609         font=dict(
610             size=14,
611             color=COLORS[-1],
612         ),
613         align="left",
614         showarrow=False
615     ))
616     y_max.append(nic_limit)
617
618     lnk_limit /= 1000000.0
619     if lnk_limit < threshold:
620         traces.append(plgo.Scatter(
621             x=x_vals,
622             y=[lnk_limit, ] * len(x_vals),
623             name="Link: {0:.2f}Mpps".format(lnk_limit),
624             showlegend=False,
625             mode="lines",
626             line=dict(
627                 dash="dot",
628                 color=COLORS[-2],
629                 width=1),
630             hoverinfo="none"
631         ))
632         annotations.append(dict(
633             x=1,
634             y=lnk_limit,
635             xref="x",
636             yref="y",
637             xanchor="left",
638             yanchor="bottom",
639             text="Link: {0:.2f}Mpps".format(lnk_limit),
640             font=dict(
641                 size=14,
642                 color=COLORS[-2],
643             ),
644             align="left",
645             showarrow=False
646         ))
647         y_max.append(lnk_limit)
648
649     pci_limit /= 1000000.0
650     if (pci_limit < threshold and
651         (pci_limit < lnk_limit * 0.95 or lnk_limit > lnk_limit * 1.05)):
652         traces.append(plgo.Scatter(
653             x=x_vals,
654             y=[pci_limit, ] * len(x_vals),
655             name="PCIe: {0:.2f}Mpps".format(pci_limit),
656             showlegend=False,
657             mode="lines",
658             line=dict(
659                 dash="dot",
660                 color=COLORS[-3],
661                 width=1),
662             hoverinfo="none"
663         ))
664         annotations.append(dict(
665             x=1,
666             y=pci_limit,
667             xref="x",
668             yref="y",
669             xanchor="left",
670             yanchor="bottom",
671             text="PCIe: {0:.2f}Mpps".format(pci_limit),
672             font=dict(
673                 size=14,
674                 color=COLORS[-3],
675             ),
676             align="left",
677             showarrow=False
678         ))
679         y_max.append(pci_limit)
680
681     # Perfect and measured:
682     cidx = 0
683     for name, val in vals.iteritems():
684         hovertext = list()
685         try:
686             for idx in range(len(val["val"])):
687                 htext = ""
688                 if isinstance(val["val"][idx], float):
689                     htext += "No. of Runs: {1}<br>" \
690                              "Mean: {0:.2f}Mpps<br>".format(val["val"][idx],
691                                                             val["count"][idx])
692                 if isinstance(val["diff"][idx], float):
693                     htext += "Diff: {0:.0f}%<br>".format(
694                         round(val["diff"][idx]))
695                 if isinstance(val["rel"][idx], float):
696                     htext += "Speedup: {0:.2f}".format(val["rel"][idx])
697                 hovertext.append(htext)
698             traces.append(plgo.Scatter(x=x_vals,
699                                        y=val["val"],
700                                        name=name,
701                                        legendgroup=name,
702                                        mode="lines+markers",
703                                        line=dict(
704                                            color=COLORS[cidx],
705                                            width=2),
706                                        marker=dict(
707                                            symbol="circle",
708                                            size=10
709                                        ),
710                                        text=hovertext,
711                                        hoverinfo="text+name"
712                                        ))
713             traces.append(plgo.Scatter(x=x_vals,
714                                        y=val["ideal"],
715                                        name="{0} perfect".format(name),
716                                        legendgroup=name,
717                                        showlegend=False,
718                                        mode="lines",
719                                        line=dict(
720                                            color=COLORS[cidx],
721                                            width=2,
722                                            dash="dash"),
723                                        text=["Perfect: {0:.2f}Mpps".format(y)
724                                              for y in val["ideal"]],
725                                        hoverinfo="text"
726                                        ))
727             cidx += 1
728         except (IndexError, ValueError, KeyError) as err:
729             logging.warning("No data for '{0}'".format(name))
730             logging.warning(repr(err))
731
732     try:
733         # Create plot
734         file_type = plot.get("output-file-type", ".html")
735         logging.info("    Writing file '{0}{1}'.".
736                      format(plot["output-file"], file_type))
737         layout = deepcopy(plot["layout"])
738         if layout.get("title", None):
739             layout["title"] = "<b>Speedup Multi-core:</b> {0}". \
740                 format(layout["title"])
741         layout["yaxis"]["range"] = [0, int(max(y_max) * 1.1)]
742         layout["annotations"].extend(annotations)
743         plpl = plgo.Figure(data=traces, layout=layout)
744
745         # Export Plot
746         ploff.plot(plpl,
747                    show_link=False, auto_open=False,
748                    filename='{0}{1}'.format(plot["output-file"], file_type))
749     except PlotlyError as err:
750         logging.error("   Finished with error: {}".
751                       format(repr(err).replace("\n", " ")))
752         return
753
754
755 def plot_performance_box(plot, input_data):
756     """Generate the plot(s) with algorithm: plot_performance_box
757     specified in the specification file.
758
759     TODO: Remove when not needed.
760
761     :param plot: Plot to generate.
762     :param input_data: Data to process.
763     :type plot: pandas.Series
764     :type input_data: InputData
765     """
766
767     # Transform the data
768     plot_title = plot.get("title", "")
769     logging.info("    Creating the data set for the {0} '{1}'.".
770                  format(plot.get("type", ""), plot_title))
771     data = input_data.filter_data(plot)
772     if data is None:
773         logging.error("No data.")
774         return
775
776     # Prepare the data for the plot
777     y_vals = dict()
778     y_tags = dict()
779     for job in data:
780         for build in job:
781             for test in build:
782                 if y_vals.get(test["parent"], None) is None:
783                     y_vals[test["parent"]] = list()
784                     y_tags[test["parent"]] = test.get("tags", None)
785                 try:
786                     if test["type"] in ("NDRPDR", ):
787                         if "-pdr" in plot_title.lower():
788                             y_vals[test["parent"]].\
789                                 append(test["throughput"]["PDR"]["LOWER"])
790                         elif "-ndr" in plot_title.lower():
791                             y_vals[test["parent"]]. \
792                                 append(test["throughput"]["NDR"]["LOWER"])
793                         else:
794                             continue
795                     elif test["type"] in ("SOAK", ):
796                         y_vals[test["parent"]].\
797                             append(test["throughput"]["LOWER"])
798                     else:
799                         continue
800                 except (KeyError, TypeError):
801                     y_vals[test["parent"]].append(None)
802
803     # Sort the tests
804     order = plot.get("sort", None)
805     if order and y_tags:
806         y_sorted = OrderedDict()
807         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
808         for tag in order:
809             logging.debug(tag)
810             for suite, tags in y_tags_l.items():
811                 if "not " in tag:
812                     tag = tag.split(" ")[-1]
813                     if tag.lower() in tags:
814                         continue
815                 else:
816                     if tag.lower() not in tags:
817                         continue
818                 try:
819                     y_sorted[suite] = y_vals.pop(suite)
820                     y_tags_l.pop(suite)
821                     logging.debug(suite)
822                 except KeyError as err:
823                     logging.error("Not found: {0}".format(repr(err)))
824                 finally:
825                     break
826     else:
827         y_sorted = y_vals
828
829     # Add None to the lists with missing data
830     max_len = 0
831     nr_of_samples = list()
832     for val in y_sorted.values():
833         if len(val) > max_len:
834             max_len = len(val)
835         nr_of_samples.append(len(val))
836     for key, val in y_sorted.items():
837         if len(val) < max_len:
838             val.extend([None for _ in range(max_len - len(val))])
839
840     # Add plot traces
841     traces = list()
842     df = pd.DataFrame(y_sorted)
843     df.head()
844     y_max = list()
845     for i, col in enumerate(df.columns):
846         tst_name = re.sub(REGEX_NIC, "",
847                           col.lower().replace('-ndrpdr', '').
848                           replace('2n1l-', ''))
849         name = "{nr}. ({samples:02d} run{plural}) {name}".\
850             format(nr=(i + 1),
851                    samples=nr_of_samples[i],
852                    plural='s' if nr_of_samples[i] > 1 else '',
853                    name=tst_name)
854
855         logging.debug(name)
856         traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
857                                y=[y / 1000000 if y else None for y in df[col]],
858                                name=name,
859                                **plot["traces"]))
860         try:
861             val_max = max(df[col])
862         except ValueError as err:
863             logging.error(repr(err))
864             continue
865         if val_max:
866             y_max.append(int(val_max / 1000000) + 2)
867
868     try:
869         # Create plot
870         layout = deepcopy(plot["layout"])
871         if layout.get("title", None):
872             layout["title"] = "<b>Throughput:</b> {0}". \
873                 format(layout["title"])
874         if y_max:
875             layout["yaxis"]["range"] = [0, max(y_max)]
876         plpl = plgo.Figure(data=traces, layout=layout)
877
878         # Export Plot
879         logging.info("    Writing file '{0}{1}'.".
880                      format(plot["output-file"], plot["output-file-type"]))
881         ploff.plot(plpl, show_link=False, auto_open=False,
882                    filename='{0}{1}'.format(plot["output-file"],
883                                             plot["output-file-type"]))
884     except PlotlyError as err:
885         logging.error("   Finished with error: {}".
886                       format(repr(err).replace("\n", " ")))
887         return
888
889
890 def plot_soak_bars(plot, input_data):
891     """Generate the plot(s) with algorithm: plot_soak_bars
892     specified in the specification file.
893
894     :param plot: Plot to generate.
895     :param input_data: Data to process.
896     :type plot: pandas.Series
897     :type input_data: InputData
898     """
899
900     # Transform the data
901     plot_title = plot.get("title", "")
902     logging.info("    Creating the data set for the {0} '{1}'.".
903                  format(plot.get("type", ""), plot_title))
904     data = input_data.filter_data(plot)
905     if data is None:
906         logging.error("No data.")
907         return
908
909     # Prepare the data for the plot
910     y_vals = dict()
911     y_tags = dict()
912     for job in data:
913         for build in job:
914             for test in build:
915                 if y_vals.get(test["parent"], None) is None:
916                     y_tags[test["parent"]] = test.get("tags", None)
917                 try:
918                     if test["type"] in ("SOAK", ):
919                         y_vals[test["parent"]] = test["throughput"]
920                     else:
921                         continue
922                 except (KeyError, TypeError):
923                     y_vals[test["parent"]] = dict()
924
925     # Sort the tests
926     order = plot.get("sort", None)
927     if order and y_tags:
928         y_sorted = OrderedDict()
929         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
930         for tag in order:
931             logging.debug(tag)
932             for suite, tags in y_tags_l.items():
933                 if "not " in tag:
934                     tag = tag.split(" ")[-1]
935                     if tag.lower() in tags:
936                         continue
937                 else:
938                     if tag.lower() not in tags:
939                         continue
940                 try:
941                     y_sorted[suite] = y_vals.pop(suite)
942                     y_tags_l.pop(suite)
943                     logging.debug(suite)
944                 except KeyError as err:
945                     logging.error("Not found: {0}".format(repr(err)))
946                 finally:
947                     break
948     else:
949         y_sorted = y_vals
950
951     idx = 0
952     y_max = 0
953     traces = list()
954     for test_name, test_data in y_sorted.items():
955         idx += 1
956         name = "{nr}. {name}".\
957             format(nr=idx, name=test_name.lower().replace('-soak', ''))
958         if len(name) > 50:
959             name_lst = name.split('-')
960             name = ""
961             split_name = True
962             for segment in name_lst:
963                 if (len(name) + len(segment) + 1) > 50 and split_name:
964                     name += "<br>    "
965                     split_name = False
966                 name += segment + '-'
967             name = name[:-1]
968
969         y_val = test_data.get("LOWER", None)
970         if y_val:
971             y_val /= 1000000
972             if y_val > y_max:
973                 y_max = y_val
974
975         time = "No Information"
976         result = "No Information"
977         hovertext = ("{name}<br>"
978                      "Packet Throughput: {val:.2f}Mpps<br>"
979                      "Final Duration: {time}<br>"
980                      "Result: {result}".format(name=name,
981                                                val=y_val,
982                                                time=time,
983                                                result=result))
984         traces.append(plgo.Bar(x=[str(idx) + '.', ],
985                                y=[y_val, ],
986                                name=name,
987                                text=hovertext,
988                                hoverinfo="text"))
989     try:
990         # Create plot
991         layout = deepcopy(plot["layout"])
992         if layout.get("title", None):
993             layout["title"] = "<b>Packet Throughput:</b> {0}". \
994                 format(layout["title"])
995         if y_max:
996             layout["yaxis"]["range"] = [0, y_max + 1]
997         plpl = plgo.Figure(data=traces, layout=layout)
998         # Export Plot
999         logging.info("    Writing file '{0}{1}'.".
1000                      format(plot["output-file"], plot["output-file-type"]))
1001         ploff.plot(plpl, show_link=False, auto_open=False,
1002                    filename='{0}{1}'.format(plot["output-file"],
1003                                             plot["output-file-type"]))
1004     except PlotlyError as err:
1005         logging.error("   Finished with error: {}".
1006                       format(repr(err).replace("\n", " ")))
1007         return
1008
1009
1010 def plot_soak_boxes(plot, input_data):
1011     """Generate the plot(s) with algorithm: plot_soak_boxes
1012     specified in the specification file.
1013
1014     :param plot: Plot to generate.
1015     :param input_data: Data to process.
1016     :type plot: pandas.Series
1017     :type input_data: InputData
1018     """
1019
1020     # Transform the data
1021     plot_title = plot.get("title", "")
1022     logging.info("    Creating the data set for the {0} '{1}'.".
1023                  format(plot.get("type", ""), plot_title))
1024     data = input_data.filter_data(plot)
1025     if data is None:
1026         logging.error("No data.")
1027         return
1028
1029     # Prepare the data for the plot
1030     y_vals = dict()
1031     y_tags = dict()
1032     for job in data:
1033         for build in job:
1034             for test in build:
1035                 if y_vals.get(test["parent"], None) is None:
1036                     y_tags[test["parent"]] = test.get("tags", None)
1037                 try:
1038                     if test["type"] in ("SOAK", ):
1039                         y_vals[test["parent"]] = test["throughput"]
1040                     else:
1041                         continue
1042                 except (KeyError, TypeError):
1043                     y_vals[test["parent"]] = dict()
1044
1045     # Sort the tests
1046     order = plot.get("sort", None)
1047     if order and y_tags:
1048         y_sorted = OrderedDict()
1049         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
1050         for tag in order:
1051             logging.debug(tag)
1052             for suite, tags in y_tags_l.items():
1053                 if "not " in tag:
1054                     tag = tag.split(" ")[-1]
1055                     if tag.lower() in tags:
1056                         continue
1057                 else:
1058                     if tag.lower() not in tags:
1059                         continue
1060                 try:
1061                     y_sorted[suite] = y_vals.pop(suite)
1062                     y_tags_l.pop(suite)
1063                     logging.debug(suite)
1064                 except KeyError as err:
1065                     logging.error("Not found: {0}".format(repr(err)))
1066                 finally:
1067                     break
1068     else:
1069         y_sorted = y_vals
1070
1071     idx = 0
1072     y_max = 0
1073     traces = list()
1074     for test_name, test_data in y_sorted.items():
1075         idx += 1
1076         name = "{nr}. {name}".\
1077             format(nr=idx, name=test_name.lower().replace('-soak', '').
1078                    replace('2n1l-', ''))
1079         if len(name) > 55:
1080             name_lst = name.split('-')
1081             name = ""
1082             split_name = True
1083             for segment in name_lst:
1084                 if (len(name) + len(segment) + 1) > 55 and split_name:
1085                     name += "<br>    "
1086                     split_name = False
1087                 name += segment + '-'
1088             name = name[:-1]
1089
1090         y_val = test_data.get("UPPER", None)
1091         if y_val:
1092             y_val /= 1000000
1093             if y_val > y_max:
1094                 y_max = y_val
1095
1096         y_base = test_data.get("LOWER", None)
1097         if y_base:
1098             y_base /= 1000000
1099
1100         hovertext = ("Upper bound: {upper:.2f}<br>"
1101                      "Lower bound: {lower:.2f}".format(upper=y_val,
1102                                                            lower=y_base))
1103         traces.append(plgo.Bar(x=[str(idx) + '.', ],
1104                                # +0.05 to see the value in case lower == upper
1105                                y=[y_val - y_base + 0.05, ],
1106                                base=y_base,
1107                                name=name,
1108                                text=hovertext,
1109                                hoverinfo="text"))
1110     try:
1111         # Create plot
1112         layout = deepcopy(plot["layout"])
1113         if layout.get("title", None):
1114             layout["title"] = "<b>Throughput:</b> {0}". \
1115                 format(layout["title"])
1116         if y_max:
1117             layout["yaxis"]["range"] = [0, y_max + 1]
1118         plpl = plgo.Figure(data=traces, layout=layout)
1119         # Export Plot
1120         logging.info("    Writing file '{0}{1}'.".
1121                      format(plot["output-file"], plot["output-file-type"]))
1122         ploff.plot(plpl, show_link=False, auto_open=False,
1123                    filename='{0}{1}'.format(plot["output-file"],
1124                                             plot["output-file-type"]))
1125     except PlotlyError as err:
1126         logging.error("   Finished with error: {}".
1127                       format(repr(err).replace("\n", " ")))
1128         return
1129
1130
1131 def plot_latency_error_bars(plot, input_data):
1132     """Generate the plot(s) with algorithm: plot_latency_error_bars
1133     specified in the specification file.
1134
1135     TODO: Remove when not needed.
1136
1137     :param plot: Plot to generate.
1138     :param input_data: Data to process.
1139     :type plot: pandas.Series
1140     :type input_data: InputData
1141     """
1142
1143     # Transform the data
1144     plot_title = plot.get("title", "")
1145     logging.info("    Creating the data set for the {0} '{1}'.".
1146                  format(plot.get("type", ""), plot_title))
1147     data = input_data.filter_data(plot)
1148     if data is None:
1149         logging.error("No data.")
1150         return
1151
1152     # Prepare the data for the plot
1153     y_tmp_vals = dict()
1154     y_tags = dict()
1155     for job in data:
1156         for build in job:
1157             for test in build:
1158                 try:
1159                     logging.debug("test['latency']: {0}\n".
1160                                  format(test["latency"]))
1161                 except ValueError as err:
1162                     logging.warning(repr(err))
1163                 if y_tmp_vals.get(test["parent"], None) is None:
1164                     y_tmp_vals[test["parent"]] = [
1165                         list(),  # direction1, min
1166                         list(),  # direction1, avg
1167                         list(),  # direction1, max
1168                         list(),  # direction2, min
1169                         list(),  # direction2, avg
1170                         list()   # direction2, max
1171                     ]
1172                     y_tags[test["parent"]] = test.get("tags", None)
1173                 try:
1174                     if test["type"] in ("NDRPDR", ):
1175                         if "-pdr" in plot_title.lower():
1176                             ttype = "PDR"
1177                         elif "-ndr" in plot_title.lower():
1178                             ttype = "NDR"
1179                         else:
1180                             logging.warning("Invalid test type: {0}".
1181                                             format(test["type"]))
1182                             continue
1183                         y_tmp_vals[test["parent"]][0].append(
1184                             test["latency"][ttype]["direction1"]["min"])
1185                         y_tmp_vals[test["parent"]][1].append(
1186                             test["latency"][ttype]["direction1"]["avg"])
1187                         y_tmp_vals[test["parent"]][2].append(
1188                             test["latency"][ttype]["direction1"]["max"])
1189                         y_tmp_vals[test["parent"]][3].append(
1190                             test["latency"][ttype]["direction2"]["min"])
1191                         y_tmp_vals[test["parent"]][4].append(
1192                             test["latency"][ttype]["direction2"]["avg"])
1193                         y_tmp_vals[test["parent"]][5].append(
1194                             test["latency"][ttype]["direction2"]["max"])
1195                     else:
1196                         logging.warning("Invalid test type: {0}".
1197                                         format(test["type"]))
1198                         continue
1199                 except (KeyError, TypeError) as err:
1200                     logging.warning(repr(err))
1201     logging.debug("y_tmp_vals: {0}\n".format(y_tmp_vals))
1202
1203     # Sort the tests
1204     order = plot.get("sort", None)
1205     if order and y_tags:
1206         y_sorted = OrderedDict()
1207         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
1208         for tag in order:
1209             logging.debug(tag)
1210             for suite, tags in y_tags_l.items():
1211                 if "not " in tag:
1212                     tag = tag.split(" ")[-1]
1213                     if tag.lower() in tags:
1214                         continue
1215                 else:
1216                     if tag.lower() not in tags:
1217                         continue
1218                 try:
1219                     y_sorted[suite] = y_tmp_vals.pop(suite)
1220                     y_tags_l.pop(suite)
1221                     logging.debug(suite)
1222                 except KeyError as err:
1223                     logging.error("Not found: {0}".format(repr(err)))
1224                 finally:
1225                     break
1226     else:
1227         y_sorted = y_tmp_vals
1228
1229     logging.debug("y_sorted: {0}\n".format(y_sorted))
1230     x_vals = list()
1231     y_vals = list()
1232     y_mins = list()
1233     y_maxs = list()
1234     nr_of_samples = list()
1235     for key, val in y_sorted.items():
1236         name = re.sub(REGEX_NIC, "", key.replace('-ndrpdr', '').
1237                       replace('2n1l-', ''))
1238         x_vals.append(name)  # dir 1
1239         y_vals.append(mean(val[1]) if val[1] else None)
1240         y_mins.append(mean(val[0]) if val[0] else None)
1241         y_maxs.append(mean(val[2]) if val[2] else None)
1242         nr_of_samples.append(len(val[1]) if val[1] else 0)
1243         x_vals.append(name)  # dir 2
1244         y_vals.append(mean(val[4]) if val[4] else None)
1245         y_mins.append(mean(val[3]) if val[3] else None)
1246         y_maxs.append(mean(val[5]) if val[5] else None)
1247         nr_of_samples.append(len(val[3]) if val[3] else 0)
1248
1249     logging.debug("x_vals :{0}\n".format(x_vals))
1250     logging.debug("y_vals :{0}\n".format(y_vals))
1251     logging.debug("y_mins :{0}\n".format(y_mins))
1252     logging.debug("y_maxs :{0}\n".format(y_maxs))
1253     logging.debug("nr_of_samples :{0}\n".format(nr_of_samples))
1254     traces = list()
1255     annotations = list()
1256
1257     for idx in range(len(x_vals)):
1258         if not bool(int(idx % 2)):
1259             direction = "West-East"
1260         else:
1261             direction = "East-West"
1262         hovertext = ("No. of Runs: {nr}<br>"
1263                      "Test: {test}<br>"
1264                      "Direction: {dir}<br>".format(test=x_vals[idx],
1265                                                    dir=direction,
1266                                                    nr=nr_of_samples[idx]))
1267         if isinstance(y_maxs[idx], float):
1268             hovertext += "Max: {max:.2f}uSec<br>".format(max=y_maxs[idx])
1269         if isinstance(y_vals[idx], float):
1270             hovertext += "Mean: {avg:.2f}uSec<br>".format(avg=y_vals[idx])
1271         if isinstance(y_mins[idx], float):
1272             hovertext += "Min: {min:.2f}uSec".format(min=y_mins[idx])
1273
1274         if isinstance(y_maxs[idx], float) and isinstance(y_vals[idx], float):
1275             array = [y_maxs[idx] - y_vals[idx], ]
1276         else:
1277             array = [None, ]
1278         if isinstance(y_mins[idx], float) and isinstance(y_vals[idx], float):
1279             arrayminus = [y_vals[idx] - y_mins[idx], ]
1280         else:
1281             arrayminus = [None, ]
1282         logging.debug("y_vals[{1}] :{0}\n".format(y_vals[idx], idx))
1283         logging.debug("array :{0}\n".format(array))
1284         logging.debug("arrayminus :{0}\n".format(arrayminus))
1285         traces.append(plgo.Scatter(
1286             x=[idx, ],
1287             y=[y_vals[idx], ],
1288             name=x_vals[idx],
1289             legendgroup=x_vals[idx],
1290             showlegend=bool(int(idx % 2)),
1291             mode="markers",
1292             error_y=dict(
1293                 type='data',
1294                 symmetric=False,
1295                 array=array,
1296                 arrayminus=arrayminus,
1297                 color=COLORS[int(idx / 2)]
1298             ),
1299             marker=dict(
1300                 size=10,
1301                 color=COLORS[int(idx / 2)],
1302             ),
1303             text=hovertext,
1304             hoverinfo="text",
1305         ))
1306         annotations.append(dict(
1307             x=idx,
1308             y=0,
1309             xref="x",
1310             yref="y",
1311             xanchor="center",
1312             yanchor="top",
1313             text="E-W" if bool(int(idx % 2)) else "W-E",
1314             font=dict(
1315                 size=16,
1316             ),
1317             align="center",
1318             showarrow=False
1319         ))
1320
1321     try:
1322         # Create plot
1323         logging.info("    Writing file '{0}{1}'.".
1324                      format(plot["output-file"], plot["output-file-type"]))
1325         layout = deepcopy(plot["layout"])
1326         if layout.get("title", None):
1327             layout["title"] = "<b>Latency:</b> {0}".\
1328                 format(layout["title"])
1329         layout["annotations"] = annotations
1330         plpl = plgo.Figure(data=traces, layout=layout)
1331
1332         # Export Plot
1333         ploff.plot(plpl,
1334                    show_link=False, auto_open=False,
1335                    filename='{0}{1}'.format(plot["output-file"],
1336                                             plot["output-file-type"]))
1337     except PlotlyError as err:
1338         logging.error("   Finished with error: {}".
1339                       format(str(err).replace("\n", " ")))
1340         return
1341
1342
1343 def plot_throughput_speedup_analysis(plot, input_data):
1344     """Generate the plot(s) with algorithm:
1345     plot_throughput_speedup_analysis
1346     specified in the specification file.
1347
1348     TODO: Remove when not needed.
1349
1350     :param plot: Plot to generate.
1351     :param input_data: Data to process.
1352     :type plot: pandas.Series
1353     :type input_data: InputData
1354     """
1355
1356     # Transform the data
1357     plot_title = plot.get("title", "")
1358     logging.info("    Creating the data set for the {0} '{1}'.".
1359                  format(plot.get("type", ""), plot_title))
1360     data = input_data.filter_data(plot)
1361     if data is None:
1362         logging.error("No data.")
1363         return
1364
1365     y_vals = dict()
1366     y_tags = dict()
1367     for job in data:
1368         for build in job:
1369             for test in build:
1370                 if y_vals.get(test["parent"], None) is None:
1371                     y_vals[test["parent"]] = {"1": list(),
1372                                               "2": list(),
1373                                               "4": list()}
1374                     y_tags[test["parent"]] = test.get("tags", None)
1375                 try:
1376                     if test["type"] in ("NDRPDR",):
1377                         if "-pdr" in plot_title.lower():
1378                             ttype = "PDR"
1379                         elif "-ndr" in plot_title.lower():
1380                             ttype = "NDR"
1381                         else:
1382                             continue
1383                         if "1C" in test["tags"]:
1384                             y_vals[test["parent"]]["1"]. \
1385                                 append(test["throughput"][ttype]["LOWER"])
1386                         elif "2C" in test["tags"]:
1387                             y_vals[test["parent"]]["2"]. \
1388                                 append(test["throughput"][ttype]["LOWER"])
1389                         elif "4C" in test["tags"]:
1390                             y_vals[test["parent"]]["4"]. \
1391                                 append(test["throughput"][ttype]["LOWER"])
1392                 except (KeyError, TypeError):
1393                     pass
1394
1395     if not y_vals:
1396         logging.warning("No data for the plot '{}'".
1397                         format(plot.get("title", "")))
1398         return
1399
1400     y_1c_max = dict()
1401     for test_name, test_vals in y_vals.items():
1402         for key, test_val in test_vals.items():
1403             if test_val:
1404                 avg_val = sum(test_val) / len(test_val)
1405                 y_vals[test_name][key] = (avg_val, len(test_val))
1406                 ideal = avg_val / (int(key) * 1000000.0)
1407                 if test_name not in y_1c_max or ideal > y_1c_max[test_name]:
1408                     y_1c_max[test_name] = ideal
1409
1410     vals = dict()
1411     y_max = list()
1412     nic_limit = 0
1413     lnk_limit = 0
1414     pci_limit = plot["limits"]["pci"]["pci-g3-x8"]
1415     for test_name, test_vals in y_vals.items():
1416         try:
1417             if test_vals["1"][1]:
1418                 name = re.sub(REGEX_NIC, "", test_name.replace('-ndrpdr', '').
1419                               replace('2n1l-', ''))
1420                 vals[name] = dict()
1421                 y_val_1 = test_vals["1"][0] / 1000000.0
1422                 y_val_2 = test_vals["2"][0] / 1000000.0 if test_vals["2"][0] \
1423                     else None
1424                 y_val_4 = test_vals["4"][0] / 1000000.0 if test_vals["4"][0] \
1425                     else None
1426
1427                 vals[name]["val"] = [y_val_1, y_val_2, y_val_4]
1428                 vals[name]["rel"] = [1.0, None, None]
1429                 vals[name]["ideal"] = [y_1c_max[test_name],
1430                                        y_1c_max[test_name] * 2,
1431                                        y_1c_max[test_name] * 4]
1432                 vals[name]["diff"] = [(y_val_1 - y_1c_max[test_name]) * 100 /
1433                                       y_val_1, None, None]
1434                 vals[name]["count"] = [test_vals["1"][1],
1435                                        test_vals["2"][1],
1436                                        test_vals["4"][1]]
1437
1438                 try:
1439                     # val_max = max(max(vals[name]["val"], vals[name]["ideal"]))
1440                     val_max = max(vals[name]["val"])
1441                 except ValueError as err:
1442                     logging.error(err)
1443                     continue
1444                 if val_max:
1445                     # y_max.append(int((val_max / 10) + 1) * 10)
1446                     y_max.append(val_max)
1447
1448                 if y_val_2:
1449                     vals[name]["rel"][1] = round(y_val_2 / y_val_1, 2)
1450                     vals[name]["diff"][1] = \
1451                         (y_val_2 - vals[name]["ideal"][1]) * 100 / y_val_2
1452                 if y_val_4:
1453                     vals[name]["rel"][2] = round(y_val_4 / y_val_1, 2)
1454                     vals[name]["diff"][2] = \
1455                         (y_val_4 - vals[name]["ideal"][2]) * 100 / y_val_4
1456         except IndexError as err:
1457             logging.warning("No data for '{0}'".format(test_name))
1458             logging.warning(repr(err))
1459
1460         # Limits:
1461         if "x520" in test_name:
1462             limit = plot["limits"]["nic"]["x520"]
1463         elif "x710" in test_name:
1464             limit = plot["limits"]["nic"]["x710"]
1465         elif "xxv710" in test_name:
1466             limit = plot["limits"]["nic"]["xxv710"]
1467         elif "xl710" in test_name:
1468             limit = plot["limits"]["nic"]["xl710"]
1469         elif "x553" in test_name:
1470             limit = plot["limits"]["nic"]["x553"]
1471         else:
1472             limit = 0
1473         if limit > nic_limit:
1474             nic_limit = limit
1475
1476         mul = 2 if "ge2p" in test_name else 1
1477         if "10ge" in test_name:
1478             limit = plot["limits"]["link"]["10ge"] * mul
1479         elif "25ge" in test_name:
1480             limit = plot["limits"]["link"]["25ge"] * mul
1481         elif "40ge" in test_name:
1482             limit = plot["limits"]["link"]["40ge"] * mul
1483         elif "100ge" in test_name:
1484             limit = plot["limits"]["link"]["100ge"] * mul
1485         else:
1486             limit = 0
1487         if limit > lnk_limit:
1488             lnk_limit = limit
1489
1490     # Sort the tests
1491     order = plot.get("sort", None)
1492     if order and y_tags:
1493         y_sorted = OrderedDict()
1494         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
1495         for tag in order:
1496             for test, tags in y_tags_l.items():
1497                 if tag.lower() in tags:
1498                     name = re.sub(REGEX_NIC, "",
1499                                   test.replace('-ndrpdr', '').
1500                                   replace('2n1l-', ''))
1501                     try:
1502                         y_sorted[name] = vals.pop(name)
1503                         y_tags_l.pop(test)
1504                     except KeyError as err:
1505                         logging.error("Not found: {0}".format(err))
1506                     finally:
1507                         break
1508     else:
1509         y_sorted = vals
1510
1511     traces = list()
1512     annotations = list()
1513     x_vals = [1, 2, 4]
1514
1515     # Limits:
1516     try:
1517         threshold = 1.1 * max(y_max)  # 10%
1518     except ValueError as err:
1519         logging.error(err)
1520         return
1521     nic_limit /= 1000000.0
1522     # if nic_limit < threshold:
1523     traces.append(plgo.Scatter(
1524         x=x_vals,
1525         y=[nic_limit, ] * len(x_vals),
1526         name="NIC: {0:.2f}Mpps".format(nic_limit),
1527         showlegend=False,
1528         mode="lines",
1529         line=dict(
1530             dash="dot",
1531             color=COLORS[-1],
1532             width=1),
1533         hoverinfo="none"
1534     ))
1535     annotations.append(dict(
1536         x=1,
1537         y=nic_limit,
1538         xref="x",
1539         yref="y",
1540         xanchor="left",
1541         yanchor="bottom",
1542         text="NIC: {0:.2f}Mpps".format(nic_limit),
1543         font=dict(
1544             size=14,
1545             color=COLORS[-1],
1546         ),
1547         align="left",
1548         showarrow=False
1549     ))
1550     # y_max.append(int((nic_limit / 10) + 1) * 10)
1551     y_max.append(nic_limit)
1552
1553     lnk_limit /= 1000000.0
1554     if lnk_limit < threshold:
1555         traces.append(plgo.Scatter(
1556             x=x_vals,
1557             y=[lnk_limit, ] * len(x_vals),
1558             name="Link: {0:.2f}Mpps".format(lnk_limit),
1559             showlegend=False,
1560             mode="lines",
1561             line=dict(
1562                 dash="dot",
1563                 color=COLORS[-2],
1564                 width=1),
1565             hoverinfo="none"
1566         ))
1567         annotations.append(dict(
1568             x=1,
1569             y=lnk_limit,
1570             xref="x",
1571             yref="y",
1572             xanchor="left",
1573             yanchor="bottom",
1574             text="Link: {0:.2f}Mpps".format(lnk_limit),
1575             font=dict(
1576                 size=14,
1577                 color=COLORS[-2],
1578             ),
1579             align="left",
1580             showarrow=False
1581         ))
1582         # y_max.append(int((lnk_limit / 10) + 1) * 10)
1583         y_max.append(lnk_limit)
1584
1585     pci_limit /= 1000000.0
1586     if (pci_limit < threshold and
1587         (pci_limit < lnk_limit * 0.95 or lnk_limit > lnk_limit * 1.05)):
1588         traces.append(plgo.Scatter(
1589             x=x_vals,
1590             y=[pci_limit, ] * len(x_vals),
1591             name="PCIe: {0:.2f}Mpps".format(pci_limit),
1592             showlegend=False,
1593             mode="lines",
1594             line=dict(
1595                 dash="dot",
1596                 color=COLORS[-3],
1597                 width=1),
1598             hoverinfo="none"
1599         ))
1600         annotations.append(dict(
1601             x=1,
1602             y=pci_limit,
1603             xref="x",
1604             yref="y",
1605             xanchor="left",
1606             yanchor="bottom",
1607             text="PCIe: {0:.2f}Mpps".format(pci_limit),
1608             font=dict(
1609                 size=14,
1610                 color=COLORS[-3],
1611             ),
1612             align="left",
1613             showarrow=False
1614         ))
1615         # y_max.append(int((pci_limit / 10) + 1) * 10)
1616         y_max.append(pci_limit)
1617
1618     # Perfect and measured:
1619     cidx = 0
1620     for name, val in y_sorted.iteritems():
1621         hovertext = list()
1622         try:
1623             for idx in range(len(val["val"])):
1624                 htext = ""
1625                 if isinstance(val["val"][idx], float):
1626                     htext += "No. of Runs: {1}<br>" \
1627                              "Mean: {0:.2f}Mpps<br>".format(val["val"][idx],
1628                                                             val["count"][idx])
1629                 if isinstance(val["diff"][idx], float):
1630                     htext += "Diff: {0:.0f}%<br>".format(round(val["diff"][idx]))
1631                 if isinstance(val["rel"][idx], float):
1632                     htext += "Speedup: {0:.2f}".format(val["rel"][idx])
1633                 hovertext.append(htext)
1634             traces.append(plgo.Scatter(x=x_vals,
1635                                        y=val["val"],
1636                                        name=name,
1637                                        legendgroup=name,
1638                                        mode="lines+markers",
1639                                        line=dict(
1640                                            color=COLORS[cidx],
1641                                            width=2),
1642                                        marker=dict(
1643                                            symbol="circle",
1644                                            size=10
1645                                        ),
1646                                        text=hovertext,
1647                                        hoverinfo="text+name"
1648                                        ))
1649             traces.append(plgo.Scatter(x=x_vals,
1650                                        y=val["ideal"],
1651                                        name="{0} perfect".format(name),
1652                                        legendgroup=name,
1653                                        showlegend=False,
1654                                        mode="lines",
1655                                        line=dict(
1656                                            color=COLORS[cidx],
1657                                            width=2,
1658                                            dash="dash"),
1659                                        text=["Perfect: {0:.2f}Mpps".format(y)
1660                                              for y in val["ideal"]],
1661                                        hoverinfo="text"
1662                                        ))
1663             cidx += 1
1664         except (IndexError, ValueError, KeyError) as err:
1665             logging.warning("No data for '{0}'".format(name))
1666             logging.warning(repr(err))
1667
1668     try:
1669         # Create plot
1670         logging.info("    Writing file '{0}{1}'.".
1671                      format(plot["output-file"], plot["output-file-type"]))
1672         layout = deepcopy(plot["layout"])
1673         if layout.get("title", None):
1674             layout["title"] = "<b>Speedup Multi-core:</b> {0}". \
1675                 format(layout["title"])
1676         # layout["yaxis"]["range"] = [0, int((max(y_max) / 10) + 1) * 10]
1677         layout["yaxis"]["range"] = [0, int(max(y_max) * 1.1)]
1678         layout["annotations"].extend(annotations)
1679         plpl = plgo.Figure(data=traces, layout=layout)
1680
1681         # Export Plot
1682         ploff.plot(plpl,
1683                    show_link=False, auto_open=False,
1684                    filename='{0}{1}'.format(plot["output-file"],
1685                                             plot["output-file-type"]))
1686     except PlotlyError as err:
1687         logging.error("   Finished with error: {}".
1688                       format(str(err).replace("\n", " ")))
1689         return
1690
1691
1692 def plot_http_server_performance_box(plot, input_data):
1693     """Generate the plot(s) with algorithm: plot_http_server_performance_box
1694     specified in the specification file.
1695
1696     :param plot: Plot to generate.
1697     :param input_data: Data to process.
1698     :type plot: pandas.Series
1699     :type input_data: InputData
1700     """
1701
1702     # Transform the data
1703     logging.info("    Creating the data set for the {0} '{1}'.".
1704                  format(plot.get("type", ""), plot.get("title", "")))
1705     data = input_data.filter_data(plot)
1706     if data is None:
1707         logging.error("No data.")
1708         return
1709
1710     # Prepare the data for the plot
1711     y_vals = dict()
1712     for job in data:
1713         for build in job:
1714             for test in build:
1715                 if y_vals.get(test["name"], None) is None:
1716                     y_vals[test["name"]] = list()
1717                 try:
1718                     y_vals[test["name"]].append(test["result"])
1719                 except (KeyError, TypeError):
1720                     y_vals[test["name"]].append(None)
1721
1722     # Add None to the lists with missing data
1723     max_len = 0
1724     nr_of_samples = list()
1725     for val in y_vals.values():
1726         if len(val) > max_len:
1727             max_len = len(val)
1728         nr_of_samples.append(len(val))
1729     for key, val in y_vals.items():
1730         if len(val) < max_len:
1731             val.extend([None for _ in range(max_len - len(val))])
1732
1733     # Add plot traces
1734     traces = list()
1735     df = pd.DataFrame(y_vals)
1736     df.head()
1737     for i, col in enumerate(df.columns):
1738         name = "{nr}. ({samples:02d} run{plural}) {name}".\
1739             format(nr=(i + 1),
1740                    samples=nr_of_samples[i],
1741                    plural='s' if nr_of_samples[i] > 1 else '',
1742                    name=col.lower().replace('-ndrpdr', ''))
1743         if len(name) > 50:
1744             name_lst = name.split('-')
1745             name = ""
1746             split_name = True
1747             for segment in name_lst:
1748                 if (len(name) + len(segment) + 1) > 50 and split_name:
1749                     name += "<br>    "
1750                     split_name = False
1751                 name += segment + '-'
1752             name = name[:-1]
1753
1754         traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
1755                                y=df[col],
1756                                name=name,
1757                                **plot["traces"]))
1758     try:
1759         # Create plot
1760         plpl = plgo.Figure(data=traces, layout=plot["layout"])
1761
1762         # Export Plot
1763         logging.info("    Writing file '{0}{1}'.".
1764                      format(plot["output-file"], plot["output-file-type"]))
1765         ploff.plot(plpl, show_link=False, auto_open=False,
1766                    filename='{0}{1}'.format(plot["output-file"],
1767                                             plot["output-file-type"]))
1768     except PlotlyError as err:
1769         logging.error("   Finished with error: {}".
1770                       format(str(err).replace("\n", " ")))
1771         return
1772
1773
1774 def plot_service_density_heatmap(plot, input_data):
1775     """Generate the plot(s) with algorithm: plot_service_density_heatmap
1776     specified in the specification file.
1777
1778     :param plot: Plot to generate.
1779     :param input_data: Data to process.
1780     :type plot: pandas.Series
1781     :type input_data: InputData
1782     """
1783
1784     REGEX_CN = re.compile(r'^(\d*)R(\d*)C$')
1785     REGEX_TEST_NAME = re.compile(r'^.*-(\d+ch|\d+pl)-'
1786                                  r'(\d+mif|\d+vh)-'
1787                                  r'(\d+vm\d+t|\d+dcr\d+t).*$')
1788
1789     txt_chains = list()
1790     txt_nodes = list()
1791     vals = dict()
1792
1793     # Transform the data
1794     logging.info("    Creating the data set for the {0} '{1}'.".
1795                  format(plot.get("type", ""), plot.get("title", "")))
1796     data = input_data.filter_data(plot, continue_on_error=True)
1797     if data is None or data.empty:
1798         logging.error("No data.")
1799         return
1800
1801     for job in data:
1802         for build in job:
1803             for test in build:
1804                 for tag in test['tags']:
1805                     groups = re.search(REGEX_CN, tag)
1806                     if groups:
1807                         c = str(groups.group(1))
1808                         n = str(groups.group(2))
1809                         break
1810                 else:
1811                     continue
1812                 groups = re.search(REGEX_TEST_NAME, test["name"])
1813                 if groups and len(groups.groups()) == 3:
1814                     hover_name = "{chain}-{vhost}-{vm}".format(
1815                         chain=str(groups.group(1)),
1816                         vhost=str(groups.group(2)),
1817                         vm=str(groups.group(3)))
1818                 else:
1819                     hover_name = ""
1820                 if vals.get(c, None) is None:
1821                     vals[c] = dict()
1822                 if vals[c].get(n, None) is None:
1823                     vals[c][n] = dict(name=hover_name,
1824                                       vals=list(),
1825                                       nr=None,
1826                                       mean=None,
1827                                       stdev=None)
1828                 try:
1829                     if plot["include-tests"] == "MRR":
1830                         result = test["result"]["receive-rate"].avg
1831                     elif plot["include-tests"] == "PDR":
1832                         result = test["throughput"]["PDR"]["LOWER"]
1833                     elif plot["include-tests"] == "NDR":
1834                         result = test["throughput"]["NDR"]["LOWER"]
1835                     else:
1836                         result = None
1837                 except TypeError:
1838                     result = None
1839
1840                 if result:
1841                     vals[c][n]["vals"].append(result)
1842
1843     if not vals:
1844         logging.error("No data.")
1845         return
1846
1847     for key_c in vals.keys():
1848         txt_chains.append(key_c)
1849         for key_n in vals[key_c].keys():
1850             txt_nodes.append(key_n)
1851             if vals[key_c][key_n]["vals"]:
1852                 vals[key_c][key_n]["nr"] = len(vals[key_c][key_n]["vals"])
1853                 vals[key_c][key_n]["mean"] = \
1854                     round(mean(vals[key_c][key_n]["vals"]) / 1000000, 1)
1855                 vals[key_c][key_n]["stdev"] = \
1856                     round(stdev(vals[key_c][key_n]["vals"]) / 1000000, 1)
1857     txt_nodes = list(set(txt_nodes))
1858
1859     txt_chains = sorted(txt_chains, key=lambda chain: int(chain))
1860     txt_nodes = sorted(txt_nodes, key=lambda node: int(node))
1861
1862     chains = [i + 1 for i in range(len(txt_chains))]
1863     nodes = [i + 1 for i in range(len(txt_nodes))]
1864
1865     data = [list() for _ in range(len(chains))]
1866     for c in chains:
1867         for n in nodes:
1868             try:
1869                 val = vals[txt_chains[c - 1]][txt_nodes[n - 1]]["mean"]
1870             except (KeyError, IndexError):
1871                 val = None
1872             data[c - 1].append(val)
1873
1874     # Colorscales:
1875     my_green = [[0.0, 'rgb(235, 249, 242)'],
1876                 [1.0, 'rgb(45, 134, 89)']]
1877
1878     my_blue = [[0.0, 'rgb(236, 242, 248)'],
1879                [1.0, 'rgb(57, 115, 172)']]
1880
1881     my_grey = [[0.0, 'rgb(230, 230, 230)'],
1882                [1.0, 'rgb(102, 102, 102)']]
1883
1884     hovertext = list()
1885     annotations = list()
1886
1887     text = ("Test: {name}<br>"
1888             "Runs: {nr}<br>"
1889             "Thput: {val}<br>"
1890             "StDev: {stdev}")
1891
1892     for c in range(len(txt_chains)):
1893         hover_line = list()
1894         for n in range(len(txt_nodes)):
1895             if data[c][n] is not None:
1896                 annotations.append(dict(
1897                     x=n+1,
1898                     y=c+1,
1899                     xref="x",
1900                     yref="y",
1901                     xanchor="center",
1902                     yanchor="middle",
1903                     text=str(data[c][n]),
1904                     font=dict(
1905                         size=14,
1906                     ),
1907                     align="center",
1908                     showarrow=False
1909                 ))
1910                 hover_line.append(text.format(
1911                     name=vals[txt_chains[c]][txt_nodes[n]]["name"],
1912                     nr=vals[txt_chains[c]][txt_nodes[n]]["nr"],
1913                     val=data[c][n],
1914                     stdev=vals[txt_chains[c]][txt_nodes[n]]["stdev"]))
1915         hovertext.append(hover_line)
1916
1917     traces = [
1918         plgo.Heatmap(x=nodes,
1919                      y=chains,
1920                      z=data,
1921                      colorbar=dict(
1922                          title=plot.get("z-axis", ""),
1923                          titleside="right",
1924                          titlefont=dict(
1925                             size=16
1926                          ),
1927                          tickfont=dict(
1928                              size=16,
1929                          ),
1930                          tickformat=".1f",
1931                          yanchor="bottom",
1932                          y=-0.02,
1933                          len=0.925,
1934                      ),
1935                      showscale=True,
1936                      colorscale=my_green,
1937                      text=hovertext,
1938                      hoverinfo="text")
1939     ]
1940
1941     for idx, item in enumerate(txt_nodes):
1942         # X-axis, numbers:
1943         annotations.append(dict(
1944             x=idx+1,
1945             y=0.05,
1946             xref="x",
1947             yref="y",
1948             xanchor="center",
1949             yanchor="top",
1950             text=item,
1951             font=dict(
1952                 size=16,
1953             ),
1954             align="center",
1955             showarrow=False
1956         ))
1957     for idx, item in enumerate(txt_chains):
1958         # Y-axis, numbers:
1959         annotations.append(dict(
1960             x=0.35,
1961             y=idx+1,
1962             xref="x",
1963             yref="y",
1964             xanchor="right",
1965             yanchor="middle",
1966             text=item,
1967             font=dict(
1968                 size=16,
1969             ),
1970             align="center",
1971             showarrow=False
1972         ))
1973     # X-axis, title:
1974     annotations.append(dict(
1975         x=0.55,
1976         y=-0.15,
1977         xref="paper",
1978         yref="y",
1979         xanchor="center",
1980         yanchor="bottom",
1981         text=plot.get("x-axis", ""),
1982         font=dict(
1983             size=16,
1984         ),
1985         align="center",
1986         showarrow=False
1987     ))
1988     # Y-axis, title:
1989     annotations.append(dict(
1990         x=-0.1,
1991         y=0.5,
1992         xref="x",
1993         yref="paper",
1994         xanchor="center",
1995         yanchor="middle",
1996         text=plot.get("y-axis", ""),
1997         font=dict(
1998             size=16,
1999         ),
2000         align="center",
2001         textangle=270,
2002         showarrow=False
2003     ))
2004     updatemenus = list([
2005         dict(
2006             x=1.0,
2007             y=0.0,
2008             xanchor='right',
2009             yanchor='bottom',
2010             direction='up',
2011             buttons=list([
2012                 dict(
2013                     args=[{"colorscale": [my_green, ], "reversescale": False}],
2014                     label="Green",
2015                     method="update"
2016                 ),
2017                 dict(
2018                     args=[{"colorscale": [my_blue, ], "reversescale": False}],
2019                     label="Blue",
2020                     method="update"
2021                 ),
2022                 dict(
2023                     args=[{"colorscale": [my_grey, ], "reversescale": False}],
2024                     label="Grey",
2025                     method="update"
2026                 )
2027             ])
2028         )
2029     ])
2030
2031     try:
2032         layout = deepcopy(plot["layout"])
2033     except KeyError as err:
2034         logging.error("Finished with error: No layout defined")
2035         logging.error(repr(err))
2036         return
2037
2038     layout["annotations"] = annotations
2039     layout['updatemenus'] = updatemenus
2040
2041     try:
2042         # Create plot
2043         plpl = plgo.Figure(data=traces, layout=layout)
2044
2045         # Export Plot
2046         logging.info("    Writing file '{0}{1}'.".
2047                      format(plot["output-file"], plot["output-file-type"]))
2048         ploff.plot(plpl, show_link=False, auto_open=False,
2049                    filename='{0}{1}'.format(plot["output-file"],
2050                                             plot["output-file-type"]))
2051     except PlotlyError as err:
2052         logging.error("   Finished with error: {}".
2053                       format(str(err).replace("\n", " ")))
2054         return
2055
2056
2057 def plot_service_density_heatmap_compare(plot, input_data):
2058     """Generate the plot(s) with algorithm: plot_service_density_heatmap_compare
2059     specified in the specification file.
2060
2061     :param plot: Plot to generate.
2062     :param input_data: Data to process.
2063     :type plot: pandas.Series
2064     :type input_data: InputData
2065     """
2066
2067     REGEX_CN = re.compile(r'^(\d*)R(\d*)C$')
2068     REGEX_TEST_NAME = re.compile(r'^.*-(\d+ch|\d+pl)-'
2069                                  r'(\d+mif|\d+vh)-'
2070                                  r'(\d+vm\d+t|\d+dcr\d+t).*$')
2071     REGEX_THREADS = re.compile(r'^(\d+)(VM|DCR)(\d+)T$')
2072
2073     txt_chains = list()
2074     txt_nodes = list()
2075     vals = dict()
2076
2077     # Transform the data
2078     logging.info("    Creating the data set for the {0} '{1}'.".
2079                  format(plot.get("type", ""), plot.get("title", "")))
2080     data = input_data.filter_data(plot, continue_on_error=True)
2081     if data is None or data.empty:
2082         logging.error("No data.")
2083         return
2084
2085     for job in data:
2086         for build in job:
2087             for test in build:
2088                 for tag in test['tags']:
2089                     groups = re.search(REGEX_CN, tag)
2090                     if groups:
2091                         c = str(groups.group(1))
2092                         n = str(groups.group(2))
2093                         break
2094                 else:
2095                     continue
2096                 groups = re.search(REGEX_TEST_NAME, test["name"])
2097                 if groups and len(groups.groups()) == 3:
2098                     hover_name = "{chain}-{vhost}-{vm}".format(
2099                         chain=str(groups.group(1)),
2100                         vhost=str(groups.group(2)),
2101                         vm=str(groups.group(3)))
2102                 else:
2103                     hover_name = ""
2104                 if vals.get(c, None) is None:
2105                     vals[c] = dict()
2106                 if vals[c].get(n, None) is None:
2107                     vals[c][n] = dict(name=hover_name,
2108                                       vals_r=list(),
2109                                       vals_c=list(),
2110                                       nr_r=None,
2111                                       nr_c=None,
2112                                       mean_r=None,
2113                                       mean_c=None,
2114                                       stdev_r=None,
2115                                       stdev_c=None)
2116                 try:
2117                     if plot["include-tests"] == "MRR":
2118                         result = test["result"]["receive-rate"].avg
2119                     elif plot["include-tests"] == "PDR":
2120                         result = test["throughput"]["PDR"]["LOWER"]
2121                     elif plot["include-tests"] == "NDR":
2122                         result = test["throughput"]["NDR"]["LOWER"]
2123                     else:
2124                         result = None
2125                 except TypeError:
2126                     result = None
2127
2128                 if result:
2129                     for tag in test['tags']:
2130                         groups = re.search(REGEX_THREADS, tag)
2131                         if groups and len(groups.groups()) == 3:
2132                             if str(groups.group(3)) == \
2133                                     plot["reference"]["include"]:
2134                                 vals[c][n]["vals_r"].append(result)
2135                             elif str(groups.group(3)) == \
2136                                     plot["compare"]["include"]:
2137                                 vals[c][n]["vals_c"].append(result)
2138                             break
2139     if not vals:
2140         logging.error("No data.")
2141         return
2142
2143     for key_c in vals.keys():
2144         txt_chains.append(key_c)
2145         for key_n in vals[key_c].keys():
2146             txt_nodes.append(key_n)
2147             if vals[key_c][key_n]["vals_r"]:
2148                 vals[key_c][key_n]["nr_r"] = len(vals[key_c][key_n]["vals_r"])
2149                 vals[key_c][key_n]["mean_r"] = \
2150                     mean(vals[key_c][key_n]["vals_r"])
2151                 vals[key_c][key_n]["stdev_r"] = \
2152                     round(stdev(vals[key_c][key_n]["vals_r"]) / 1000000, 1)
2153             if vals[key_c][key_n]["vals_c"]:
2154                 vals[key_c][key_n]["nr_c"] = len(vals[key_c][key_n]["vals_c"])
2155                 vals[key_c][key_n]["mean_c"] = \
2156                     mean(vals[key_c][key_n]["vals_c"])
2157                 vals[key_c][key_n]["stdev_c"] = \
2158                     round(stdev(vals[key_c][key_n]["vals_c"]) / 1000000, 1)
2159
2160     txt_nodes = list(set(txt_nodes))
2161
2162     txt_chains = sorted(txt_chains, key=lambda chain: int(chain))
2163     txt_nodes = sorted(txt_nodes, key=lambda node: int(node))
2164
2165     chains = [i + 1 for i in range(len(txt_chains))]
2166     nodes = [i + 1 for i in range(len(txt_nodes))]
2167
2168     data_r = [list() for _ in range(len(chains))]
2169     data_c = [list() for _ in range(len(chains))]
2170     diff = [list() for _ in range(len(chains))]
2171     for c in chains:
2172         for n in nodes:
2173             try:
2174                 val_r = vals[txt_chains[c - 1]][txt_nodes[n - 1]]["mean_r"]
2175             except (KeyError, IndexError):
2176                 val_r = None
2177             try:
2178                 val_c = vals[txt_chains[c - 1]][txt_nodes[n - 1]]["mean_c"]
2179             except (KeyError, IndexError):
2180                 val_c = None
2181             if val_c is not None and val_r:
2182                 val_d = (val_c - val_r) * 100 / val_r
2183             else:
2184                 val_d = None
2185
2186             if val_r is not None:
2187                 val_r = round(val_r / 1000000, 1)
2188             data_r[c - 1].append(val_r)
2189             if val_c is not None:
2190                 val_c = round(val_c / 1000000, 1)
2191             data_c[c - 1].append(val_c)
2192             if val_d is not None:
2193                 val_d = int(round(val_d, 0))
2194             diff[c - 1].append(val_d)
2195
2196     # Colorscales:
2197     my_green = [[0.0, 'rgb(235, 249, 242)'],
2198                 [1.0, 'rgb(45, 134, 89)']]
2199
2200     my_blue = [[0.0, 'rgb(236, 242, 248)'],
2201                [1.0, 'rgb(57, 115, 172)']]
2202
2203     my_grey = [[0.0, 'rgb(230, 230, 230)'],
2204                [1.0, 'rgb(102, 102, 102)']]
2205
2206     hovertext = list()
2207
2208     annotations = list()
2209     annotations_r = list()
2210     annotations_c = list()
2211     annotations_diff = list()
2212
2213     text = ("Test: {name}"
2214             "<br>{title_r}: {text_r}"
2215             "<br>{title_c}: {text_c}{text_diff}")
2216     text_r = "Thput: {val_r}; StDev: {stdev_r}; Runs: {nr_r}"
2217     text_c = "Thput: {val_c}; StDev: {stdev_c}; Runs: {nr_c}"
2218     text_diff = "<br>Relative Difference {title_c} vs. {title_r}: {diff}%"
2219
2220     for c in range(len(txt_chains)):
2221         hover_line = list()
2222         for n in range(len(txt_nodes)):
2223             point = dict(
2224                 x=n + 1,
2225                 y=c + 1,
2226                 xref="x",
2227                 yref="y",
2228                 xanchor="center",
2229                 yanchor="middle",
2230                 text="",
2231                 font=dict(
2232                     size=14,
2233                 ),
2234                 align="center",
2235                 showarrow=False
2236             )
2237
2238             point_text_r = "Not present"
2239             point_text_c = "Not present"
2240             point_text_diff = ""
2241             try:
2242                 point_r = data_r[c][n]
2243                 if point_r is not None:
2244                     point_text_r = text_r.format(
2245                         val_r=point_r,
2246                         stdev_r=vals[txt_chains[c]][txt_nodes[n]]["stdev_r"],
2247                         nr_r=vals[txt_chains[c]][txt_nodes[n]]["nr_r"])
2248             except KeyError:
2249                 point_r = None
2250             point["text"] = "" if point_r is None else point_r
2251             annotations_r.append(deepcopy(point))
2252
2253             try:
2254                 point_c = data_c[c][n]
2255                 if point_c is not None:
2256                     point_text_c = text_c.format(
2257                         val_c=point_c,
2258                         stdev_c=vals[txt_chains[c]][txt_nodes[n]]["stdev_c"],
2259                         nr_c=vals[txt_chains[c]][txt_nodes[n]]["nr_c"])
2260             except KeyError:
2261                 point_c = None
2262             point["text"] = "" if point_c is None else point_c
2263             annotations_c.append(deepcopy(point))
2264
2265             try:
2266                 point_d = diff[c][n]
2267                 if point_d is not None:
2268                     point_text_diff = text_diff.format(
2269                         title_r=plot["reference"]["name"],
2270                         title_c=plot["compare"]["name"],
2271                         diff=point_d)
2272             except KeyError:
2273                 point_d = None
2274             point["text"] = "" if point_d is None else point_d
2275             annotations_diff.append(deepcopy(point))
2276
2277             try:
2278                 name = vals[txt_chains[c]][txt_nodes[n]]["name"]
2279             except KeyError:
2280                 continue
2281
2282             hover_line.append(text.format(
2283                 name=name,
2284                 title_r=plot["reference"]["name"],
2285                 text_r=point_text_r,
2286                 title_c=plot["compare"]["name"],
2287                 text_c=point_text_c,
2288                 text_diff=point_text_diff
2289             ))
2290
2291         hovertext.append(hover_line)
2292
2293     traces = [
2294         plgo.Heatmap(x=nodes,
2295                      y=chains,
2296                      z=data_r,
2297                      visible=True,
2298                      colorbar=dict(
2299                          title=plot.get("z-axis", ""),
2300                          titleside="right",
2301                          titlefont=dict(
2302                             size=16
2303                          ),
2304                          tickfont=dict(
2305                              size=16,
2306                          ),
2307                          tickformat=".1f",
2308                          yanchor="bottom",
2309                          y=-0.02,
2310                          len=0.925,
2311                      ),
2312                      showscale=True,
2313                      colorscale=my_green,
2314                      reversescale=False,
2315                      text=hovertext,
2316                      hoverinfo="text"),
2317         plgo.Heatmap(x=nodes,
2318                      y=chains,
2319                      z=data_c,
2320                      visible=False,
2321                      colorbar=dict(
2322                          title=plot.get("z-axis", ""),
2323                          titleside="right",
2324                          titlefont=dict(
2325                              size=16
2326                          ),
2327                          tickfont=dict(
2328                              size=16,
2329                          ),
2330                          tickformat=".1f",
2331                          yanchor="bottom",
2332                          y=-0.02,
2333                          len=0.925,
2334                      ),
2335                      showscale=True,
2336                      colorscale=my_blue,
2337                      reversescale=False,
2338                      text=hovertext,
2339                      hoverinfo="text"),
2340         plgo.Heatmap(x=nodes,
2341                      y=chains,
2342                      z=diff,
2343                      name="Diff",
2344                      visible=False,
2345                      colorbar=dict(
2346                          title="Relative Difference {name_c} vs. {name_r} [%]".
2347                              format(name_c=plot["compare"]["name"],
2348                                     name_r=plot["reference"]["name"]),
2349                          titleside="right",
2350                          titlefont=dict(
2351                              size=16
2352                          ),
2353                          tickfont=dict(
2354                              size=16,
2355                          ),
2356                          tickformat=".1f",
2357                          yanchor="bottom",
2358                          y=-0.02,
2359                          len=0.925,
2360                      ),
2361                      showscale=True,
2362                      colorscale=my_grey,
2363                      reversescale=False,
2364                      text=hovertext,
2365                      hoverinfo="text")
2366     ]
2367
2368     for idx, item in enumerate(txt_nodes):
2369         # X-axis, numbers:
2370         annotations.append(dict(
2371             x=idx+1,
2372             y=0.05,
2373             xref="x",
2374             yref="y",
2375             xanchor="center",
2376             yanchor="top",
2377             text=item,
2378             font=dict(
2379                 size=16,
2380             ),
2381             align="center",
2382             showarrow=False
2383         ))
2384     for idx, item in enumerate(txt_chains):
2385         # Y-axis, numbers:
2386         annotations.append(dict(
2387             x=0.35,
2388             y=idx+1,
2389             xref="x",
2390             yref="y",
2391             xanchor="right",
2392             yanchor="middle",
2393             text=item,
2394             font=dict(
2395                 size=16,
2396             ),
2397             align="center",
2398             showarrow=False
2399         ))
2400     # X-axis, title:
2401     annotations.append(dict(
2402         x=0.55,
2403         y=-0.15,
2404         xref="paper",
2405         yref="y",
2406         xanchor="center",
2407         yanchor="bottom",
2408         text=plot.get("x-axis", ""),
2409         font=dict(
2410             size=16,
2411         ),
2412         align="center",
2413         showarrow=False
2414     ))
2415     # Y-axis, title:
2416     annotations.append(dict(
2417         x=-0.1,
2418         y=0.5,
2419         xref="x",
2420         yref="paper",
2421         xanchor="center",
2422         yanchor="middle",
2423         text=plot.get("y-axis", ""),
2424         font=dict(
2425             size=16,
2426         ),
2427         align="center",
2428         textangle=270,
2429         showarrow=False
2430     ))
2431     updatemenus = list([
2432         dict(
2433             active=0,
2434             x=1.0,
2435             y=0.0,
2436             xanchor='right',
2437             yanchor='bottom',
2438             direction='up',
2439             buttons=list([
2440                 dict(
2441                     label=plot["reference"]["name"],
2442                     method="update",
2443                     args=[
2444                         {
2445                             "visible": [True, False, False]
2446                         },
2447                         {
2448                             "colorscale": [my_green, ],
2449                             "reversescale": False,
2450                             "annotations": annotations + annotations_r,
2451                         },
2452                     ]
2453                 ),
2454                 dict(
2455                     label=plot["compare"]["name"],
2456                     method="update",
2457                     args=[
2458                         {
2459                             "visible": [False, True, False]
2460                         },
2461                         {
2462                             "colorscale": [my_blue, ],
2463                             "reversescale": False,
2464                             "annotations": annotations + annotations_c,
2465                         },
2466                     ]
2467                 ),
2468                 dict(
2469                     label="Diff",
2470                     method="update",
2471                     args=[
2472                         {
2473                             "visible": [False, False, True]
2474                         },
2475                         {
2476                             "colorscale": [my_grey, ],
2477                             "reversescale": False,
2478                             "annotations": annotations + annotations_diff,
2479                         },
2480                     ]
2481                 ),
2482             ])
2483         )
2484     ])
2485
2486     try:
2487         layout = deepcopy(plot["layout"])
2488     except KeyError as err:
2489         logging.error("Finished with error: No layout defined")
2490         logging.error(repr(err))
2491         return
2492
2493     layout["annotations"] = annotations + annotations_r
2494     layout['updatemenus'] = updatemenus
2495
2496     try:
2497         # Create plot
2498         plpl = plgo.Figure(data=traces, layout=layout)
2499
2500         # Export Plot
2501         logging.info("    Writing file '{0}{1}'.".
2502                      format(plot["output-file"], plot["output-file-type"]))
2503         ploff.plot(plpl, show_link=False, auto_open=False,
2504                    filename='{0}{1}'.format(plot["output-file"],
2505                                             plot["output-file-type"]))
2506     except PlotlyError as err:
2507         logging.error("   Finished with error: {}".
2508                       format(str(err).replace("\n", " ")))
2509         return