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