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