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