Report: Add rls data
[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"]
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"]
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         if test_type in (u"VSAP_CPS", u"VSAP_RPS"):
649             data_y = [y if y else None for y in df_y[col]]
650         else:
651             data_y = [y / 1e6 if y else None for y in df_y[col]]
652         kwargs = dict(
653             x=[str(i + 1) + u'.'] * len(df_y[col]),
654             y=data_y,
655             name=(
656                 f"{i + 1}. "
657                 f"({nr_of_samples[i]:02d} "
658                 f"run{u's' if nr_of_samples[i] > 1 else u''}) "
659                 f"{tst_name}"
660             ),
661             hoverinfo=u"y+name"
662         )
663         if test_type in (u"SOAK", ):
664             kwargs[u"boxpoints"] = u"all"
665
666         traces.append(plgo.Box(**kwargs))
667
668         try:
669             val_max = max(df_y[col])
670             if val_max:
671                 y_max.append(int(val_max / 1e6))
672         except (ValueError, TypeError) as err:
673             logging.error(repr(err))
674             continue
675
676     try:
677         # Create plot
678         layout = deepcopy(plot[u"layout"])
679         if layout.get(u"title", None):
680             if test_type in (u"HOSTSTACK", ):
681                 layout[u"title"] = f"<b>Bandwidth:</b> {layout[u'title']}"
682             elif test_type == u"VSAP_CPS":
683                 layout[u"title"] = f"<b>CPS:</b> {layout[u'title']}"
684                 layout[u"yaxis"][u"title"] = u"<b>Connection Rate [cps]</b>"
685             elif test_type == u"VSAP_RPS":
686                 layout[u"title"] = f"<b>RPS:</b> {layout[u'title']}"
687                 layout[u"yaxis"][u"title"] = u"<b>Connection Rate [rps]</b>"
688             else:
689                 layout[u"title"] = f"<b>Tput:</b> {layout[u'title']}"
690         if y_max and max(y_max) > 1:
691             layout[u"yaxis"][u"range"] = [0, max(y_max) + 2]
692         plpl = plgo.Figure(data=traces, layout=layout)
693
694         # Export Plot
695         logging.info(f"    Writing file {plot[u'output-file']}.html.")
696         ploff.plot(
697             plpl,
698             show_link=False,
699             auto_open=False,
700             filename=f"{plot[u'output-file']}.html"
701         )
702     except PlotlyError as err:
703         logging.error(
704             f"   Finished with error: {repr(err)}".replace(u"\n", u" ")
705         )
706         return
707
708
709 def plot_ndrpdr_box_name(plot, input_data):
710     """Generate the plot(s) with algorithm: plot_ndrpdr_box_name
711     specified in the specification file.
712
713     :param plot: Plot to generate.
714     :param input_data: Data to process.
715     :type plot: pandas.Series
716     :type input_data: InputData
717     """
718
719     # Transform the data
720     logging.info(
721         f"    Creating data set for the {plot.get(u'type', u'')} "
722         f"{plot.get(u'title', u'')}."
723     )
724     data = input_data.filter_tests_by_name(
725         plot,
726         params=[u"throughput", u"gbps", u"parent", u"tags", u"type"]
727     )
728     if data is None:
729         logging.error(u"No data.")
730         return
731
732     if u"-gbps" in plot.get(u"title", u"").lower():
733         value = u"gbps"
734         multiplier = 1e6
735     else:
736         value = u"throughput"
737         multiplier = 1.0
738
739     test_type = u""
740
741     for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
742         for core in plot.get(u"core", tuple()):
743             # Prepare the data for the plot
744             data_x = list()
745             data_y = OrderedDict()
746             data_y_max = list()
747             idx = 1
748             for item in plot.get(u"include", tuple()):
749                 reg_ex = re.compile(str(item.format(core=core)).lower())
750                 for job in data:
751                     for build in job:
752                         for test_id, test in build.iteritems():
753                             if not re.match(reg_ex, str(test_id).lower()):
754                                 continue
755                             if data_y.get(test[u"parent"], None) is None:
756                                 data_y[test[u"parent"]] = list()
757                                 test_type = test[u"type"]
758                                 data_x.append(idx)
759                                 idx += 1
760                             try:
761                                 data_y[test[u"parent"]].append(
762                                     test[value][ttype.upper()][u"LOWER"] *
763                                     multiplier
764                                 )
765                             except (KeyError, TypeError):
766                                 pass
767
768             # Add plot traces
769             traces = list()
770             for idx, (key, vals) in enumerate(data_y.items()):
771                 name = re.sub(
772                     REGEX_NIC, u'', key.lower().replace(u'-ndrpdr', u'').
773                     replace(u'2n1l-', u'')
774                 )
775                 traces.append(
776                     plgo.Box(
777                         x=[data_x[idx], ] * len(data_x),
778                         y=[y / 1e6 if y else None for y in vals],
779                         name=(
780                             f"{idx+1}."
781                             f"({len(vals):02d} "
782                             f"run"
783                             f"{u's' if len(vals) > 1 else u''}) "
784                             f"{name}"
785                         ),
786                         hoverinfo=u"y+name"
787                     )
788                 )
789                 try:
790                     data_y_max.append(max(vals))
791                 except ValueError as err:
792                     logging.warning(f"No values to use.\n{err!r}")
793             try:
794                 # Create plot
795                 layout = deepcopy(plot[u"layout"])
796                 if layout.get(u"title", None):
797                     layout[u"title"] = \
798                         layout[u'title'].format(core=core, test_type=ttype)
799                     if test_type in (u"CPS", ):
800                         layout[u"title"] = f"<b>CPS:</b> {layout[u'title']}"
801                     else:
802                         layout[u"title"] = \
803                             f"<b>Tput:</b> {layout[u'title']}"
804                 if data_y_max:
805                     layout[u"yaxis"][u"range"] = [0, max(data_y_max) / 1e6 + 1]
806                 plpl = plgo.Figure(data=traces, layout=layout)
807
808                 # Export Plot
809                 file_name = (
810                     f"{plot[u'output-file'].format(core=core, test_type=ttype)}"
811                     f".html"
812                 )
813                 logging.info(f"    Writing file {file_name}")
814                 ploff.plot(
815                     plpl,
816                     show_link=False,
817                     auto_open=False,
818                     filename=file_name
819                 )
820             except PlotlyError as err:
821                 logging.error(
822                     f"   Finished with error: {repr(err)}".replace(u"\n", u" ")
823                 )
824
825
826 def plot_mrr_box_name(plot, input_data):
827     """Generate the plot(s) with algorithm: plot_mrr_box_name
828     specified in the specification file.
829
830     :param plot: Plot to generate.
831     :param input_data: Data to process.
832     :type plot: pandas.Series
833     :type input_data: InputData
834     """
835
836     # Transform the data
837     logging.info(
838         f"    Creating data set for the {plot.get(u'type', u'')} "
839         f"{plot.get(u'title', u'')}."
840     )
841     data = input_data.filter_tests_by_name(
842         plot,
843         params=[u"result", u"parent", u"tags", u"type"]
844     )
845     if data is None:
846         logging.error(u"No data.")
847         return
848
849     for core in plot.get(u"core", tuple()):
850         # Prepare the data for the plot
851         data_x = list()
852         data_names = list()
853         data_y = list()
854         data_y_max = list()
855         idx = 1
856         for item in plot.get(u"include", tuple()):
857             reg_ex = re.compile(str(item.format(core=core)).lower())
858             for job in data:
859                 for build in job:
860                     for test_id, test in build.iteritems():
861                         if not re.match(reg_ex, str(test_id).lower()):
862                             continue
863                         try:
864                             data_x.append(idx)
865                             name = re.sub(
866                                 REGEX_NIC, u'', test[u'parent'].lower().
867                                 replace(u'-mrr', u'').replace(u'2n1l-', u'')
868                             )
869                             data_y.append(test[u"result"][u"samples"])
870                             data_names.append(
871                                 f"{idx}."
872                                 f"({len(data_y[-1]):02d} "
873                                 f"run{u's' if len(data_y[-1]) > 1 else u''}) "
874                                 f"{name}"
875                             )
876                             data_y_max.append(max(data_y[-1]))
877                             idx += 1
878                         except (KeyError, TypeError):
879                             pass
880
881         # Add plot traces
882         traces = list()
883         for idx, x_item in enumerate(data_x):
884             traces.append(
885                 plgo.Box(
886                     x=[x_item, ] * len(data_y[idx]),
887                     y=data_y[idx],
888                     name=data_names[idx],
889                     hoverinfo=u"y+name"
890                 )
891             )
892
893         try:
894             # Create plot
895             layout = deepcopy(plot[u"layout"])
896             if layout.get(u"title", None):
897                 layout[u"title"] = (
898                     f"<b>Tput:</b> {layout[u'title'].format(core=core)}"
899                 )
900             if data_y_max:
901                 layout[u"yaxis"][u"range"] = [0, max(data_y_max) + 1]
902             plpl = plgo.Figure(data=traces, layout=layout)
903
904             # Export Plot
905             file_name = f"{plot[u'output-file'].format(core=core)}.html"
906             logging.info(f"    Writing file {file_name}")
907             ploff.plot(
908                 plpl,
909                 show_link=False,
910                 auto_open=False,
911                 filename=file_name
912             )
913         except PlotlyError as err:
914             logging.error(
915                 f"   Finished with error: {repr(err)}".replace(u"\n", u" ")
916             )
917
918
919 def plot_tsa_name(plot, input_data):
920     """Generate the plot(s) with algorithm:
921     plot_tsa_name
922     specified in the specification file.
923
924     :param plot: Plot to generate.
925     :param input_data: Data to process.
926     :type plot: pandas.Series
927     :type input_data: InputData
928     """
929
930     # Transform the data
931     plot_title = plot.get(u"title", u"")
932     logging.info(
933         f"    Creating data set for the {plot.get(u'type', u'')} {plot_title}."
934     )
935     data = input_data.filter_tests_by_name(
936         plot,
937         params=[u"throughput", u"gbps", u"parent", u"tags", u"type"]
938     )
939     if data is None:
940         logging.error(u"No data.")
941         return
942
943     plot_title = plot_title.lower()
944
945     if u"-gbps" in plot_title:
946         value = u"gbps"
947         h_unit = u"Gbps"
948         multiplier = 1e6
949     else:
950         value = u"throughput"
951         h_unit = u"Mpps"
952         multiplier = 1.0
953
954     for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
955         y_vals = OrderedDict()
956         for item in plot.get(u"include", tuple()):
957             reg_ex = re.compile(str(item).lower())
958             for job in data:
959                 for build in job:
960                     for test_id, test in build.iteritems():
961                         if re.match(reg_ex, str(test_id).lower()):
962                             if y_vals.get(test[u"parent"], None) is None:
963                                 y_vals[test[u"parent"]] = {
964                                     u"1": list(),
965                                     u"2": list(),
966                                     u"4": list()
967                                 }
968                             try:
969                                 if test[u"type"] not in (u"NDRPDR", u"CPS"):
970                                     continue
971
972                                 if u"1C" in test[u"tags"]:
973                                     y_vals[test[u"parent"]][u"1"].append(
974                                         test[value][ttype.upper()][u"LOWER"] *
975                                         multiplier
976                                     )
977                                 elif u"2C" in test[u"tags"]:
978                                     y_vals[test[u"parent"]][u"2"].append(
979                                         test[value][ttype.upper()][u"LOWER"] *
980                                         multiplier
981                                     )
982                                 elif u"4C" in test[u"tags"]:
983                                     y_vals[test[u"parent"]][u"4"].append(
984                                         test[value][ttype.upper()][u"LOWER"] *
985                                         multiplier
986                                     )
987                             except (KeyError, TypeError):
988                                 pass
989
990         if not y_vals:
991             logging.warning(f"No data for the plot {plot.get(u'title', u'')}")
992             return
993
994         y_1c_max = dict()
995         for test_name, test_vals in y_vals.items():
996             for key, test_val in test_vals.items():
997                 if test_val:
998                     avg_val = sum(test_val) / len(test_val)
999                     y_vals[test_name][key] = [avg_val, len(test_val)]
1000                     ideal = avg_val / (int(key) * 1e6)
1001                     if test_name not in y_1c_max or ideal > y_1c_max[test_name]:
1002                         y_1c_max[test_name] = ideal
1003
1004         vals = OrderedDict()
1005         y_max = list()
1006         nic_limit = 0
1007         lnk_limit = 0
1008         pci_limit = 0
1009         for test_name, test_vals in y_vals.items():
1010             try:
1011                 if test_vals[u"1"][1]:
1012                     name = re.sub(
1013                         REGEX_NIC,
1014                         u"",
1015                         test_name.replace(u'-ndrpdr', u'').
1016                         replace(u'2n1l-', u'')
1017                     )
1018                     vals[name] = OrderedDict()
1019                     y_val_1 = test_vals[u"1"][0] / 1e6
1020                     y_val_2 = test_vals[u"2"][0] / 1e6 if test_vals[u"2"][0] \
1021                         else None
1022                     y_val_4 = test_vals[u"4"][0] / 1e6 if test_vals[u"4"][0] \
1023                         else None
1024
1025                     vals[name][u"val"] = [y_val_1, y_val_2, y_val_4]
1026                     vals[name][u"rel"] = [1.0, None, None]
1027                     vals[name][u"ideal"] = [
1028                         y_1c_max[test_name],
1029                         y_1c_max[test_name] * 2,
1030                         y_1c_max[test_name] * 4
1031                     ]
1032                     vals[name][u"diff"] = [
1033                         (y_val_1 - y_1c_max[test_name]) * 100 / y_val_1,
1034                         None,
1035                         None
1036                     ]
1037                     vals[name][u"count"] = [
1038                         test_vals[u"1"][1],
1039                         test_vals[u"2"][1],
1040                         test_vals[u"4"][1]
1041                     ]
1042
1043                     try:
1044                         val_max = max(vals[name][u"val"])
1045                     except ValueError as err:
1046                         logging.error(repr(err))
1047                         continue
1048                     if val_max:
1049                         y_max.append(val_max)
1050
1051                     if y_val_2:
1052                         vals[name][u"rel"][1] = round(y_val_2 / y_val_1, 2)
1053                         vals[name][u"diff"][1] = \
1054                             (y_val_2 - vals[name][u"ideal"][1]) * 100 / y_val_2
1055                     if y_val_4:
1056                         vals[name][u"rel"][2] = round(y_val_4 / y_val_1, 2)
1057                         vals[name][u"diff"][2] = \
1058                             (y_val_4 - vals[name][u"ideal"][2]) * 100 / y_val_4
1059             except IndexError as err:
1060                 logging.warning(f"No data for {test_name}")
1061                 logging.warning(repr(err))
1062
1063             # Limits:
1064             if u"x520" in test_name:
1065                 limit = plot[u"limits"][u"nic"][u"x520"]
1066             elif u"x710" in test_name:
1067                 limit = plot[u"limits"][u"nic"][u"x710"]
1068             elif u"xxv710" in test_name:
1069                 limit = plot[u"limits"][u"nic"][u"xxv710"]
1070             elif u"xl710" in test_name:
1071                 limit = plot[u"limits"][u"nic"][u"xl710"]
1072             elif u"x553" in test_name:
1073                 limit = plot[u"limits"][u"nic"][u"x553"]
1074             elif u"cx556a" in test_name:
1075                 limit = plot[u"limits"][u"nic"][u"cx556a"]
1076             elif u"e810cq" in test_name:
1077                 limit = plot[u"limits"][u"nic"][u"e810cq"]
1078             else:
1079                 limit = 0
1080             if limit > nic_limit:
1081                 nic_limit = limit
1082
1083             mul = 2 if u"ge2p" in test_name else 1
1084             if u"10ge" in test_name:
1085                 limit = plot[u"limits"][u"link"][u"10ge"] * mul
1086             elif u"25ge" in test_name:
1087                 limit = plot[u"limits"][u"link"][u"25ge"] * mul
1088             elif u"40ge" in test_name:
1089                 limit = plot[u"limits"][u"link"][u"40ge"] * mul
1090             elif u"100ge" in test_name:
1091                 limit = plot[u"limits"][u"link"][u"100ge"] * mul
1092             else:
1093                 limit = 0
1094             if limit > lnk_limit:
1095                 lnk_limit = limit
1096
1097             if u"cx556a" in test_name:
1098                 limit = plot[u"limits"][u"pci"][u"pci-g3-x8"]
1099             else:
1100                 limit = plot[u"limits"][u"pci"][u"pci-g3-x16"]
1101             if limit > pci_limit:
1102                 pci_limit = limit
1103
1104         traces = list()
1105         annotations = list()
1106         x_vals = [1, 2, 4]
1107
1108         # Limits:
1109         if u"-gbps" not in plot_title and u"-cps-" not in plot_title:
1110             nic_limit /= 1e6
1111             lnk_limit /= 1e6
1112             pci_limit /= 1e6
1113             min_limit = min((nic_limit, lnk_limit, pci_limit))
1114             if nic_limit == min_limit:
1115                 traces.append(plgo.Scatter(
1116                     x=x_vals,
1117                     y=[nic_limit, ] * len(x_vals),
1118                     name=f"NIC: {nic_limit:.2f}Mpps",
1119                     showlegend=False,
1120                     mode=u"lines",
1121                     line=dict(
1122                         dash=u"dot",
1123                         color=COLORS[-1],
1124                         width=1),
1125                     hoverinfo=u"none"
1126                 ))
1127                 annotations.append(dict(
1128                     x=1,
1129                     y=nic_limit,
1130                     xref=u"x",
1131                     yref=u"y",
1132                     xanchor=u"left",
1133                     yanchor=u"bottom",
1134                     text=f"NIC: {nic_limit:.2f}Mpps",
1135                     font=dict(
1136                         size=14,
1137                         color=COLORS[-1],
1138                     ),
1139                     align=u"left",
1140                     showarrow=False
1141                 ))
1142                 y_max.append(nic_limit)
1143             elif lnk_limit == min_limit:
1144                 traces.append(plgo.Scatter(
1145                     x=x_vals,
1146                     y=[lnk_limit, ] * len(x_vals),
1147                     name=f"Link: {lnk_limit:.2f}Mpps",
1148                     showlegend=False,
1149                     mode=u"lines",
1150                     line=dict(
1151                         dash=u"dot",
1152                         color=COLORS[-1],
1153                         width=1),
1154                     hoverinfo=u"none"
1155                 ))
1156                 annotations.append(dict(
1157                     x=1,
1158                     y=lnk_limit,
1159                     xref=u"x",
1160                     yref=u"y",
1161                     xanchor=u"left",
1162                     yanchor=u"bottom",
1163                     text=f"Link: {lnk_limit:.2f}Mpps",
1164                     font=dict(
1165                         size=14,
1166                         color=COLORS[-1],
1167                     ),
1168                     align=u"left",
1169                     showarrow=False
1170                 ))
1171                 y_max.append(lnk_limit)
1172             elif pci_limit == min_limit:
1173                 traces.append(plgo.Scatter(
1174                     x=x_vals,
1175                     y=[pci_limit, ] * len(x_vals),
1176                     name=f"PCIe: {pci_limit:.2f}Mpps",
1177                     showlegend=False,
1178                     mode=u"lines",
1179                     line=dict(
1180                         dash=u"dot",
1181                         color=COLORS[-1],
1182                         width=1),
1183                     hoverinfo=u"none"
1184                 ))
1185                 annotations.append(dict(
1186                     x=1,
1187                     y=pci_limit,
1188                     xref=u"x",
1189                     yref=u"y",
1190                     xanchor=u"left",
1191                     yanchor=u"bottom",
1192                     text=f"PCIe: {pci_limit:.2f}Mpps",
1193                     font=dict(
1194                         size=14,
1195                         color=COLORS[-1],
1196                     ),
1197                     align=u"left",
1198                     showarrow=False
1199                 ))
1200                 y_max.append(pci_limit)
1201
1202         # Perfect and measured:
1203         cidx = 0
1204         for name, val in vals.items():
1205             hovertext = list()
1206             try:
1207                 for idx in range(len(val[u"val"])):
1208                     htext = ""
1209                     if isinstance(val[u"val"][idx], float):
1210                         htext += (
1211                             f"No. of Runs: {val[u'count'][idx]}<br>"
1212                             f"Mean: {val[u'val'][idx]:.2f}{h_unit}<br>"
1213                         )
1214                     if isinstance(val[u"diff"][idx], float):
1215                         htext += f"Diff: {round(val[u'diff'][idx]):.0f}%<br>"
1216                     if isinstance(val[u"rel"][idx], float):
1217                         htext += f"Speedup: {val[u'rel'][idx]:.2f}"
1218                     hovertext.append(htext)
1219                 traces.append(
1220                     plgo.Scatter(
1221                         x=x_vals,
1222                         y=val[u"val"],
1223                         name=name,
1224                         legendgroup=name,
1225                         mode=u"lines+markers",
1226                         line=dict(
1227                             color=COLORS[cidx],
1228                             width=2),
1229                         marker=dict(
1230                             symbol=u"circle",
1231                             size=10
1232                         ),
1233                         text=hovertext,
1234                         hoverinfo=u"text+name"
1235                     )
1236                 )
1237                 traces.append(
1238                     plgo.Scatter(
1239                         x=x_vals,
1240                         y=val[u"ideal"],
1241                         name=f"{name} perfect",
1242                         legendgroup=name,
1243                         showlegend=False,
1244                         mode=u"lines",
1245                         line=dict(
1246                             color=COLORS[cidx],
1247                             width=2,
1248                             dash=u"dash"),
1249                         text=[f"Perfect: {y:.2f}Mpps" for y in val[u"ideal"]],
1250                         hoverinfo=u"text"
1251                     )
1252                 )
1253                 cidx += 1
1254             except (IndexError, ValueError, KeyError) as err:
1255                 logging.warning(f"No data for {name}\n{repr(err)}")
1256
1257         try:
1258             # Create plot
1259             file_name = f"{plot[u'output-file'].format(test_type=ttype)}.html"
1260             logging.info(f"    Writing file {file_name}")
1261             layout = deepcopy(plot[u"layout"])
1262             if layout.get(u"title", None):
1263                 layout[u"title"] = (
1264                     f"<b>Speedup Multi-core:</b> "
1265                     f"{layout[u'title'].format(test_type=ttype)}"
1266                 )
1267             layout[u"yaxis"][u"range"] = [0, int(max(y_max) * 1.1)]
1268             layout[u"annotations"].extend(annotations)
1269             plpl = plgo.Figure(data=traces, layout=layout)
1270
1271             # Export Plot
1272             ploff.plot(
1273                 plpl,
1274                 show_link=False,
1275                 auto_open=False,
1276                 filename=file_name
1277             )
1278         except PlotlyError as err:
1279             logging.error(
1280                 f"   Finished with error: {repr(err)}".replace(u"\n", u" ")
1281             )
1282
1283
1284 def plot_http_server_perf_box(plot, input_data):
1285     """Generate the plot(s) with algorithm: plot_http_server_perf_box
1286     specified in the specification file.
1287
1288     :param plot: Plot to generate.
1289     :param input_data: Data to process.
1290     :type plot: pandas.Series
1291     :type input_data: InputData
1292     """
1293
1294     # Transform the data
1295     logging.info(
1296         f"    Creating the data set for the {plot.get(u'type', u'')} "
1297         f"{plot.get(u'title', u'')}."
1298     )
1299     data = input_data.filter_data(plot)
1300     if data is None:
1301         logging.error(u"No data.")
1302         return
1303
1304     # Prepare the data for the plot
1305     y_vals = dict()
1306     for job in data:
1307         for build in job:
1308             for test in build:
1309                 if y_vals.get(test[u"name"], None) is None:
1310                     y_vals[test[u"name"]] = list()
1311                 try:
1312                     y_vals[test[u"name"]].append(test[u"result"])
1313                 except (KeyError, TypeError):
1314                     y_vals[test[u"name"]].append(None)
1315
1316     # Add None to the lists with missing data
1317     max_len = 0
1318     nr_of_samples = list()
1319     for val in y_vals.values():
1320         if len(val) > max_len:
1321             max_len = len(val)
1322         nr_of_samples.append(len(val))
1323     for val in y_vals.values():
1324         if len(val) < max_len:
1325             val.extend([None for _ in range(max_len - len(val))])
1326
1327     # Add plot traces
1328     traces = list()
1329     df_y = pd.DataFrame(y_vals)
1330     df_y.head()
1331     for i, col in enumerate(df_y.columns):
1332         name = \
1333             f"{i + 1}. " \
1334             f"({nr_of_samples[i]:02d} " \
1335             f"run{u's' if nr_of_samples[i] > 1 else u''}) " \
1336             f"{col.lower().replace(u'-ndrpdr', u'')}"
1337         if len(name) > 50:
1338             name_lst = name.split(u'-')
1339             name = u""
1340             split_name = True
1341             for segment in name_lst:
1342                 if (len(name) + len(segment) + 1) > 50 and split_name:
1343                     name += u"<br>    "
1344                     split_name = False
1345                 name += segment + u'-'
1346             name = name[:-1]
1347
1348         traces.append(plgo.Box(x=[str(i + 1) + u'.'] * len(df_y[col]),
1349                                y=df_y[col],
1350                                name=name,
1351                                **plot[u"traces"]))
1352     try:
1353         # Create plot
1354         plpl = plgo.Figure(data=traces, layout=plot[u"layout"])
1355
1356         # Export Plot
1357         logging.info(
1358             f"    Writing file {plot[u'output-file']}"
1359             f"{plot[u'output-file-type']}."
1360         )
1361         ploff.plot(
1362             plpl,
1363             show_link=False,
1364             auto_open=False,
1365             filename=f"{plot[u'output-file']}{plot[u'output-file-type']}"
1366         )
1367     except PlotlyError as err:
1368         logging.error(
1369             f"   Finished with error: {repr(err)}".replace(u"\n", u" ")
1370         )
1371         return
1372
1373
1374 def plot_nf_heatmap(plot, input_data):
1375     """Generate the plot(s) with algorithm: plot_nf_heatmap
1376     specified in the specification file.
1377
1378     :param plot: Plot to generate.
1379     :param input_data: Data to process.
1380     :type plot: pandas.Series
1381     :type input_data: InputData
1382     """
1383
1384     def sort_by_int(value):
1385         """Makes possible to sort a list of strings which represent integers.
1386
1387         :param value: Integer as a string.
1388         :type value: str
1389         :returns: Integer representation of input parameter 'value'.
1390         :rtype: int
1391         """
1392         return int(value)
1393
1394     regex_cn = re.compile(r'^(\d*)R(\d*)C$')
1395     regex_test_name = re.compile(r'^.*-(\d+ch|\d+pl)-'
1396                                  r'(\d+mif|\d+vh)-'
1397                                  r'(\d+vm\d+t|\d+dcr\d+t|\d+dcr\d+c).*$')
1398     vals = dict()
1399
1400     # Transform the data
1401     logging.info(
1402         f"    Creating the data set for the {plot.get(u'type', u'')} "
1403         f"{plot.get(u'title', u'')}."
1404     )
1405     in_data = input_data.filter_tests_by_name(
1406         plot,
1407         continue_on_error=True,
1408         params=[u"throughput", u"result", u"name", u"tags", u"type"]
1409     )
1410     if in_data is None or in_data.empty:
1411         logging.error(u"No data.")
1412         return
1413
1414     for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
1415         for core in plot.get(u"core", tuple()):
1416             for item in plot.get(u"include", tuple()):
1417                 reg_ex = re.compile(str(item.format(core=core)).lower())
1418                 for job in in_data:
1419                     for build in job:
1420                         for test_id, test in build.iteritems():
1421                             if not re.match(reg_ex, str(test_id).lower()):
1422                                 continue
1423                             for tag in test[u"tags"]:
1424                                 groups = re.search(regex_cn, tag)
1425                                 if groups:
1426                                     chain = str(groups.group(1))
1427                                     node = str(groups.group(2))
1428                                     break
1429                             else:
1430                                 continue
1431                             groups = re.search(regex_test_name, test[u"name"])
1432                             if groups and len(groups.groups()) == 3:
1433                                 hover_name = (
1434                                     f"{str(groups.group(1))}-"
1435                                     f"{str(groups.group(2))}-"
1436                                     f"{str(groups.group(3))}"
1437                                 )
1438                             else:
1439                                 hover_name = u""
1440                             if vals.get(chain, None) is None:
1441                                 vals[chain] = dict()
1442                             if vals[chain].get(node, None) is None:
1443                                 vals[chain][node] = dict(
1444                                     name=hover_name,
1445                                     vals=list(),
1446                                     nr=None,
1447                                     mean=None,
1448                                     stdev=None
1449                                 )
1450                             try:
1451                                 if ttype == u"mrr":
1452                                     result = test[u"result"][u"receive-rate"]
1453                                 elif ttype == u"pdr":
1454                                     result = \
1455                                         test[u"throughput"][u"PDR"][u"LOWER"]
1456                                 elif ttype == u"ndr":
1457                                     result = \
1458                                         test[u"throughput"][u"NDR"][u"LOWER"]
1459                                 else:
1460                                     result = None
1461                             except TypeError:
1462                                 result = None
1463
1464                             if result:
1465                                 vals[chain][node][u"vals"].append(result)
1466
1467             if not vals:
1468                 logging.error(u"No data.")
1469                 return
1470
1471             txt_chains = list()
1472             txt_nodes = list()
1473             for key_c in vals:
1474                 txt_chains.append(key_c)
1475                 for key_n in vals[key_c].keys():
1476                     txt_nodes.append(key_n)
1477                     if vals[key_c][key_n][u"vals"]:
1478                         vals[key_c][key_n][u"nr"] = \
1479                             len(vals[key_c][key_n][u"vals"])
1480                         vals[key_c][key_n][u"mean"] = \
1481                             round(mean(vals[key_c][key_n][u"vals"]) / 1e6, 1)
1482                         vals[key_c][key_n][u"stdev"] = \
1483                             round(stdev(vals[key_c][key_n][u"vals"]) / 1e6, 1)
1484             txt_nodes = list(set(txt_nodes))
1485
1486             txt_chains = sorted(txt_chains, key=sort_by_int)
1487             txt_nodes = sorted(txt_nodes, key=sort_by_int)
1488
1489             chains = [i + 1 for i in range(len(txt_chains))]
1490             nodes = [i + 1 for i in range(len(txt_nodes))]
1491
1492             data = [list() for _ in range(len(chains))]
1493             for chain in chains:
1494                 for node in nodes:
1495                     try:
1496                         val = vals[txt_chains[chain - 1]] \
1497                             [txt_nodes[node - 1]][u"mean"]
1498                     except (KeyError, IndexError):
1499                         val = None
1500                     data[chain - 1].append(val)
1501
1502             # Color scales:
1503             my_green = [[0.0, u"rgb(235, 249, 242)"],
1504                         [1.0, u"rgb(45, 134, 89)"]]
1505
1506             my_blue = [[0.0, u"rgb(236, 242, 248)"],
1507                        [1.0, u"rgb(57, 115, 172)"]]
1508
1509             my_grey = [[0.0, u"rgb(230, 230, 230)"],
1510                        [1.0, u"rgb(102, 102, 102)"]]
1511
1512             hovertext = list()
1513             annotations = list()
1514
1515             text = (u"Test: {name}<br>"
1516                     u"Runs: {nr}<br>"
1517                     u"Thput: {val}<br>"
1518                     u"StDev: {stdev}")
1519
1520             for chain, _ in enumerate(txt_chains):
1521                 hover_line = list()
1522                 for node, _ in enumerate(txt_nodes):
1523                     if data[chain][node] is not None:
1524                         annotations.append(
1525                             dict(
1526                                 x=node+1,
1527                                 y=chain+1,
1528                                 xref=u"x",
1529                                 yref=u"y",
1530                                 xanchor=u"center",
1531                                 yanchor=u"middle",
1532                                 text=str(data[chain][node]),
1533                                 font=dict(
1534                                     size=14,
1535                                 ),
1536                                 align=u"center",
1537                                 showarrow=False
1538                             )
1539                         )
1540                         hover_line.append(text.format(
1541                             name=vals[txt_chains[chain]][txt_nodes[node]]
1542                             [u"name"],
1543                             nr=vals[txt_chains[chain]][txt_nodes[node]][u"nr"],
1544                             val=data[chain][node],
1545                             stdev=vals[txt_chains[chain]][txt_nodes[node]]
1546                             [u"stdev"]
1547                         ))
1548                 hovertext.append(hover_line)
1549
1550             traces = [
1551                 plgo.Heatmap(
1552                     x=nodes,
1553                     y=chains,
1554                     z=data,
1555                     colorbar=dict(
1556                         title=plot.get(u"z-axis", u"{test_type}").
1557                         format(test_type=ttype.upper()),
1558                         titleside=u"right",
1559                         titlefont=dict(
1560                             size=16
1561                         ),
1562                         tickfont=dict(
1563                             size=16,
1564                         ),
1565                         tickformat=u".1f",
1566                         yanchor=u"bottom",
1567                         y=-0.02,
1568                         len=0.925,
1569                     ),
1570                     showscale=True,
1571                     colorscale=my_green,
1572                     text=hovertext,
1573                     hoverinfo=u"text"
1574                 )
1575             ]
1576
1577             for idx, item in enumerate(txt_nodes):
1578                 # X-axis, numbers:
1579                 annotations.append(
1580                     dict(
1581                         x=idx+1,
1582                         y=0.05,
1583                         xref=u"x",
1584                         yref=u"y",
1585                         xanchor=u"center",
1586                         yanchor=u"top",
1587                         text=item,
1588                         font=dict(
1589                             size=16,
1590                         ),
1591                         align=u"center",
1592                         showarrow=False
1593                     )
1594                 )
1595             for idx, item in enumerate(txt_chains):
1596                 # Y-axis, numbers:
1597                 annotations.append(
1598                     dict(
1599                         x=0.35,
1600                         y=idx+1,
1601                         xref=u"x",
1602                         yref=u"y",
1603                         xanchor=u"right",
1604                         yanchor=u"middle",
1605                         text=item,
1606                         font=dict(
1607                             size=16,
1608                         ),
1609                         align=u"center",
1610                         showarrow=False
1611                     )
1612                 )
1613             # X-axis, title:
1614             annotations.append(
1615                 dict(
1616                     x=0.55,
1617                     y=-0.15,
1618                     xref=u"paper",
1619                     yref=u"y",
1620                     xanchor=u"center",
1621                     yanchor=u"bottom",
1622                     text=plot.get(u"x-axis", u""),
1623                     font=dict(
1624                         size=16,
1625                     ),
1626                     align=u"center",
1627                     showarrow=False
1628                 )
1629             )
1630             # Y-axis, title:
1631             annotations.append(
1632                 dict(
1633                     x=-0.1,
1634                     y=0.5,
1635                     xref=u"x",
1636                     yref=u"paper",
1637                     xanchor=u"center",
1638                     yanchor=u"middle",
1639                     text=plot.get(u"y-axis", u""),
1640                     font=dict(
1641                         size=16,
1642                     ),
1643                     align=u"center",
1644                     textangle=270,
1645                     showarrow=False
1646                 )
1647             )
1648             updatemenus = list([
1649                 dict(
1650                     x=1.0,
1651                     y=0.0,
1652                     xanchor=u"right",
1653                     yanchor=u"bottom",
1654                     direction=u"up",
1655                     buttons=list([
1656                         dict(
1657                             args=[
1658                                 {
1659                                     u"colorscale": [my_green, ],
1660                                     u"reversescale": False
1661                                 }
1662                             ],
1663                             label=u"Green",
1664                             method=u"update"
1665                         ),
1666                         dict(
1667                             args=[
1668                                 {
1669                                     u"colorscale": [my_blue, ],
1670                                     u"reversescale": False
1671                                 }
1672                             ],
1673                             label=u"Blue",
1674                             method=u"update"
1675                         ),
1676                         dict(
1677                             args=[
1678                                 {
1679                                     u"colorscale": [my_grey, ],
1680                                     u"reversescale": False
1681                                 }
1682                             ],
1683                             label=u"Grey",
1684                             method=u"update"
1685                         )
1686                     ])
1687                 )
1688             ])
1689
1690             try:
1691                 layout = deepcopy(plot[u"layout"])
1692             except KeyError as err:
1693                 logging.error(
1694                     f"Finished with error: No layout defined\n{repr(err)}"
1695                 )
1696                 return
1697
1698             layout[u"annotations"] = annotations
1699             layout[u'updatemenus'] = updatemenus
1700             if layout.get(u"title", None):
1701                 layout[u"title"] = layout[u'title'].replace(u"test_type", ttype)
1702
1703             try:
1704                 # Create plot
1705                 plpl = plgo.Figure(data=traces, layout=layout)
1706
1707                 # Export Plot
1708                 file_name = (
1709                     f"{plot[u'output-file'].format(core=core, test_type=ttype)}"
1710                     f".html"
1711                 )
1712                 logging.info(f"    Writing file {file_name}")
1713                 ploff.plot(
1714                     plpl,
1715                     show_link=False,
1716                     auto_open=False,
1717                     filename=file_name
1718                 )
1719             except PlotlyError as err:
1720                 logging.error(
1721                     f"   Finished with error: {repr(err)}".replace(u"\n", u" ")
1722                 )