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