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