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