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