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