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