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