Report: Implement changes from job-specs
[csit.git] / resources / tools / presentation / generator_plots.py
1 # Copyright (c) 2021 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
21 from collections import OrderedDict
22 from copy import deepcopy
23 from math import log
24
25 import hdrh.histogram
26 import hdrh.codec
27 import pandas as pd
28 import plotly.offline as ploff
29 import plotly.graph_objs as plgo
30
31 from plotly.exceptions import PlotlyError
32
33 from pal_utils import mean, stdev
34
35
36 COLORS = (
37     u"#1A1110",
38     u"#DA2647",
39     u"#214FC6",
40     u"#01786F",
41     u"#BD8260",
42     u"#FFD12A",
43     u"#A6E7FF",
44     u"#738276",
45     u"#C95A49",
46     u"#FC5A8D",
47     u"#CEC8EF",
48     u"#391285",
49     u"#6F2DA8",
50     u"#FF878D",
51     u"#45A27D",
52     u"#FFD0B9",
53     u"#FD5240",
54     u"#DB91EF",
55     u"#44D7A8",
56     u"#4F86F7",
57     u"#84DE02",
58     u"#FFCFF1",
59     u"#614051"
60 )
61
62 REGEX_NIC = re.compile(r'(\d*ge\dp\d\D*\d*[a-z]*)-')
63
64 # This value depends on latency stream rate (9001 pps) and duration (5s).
65 # Keep it slightly higher to ensure rounding errors to not remove tick mark.
66 PERCENTILE_MAX = 99.999501
67
68
69 def generate_plots(spec, data):
70     """Generate all plots specified in the specification file.
71
72     :param spec: Specification read from the specification file.
73     :param data: Data to process.
74     :type spec: Specification
75     :type data: InputData
76     """
77
78     generator = {
79         u"plot_nf_reconf_box_name": plot_nf_reconf_box_name,
80         u"plot_perf_box_name": plot_perf_box_name,
81         u"plot_tsa_name": plot_tsa_name,
82         u"plot_http_server_perf_box": plot_http_server_perf_box,
83         u"plot_nf_heatmap": plot_nf_heatmap,
84         u"plot_hdrh_lat_by_percentile": plot_hdrh_lat_by_percentile,
85         u"plot_hdrh_lat_by_percentile_x_log": plot_hdrh_lat_by_percentile_x_log,
86         u"plot_mrr_box_name": plot_mrr_box_name,
87         u"plot_ndrpdr_box_name": plot_ndrpdr_box_name
88     }
89
90     logging.info(u"Generating the plots ...")
91     for index, plot in enumerate(spec.plots):
92         try:
93             logging.info(f"  Plot nr {index + 1}: {plot.get(u'title', u'')}")
94             plot[u"limits"] = spec.environment[u"limits"]
95             generator[plot[u"algorithm"]](plot, data)
96             logging.info(u"  Done.")
97         except NameError as err:
98             logging.error(
99                 f"Probably algorithm {plot[u'algorithm']} is not defined: "
100                 f"{repr(err)}"
101             )
102     logging.info(u"Done.")
103
104
105 def plot_hdrh_lat_by_percentile(plot, input_data):
106     """Generate the plot(s) with algorithm: plot_hdrh_lat_by_percentile
107     specified in the specification file.
108
109     :param plot: Plot to generate.
110     :param input_data: Data to process.
111     :type plot: pandas.Series
112     :type input_data: InputData
113     """
114
115     # Transform the data
116     logging.info(
117         f"    Creating the data set for the {plot.get(u'type', u'')} "
118         f"{plot.get(u'title', u'')}."
119     )
120     if plot.get(u"include", None):
121         data = input_data.filter_tests_by_name(
122             plot,
123             params=[u"name", u"latency", u"parent", u"tags", u"type"]
124         )[0][0]
125     elif plot.get(u"filter", None):
126         data = input_data.filter_data(
127             plot,
128             params=[u"name", u"latency", u"parent", u"tags", u"type"],
129             continue_on_error=True
130         )[0][0]
131     else:
132         job = list(plot[u"data"].keys())[0]
133         build = str(plot[u"data"][job][0])
134         data = input_data.tests(job, build)
135
136     if data is None or len(data) == 0:
137         logging.error(u"No data.")
138         return
139
140     desc = {
141         u"LAT0": u"No-load.",
142         u"PDR10": u"Low-load, 10% PDR.",
143         u"PDR50": u"Mid-load, 50% PDR.",
144         u"PDR90": u"High-load, 90% PDR.",
145         u"PDR": u"Full-load, 100% PDR.",
146         u"NDR10": u"Low-load, 10% NDR.",
147         u"NDR50": u"Mid-load, 50% NDR.",
148         u"NDR90": u"High-load, 90% NDR.",
149         u"NDR": u"Full-load, 100% NDR."
150     }
151
152     graphs = [
153         u"LAT0",
154         u"PDR10",
155         u"PDR50",
156         u"PDR90"
157     ]
158
159     file_links = plot.get(u"output-file-links", None)
160     target_links = plot.get(u"target-links", None)
161
162     for test in data:
163         try:
164             if test[u"type"] not in (u"NDRPDR",):
165                 logging.warning(f"Invalid test type: {test[u'type']}")
166                 continue
167             name = re.sub(REGEX_NIC, u"", test[u"parent"].
168                           replace(u'-ndrpdr', u'').replace(u'2n1l-', u''))
169             try:
170                 nic = re.search(REGEX_NIC, test[u"parent"]).group(1)
171             except (IndexError, AttributeError, KeyError, ValueError):
172                 nic = u""
173             name_link = f"{nic}-{test[u'name']}".replace(u'-ndrpdr', u'')
174
175             logging.info(f"    Generating the graph: {name_link}")
176
177             fig = plgo.Figure()
178             layout = deepcopy(plot[u"layout"])
179
180             for color, graph in enumerate(graphs):
181                 for idx, direction in enumerate((u"direction1", u"direction2")):
182                     previous_x = 0.0
183                     xaxis = list()
184                     yaxis = list()
185                     hovertext = list()
186                     try:
187                         decoded = hdrh.histogram.HdrHistogram.decode(
188                             test[u"latency"][graph][direction][u"hdrh"]
189                         )
190                     except hdrh.codec.HdrLengthException:
191                         logging.warning(
192                             f"No data for direction {(u'W-E', u'E-W')[idx % 2]}"
193                         )
194                         continue
195
196                     for item in decoded.get_recorded_iterator():
197                         percentile = item.percentile_level_iterated_to
198                         xaxis.append(previous_x)
199                         yaxis.append(item.value_iterated_to)
200                         hovertext.append(
201                             f"<b>{desc[graph]}</b><br>"
202                             f"Direction: {(u'W-E', u'E-W')[idx % 2]}<br>"
203                             f"Percentile: "
204                             f"{previous_x:.5f}-{percentile:.5f}%<br>"
205                             f"Latency: {item.value_iterated_to}uSec"
206                         )
207                         xaxis.append(percentile)
208                         yaxis.append(item.value_iterated_to)
209                         hovertext.append(
210                             f"<b>{desc[graph]}</b><br>"
211                             f"Direction: {(u'W-E', u'E-W')[idx % 2]}<br>"
212                             f"Percentile: "
213                             f"{previous_x:.5f}-{percentile:.5f}%<br>"
214                             f"Latency: {item.value_iterated_to}uSec"
215                         )
216                         previous_x = percentile
217                     fig.add_trace(
218                         plgo.Scatter(
219                             x=xaxis,
220                             y=yaxis,
221                             name=desc[graph],
222                             mode=u"lines",
223                             legendgroup=desc[graph],
224                             showlegend=bool(idx),
225                             line=dict(
226                                 color=COLORS[color],
227                                 dash=u"solid",
228                                 width=1 if idx % 2 else 2
229                             ),
230                             hovertext=hovertext,
231                             hoverinfo=u"text"
232                         )
233                     )
234
235             layout[u"title"][u"text"] = f"<b>Latency:</b> {name}"
236             fig.update_layout(layout)
237
238             # Create plot
239             file_name = f"{plot[u'output-file']}-{name_link}.html"
240             logging.info(f"    Writing file {file_name}")
241
242             try:
243                 # Export Plot
244                 ploff.plot(fig, show_link=False, auto_open=False,
245                            filename=file_name)
246                 # Add link to the file:
247                 if file_links and target_links:
248                     with open(file_links, u"a") as file_handler:
249                         file_handler.write(
250                             f"- `{name_link} "
251                             f"<{target_links}/{file_name.split(u'/')[-1]}>`_\n"
252                         )
253             except FileNotFoundError as err:
254                 logging.error(
255                     f"Not possible to write the link to the file "
256                     f"{file_links}\n{err}"
257                 )
258             except PlotlyError as err:
259                 logging.error(f"   Finished with error: {repr(err)}")
260
261         except hdrh.codec.HdrLengthException as err:
262             logging.warning(repr(err))
263             continue
264
265         except (ValueError, KeyError) as err:
266             logging.warning(repr(err))
267             continue
268
269
270 def plot_hdrh_lat_by_percentile_x_log(plot, input_data):
271     """Generate the plot(s) with algorithm: plot_hdrh_lat_by_percentile_x_log
272     specified in the specification file.
273
274     :param plot: Plot to generate.
275     :param input_data: Data to process.
276     :type plot: pandas.Series
277     :type input_data: InputData
278     """
279
280     # Transform the data
281     logging.info(
282         f"    Creating the data set for the {plot.get(u'type', u'')} "
283         f"{plot.get(u'title', u'')}."
284     )
285     if plot.get(u"include", None):
286         data = input_data.filter_tests_by_name(
287             plot,
288             params=[u"name", u"latency", u"parent", u"tags", u"type"]
289         )[0][0]
290     elif plot.get(u"filter", None):
291         data = input_data.filter_data(
292             plot,
293             params=[u"name", u"latency", u"parent", u"tags", u"type"],
294             continue_on_error=True
295         )[0][0]
296     else:
297         job = list(plot[u"data"].keys())[0]
298         build = str(plot[u"data"][job][0])
299         data = input_data.tests(job, build)
300
301     if data is None or len(data) == 0:
302         logging.error(u"No data.")
303         return
304
305     desc = {
306         u"LAT0": u"No-load.",
307         u"PDR10": u"Low-load, 10% PDR.",
308         u"PDR50": u"Mid-load, 50% PDR.",
309         u"PDR90": u"High-load, 90% PDR.",
310         u"PDR": u"Full-load, 100% PDR.",
311         u"NDR10": u"Low-load, 10% NDR.",
312         u"NDR50": u"Mid-load, 50% NDR.",
313         u"NDR90": u"High-load, 90% NDR.",
314         u"NDR": u"Full-load, 100% NDR."
315     }
316
317     graphs = [
318         u"LAT0",
319         u"PDR10",
320         u"PDR50",
321         u"PDR90"
322     ]
323
324     file_links = plot.get(u"output-file-links", None)
325     target_links = plot.get(u"target-links", None)
326
327     for test in data:
328         try:
329             if test[u"type"] not in (u"NDRPDR",):
330                 logging.warning(f"Invalid test type: {test[u'type']}")
331                 continue
332             name = re.sub(REGEX_NIC, u"", test[u"parent"].
333                           replace(u'-ndrpdr', u'').replace(u'2n1l-', u''))
334             try:
335                 nic = re.search(REGEX_NIC, test[u"parent"]).group(1)
336             except (IndexError, AttributeError, KeyError, ValueError):
337                 nic = u""
338             name_link = f"{nic}-{test[u'name']}".replace(u'-ndrpdr', u'')
339
340             logging.info(f"    Generating the graph: {name_link}")
341
342             fig = plgo.Figure()
343             layout = deepcopy(plot[u"layout"])
344
345             for color, graph in enumerate(graphs):
346                 for idx, direction in enumerate((u"direction1", u"direction2")):
347                     previous_x = 0.0
348                     prev_perc = 0.0
349                     xaxis = list()
350                     yaxis = list()
351                     hovertext = list()
352                     try:
353                         decoded = hdrh.histogram.HdrHistogram.decode(
354                             test[u"latency"][graph][direction][u"hdrh"]
355                         )
356                     except (hdrh.codec.HdrLengthException, TypeError):
357                         logging.warning(
358                             f"No data for direction {(u'W-E', u'E-W')[idx % 2]}"
359                         )
360                         continue
361
362                     for item in decoded.get_recorded_iterator():
363                         # The real value is "percentile".
364                         # For 100%, we cut that down to "x_perc" to avoid
365                         # infinity.
366                         percentile = item.percentile_level_iterated_to
367                         x_perc = min(percentile, PERCENTILE_MAX)
368                         xaxis.append(previous_x)
369                         yaxis.append(item.value_iterated_to)
370                         hovertext.append(
371                             f"<b>{desc[graph]}</b><br>"
372                             f"Direction: {(u'W-E', u'E-W')[idx % 2]}<br>"
373                             f"Percentile: {prev_perc:.5f}-{percentile:.5f}%<br>"
374                             f"Latency: {item.value_iterated_to}uSec"
375                         )
376                         next_x = 100.0 / (100.0 - x_perc)
377                         xaxis.append(next_x)
378                         yaxis.append(item.value_iterated_to)
379                         hovertext.append(
380                             f"<b>{desc[graph]}</b><br>"
381                             f"Direction: {(u'W-E', u'E-W')[idx % 2]}<br>"
382                             f"Percentile: {prev_perc:.5f}-{percentile:.5f}%<br>"
383                             f"Latency: {item.value_iterated_to}uSec"
384                         )
385                         previous_x = next_x
386                         prev_perc = percentile
387                     fig.add_trace(
388                         plgo.Scatter(
389                             x=xaxis,
390                             y=yaxis,
391                             name=desc[graph],
392                             mode=u"lines",
393                             legendgroup=desc[graph],
394                             showlegend=not(bool(idx)),
395                             line=dict(
396                                 color=COLORS[color],
397                                 dash=u"solid",
398                                 width=1 if idx % 2 else 2
399                             ),
400                             hovertext=hovertext,
401                             hoverinfo=u"text"
402                         )
403                     )
404
405             layout[u"title"][u"text"] = f"<b>Latency:</b> {name}"
406             x_max = log(100.0 / (100.0 - PERCENTILE_MAX), 10)
407             layout[u"xaxis"][u"range"] = [0, x_max]
408             fig.update_layout(layout)
409
410             # Create plot
411             file_name = f"{plot[u'output-file']}-{name_link}.html"
412             logging.info(f"    Writing file {file_name}")
413
414             try:
415                 # Export Plot
416                 ploff.plot(fig, show_link=False, auto_open=False,
417                            filename=file_name)
418                 # Add link to the file:
419                 if file_links and target_links:
420                     with open(file_links, u"a") as file_handler:
421                         file_handler.write(
422                             f"- `{name_link} "
423                             f"<{target_links}/{file_name.split(u'/')[-1]}>`_\n"
424                         )
425             except FileNotFoundError as err:
426                 logging.error(
427                     f"Not possible to write the link to the file "
428                     f"{file_links}\n{err}"
429                 )
430             except PlotlyError as err:
431                 logging.error(f"   Finished with error: {repr(err)}")
432
433         except hdrh.codec.HdrLengthException as err:
434             logging.warning(repr(err))
435             continue
436
437         except (ValueError, KeyError) as err:
438             logging.warning(repr(err))
439             continue
440
441
442 def plot_nf_reconf_box_name(plot, input_data):
443     """Generate the plot(s) with algorithm: plot_nf_reconf_box_name
444     specified in the specification file.
445
446     :param plot: Plot to generate.
447     :param input_data: Data to process.
448     :type plot: pandas.Series
449     :type input_data: InputData
450     """
451
452     # Transform the data
453     logging.info(
454         f"    Creating the data set for the {plot.get(u'type', u'')} "
455         f"{plot.get(u'title', u'')}."
456     )
457     data = input_data.filter_tests_by_name(
458         plot, params=[u"result", u"parent", u"tags", u"type"]
459     )
460     if data is None:
461         logging.error(u"No data.")
462         return
463
464     for core in plot.get(u"core", tuple()):
465         # Prepare the data for the plot
466         y_vals = OrderedDict()
467         loss = dict()
468         for item in plot.get(u"include", tuple()):
469             reg_ex = re.compile(str(item.format(core=core)).lower())
470             for job in data:
471                 for build in job:
472                     for test_id, test in build.iteritems():
473                         if not re.match(reg_ex, str(test_id).lower()):
474                             continue
475                         if y_vals.get(test[u"parent"], None) is None:
476                             y_vals[test[u"parent"]] = list()
477                             loss[test[u"parent"]] = list()
478                         try:
479                             y_vals[test[u"parent"]].append(
480                                 test[u"result"][u"time"]
481                             )
482                             loss[test[u"parent"]].append(
483                                 test[u"result"][u"loss"]
484                             )
485                         except (KeyError, TypeError):
486                             y_vals[test[u"parent"]].append(None)
487
488         # Add None to the lists with missing data
489         max_len = 0
490         nr_of_samples = list()
491         for val in y_vals.values():
492             if len(val) > max_len:
493                 max_len = len(val)
494             nr_of_samples.append(len(val))
495         for val in y_vals.values():
496             if len(val) < max_len:
497                 val.extend([None for _ in range(max_len - len(val))])
498
499         # Add plot traces
500         traces = list()
501         df_y = pd.DataFrame(y_vals)
502         df_y.head()
503         for i, col in enumerate(df_y.columns):
504             tst_name = re.sub(
505                 REGEX_NIC, u"",
506                 col.lower().replace(u'-reconf', u'').replace(u'2n1l-', u'').
507                 replace(u'2n-', u'').replace(u'-testpmd', u'')
508             )
509             traces.append(plgo.Box(
510                 x=[str(i + 1) + u'.'] * len(df_y[col]),
511                 y=df_y[col],
512                 name=(
513                     f"{i + 1}. "
514                     f"({nr_of_samples[i]:02d} "
515                     f"run{u's' if nr_of_samples[i] > 1 else u''}, "
516                     f"packets lost average: {mean(loss[col]):.1f}) "
517                     f"{u'-'.join(tst_name.split(u'-')[2:])}"
518                 ),
519                 hoverinfo=u"y+name"
520             ))
521         try:
522             # Create plot
523             layout = deepcopy(plot[u"layout"])
524             layout[u"title"] = f"<b>Time Lost:</b> {layout[u'title']}"
525             layout[u"yaxis"][u"title"] = u"<b>Effective Blocked Time [s]</b>"
526             layout[u"legend"][u"font"][u"size"] = 14
527             layout[u"yaxis"].pop(u"range")
528             plpl = plgo.Figure(data=traces, layout=layout)
529
530             # Export Plot
531             file_name = f"{plot[u'output-file'].format(core=core)}.html"
532             logging.info(f"    Writing file {file_name}")
533             ploff.plot(
534                 plpl,
535                 show_link=False,
536                 auto_open=False,
537                 filename=file_name
538             )
539         except PlotlyError as err:
540             logging.error(
541                 f"   Finished with error: {repr(err)}".replace(u"\n", u" ")
542             )
543
544
545 def plot_perf_box_name(plot, input_data):
546     """Generate the plot(s) with algorithm: plot_perf_box_name
547     specified in the specification file.
548
549     Use only for soak and hoststack tests.
550
551     :param plot: Plot to generate.
552     :param input_data: Data to process.
553     :type plot: pandas.Series
554     :type input_data: InputData
555     """
556
557     # Transform the data
558     logging.info(
559         f"    Creating data set for the {plot.get(u'type', u'')} "
560         f"{plot.get(u'title', u'')}."
561     )
562     data = input_data.filter_tests_by_name(
563         plot,
564         params=[u"throughput", u"gbps", u"result", u"parent", u"tags", u"type"])
565     if data is None:
566         logging.error(u"No data.")
567         return
568
569     # Prepare the data for the plot
570     y_vals = OrderedDict()
571     test_type = u""
572
573     for item in plot.get(u"include", tuple()):
574         reg_ex = re.compile(str(item).lower())
575         for job in data:
576             for build in job:
577                 for test_id, test in build.iteritems():
578                     if not re.match(reg_ex, str(test_id).lower()):
579                         continue
580                     if y_vals.get(test[u"parent"], None) is None:
581                         y_vals[test[u"parent"]] = list()
582                     try:
583                         if test[u"type"] in (u"SOAK",):
584                             y_vals[test[u"parent"]]. \
585                                 append(test[u"throughput"][u"LOWER"])
586                             test_type = u"SOAK"
587
588                         elif test[u"type"] in (u"HOSTSTACK",):
589                             if u"LDPRELOAD" in test[u"tags"]:
590                                 y_vals[test[u"parent"]].append(
591                                     float(
592                                         test[u"result"][u"bits_per_second"]
593                                     ) / 1e3
594                                 )
595                             elif u"VPPECHO" in test[u"tags"]:
596                                 y_vals[test[u"parent"]].append(
597                                     (float(
598                                         test[u"result"][u"client"][u"tx_data"]
599                                     ) * 8 / 1e3) /
600                                     ((float(
601                                         test[u"result"][u"client"][u"time"]
602                                     ) +
603                                       float(
604                                           test[u"result"][u"server"][u"time"])
605                                       ) / 2)
606                                 )
607                             test_type = u"HOSTSTACK"
608
609                         elif test[u"type"] in (u"LDP_NGINX",):
610                             if u"TCP_CPS" in test[u"tags"]:
611                                 test_type = u"VSAP_CPS"
612                                 y_vals[test[u"parent"]].append(
613                                     test[u"result"][u"cps"] / 1e6
614                                 )
615                             elif u"TCP_RPS" in test[u"tags"]:
616                                 test_type = u"VSAP_RPS"
617                                 y_vals[test[u"parent"]].append(
618                                     test[u"result"][u"rps"] / 1e6
619                                 )
620                             else:
621                                 continue
622                         else:
623                             continue
624
625                     except (KeyError, TypeError):
626                         y_vals[test[u"parent"]].append(None)
627
628     # Add None to the lists with missing data
629     max_len = 0
630     nr_of_samples = list()
631     for val in y_vals.values():
632         if len(val) > max_len:
633             max_len = len(val)
634         nr_of_samples.append(len(val))
635     for val in y_vals.values():
636         if len(val) < max_len:
637             val.extend([None for _ in range(max_len - len(val))])
638
639     # Add plot traces
640     traces = list()
641     df_y = pd.DataFrame(y_vals)
642     df_y.head()
643     y_max = list()
644     for i, col in enumerate(df_y.columns):
645         tst_name = re.sub(REGEX_NIC, u"",
646                           col.lower().replace(u'-ndrpdr', u'').
647                           replace(u'2n1l-', u''))
648         kwargs = dict(
649             x=[str(i + 1) + u'.'] * len(df_y[col]),
650             y=[y / 1e6 if y else None for y in df_y[col]],
651             name=(
652                 f"{i + 1}. "
653                 f"({nr_of_samples[i]:02d} "
654                 f"run{u's' if nr_of_samples[i] > 1 else u''}) "
655                 f"{tst_name}"
656             ),
657             hoverinfo=u"y+name"
658         )
659         if test_type in (u"SOAK", ):
660             kwargs[u"boxpoints"] = u"all"
661
662         traces.append(plgo.Box(**kwargs))
663
664         try:
665             val_max = max(df_y[col])
666             if val_max:
667                 y_max.append(int(val_max / 1e6))
668         except (ValueError, TypeError) as err:
669             logging.error(repr(err))
670             continue
671
672     try:
673         # Create plot
674         layout = deepcopy(plot[u"layout"])
675         if layout.get(u"title", None):
676             if test_type in (u"HOSTSTACK", ):
677                 layout[u"title"] = f"<b>Bandwidth:</b> {layout[u'title']}"
678             elif test_type == u"VSAP_CPS":
679                 layout[u"title"] = f"<b>CPS:</b> {layout[u'title']}"
680                 layout[u"yaxis"][u"title"] = u"<b>Connection Rate [Mcps]</b>"
681             elif test_type == u"VSAP_RPS":
682                 layout[u"title"] = f"<b>RPS:</b> {layout[u'title']}"
683                 layout[u"yaxis"][u"title"] = u"<b>Connection Rate [Mrps]</b>"
684             else:
685                 layout[u"title"] = f"<b>Throughput:</b> {layout[u'title']}"
686         if y_max and max(y_max) > 1:
687             layout[u"yaxis"][u"range"] = [0, max(y_max) + 2]
688         plpl = plgo.Figure(data=traces, layout=layout)
689
690         # Export Plot
691         logging.info(f"    Writing file {plot[u'output-file']}.html.")
692         ploff.plot(
693             plpl,
694             show_link=False,
695             auto_open=False,
696             filename=f"{plot[u'output-file']}.html"
697         )
698     except PlotlyError as err:
699         logging.error(
700             f"   Finished with error: {repr(err)}".replace(u"\n", u" ")
701         )
702         return
703
704
705 def plot_ndrpdr_box_name(plot, input_data):
706     """Generate the plot(s) with algorithm: plot_ndrpdr_box_name
707     specified in the specification file.
708
709     :param plot: Plot to generate.
710     :param input_data: Data to process.
711     :type plot: pandas.Series
712     :type input_data: InputData
713     """
714
715     # Transform the data
716     logging.info(
717         f"    Creating data set for the {plot.get(u'type', u'')} "
718         f"{plot.get(u'title', u'')}."
719     )
720     data = input_data.filter_tests_by_name(
721         plot,
722         params=[u"throughput", u"gbps", u"parent", u"tags", u"type"]
723     )
724     if data is None:
725         logging.error(u"No data.")
726         return
727
728     if u"-gbps" in plot.get(u"title", u"").lower():
729         value = u"gbps"
730         multiplier = 1e6
731     else:
732         value = u"throughput"
733         multiplier = 1.0
734
735     test_type = u""
736
737     for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
738         for core in plot.get(u"core", tuple()):
739             # Prepare the data for the plot
740             data_x = list()
741             data_y = OrderedDict()
742             data_y_max = list()
743             idx = 1
744             for item in plot.get(u"include", tuple()):
745                 reg_ex = re.compile(str(item.format(core=core)).lower())
746                 for job in data:
747                     for build in job:
748                         for test_id, test in build.iteritems():
749                             if not re.match(reg_ex, str(test_id).lower()):
750                                 continue
751                             if data_y.get(test[u"parent"], None) is None:
752                                 data_y[test[u"parent"]] = list()
753                                 test_type = test[u"type"]
754                                 data_x.append(idx)
755                                 idx += 1
756                             try:
757                                 data_y[test[u"parent"]].append(
758                                     test[value][ttype.upper()][u"LOWER"] *
759                                     multiplier
760                                 )
761                             except (KeyError, TypeError):
762                                 pass
763
764             # Add plot traces
765             traces = list()
766             for idx, (key, vals) in enumerate(data_y.items()):
767                 name = re.sub(
768                     REGEX_NIC, u'', key.lower().replace(u'-ndrpdr', u'').
769                     replace(u'2n1l-', u'')
770                 )
771                 traces.append(
772                     plgo.Box(
773                         x=[data_x[idx], ] * len(data_x),
774                         y=[y / 1e6 if y else None for y in vals],
775                         name=(
776                             f"{idx+1}."
777                             f"({len(vals):02d} "
778                             f"run"
779                             f"{u's' if len(vals) > 1 else u''}) "
780                             f"{name}"
781                         ),
782                         hoverinfo=u"y+name"
783                     )
784                 )
785                 try:
786                     data_y_max.append(max(vals))
787                 except ValueError as err:
788                     logging.warning(f"No values to use.\n{err!r}")
789             try:
790                 # Create plot
791                 layout = deepcopy(plot[u"layout"])
792                 if layout.get(u"title", None):
793                     layout[u"title"] = \
794                         layout[u'title'].format(core=core, test_type=ttype)
795                     if test_type in (u"CPS", ):
796                         layout[u"title"] = f"<b>CPS:</b> {layout[u'title']}"
797                     else:
798                         layout[u"title"] = \
799                             f"<b>Throughput:</b> {layout[u'title']}"
800                 if data_y_max:
801                     layout[u"yaxis"][u"range"] = [0, max(data_y_max) / 1e6 + 1]
802                 plpl = plgo.Figure(data=traces, layout=layout)
803
804                 # Export Plot
805                 file_name = (
806                     f"{plot[u'output-file'].format(core=core, test_type=ttype)}"
807                     f".html"
808                 )
809                 logging.info(f"    Writing file {file_name}")
810                 ploff.plot(
811                     plpl,
812                     show_link=False,
813                     auto_open=False,
814                     filename=file_name
815                 )
816             except PlotlyError as err:
817                 logging.error(
818                     f"   Finished with error: {repr(err)}".replace(u"\n", u" ")
819                 )
820
821
822 def plot_mrr_box_name(plot, input_data):
823     """Generate the plot(s) with algorithm: plot_mrr_box_name
824     specified in the specification file.
825
826     :param plot: Plot to generate.
827     :param input_data: Data to process.
828     :type plot: pandas.Series
829     :type input_data: InputData
830     """
831
832     # Transform the data
833     logging.info(
834         f"    Creating data set for the {plot.get(u'type', u'')} "
835         f"{plot.get(u'title', u'')}."
836     )
837     data = input_data.filter_tests_by_name(
838         plot,
839         params=[u"result", u"parent", u"tags", u"type"]
840     )
841     if data is None:
842         logging.error(u"No data.")
843         return
844
845     for core in plot.get(u"core", tuple()):
846         # Prepare the data for the plot
847         data_x = list()
848         data_names = list()
849         data_y = list()
850         data_y_max = list()
851         idx = 1
852         for item in plot.get(u"include", tuple()):
853             reg_ex = re.compile(str(item.format(core=core)).lower())
854             for job in data:
855                 for build in job:
856                     for test_id, test in build.iteritems():
857                         if not re.match(reg_ex, str(test_id).lower()):
858                             continue
859                         try:
860                             data_x.append(idx)
861                             name = re.sub(
862                                 REGEX_NIC, u'', test[u'parent'].lower().
863                                 replace(u'-mrr', u'').replace(u'2n1l-', u'')
864                             )
865                             data_y.append(test[u"result"][u"samples"])
866                             data_names.append(
867                                 f"{idx}."
868                                 f"({len(data_y[-1]):02d} "
869                                 f"run{u's' if len(data_y[-1]) > 1 else u''}) "
870                                 f"{name}"
871                             )
872                             data_y_max.append(max(data_y[-1]))
873                             idx += 1
874                         except (KeyError, TypeError):
875                             pass
876
877         # Add plot traces
878         traces = list()
879         for idx, x_item in enumerate(data_x):
880             traces.append(
881                 plgo.Box(
882                     x=[x_item, ] * len(data_y[idx]),
883                     y=data_y[idx],
884                     name=data_names[idx],
885                     hoverinfo=u"y+name"
886                 )
887             )
888
889         try:
890             # Create plot
891             layout = deepcopy(plot[u"layout"])
892             if layout.get(u"title", None):
893                 layout[u"title"] = (
894                     f"<b>Throughput:</b> {layout[u'title'].format(core=core)}"
895                 )
896             if data_y_max:
897                 layout[u"yaxis"][u"range"] = [0, max(data_y_max) + 1]
898             plpl = plgo.Figure(data=traces, layout=layout)
899
900             # Export Plot
901             file_name = f"{plot[u'output-file'].format(core=core)}.html"
902             logging.info(f"    Writing file {file_name}")
903             ploff.plot(
904                 plpl,
905                 show_link=False,
906                 auto_open=False,
907                 filename=file_name
908             )
909         except PlotlyError as err:
910             logging.error(
911                 f"   Finished with error: {repr(err)}".replace(u"\n", u" ")
912             )
913
914
915 def plot_tsa_name(plot, input_data):
916     """Generate the plot(s) with algorithm:
917     plot_tsa_name
918     specified in the specification file.
919
920     :param plot: Plot to generate.
921     :param input_data: Data to process.
922     :type plot: pandas.Series
923     :type input_data: InputData
924     """
925
926     # Transform the data
927     plot_title = plot.get(u"title", u"")
928     logging.info(
929         f"    Creating data set for the {plot.get(u'type', u'')} {plot_title}."
930     )
931     data = input_data.filter_tests_by_name(
932         plot,
933         params=[u"throughput", u"gbps", u"parent", u"tags", u"type"]
934     )
935     if data is None:
936         logging.error(u"No data.")
937         return
938
939     plot_title = plot_title.lower()
940
941     if u"-gbps" in plot_title:
942         value = u"gbps"
943         h_unit = u"Gbps"
944         multiplier = 1e6
945     else:
946         value = u"throughput"
947         h_unit = u"Mpps"
948         multiplier = 1.0
949
950     for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
951         y_vals = OrderedDict()
952         for item in plot.get(u"include", tuple()):
953             reg_ex = re.compile(str(item).lower())
954             for job in data:
955                 for build in job:
956                     for test_id, test in build.iteritems():
957                         if re.match(reg_ex, str(test_id).lower()):
958                             if y_vals.get(test[u"parent"], None) is None:
959                                 y_vals[test[u"parent"]] = {
960                                     u"1": list(),
961                                     u"2": list(),
962                                     u"4": list()
963                                 }
964                             try:
965                                 if test[u"type"] not in (u"NDRPDR", u"CPS"):
966                                     continue
967
968                                 if u"1C" in test[u"tags"]:
969                                     y_vals[test[u"parent"]][u"1"].append(
970                                         test[value][ttype.upper()][u"LOWER"] *
971                                         multiplier
972                                     )
973                                 elif u"2C" in test[u"tags"]:
974                                     y_vals[test[u"parent"]][u"2"].append(
975                                         test[value][ttype.upper()][u"LOWER"] *
976                                         multiplier
977                                     )
978                                 elif u"4C" in test[u"tags"]:
979                                     y_vals[test[u"parent"]][u"4"].append(
980                                         test[value][ttype.upper()][u"LOWER"] *
981                                         multiplier
982                                     )
983                             except (KeyError, TypeError):
984                                 pass
985
986         if not y_vals:
987             logging.warning(f"No data for the plot {plot.get(u'title', u'')}")
988             return
989
990         y_1c_max = dict()
991         for test_name, test_vals in y_vals.items():
992             for key, test_val in test_vals.items():
993                 if test_val:
994                     avg_val = sum(test_val) / len(test_val)
995                     y_vals[test_name][key] = [avg_val, len(test_val)]
996                     ideal = avg_val / (int(key) * 1e6)
997                     if test_name not in y_1c_max or ideal > y_1c_max[test_name]:
998                         y_1c_max[test_name] = ideal
999
1000         vals = OrderedDict()
1001         y_max = list()
1002         nic_limit = 0
1003         lnk_limit = 0
1004         pci_limit = 0
1005         for test_name, test_vals in y_vals.items():
1006             try:
1007                 if test_vals[u"1"][1]:
1008                     name = re.sub(
1009                         REGEX_NIC,
1010                         u"",
1011                         test_name.replace(u'-ndrpdr', u'').
1012                         replace(u'2n1l-', u'')
1013                     )
1014                     vals[name] = OrderedDict()
1015                     y_val_1 = test_vals[u"1"][0] / 1e6
1016                     y_val_2 = test_vals[u"2"][0] / 1e6 if test_vals[u"2"][0] \
1017                         else None
1018                     y_val_4 = test_vals[u"4"][0] / 1e6 if test_vals[u"4"][0] \
1019                         else None
1020
1021                     vals[name][u"val"] = [y_val_1, y_val_2, y_val_4]
1022                     vals[name][u"rel"] = [1.0, None, None]
1023                     vals[name][u"ideal"] = [
1024                         y_1c_max[test_name],
1025                         y_1c_max[test_name] * 2,
1026                         y_1c_max[test_name] * 4
1027                     ]
1028                     vals[name][u"diff"] = [
1029                         (y_val_1 - y_1c_max[test_name]) * 100 / y_val_1,
1030                         None,
1031                         None
1032                     ]
1033                     vals[name][u"count"] = [
1034                         test_vals[u"1"][1],
1035                         test_vals[u"2"][1],
1036                         test_vals[u"4"][1]
1037                     ]
1038
1039                     try:
1040                         val_max = max(vals[name][u"val"])
1041                     except ValueError as err:
1042                         logging.error(repr(err))
1043                         continue
1044                     if val_max:
1045                         y_max.append(val_max)
1046
1047                     if y_val_2:
1048                         vals[name][u"rel"][1] = round(y_val_2 / y_val_1, 2)
1049                         vals[name][u"diff"][1] = \
1050                             (y_val_2 - vals[name][u"ideal"][1]) * 100 / y_val_2
1051                     if y_val_4:
1052                         vals[name][u"rel"][2] = round(y_val_4 / y_val_1, 2)
1053                         vals[name][u"diff"][2] = \
1054                             (y_val_4 - vals[name][u"ideal"][2]) * 100 / y_val_4
1055             except IndexError as err:
1056                 logging.warning(f"No data for {test_name}")
1057                 logging.warning(repr(err))
1058
1059             # Limits:
1060             if u"x520" in test_name:
1061                 limit = plot[u"limits"][u"nic"][u"x520"]
1062             elif u"x710" in test_name:
1063                 limit = plot[u"limits"][u"nic"][u"x710"]
1064             elif u"xxv710" in test_name:
1065                 limit = plot[u"limits"][u"nic"][u"xxv710"]
1066             elif u"xl710" in test_name:
1067                 limit = plot[u"limits"][u"nic"][u"xl710"]
1068             elif u"x553" in test_name:
1069                 limit = plot[u"limits"][u"nic"][u"x553"]
1070             elif u"cx556a" in test_name:
1071                 limit = plot[u"limits"][u"nic"][u"cx556a"]
1072             elif u"e810cq" in test_name:
1073                 limit = plot[u"limits"][u"nic"][u"e810cq"]
1074             else:
1075                 limit = 0
1076             if limit > nic_limit:
1077                 nic_limit = limit
1078
1079             mul = 2 if u"ge2p" in test_name else 1
1080             if u"10ge" in test_name:
1081                 limit = plot[u"limits"][u"link"][u"10ge"] * mul
1082             elif u"25ge" in test_name:
1083                 limit = plot[u"limits"][u"link"][u"25ge"] * mul
1084             elif u"40ge" in test_name:
1085                 limit = plot[u"limits"][u"link"][u"40ge"] * mul
1086             elif u"100ge" in test_name:
1087                 limit = plot[u"limits"][u"link"][u"100ge"] * mul
1088             else:
1089                 limit = 0
1090             if limit > lnk_limit:
1091                 lnk_limit = limit
1092
1093             if u"cx556a" in test_name:
1094                 limit = plot[u"limits"][u"pci"][u"pci-g3-x8"]
1095             else:
1096                 limit = plot[u"limits"][u"pci"][u"pci-g3-x16"]
1097             if limit > pci_limit:
1098                 pci_limit = limit
1099
1100         traces = list()
1101         annotations = list()
1102         x_vals = [1, 2, 4]
1103
1104         # Limits:
1105         if u"-gbps" not in plot_title and u"-cps-" not in plot_title:
1106             nic_limit /= 1e6
1107             lnk_limit /= 1e6
1108             pci_limit /= 1e6
1109             min_limit = min((nic_limit, lnk_limit, pci_limit))
1110             if nic_limit == min_limit:
1111                 traces.append(plgo.Scatter(
1112                     x=x_vals,
1113                     y=[nic_limit, ] * len(x_vals),
1114                     name=f"NIC: {nic_limit:.2f}Mpps",
1115                     showlegend=False,
1116                     mode=u"lines",
1117                     line=dict(
1118                         dash=u"dot",
1119                         color=COLORS[-1],
1120                         width=1),
1121                     hoverinfo=u"none"
1122                 ))
1123                 annotations.append(dict(
1124                     x=1,
1125                     y=nic_limit,
1126                     xref=u"x",
1127                     yref=u"y",
1128                     xanchor=u"left",
1129                     yanchor=u"bottom",
1130                     text=f"NIC: {nic_limit:.2f}Mpps",
1131                     font=dict(
1132                         size=14,
1133                         color=COLORS[-1],
1134                     ),
1135                     align=u"left",
1136                     showarrow=False
1137                 ))
1138                 y_max.append(nic_limit)
1139             elif lnk_limit == min_limit:
1140                 traces.append(plgo.Scatter(
1141                     x=x_vals,
1142                     y=[lnk_limit, ] * len(x_vals),
1143                     name=f"Link: {lnk_limit:.2f}Mpps",
1144                     showlegend=False,
1145                     mode=u"lines",
1146                     line=dict(
1147                         dash=u"dot",
1148                         color=COLORS[-1],
1149                         width=1),
1150                     hoverinfo=u"none"
1151                 ))
1152                 annotations.append(dict(
1153                     x=1,
1154                     y=lnk_limit,
1155                     xref=u"x",
1156                     yref=u"y",
1157                     xanchor=u"left",
1158                     yanchor=u"bottom",
1159                     text=f"Link: {lnk_limit:.2f}Mpps",
1160                     font=dict(
1161                         size=14,
1162                         color=COLORS[-1],
1163                     ),
1164                     align=u"left",
1165                     showarrow=False
1166                 ))
1167                 y_max.append(lnk_limit)
1168             elif pci_limit == min_limit:
1169                 traces.append(plgo.Scatter(
1170                     x=x_vals,
1171                     y=[pci_limit, ] * len(x_vals),
1172                     name=f"PCIe: {pci_limit:.2f}Mpps",
1173                     showlegend=False,
1174                     mode=u"lines",
1175                     line=dict(
1176                         dash=u"dot",
1177                         color=COLORS[-1],
1178                         width=1),
1179                     hoverinfo=u"none"
1180                 ))
1181                 annotations.append(dict(
1182                     x=1,
1183                     y=pci_limit,
1184                     xref=u"x",
1185                     yref=u"y",
1186                     xanchor=u"left",
1187                     yanchor=u"bottom",
1188                     text=f"PCIe: {pci_limit:.2f}Mpps",
1189                     font=dict(
1190                         size=14,
1191                         color=COLORS[-1],
1192                     ),
1193                     align=u"left",
1194                     showarrow=False
1195                 ))
1196                 y_max.append(pci_limit)
1197
1198         # Perfect and measured:
1199         cidx = 0
1200         for name, val in vals.items():
1201             hovertext = list()
1202             try:
1203                 for idx in range(len(val[u"val"])):
1204                     htext = ""
1205                     if isinstance(val[u"val"][idx], float):
1206                         htext += (
1207                             f"No. of Runs: {val[u'count'][idx]}<br>"
1208                             f"Mean: {val[u'val'][idx]:.2f}{h_unit}<br>"
1209                         )
1210                     if isinstance(val[u"diff"][idx], float):
1211                         htext += f"Diff: {round(val[u'diff'][idx]):.0f}%<br>"
1212                     if isinstance(val[u"rel"][idx], float):
1213                         htext += f"Speedup: {val[u'rel'][idx]:.2f}"
1214                     hovertext.append(htext)
1215                 traces.append(
1216                     plgo.Scatter(
1217                         x=x_vals,
1218                         y=val[u"val"],
1219                         name=name,
1220                         legendgroup=name,
1221                         mode=u"lines+markers",
1222                         line=dict(
1223                             color=COLORS[cidx],
1224                             width=2),
1225                         marker=dict(
1226                             symbol=u"circle",
1227                             size=10
1228                         ),
1229                         text=hovertext,
1230                         hoverinfo=u"text+name"
1231                     )
1232                 )
1233                 traces.append(
1234                     plgo.Scatter(
1235                         x=x_vals,
1236                         y=val[u"ideal"],
1237                         name=f"{name} perfect",
1238                         legendgroup=name,
1239                         showlegend=False,
1240                         mode=u"lines",
1241                         line=dict(
1242                             color=COLORS[cidx],
1243                             width=2,
1244                             dash=u"dash"),
1245                         text=[f"Perfect: {y:.2f}Mpps" for y in val[u"ideal"]],
1246                         hoverinfo=u"text"
1247                     )
1248                 )
1249                 cidx += 1
1250             except (IndexError, ValueError, KeyError) as err:
1251                 logging.warning(f"No data for {name}\n{repr(err)}")
1252
1253         try:
1254             # Create plot
1255             file_name = f"{plot[u'output-file'].format(test_type=ttype)}.html"
1256             logging.info(f"    Writing file {file_name}")
1257             layout = deepcopy(plot[u"layout"])
1258             if layout.get(u"title", None):
1259                 layout[u"title"] = (
1260                     f"<b>Speedup Multi-core:</b> "
1261                     f"{layout[u'title'].format(test_type=ttype)}"
1262                 )
1263             layout[u"yaxis"][u"range"] = [0, int(max(y_max) * 1.1)]
1264             layout[u"annotations"].extend(annotations)
1265             plpl = plgo.Figure(data=traces, layout=layout)
1266
1267             # Export Plot
1268             ploff.plot(
1269                 plpl,
1270                 show_link=False,
1271                 auto_open=False,
1272                 filename=file_name
1273             )
1274         except PlotlyError as err:
1275             logging.error(
1276                 f"   Finished with error: {repr(err)}".replace(u"\n", u" ")
1277             )
1278
1279
1280 def plot_http_server_perf_box(plot, input_data):
1281     """Generate the plot(s) with algorithm: plot_http_server_perf_box
1282     specified in the specification file.
1283
1284     :param plot: Plot to generate.
1285     :param input_data: Data to process.
1286     :type plot: pandas.Series
1287     :type input_data: InputData
1288     """
1289
1290     # Transform the data
1291     logging.info(
1292         f"    Creating the data set for the {plot.get(u'type', u'')} "
1293         f"{plot.get(u'title', u'')}."
1294     )
1295     data = input_data.filter_data(plot)
1296     if data is None:
1297         logging.error(u"No data.")
1298         return
1299
1300     # Prepare the data for the plot
1301     y_vals = dict()
1302     for job in data:
1303         for build in job:
1304             for test in build:
1305                 if y_vals.get(test[u"name"], None) is None:
1306                     y_vals[test[u"name"]] = list()
1307                 try:
1308                     y_vals[test[u"name"]].append(test[u"result"])
1309                 except (KeyError, TypeError):
1310                     y_vals[test[u"name"]].append(None)
1311
1312     # Add None to the lists with missing data
1313     max_len = 0
1314     nr_of_samples = list()
1315     for val in y_vals.values():
1316         if len(val) > max_len:
1317             max_len = len(val)
1318         nr_of_samples.append(len(val))
1319     for val in y_vals.values():
1320         if len(val) < max_len:
1321             val.extend([None for _ in range(max_len - len(val))])
1322
1323     # Add plot traces
1324     traces = list()
1325     df_y = pd.DataFrame(y_vals)
1326     df_y.head()
1327     for i, col in enumerate(df_y.columns):
1328         name = \
1329             f"{i + 1}. " \
1330             f"({nr_of_samples[i]:02d} " \
1331             f"run{u's' if nr_of_samples[i] > 1 else u''}) " \
1332             f"{col.lower().replace(u'-ndrpdr', u'')}"
1333         if len(name) > 50:
1334             name_lst = name.split(u'-')
1335             name = u""
1336             split_name = True
1337             for segment in name_lst:
1338                 if (len(name) + len(segment) + 1) > 50 and split_name:
1339                     name += u"<br>    "
1340                     split_name = False
1341                 name += segment + u'-'
1342             name = name[:-1]
1343
1344         traces.append(plgo.Box(x=[str(i + 1) + u'.'] * len(df_y[col]),
1345                                y=df_y[col],
1346                                name=name,
1347                                **plot[u"traces"]))
1348     try:
1349         # Create plot
1350         plpl = plgo.Figure(data=traces, layout=plot[u"layout"])
1351
1352         # Export Plot
1353         logging.info(
1354             f"    Writing file {plot[u'output-file']}"
1355             f"{plot[u'output-file-type']}."
1356         )
1357         ploff.plot(
1358             plpl,
1359             show_link=False,
1360             auto_open=False,
1361             filename=f"{plot[u'output-file']}{plot[u'output-file-type']}"
1362         )
1363     except PlotlyError as err:
1364         logging.error(
1365             f"   Finished with error: {repr(err)}".replace(u"\n", u" ")
1366         )
1367         return
1368
1369
1370 def plot_nf_heatmap(plot, input_data):
1371     """Generate the plot(s) with algorithm: plot_nf_heatmap
1372     specified in the specification file.
1373
1374     :param plot: Plot to generate.
1375     :param input_data: Data to process.
1376     :type plot: pandas.Series
1377     :type input_data: InputData
1378     """
1379
1380     def sort_by_int(value):
1381         """Makes possible to sort a list of strings which represent integers.
1382
1383         :param value: Integer as a string.
1384         :type value: str
1385         :returns: Integer representation of input parameter 'value'.
1386         :rtype: int
1387         """
1388         return int(value)
1389
1390     regex_cn = re.compile(r'^(\d*)R(\d*)C$')
1391     regex_test_name = re.compile(r'^.*-(\d+ch|\d+pl)-'
1392                                  r'(\d+mif|\d+vh)-'
1393                                  r'(\d+vm\d+t|\d+dcr\d+t|\d+dcr\d+c).*$')
1394     vals = dict()
1395
1396     # Transform the data
1397     logging.info(
1398         f"    Creating the data set for the {plot.get(u'type', u'')} "
1399         f"{plot.get(u'title', u'')}."
1400     )
1401     in_data = input_data.filter_tests_by_name(
1402         plot,
1403         continue_on_error=True,
1404         params=[u"throughput", u"result", u"name", u"tags", u"type"]
1405     )
1406     if in_data is None or in_data.empty:
1407         logging.error(u"No data.")
1408         return
1409
1410     for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
1411         for core in plot.get(u"core", tuple()):
1412             for item in plot.get(u"include", tuple()):
1413                 reg_ex = re.compile(str(item.format(core=core)).lower())
1414                 for job in in_data:
1415                     for build in job:
1416                         for test_id, test in build.iteritems():
1417                             if not re.match(reg_ex, str(test_id).lower()):
1418                                 continue
1419                             for tag in test[u"tags"]:
1420                                 groups = re.search(regex_cn, tag)
1421                                 if groups:
1422                                     chain = str(groups.group(1))
1423                                     node = str(groups.group(2))
1424                                     break
1425                             else:
1426                                 continue
1427                             groups = re.search(regex_test_name, test[u"name"])
1428                             if groups and len(groups.groups()) == 3:
1429                                 hover_name = (
1430                                     f"{str(groups.group(1))}-"
1431                                     f"{str(groups.group(2))}-"
1432                                     f"{str(groups.group(3))}"
1433                                 )
1434                             else:
1435                                 hover_name = u""
1436                             if vals.get(chain, None) is None:
1437                                 vals[chain] = dict()
1438                             if vals[chain].get(node, None) is None:
1439                                 vals[chain][node] = dict(
1440                                     name=hover_name,
1441                                     vals=list(),
1442                                     nr=None,
1443                                     mean=None,
1444                                     stdev=None
1445                                 )
1446                             try:
1447                                 if ttype == u"mrr":
1448                                     result = test[u"result"][u"receive-rate"]
1449                                 elif ttype == u"pdr":
1450                                     result = \
1451                                         test[u"throughput"][u"PDR"][u"LOWER"]
1452                                 elif ttype == u"ndr":
1453                                     result = \
1454                                         test[u"throughput"][u"NDR"][u"LOWER"]
1455                                 else:
1456                                     result = None
1457                             except TypeError:
1458                                 result = None
1459
1460                             if result:
1461                                 vals[chain][node][u"vals"].append(result)
1462
1463             if not vals:
1464                 logging.error(u"No data.")
1465                 return
1466
1467             txt_chains = list()
1468             txt_nodes = list()
1469             for key_c in vals:
1470                 txt_chains.append(key_c)
1471                 for key_n in vals[key_c].keys():
1472                     txt_nodes.append(key_n)
1473                     if vals[key_c][key_n][u"vals"]:
1474                         vals[key_c][key_n][u"nr"] = \
1475                             len(vals[key_c][key_n][u"vals"])
1476                         vals[key_c][key_n][u"mean"] = \
1477                             round(mean(vals[key_c][key_n][u"vals"]) / 1e6, 1)
1478                         vals[key_c][key_n][u"stdev"] = \
1479                             round(stdev(vals[key_c][key_n][u"vals"]) / 1e6, 1)
1480             txt_nodes = list(set(txt_nodes))
1481
1482             txt_chains = sorted(txt_chains, key=sort_by_int)
1483             txt_nodes = sorted(txt_nodes, key=sort_by_int)
1484
1485             chains = [i + 1 for i in range(len(txt_chains))]
1486             nodes = [i + 1 for i in range(len(txt_nodes))]
1487
1488             data = [list() for _ in range(len(chains))]
1489             for chain in chains:
1490                 for node in nodes:
1491                     try:
1492                         val = vals[txt_chains[chain - 1]] \
1493                             [txt_nodes[node - 1]][u"mean"]
1494                     except (KeyError, IndexError):
1495                         val = None
1496                     data[chain - 1].append(val)
1497
1498             # Color scales:
1499             my_green = [[0.0, u"rgb(235, 249, 242)"],
1500                         [1.0, u"rgb(45, 134, 89)"]]
1501
1502             my_blue = [[0.0, u"rgb(236, 242, 248)"],
1503                        [1.0, u"rgb(57, 115, 172)"]]
1504
1505             my_grey = [[0.0, u"rgb(230, 230, 230)"],
1506                        [1.0, u"rgb(102, 102, 102)"]]
1507
1508             hovertext = list()
1509             annotations = list()
1510
1511             text = (u"Test: {name}<br>"
1512                     u"Runs: {nr}<br>"
1513                     u"Thput: {val}<br>"
1514                     u"StDev: {stdev}")
1515
1516             for chain, _ in enumerate(txt_chains):
1517                 hover_line = list()
1518                 for node, _ in enumerate(txt_nodes):
1519                     if data[chain][node] is not None:
1520                         annotations.append(
1521                             dict(
1522                                 x=node+1,
1523                                 y=chain+1,
1524                                 xref=u"x",
1525                                 yref=u"y",
1526                                 xanchor=u"center",
1527                                 yanchor=u"middle",
1528                                 text=str(data[chain][node]),
1529                                 font=dict(
1530                                     size=14,
1531                                 ),
1532                                 align=u"center",
1533                                 showarrow=False
1534                             )
1535                         )
1536                         hover_line.append(text.format(
1537                             name=vals[txt_chains[chain]][txt_nodes[node]]
1538                             [u"name"],
1539                             nr=vals[txt_chains[chain]][txt_nodes[node]][u"nr"],
1540                             val=data[chain][node],
1541                             stdev=vals[txt_chains[chain]][txt_nodes[node]]
1542                             [u"stdev"]
1543                         ))
1544                 hovertext.append(hover_line)
1545
1546             traces = [
1547                 plgo.Heatmap(
1548                     x=nodes,
1549                     y=chains,
1550                     z=data,
1551                     colorbar=dict(
1552                         title=plot.get(u"z-axis", u"{test_type}").
1553                         format(test_type=ttype.upper()),
1554                         titleside=u"right",
1555                         titlefont=dict(
1556                             size=16
1557                         ),
1558                         tickfont=dict(
1559                             size=16,
1560                         ),
1561                         tickformat=u".1f",
1562                         yanchor=u"bottom",
1563                         y=-0.02,
1564                         len=0.925,
1565                     ),
1566                     showscale=True,
1567                     colorscale=my_green,
1568                     text=hovertext,
1569                     hoverinfo=u"text"
1570                 )
1571             ]
1572
1573             for idx, item in enumerate(txt_nodes):
1574                 # X-axis, numbers:
1575                 annotations.append(
1576                     dict(
1577                         x=idx+1,
1578                         y=0.05,
1579                         xref=u"x",
1580                         yref=u"y",
1581                         xanchor=u"center",
1582                         yanchor=u"top",
1583                         text=item,
1584                         font=dict(
1585                             size=16,
1586                         ),
1587                         align=u"center",
1588                         showarrow=False
1589                     )
1590                 )
1591             for idx, item in enumerate(txt_chains):
1592                 # Y-axis, numbers:
1593                 annotations.append(
1594                     dict(
1595                         x=0.35,
1596                         y=idx+1,
1597                         xref=u"x",
1598                         yref=u"y",
1599                         xanchor=u"right",
1600                         yanchor=u"middle",
1601                         text=item,
1602                         font=dict(
1603                             size=16,
1604                         ),
1605                         align=u"center",
1606                         showarrow=False
1607                     )
1608                 )
1609             # X-axis, title:
1610             annotations.append(
1611                 dict(
1612                     x=0.55,
1613                     y=-0.15,
1614                     xref=u"paper",
1615                     yref=u"y",
1616                     xanchor=u"center",
1617                     yanchor=u"bottom",
1618                     text=plot.get(u"x-axis", u""),
1619                     font=dict(
1620                         size=16,
1621                     ),
1622                     align=u"center",
1623                     showarrow=False
1624                 )
1625             )
1626             # Y-axis, title:
1627             annotations.append(
1628                 dict(
1629                     x=-0.1,
1630                     y=0.5,
1631                     xref=u"x",
1632                     yref=u"paper",
1633                     xanchor=u"center",
1634                     yanchor=u"middle",
1635                     text=plot.get(u"y-axis", u""),
1636                     font=dict(
1637                         size=16,
1638                     ),
1639                     align=u"center",
1640                     textangle=270,
1641                     showarrow=False
1642                 )
1643             )
1644             updatemenus = list([
1645                 dict(
1646                     x=1.0,
1647                     y=0.0,
1648                     xanchor=u"right",
1649                     yanchor=u"bottom",
1650                     direction=u"up",
1651                     buttons=list([
1652                         dict(
1653                             args=[
1654                                 {
1655                                     u"colorscale": [my_green, ],
1656                                     u"reversescale": False
1657                                 }
1658                             ],
1659                             label=u"Green",
1660                             method=u"update"
1661                         ),
1662                         dict(
1663                             args=[
1664                                 {
1665                                     u"colorscale": [my_blue, ],
1666                                     u"reversescale": False
1667                                 }
1668                             ],
1669                             label=u"Blue",
1670                             method=u"update"
1671                         ),
1672                         dict(
1673                             args=[
1674                                 {
1675                                     u"colorscale": [my_grey, ],
1676                                     u"reversescale": False
1677                                 }
1678                             ],
1679                             label=u"Grey",
1680                             method=u"update"
1681                         )
1682                     ])
1683                 )
1684             ])
1685
1686             try:
1687                 layout = deepcopy(plot[u"layout"])
1688             except KeyError as err:
1689                 logging.error(
1690                     f"Finished with error: No layout defined\n{repr(err)}"
1691                 )
1692                 return
1693
1694             layout[u"annotations"] = annotations
1695             layout[u'updatemenus'] = updatemenus
1696             if layout.get(u"title", None):
1697                 layout[u"title"] = layout[u'title'].replace(u"test_type", ttype)
1698
1699             try:
1700                 # Create plot
1701                 plpl = plgo.Figure(data=traces, layout=layout)
1702
1703                 # Export Plot
1704                 file_name = (
1705                     f"{plot[u'output-file'].format(core=core, test_type=ttype)}"
1706                     f".html"
1707                 )
1708                 logging.info(f"    Writing file {file_name}")
1709                 ploff.plot(
1710                     plpl,
1711                     show_link=False,
1712                     auto_open=False,
1713                     filename=file_name
1714                 )
1715             except PlotlyError as err:
1716                 logging.error(
1717                     f"   Finished with error: {repr(err)}".replace(u"\n", u" ")
1718                 )