Report: Final clean-up
[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             elif u"e810xxv" in test_name:
1219                 limit = plot[u"limits"][u"nic"][u"e810xxv"]
1220             elif u"e822cq" in test_name:
1221                 limit = plot[u"limits"][u"nic"][u"e822cq"]
1222             else:
1223                 limit = 0
1224             if limit > nic_limit:
1225                 nic_limit = limit
1226
1227             mul = 2 if u"ge2p" in test_name else 1
1228             if u"10ge" in test_name:
1229                 limit = plot[u"limits"][u"link"][u"10ge"] * mul
1230             elif u"25ge" in test_name:
1231                 limit = plot[u"limits"][u"link"][u"25ge"] * mul
1232             elif u"40ge" in test_name:
1233                 limit = plot[u"limits"][u"link"][u"40ge"] * mul
1234             elif u"100ge" in test_name:
1235                 limit = plot[u"limits"][u"link"][u"100ge"] * mul
1236             else:
1237                 limit = 0
1238             if limit > lnk_limit:
1239                 lnk_limit = limit
1240
1241             if u"cx556a" in test_name:
1242                 limit = plot[u"limits"][u"pci"][u"pci-g3-x8"]
1243             else:
1244                 limit = plot[u"limits"][u"pci"][u"pci-g3-x16"]
1245             if limit > pci_limit:
1246                 pci_limit = limit
1247
1248         traces = list()
1249         annotations = list()
1250         x_vals = [1, 2, 4]
1251
1252         # Limits:
1253         if u"-gbps" not in plot_title and u"-cps-" not in plot_title:
1254             nic_limit /= 1e6
1255             lnk_limit /= 1e6
1256             pci_limit /= 1e6
1257             min_limit = min((nic_limit, lnk_limit, pci_limit))
1258             if nic_limit == min_limit:
1259                 traces.append(plgo.Scatter(
1260                     x=x_vals,
1261                     y=[nic_limit, ] * len(x_vals),
1262                     name=f"NIC: {nic_limit:.2f}Mpps",
1263                     showlegend=False,
1264                     mode=u"lines",
1265                     line=dict(
1266                         dash=u"dot",
1267                         color=COLORS[-1],
1268                         width=1),
1269                     hoverinfo=u"none"
1270                 ))
1271                 annotations.append(dict(
1272                     x=1,
1273                     y=nic_limit,
1274                     xref=u"x",
1275                     yref=u"y",
1276                     xanchor=u"left",
1277                     yanchor=u"bottom",
1278                     text=f"NIC: {nic_limit:.2f}Mpps",
1279                     font=dict(
1280                         size=14,
1281                         color=COLORS[-1],
1282                     ),
1283                     align=u"left",
1284                     showarrow=False
1285                 ))
1286                 y_max.append(nic_limit)
1287             elif lnk_limit == min_limit:
1288                 traces.append(plgo.Scatter(
1289                     x=x_vals,
1290                     y=[lnk_limit, ] * len(x_vals),
1291                     name=f"Link: {lnk_limit:.2f}Mpps",
1292                     showlegend=False,
1293                     mode=u"lines",
1294                     line=dict(
1295                         dash=u"dot",
1296                         color=COLORS[-1],
1297                         width=1),
1298                     hoverinfo=u"none"
1299                 ))
1300                 annotations.append(dict(
1301                     x=1,
1302                     y=lnk_limit,
1303                     xref=u"x",
1304                     yref=u"y",
1305                     xanchor=u"left",
1306                     yanchor=u"bottom",
1307                     text=f"Link: {lnk_limit:.2f}Mpps",
1308                     font=dict(
1309                         size=14,
1310                         color=COLORS[-1],
1311                     ),
1312                     align=u"left",
1313                     showarrow=False
1314                 ))
1315                 y_max.append(lnk_limit)
1316             elif pci_limit == min_limit:
1317                 traces.append(plgo.Scatter(
1318                     x=x_vals,
1319                     y=[pci_limit, ] * len(x_vals),
1320                     name=f"PCIe: {pci_limit:.2f}Mpps",
1321                     showlegend=False,
1322                     mode=u"lines",
1323                     line=dict(
1324                         dash=u"dot",
1325                         color=COLORS[-1],
1326                         width=1),
1327                     hoverinfo=u"none"
1328                 ))
1329                 annotations.append(dict(
1330                     x=1,
1331                     y=pci_limit,
1332                     xref=u"x",
1333                     yref=u"y",
1334                     xanchor=u"left",
1335                     yanchor=u"bottom",
1336                     text=f"PCIe: {pci_limit:.2f}Mpps",
1337                     font=dict(
1338                         size=14,
1339                         color=COLORS[-1],
1340                     ),
1341                     align=u"left",
1342                     showarrow=False
1343                 ))
1344                 y_max.append(pci_limit)
1345
1346         # Perfect and measured:
1347         cidx = 0
1348         for name, val in vals.items():
1349             hovertext = list()
1350             try:
1351                 for idx in range(len(val[u"val"])):
1352                     htext = ""
1353                     if isinstance(val[u"val"][idx], float):
1354                         htext += (
1355                             f"No. of Runs: {val[u'count'][idx]}<br>"
1356                             f"Mean: {val[u'val'][idx]:.2f}{h_unit}<br>"
1357                         )
1358                     if isinstance(val[u"diff"][idx], float):
1359                         htext += f"Diff: {round(val[u'diff'][idx]):.0f}%<br>"
1360                     if isinstance(val[u"rel"][idx], float):
1361                         htext += f"Speedup: {val[u'rel'][idx]:.2f}"
1362                     hovertext.append(htext)
1363                 traces.append(
1364                     plgo.Scatter(
1365                         x=x_vals,
1366                         y=val[u"val"],
1367                         name=name,
1368                         legendgroup=name,
1369                         mode=u"lines+markers",
1370                         line=dict(
1371                             color=COLORS[cidx],
1372                             width=2),
1373                         marker=dict(
1374                             symbol=u"circle",
1375                             size=10
1376                         ),
1377                         text=hovertext,
1378                         hoverinfo=u"text+name"
1379                     )
1380                 )
1381                 traces.append(
1382                     plgo.Scatter(
1383                         x=x_vals,
1384                         y=val[u"ideal"],
1385                         name=f"{name} perfect",
1386                         legendgroup=name,
1387                         showlegend=False,
1388                         mode=u"lines",
1389                         line=dict(
1390                             color=COLORS[cidx],
1391                             width=2,
1392                             dash=u"dash"),
1393                         text=[f"Perfect: {y:.2f}Mpps" for y in val[u"ideal"]],
1394                         hoverinfo=u"text"
1395                     )
1396                 )
1397                 cidx += 1
1398             except (IndexError, ValueError, KeyError) as err:
1399                 logging.warning(f"No data for {name}\n{repr(err)}")
1400
1401         try:
1402             # Create plot
1403             file_name = f"{plot[u'output-file'].format(test_type=ttype)}.html"
1404             logging.info(f"    Writing file {file_name}")
1405             layout = deepcopy(plot[u"layout"])
1406             if layout.get(u"title", None):
1407                 layout[u"title"] = (
1408                     f"<b>Speedup Multi-core:</b> "
1409                     f"{layout[u'title'].format(test_type=ttype)}"
1410                 )
1411             layout[u"yaxis"][u"range"] = [0, int(max(y_max) * 1.1)]
1412             layout[u"annotations"].extend(annotations)
1413             plpl = plgo.Figure(data=traces, layout=layout)
1414
1415             # Export Plot
1416             ploff.plot(
1417                 plpl,
1418                 show_link=False,
1419                 auto_open=False,
1420                 filename=file_name
1421             )
1422         except PlotlyError as err:
1423             logging.error(
1424                 f"   Finished with error: {repr(err)}".replace(u"\n", u" ")
1425             )
1426
1427
1428 def plot_http_server_perf_box(plot, input_data):
1429     """Generate the plot(s) with algorithm: plot_http_server_perf_box
1430     specified in the specification file.
1431
1432     :param plot: Plot to generate.
1433     :param input_data: Data to process.
1434     :type plot: pandas.Series
1435     :type input_data: InputData
1436     """
1437
1438     # Transform the data
1439     logging.info(
1440         f"    Creating the data set for the {plot.get(u'type', u'')} "
1441         f"{plot.get(u'title', u'')}."
1442     )
1443     data = input_data.filter_data(plot)
1444     if data is None:
1445         logging.error(u"No data.")
1446         return
1447
1448     # Prepare the data for the plot
1449     y_vals = dict()
1450     for job in data:
1451         for build in job:
1452             for test in build:
1453                 if y_vals.get(test[u"name"], None) is None:
1454                     y_vals[test[u"name"]] = list()
1455                 try:
1456                     y_vals[test[u"name"]].append(test[u"result"])
1457                 except (KeyError, TypeError):
1458                     y_vals[test[u"name"]].append(None)
1459
1460     # Add None to the lists with missing data
1461     max_len = 0
1462     nr_of_samples = list()
1463     for val in y_vals.values():
1464         if len(val) > max_len:
1465             max_len = len(val)
1466         nr_of_samples.append(len(val))
1467     for val in y_vals.values():
1468         if len(val) < max_len:
1469             val.extend([None for _ in range(max_len - len(val))])
1470
1471     # Add plot traces
1472     traces = list()
1473     df_y = pd.DataFrame(y_vals)
1474     df_y.head()
1475     for i, col in enumerate(df_y.columns):
1476         name = \
1477             f"{i + 1}. " \
1478             f"({nr_of_samples[i]:02d} " \
1479             f"run{u's' if nr_of_samples[i] > 1 else u''}) " \
1480             f"{col.lower().replace(u'-ndrpdr', u'')}"
1481         if len(name) > 50:
1482             name_lst = name.split(u'-')
1483             name = u""
1484             split_name = True
1485             for segment in name_lst:
1486                 if (len(name) + len(segment) + 1) > 50 and split_name:
1487                     name += u"<br>    "
1488                     split_name = False
1489                 name += segment + u'-'
1490             name = name[:-1]
1491
1492         traces.append(plgo.Box(x=[str(i + 1) + u'.'] * len(df_y[col]),
1493                                y=df_y[col],
1494                                name=name,
1495                                **plot[u"traces"]))
1496     try:
1497         # Create plot
1498         plpl = plgo.Figure(data=traces, layout=plot[u"layout"])
1499
1500         # Export Plot
1501         logging.info(
1502             f"    Writing file {plot[u'output-file']}"
1503             f"{plot[u'output-file-type']}."
1504         )
1505         ploff.plot(
1506             plpl,
1507             show_link=False,
1508             auto_open=False,
1509             filename=f"{plot[u'output-file']}{plot[u'output-file-type']}"
1510         )
1511     except PlotlyError as err:
1512         logging.error(
1513             f"   Finished with error: {repr(err)}".replace(u"\n", u" ")
1514         )
1515         return
1516
1517
1518 def plot_nf_heatmap(plot, input_data):
1519     """Generate the plot(s) with algorithm: plot_nf_heatmap
1520     specified in the specification file.
1521
1522     :param plot: Plot to generate.
1523     :param input_data: Data to process.
1524     :type plot: pandas.Series
1525     :type input_data: InputData
1526     """
1527
1528     def sort_by_int(value):
1529         """Makes possible to sort a list of strings which represent integers.
1530
1531         :param value: Integer as a string.
1532         :type value: str
1533         :returns: Integer representation of input parameter 'value'.
1534         :rtype: int
1535         """
1536         return int(value)
1537
1538     regex_cn = re.compile(r'^(\d*)R(\d*)C$')
1539     regex_test_name = re.compile(r'^.*-(\d+ch|\d+pl)-'
1540                                  r'(\d+mif|\d+vh)-'
1541                                  r'(\d+vm\d+t|\d+dcr\d+t|\d+dcr\d+c).*$')
1542     # Transform the data
1543     logging.info(
1544         f"    Creating the data set for the {plot.get(u'type', u'')} "
1545         f"{plot.get(u'title', u'')}."
1546     )
1547     in_data = input_data.filter_tests_by_name(
1548         plot,
1549         continue_on_error=True,
1550         params=[u"throughput", u"result", u"name", u"tags", u"type"]
1551     )
1552     if in_data is None or in_data.empty:
1553         logging.error(u"No data.")
1554         return
1555
1556     for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
1557         for core in plot.get(u"core", tuple()):
1558             vals = dict()
1559             for item in plot.get(u"include", tuple()):
1560                 reg_ex = re.compile(str(item.format(core=core)).lower())
1561                 for job in in_data:
1562                     for build in job:
1563                         for test_id, test in build.iteritems():
1564                             if not re.match(reg_ex, str(test_id).lower()):
1565                                 continue
1566                             for tag in test[u"tags"]:
1567                                 groups = re.search(regex_cn, tag)
1568                                 if groups:
1569                                     chain = str(groups.group(1))
1570                                     node = str(groups.group(2))
1571                                     break
1572                             else:
1573                                 continue
1574                             groups = re.search(regex_test_name, test[u"name"])
1575                             if groups and len(groups.groups()) == 3:
1576                                 hover_name = (
1577                                     f"{str(groups.group(1))}-"
1578                                     f"{str(groups.group(2))}-"
1579                                     f"{str(groups.group(3))}"
1580                                 )
1581                             else:
1582                                 hover_name = u""
1583                             if vals.get(chain, None) is None:
1584                                 vals[chain] = dict()
1585                             if vals[chain].get(node, None) is None:
1586                                 vals[chain][node] = dict(
1587                                     name=hover_name,
1588                                     vals=list(),
1589                                     nr=None,
1590                                     mean=None,
1591                                     stdev=None
1592                                 )
1593                             try:
1594                                 if ttype == u"mrr":
1595                                     result = test[u"result"][u"receive-rate"]
1596                                 elif ttype == u"pdr":
1597                                     result = \
1598                                         test[u"throughput"][u"PDR"][u"LOWER"]
1599                                 elif ttype == u"ndr":
1600                                     result = \
1601                                         test[u"throughput"][u"NDR"][u"LOWER"]
1602                                 else:
1603                                     result = None
1604                             except TypeError:
1605                                 result = None
1606
1607                             if result:
1608                                 vals[chain][node][u"vals"].append(result)
1609
1610             if not vals:
1611                 logging.error(u"No data.")
1612                 return
1613
1614             txt_chains = list()
1615             txt_nodes = list()
1616             for key_c in vals:
1617                 txt_chains.append(key_c)
1618                 for key_n in vals[key_c].keys():
1619                     txt_nodes.append(key_n)
1620                     if vals[key_c][key_n][u"vals"]:
1621                         vals[key_c][key_n][u"nr"] = \
1622                             len(vals[key_c][key_n][u"vals"])
1623                         vals[key_c][key_n][u"mean"] = \
1624                             round(mean(vals[key_c][key_n][u"vals"]) / 1e6, 1)
1625                         vals[key_c][key_n][u"stdev"] = \
1626                             round(stdev(vals[key_c][key_n][u"vals"]) / 1e6, 1)
1627             txt_nodes = list(set(txt_nodes))
1628
1629             txt_chains = sorted(txt_chains, key=sort_by_int)
1630             txt_nodes = sorted(txt_nodes, key=sort_by_int)
1631
1632             chains = [i + 1 for i in range(len(txt_chains))]
1633             nodes = [i + 1 for i in range(len(txt_nodes))]
1634
1635             data = [list() for _ in range(len(chains))]
1636             for chain in chains:
1637                 for node in nodes:
1638                     try:
1639                         val = vals[txt_chains[chain - 1]] \
1640                             [txt_nodes[node - 1]][u"mean"]
1641                     except (KeyError, IndexError):
1642                         val = None
1643                     data[chain - 1].append(val)
1644
1645             # Color scales:
1646             my_green = [[0.0, u"rgb(235, 249, 242)"],
1647                         [1.0, u"rgb(45, 134, 89)"]]
1648
1649             my_blue = [[0.0, u"rgb(236, 242, 248)"],
1650                        [1.0, u"rgb(57, 115, 172)"]]
1651
1652             my_grey = [[0.0, u"rgb(230, 230, 230)"],
1653                        [1.0, u"rgb(102, 102, 102)"]]
1654
1655             hovertext = list()
1656             annotations = list()
1657
1658             text = (u"Test: {name}<br>"
1659                     u"Runs: {nr}<br>"
1660                     u"Thput: {val}<br>"
1661                     u"StDev: {stdev}")
1662
1663             for chain, _ in enumerate(txt_chains):
1664                 hover_line = list()
1665                 for node, _ in enumerate(txt_nodes):
1666                     if data[chain][node] is not None:
1667                         annotations.append(
1668                             dict(
1669                                 x=node+1,
1670                                 y=chain+1,
1671                                 xref=u"x",
1672                                 yref=u"y",
1673                                 xanchor=u"center",
1674                                 yanchor=u"middle",
1675                                 text=str(data[chain][node]),
1676                                 font=dict(
1677                                     size=14,
1678                                 ),
1679                                 align=u"center",
1680                                 showarrow=False
1681                             )
1682                         )
1683                         hover_line.append(text.format(
1684                             name=vals[txt_chains[chain]][txt_nodes[node]]
1685                             [u"name"],
1686                             nr=vals[txt_chains[chain]][txt_nodes[node]][u"nr"],
1687                             val=data[chain][node],
1688                             stdev=vals[txt_chains[chain]][txt_nodes[node]]
1689                             [u"stdev"]
1690                         ))
1691                 hovertext.append(hover_line)
1692
1693             traces = [
1694                 plgo.Heatmap(
1695                     x=nodes,
1696                     y=chains,
1697                     z=data,
1698                     colorbar=dict(
1699                         title=plot.get(u"z-axis", u"{test_type}").
1700                         format(test_type=ttype.upper()),
1701                         titleside=u"right",
1702                         titlefont=dict(
1703                             size=16
1704                         ),
1705                         tickfont=dict(
1706                             size=16,
1707                         ),
1708                         tickformat=u".1f",
1709                         yanchor=u"bottom",
1710                         y=-0.02,
1711                         len=0.925,
1712                     ),
1713                     showscale=True,
1714                     colorscale=my_green,
1715                     text=hovertext,
1716                     hoverinfo=u"text"
1717                 )
1718             ]
1719
1720             for idx, item in enumerate(txt_nodes):
1721                 # X-axis, numbers:
1722                 annotations.append(
1723                     dict(
1724                         x=idx+1,
1725                         y=0.05,
1726                         xref=u"x",
1727                         yref=u"y",
1728                         xanchor=u"center",
1729                         yanchor=u"top",
1730                         text=item,
1731                         font=dict(
1732                             size=16,
1733                         ),
1734                         align=u"center",
1735                         showarrow=False
1736                     )
1737                 )
1738             for idx, item in enumerate(txt_chains):
1739                 # Y-axis, numbers:
1740                 annotations.append(
1741                     dict(
1742                         x=0.35,
1743                         y=idx+1,
1744                         xref=u"x",
1745                         yref=u"y",
1746                         xanchor=u"right",
1747                         yanchor=u"middle",
1748                         text=item,
1749                         font=dict(
1750                             size=16,
1751                         ),
1752                         align=u"center",
1753                         showarrow=False
1754                     )
1755                 )
1756             # X-axis, title:
1757             annotations.append(
1758                 dict(
1759                     x=0.55,
1760                     y=-0.15,
1761                     xref=u"paper",
1762                     yref=u"y",
1763                     xanchor=u"center",
1764                     yanchor=u"bottom",
1765                     text=plot.get(u"x-axis", u""),
1766                     font=dict(
1767                         size=16,
1768                     ),
1769                     align=u"center",
1770                     showarrow=False
1771                 )
1772             )
1773             # Y-axis, title:
1774             annotations.append(
1775                 dict(
1776                     x=-0.1,
1777                     y=0.5,
1778                     xref=u"x",
1779                     yref=u"paper",
1780                     xanchor=u"center",
1781                     yanchor=u"middle",
1782                     text=plot.get(u"y-axis", u""),
1783                     font=dict(
1784                         size=16,
1785                     ),
1786                     align=u"center",
1787                     textangle=270,
1788                     showarrow=False
1789                 )
1790             )
1791             updatemenus = list([
1792                 dict(
1793                     x=1.0,
1794                     y=0.0,
1795                     xanchor=u"right",
1796                     yanchor=u"bottom",
1797                     direction=u"up",
1798                     buttons=list([
1799                         dict(
1800                             args=[
1801                                 {
1802                                     u"colorscale": [my_green, ],
1803                                     u"reversescale": False
1804                                 }
1805                             ],
1806                             label=u"Green",
1807                             method=u"update"
1808                         ),
1809                         dict(
1810                             args=[
1811                                 {
1812                                     u"colorscale": [my_blue, ],
1813                                     u"reversescale": False
1814                                 }
1815                             ],
1816                             label=u"Blue",
1817                             method=u"update"
1818                         ),
1819                         dict(
1820                             args=[
1821                                 {
1822                                     u"colorscale": [my_grey, ],
1823                                     u"reversescale": False
1824                                 }
1825                             ],
1826                             label=u"Grey",
1827                             method=u"update"
1828                         )
1829                     ])
1830                 )
1831             ])
1832
1833             try:
1834                 layout = deepcopy(plot[u"layout"])
1835             except KeyError as err:
1836                 logging.error(
1837                     f"Finished with error: No layout defined\n{repr(err)}"
1838                 )
1839                 return
1840
1841             layout[u"annotations"] = annotations
1842             layout[u'updatemenus'] = updatemenus
1843             if layout.get(u"title", None):
1844                 layout[u"title"] = layout[u'title'].replace(u"test_type", ttype)
1845
1846             try:
1847                 # Create plot
1848                 plpl = plgo.Figure(data=traces, layout=layout)
1849
1850                 # Export Plot
1851                 file_name = (
1852                     f"{plot[u'output-file'].format(core=core, test_type=ttype)}"
1853                     f".html"
1854                 )
1855                 logging.info(f"    Writing file {file_name}")
1856                 ploff.plot(
1857                     plpl,
1858                     show_link=False,
1859                     auto_open=False,
1860                     filename=file_name
1861                 )
1862             except PlotlyError as err:
1863                 logging.error(
1864                     f"   Finished with error: {repr(err)}".replace(u"\n", u" ")
1865                 )