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