PAL: Add NFV reconf tests
[csit.git] / resources / tools / presentation / generator_plots.py
1 # Copyright (c) 2019 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 import pandas as pd
21 import plotly.offline as ploff
22 import plotly.graph_objs as plgo
23
24 from plotly.exceptions import PlotlyError
25 from collections import OrderedDict
26 from copy import deepcopy
27
28 from utils import mean, stdev
29
30
31 COLORS = ["SkyBlue", "Olive", "Purple", "Coral", "Indigo", "Pink",
32           "Chocolate", "Brown", "Magenta", "Cyan", "Orange", "Black",
33           "Violet", "Blue", "Yellow", "BurlyWood", "CadetBlue", "Crimson",
34           "DarkBlue", "DarkCyan", "DarkGreen", "Green", "GoldenRod",
35           "LightGreen", "LightSeaGreen", "LightSkyBlue", "Maroon",
36           "MediumSeaGreen", "SeaGreen", "LightSlateGrey"]
37
38 REGEX_NIC = re.compile(r'\d*ge\dp\d\D*\d*-')
39
40
41 def generate_plots(spec, data):
42     """Generate all plots specified in the specification file.
43
44     :param spec: Specification read from the specification file.
45     :param data: Data to process.
46     :type spec: Specification
47     :type data: InputData
48     """
49
50     logging.info("Generating the plots ...")
51     for index, plot in enumerate(spec.plots):
52         try:
53             logging.info("  Plot nr {0}: {1}".format(index + 1,
54                                                      plot.get("title", "")))
55             plot["limits"] = spec.configuration["limits"]
56             eval(plot["algorithm"])(plot, data)
57             logging.info("  Done.")
58         except NameError as err:
59             logging.error("Probably algorithm '{alg}' is not defined: {err}".
60                           format(alg=plot["algorithm"], err=repr(err)))
61     logging.info("Done.")
62
63
64 def plot_service_density_reconf_box_name(plot, input_data):
65     """Generate the plot(s) with algorithm: plot_service_density_reconf_box_name
66     specified in the specification file.
67
68     :param plot: Plot to generate.
69     :param input_data: Data to process.
70     :type plot: pandas.Series
71     :type input_data: InputData
72     """
73
74     # Transform the data
75     plot_title = plot.get("title", "")
76     logging.info("    Creating the data set for the {0} '{1}'.".
77                  format(plot.get("type", ""), plot_title))
78     data = input_data.filter_tests_by_name(
79         plot, params=["result", "parent", "tags", "type"])
80     if data is None:
81         logging.error("No data.")
82         return
83
84     # Prepare the data for the plot
85     y_vals = OrderedDict()
86     loss = dict()
87     for job in data:
88         for build in job:
89             for test in build:
90                 if y_vals.get(test["parent"], None) is None:
91                     y_vals[test["parent"]] = list()
92                     loss[test["parent"]] = list()
93                 try:
94                     y_vals[test["parent"]].append(test["result"]["time"])
95                     loss[test["parent"]].append(test["result"]["loss"])
96                 except (KeyError, TypeError):
97                     y_vals[test["parent"]].append(None)
98
99     # Add None to the lists with missing data
100     max_len = 0
101     nr_of_samples = list()
102     for val in y_vals.values():
103         if len(val) > max_len:
104             max_len = len(val)
105         nr_of_samples.append(len(val))
106     for key, val in y_vals.items():
107         if len(val) < max_len:
108             val.extend([None for _ in range(max_len - len(val))])
109
110     # Add plot traces
111     traces = list()
112     df = pd.DataFrame(y_vals)
113     df.head()
114     y_max = list()
115     for i, col in enumerate(df.columns):
116         tst_name = re.sub(REGEX_NIC, "",
117                           col.lower().replace('-ndrpdr', '').
118                           replace('2n1l-', ''))
119         tst_name = "-".join(tst_name.split("-")[3:-2])
120         name = "{nr}. ({samples:02d} run{plural}, avg pkt loss: {loss:.1f}, " \
121                "stdev: {stdev:.2f}) {name}".format(
122                     nr=(i + 1),
123                     samples=nr_of_samples[i],
124                     plural='s' if nr_of_samples[i] > 1 else '',
125                     name=tst_name,
126                     loss=mean(loss[col]) / 1000000,
127                     stdev=stdev(loss[col]) / 1000000)
128
129         traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
130                                y=[y if y else None for y in df[col]],
131                                name=name,
132                                hoverinfo="x+y",
133                                boxpoints="outliers",
134                                whiskerwidth=0))
135         try:
136             val_max = max(df[col])
137         except ValueError as err:
138             logging.error(repr(err))
139             continue
140         if val_max:
141             y_max.append(int(val_max) + 1)
142
143     try:
144         # Create plot
145         layout = deepcopy(plot["layout"])
146         layout["title"] = "<b>Time Lost:</b> {0}".format(layout["title"])
147         layout["yaxis"]["title"] = "<b>Implied Time Lost [s]</b>"
148         layout["legend"]["font"]["size"] = 14
149         if y_max:
150             layout["yaxis"]["range"] = [0, max(y_max)]
151         plpl = plgo.Figure(data=traces, layout=layout)
152
153         # Export Plot
154         file_type = plot.get("output-file-type", ".html")
155         logging.info("    Writing file '{0}{1}'.".
156                      format(plot["output-file"], file_type))
157         ploff.plot(plpl, show_link=False, auto_open=False,
158                    filename='{0}{1}'.format(plot["output-file"], file_type))
159     except PlotlyError as err:
160         logging.error("   Finished with error: {}".
161                       format(repr(err).replace("\n", " ")))
162         return
163
164
165 def plot_performance_box_name(plot, input_data):
166     """Generate the plot(s) with algorithm: plot_performance_box_name
167     specified in the specification file.
168
169     :param plot: Plot to generate.
170     :param input_data: Data to process.
171     :type plot: pandas.Series
172     :type input_data: InputData
173     """
174
175     # Transform the data
176     plot_title = plot.get("title", "")
177     logging.info("    Creating the data set for the {0} '{1}'.".
178                  format(plot.get("type", ""), plot_title))
179     data = input_data.filter_tests_by_name(
180         plot, params=["throughput", "parent", "tags", "type"])
181     if data is None:
182         logging.error("No data.")
183         return
184
185     # Prepare the data for the plot
186     y_vals = OrderedDict()
187     for job in data:
188         for build in job:
189             for test in build:
190                 if y_vals.get(test["parent"], None) is None:
191                     y_vals[test["parent"]] = list()
192                 try:
193                     if test["type"] in ("NDRPDR", ):
194                         if "-pdr" in plot_title.lower():
195                             y_vals[test["parent"]].\
196                                 append(test["throughput"]["PDR"]["LOWER"])
197                         elif "-ndr" in plot_title.lower():
198                             y_vals[test["parent"]]. \
199                                 append(test["throughput"]["NDR"]["LOWER"])
200                         else:
201                             continue
202                     elif test["type"] in ("SOAK", ):
203                         y_vals[test["parent"]].\
204                             append(test["throughput"]["LOWER"])
205                     else:
206                         continue
207                 except (KeyError, TypeError):
208                     y_vals[test["parent"]].append(None)
209
210     # Add None to the lists with missing data
211     max_len = 0
212     nr_of_samples = list()
213     for val in y_vals.values():
214         if len(val) > max_len:
215             max_len = len(val)
216         nr_of_samples.append(len(val))
217     for key, val in y_vals.items():
218         if len(val) < max_len:
219             val.extend([None for _ in range(max_len - len(val))])
220
221     # Add plot traces
222     traces = list()
223     df = pd.DataFrame(y_vals)
224     df.head()
225     y_max = list()
226     for i, col in enumerate(df.columns):
227         tst_name = re.sub(REGEX_NIC, "",
228                           col.lower().replace('-ndrpdr', '').
229                           replace('2n1l-', ''))
230         name = "{nr}. ({samples:02d} run{plural}) {name}".\
231             format(nr=(i + 1),
232                    samples=nr_of_samples[i],
233                    plural='s' if nr_of_samples[i] > 1 else '',
234                    name=tst_name)
235
236         logging.debug(name)
237         traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
238                                y=[y / 1000000 if y else None for y in df[col]],
239                                name=name,
240                                hoverinfo="x+y",
241                                boxpoints="outliers",
242                                whiskerwidth=0))
243         try:
244             val_max = max(df[col])
245         except ValueError as err:
246             logging.error(repr(err))
247             continue
248         if val_max:
249             y_max.append(int(val_max / 1000000) + 2)
250
251     try:
252         # Create plot
253         layout = deepcopy(plot["layout"])
254         if layout.get("title", None):
255             layout["title"] = "<b>Throughput:</b> {0}". \
256                 format(layout["title"])
257         if y_max:
258             layout["yaxis"]["range"] = [0, max(y_max)]
259         plpl = plgo.Figure(data=traces, layout=layout)
260
261         # Export Plot
262         file_type = plot.get("output-file-type", ".html")
263         logging.info("    Writing file '{0}{1}'.".
264                      format(plot["output-file"], file_type))
265         ploff.plot(plpl, show_link=False, auto_open=False,
266                    filename='{0}{1}'.format(plot["output-file"], file_type))
267     except PlotlyError as err:
268         logging.error("   Finished with error: {}".
269                       format(repr(err).replace("\n", " ")))
270         return
271
272
273 def plot_latency_error_bars_name(plot, input_data):
274     """Generate the plot(s) with algorithm: plot_latency_error_bars_name
275     specified in the specification file.
276
277     :param plot: Plot to generate.
278     :param input_data: Data to process.
279     :type plot: pandas.Series
280     :type input_data: InputData
281     """
282
283     # Transform the data
284     plot_title = plot.get("title", "")
285     logging.info("    Creating the data set for the {0} '{1}'.".
286                  format(plot.get("type", ""), plot_title))
287     data = input_data.filter_tests_by_name(
288         plot, params=["latency", "parent", "tags", "type"])
289     if data is None:
290         logging.error("No data.")
291         return
292
293     # Prepare the data for the plot
294     y_tmp_vals = OrderedDict()
295     for job in data:
296         for build in job:
297             for test in build:
298                 try:
299                     logging.debug("test['latency']: {0}\n".
300                                  format(test["latency"]))
301                 except ValueError as err:
302                     logging.warning(repr(err))
303                 if y_tmp_vals.get(test["parent"], None) is None:
304                     y_tmp_vals[test["parent"]] = [
305                         list(),  # direction1, min
306                         list(),  # direction1, avg
307                         list(),  # direction1, max
308                         list(),  # direction2, min
309                         list(),  # direction2, avg
310                         list()   # direction2, max
311                     ]
312                 try:
313                     if test["type"] in ("NDRPDR", ):
314                         if "-pdr" in plot_title.lower():
315                             ttype = "PDR"
316                         elif "-ndr" in plot_title.lower():
317                             ttype = "NDR"
318                         else:
319                             logging.warning("Invalid test type: {0}".
320                                             format(test["type"]))
321                             continue
322                         y_tmp_vals[test["parent"]][0].append(
323                             test["latency"][ttype]["direction1"]["min"])
324                         y_tmp_vals[test["parent"]][1].append(
325                             test["latency"][ttype]["direction1"]["avg"])
326                         y_tmp_vals[test["parent"]][2].append(
327                             test["latency"][ttype]["direction1"]["max"])
328                         y_tmp_vals[test["parent"]][3].append(
329                             test["latency"][ttype]["direction2"]["min"])
330                         y_tmp_vals[test["parent"]][4].append(
331                             test["latency"][ttype]["direction2"]["avg"])
332                         y_tmp_vals[test["parent"]][5].append(
333                             test["latency"][ttype]["direction2"]["max"])
334                     else:
335                         logging.warning("Invalid test type: {0}".
336                                         format(test["type"]))
337                         continue
338                 except (KeyError, TypeError) as err:
339                     logging.warning(repr(err))
340
341     x_vals = list()
342     y_vals = list()
343     y_mins = list()
344     y_maxs = list()
345     nr_of_samples = list()
346     for key, val in y_tmp_vals.items():
347         name = re.sub(REGEX_NIC, "", key.replace('-ndrpdr', '').
348                       replace('2n1l-', ''))
349         x_vals.append(name)  # dir 1
350         y_vals.append(mean(val[1]) if val[1] else None)
351         y_mins.append(mean(val[0]) if val[0] else None)
352         y_maxs.append(mean(val[2]) if val[2] else None)
353         nr_of_samples.append(len(val[1]) if val[1] else 0)
354         x_vals.append(name)  # dir 2
355         y_vals.append(mean(val[4]) if val[4] else None)
356         y_mins.append(mean(val[3]) if val[3] else None)
357         y_maxs.append(mean(val[5]) if val[5] else None)
358         nr_of_samples.append(len(val[3]) if val[3] else 0)
359
360     traces = list()
361     annotations = list()
362
363     for idx in range(len(x_vals)):
364         if not bool(int(idx % 2)):
365             direction = "West-East"
366         else:
367             direction = "East-West"
368         hovertext = ("No. of Runs: {nr}<br>"
369                      "Test: {test}<br>"
370                      "Direction: {dir}<br>".format(test=x_vals[idx],
371                                                    dir=direction,
372                                                    nr=nr_of_samples[idx]))
373         if isinstance(y_maxs[idx], float):
374             hovertext += "Max: {max:.2f}uSec<br>".format(max=y_maxs[idx])
375         if isinstance(y_vals[idx], float):
376             hovertext += "Mean: {avg:.2f}uSec<br>".format(avg=y_vals[idx])
377         if isinstance(y_mins[idx], float):
378             hovertext += "Min: {min:.2f}uSec".format(min=y_mins[idx])
379
380         if isinstance(y_maxs[idx], float) and isinstance(y_vals[idx], float):
381             array = [y_maxs[idx] - y_vals[idx], ]
382         else:
383             array = [None, ]
384         if isinstance(y_mins[idx], float) and isinstance(y_vals[idx], float):
385             arrayminus = [y_vals[idx] - y_mins[idx], ]
386         else:
387             arrayminus = [None, ]
388         traces.append(plgo.Scatter(
389             x=[idx, ],
390             y=[y_vals[idx], ],
391             name=x_vals[idx],
392             legendgroup=x_vals[idx],
393             showlegend=bool(int(idx % 2)),
394             mode="markers",
395             error_y=dict(
396                 type='data',
397                 symmetric=False,
398                 array=array,
399                 arrayminus=arrayminus,
400                 color=COLORS[int(idx / 2)]
401             ),
402             marker=dict(
403                 size=10,
404                 color=COLORS[int(idx / 2)],
405             ),
406             text=hovertext,
407             hoverinfo="text",
408         ))
409         annotations.append(dict(
410             x=idx,
411             y=0,
412             xref="x",
413             yref="y",
414             xanchor="center",
415             yanchor="top",
416             text="E-W" if bool(int(idx % 2)) else "W-E",
417             font=dict(
418                 size=16,
419             ),
420             align="center",
421             showarrow=False
422         ))
423
424     try:
425         # Create plot
426         file_type = plot.get("output-file-type", ".html")
427         logging.info("    Writing file '{0}{1}'.".
428                      format(plot["output-file"], file_type))
429         layout = deepcopy(plot["layout"])
430         if layout.get("title", None):
431             layout["title"] = "<b>Latency:</b> {0}".\
432                 format(layout["title"])
433         layout["annotations"] = annotations
434         plpl = plgo.Figure(data=traces, layout=layout)
435
436         # Export Plot
437         ploff.plot(plpl,
438                    show_link=False, auto_open=False,
439                    filename='{0}{1}'.format(plot["output-file"], file_type))
440     except PlotlyError as err:
441         logging.error("   Finished with error: {}".
442                       format(str(err).replace("\n", " ")))
443         return
444
445
446 def plot_throughput_speedup_analysis_name(plot, input_data):
447     """Generate the plot(s) with algorithm:
448     plot_throughput_speedup_analysis_name
449     specified in the specification file.
450
451     :param plot: Plot to generate.
452     :param input_data: Data to process.
453     :type plot: pandas.Series
454     :type input_data: InputData
455     """
456
457     # Transform the data
458     plot_title = plot.get("title", "")
459     logging.info("    Creating the data set for the {0} '{1}'.".
460                  format(plot.get("type", ""), plot_title))
461     data = input_data.filter_tests_by_name(
462         plot, params=["throughput", "parent", "tags", "type"])
463     if data is None:
464         logging.error("No data.")
465         return
466
467     y_vals = OrderedDict()
468     for job in data:
469         for build in job:
470             for test in build:
471                 if y_vals.get(test["parent"], None) is None:
472                     y_vals[test["parent"]] = {"1": list(),
473                                               "2": list(),
474                                               "4": list()}
475                 try:
476                     if test["type"] in ("NDRPDR",):
477                         if "-pdr" in plot_title.lower():
478                             ttype = "PDR"
479                         elif "-ndr" in plot_title.lower():
480                             ttype = "NDR"
481                         else:
482                             continue
483                         if "1C" in test["tags"]:
484                             y_vals[test["parent"]]["1"]. \
485                                 append(test["throughput"][ttype]["LOWER"])
486                         elif "2C" in test["tags"]:
487                             y_vals[test["parent"]]["2"]. \
488                                 append(test["throughput"][ttype]["LOWER"])
489                         elif "4C" in test["tags"]:
490                             y_vals[test["parent"]]["4"]. \
491                                 append(test["throughput"][ttype]["LOWER"])
492                 except (KeyError, TypeError):
493                     pass
494
495     if not y_vals:
496         logging.warning("No data for the plot '{}'".
497                         format(plot.get("title", "")))
498         return
499
500     y_1c_max = dict()
501     for test_name, test_vals in y_vals.items():
502         for key, test_val in test_vals.items():
503             if test_val:
504                 avg_val = sum(test_val) / len(test_val)
505                 y_vals[test_name][key] = (avg_val, len(test_val))
506                 ideal = avg_val / (int(key) * 1000000.0)
507                 if test_name not in y_1c_max or ideal > y_1c_max[test_name]:
508                     y_1c_max[test_name] = ideal
509
510     vals = OrderedDict()
511     y_max = list()
512     nic_limit = 0
513     lnk_limit = 0
514     pci_limit = plot["limits"]["pci"]["pci-g3-x8"]
515     for test_name, test_vals in y_vals.items():
516         try:
517             if test_vals["1"][1]:
518                 name = re.sub(REGEX_NIC, "", test_name.replace('-ndrpdr', '').
519                               replace('2n1l-', ''))
520                 vals[name] = OrderedDict()
521                 y_val_1 = test_vals["1"][0] / 1000000.0
522                 y_val_2 = test_vals["2"][0] / 1000000.0 if test_vals["2"][0] \
523                     else None
524                 y_val_4 = test_vals["4"][0] / 1000000.0 if test_vals["4"][0] \
525                     else None
526
527                 vals[name]["val"] = [y_val_1, y_val_2, y_val_4]
528                 vals[name]["rel"] = [1.0, None, None]
529                 vals[name]["ideal"] = [y_1c_max[test_name],
530                                        y_1c_max[test_name] * 2,
531                                        y_1c_max[test_name] * 4]
532                 vals[name]["diff"] = [(y_val_1 - y_1c_max[test_name]) * 100 /
533                                       y_val_1, None, None]
534                 vals[name]["count"] = [test_vals["1"][1],
535                                        test_vals["2"][1],
536                                        test_vals["4"][1]]
537
538                 try:
539                     val_max = max(vals[name]["val"])
540                 except ValueError as err:
541                     logging.error(repr(err))
542                     continue
543                 if val_max:
544                     y_max.append(val_max)
545
546                 if y_val_2:
547                     vals[name]["rel"][1] = round(y_val_2 / y_val_1, 2)
548                     vals[name]["diff"][1] = \
549                         (y_val_2 - vals[name]["ideal"][1]) * 100 / y_val_2
550                 if y_val_4:
551                     vals[name]["rel"][2] = round(y_val_4 / y_val_1, 2)
552                     vals[name]["diff"][2] = \
553                         (y_val_4 - vals[name]["ideal"][2]) * 100 / y_val_4
554         except IndexError as err:
555             logging.warning("No data for '{0}'".format(test_name))
556             logging.warning(repr(err))
557
558         # Limits:
559         if "x520" in test_name:
560             limit = plot["limits"]["nic"]["x520"]
561         elif "x710" in test_name:
562             limit = plot["limits"]["nic"]["x710"]
563         elif "xxv710" in test_name:
564             limit = plot["limits"]["nic"]["xxv710"]
565         elif "xl710" in test_name:
566             limit = plot["limits"]["nic"]["xl710"]
567         elif "x553" in test_name:
568             limit = plot["limits"]["nic"]["x553"]
569         else:
570             limit = 0
571         if limit > nic_limit:
572             nic_limit = limit
573
574         mul = 2 if "ge2p" in test_name else 1
575         if "10ge" in test_name:
576             limit = plot["limits"]["link"]["10ge"] * mul
577         elif "25ge" in test_name:
578             limit = plot["limits"]["link"]["25ge"] * mul
579         elif "40ge" in test_name:
580             limit = plot["limits"]["link"]["40ge"] * mul
581         elif "100ge" in test_name:
582             limit = plot["limits"]["link"]["100ge"] * mul
583         else:
584             limit = 0
585         if limit > lnk_limit:
586             lnk_limit = limit
587
588     traces = list()
589     annotations = list()
590     x_vals = [1, 2, 4]
591
592     # Limits:
593     try:
594         threshold = 1.1 * max(y_max)  # 10%
595     except ValueError as err:
596         logging.error(err)
597         return
598     nic_limit /= 1000000.0
599     traces.append(plgo.Scatter(
600         x=x_vals,
601         y=[nic_limit, ] * len(x_vals),
602         name="NIC: {0:.2f}Mpps".format(nic_limit),
603         showlegend=False,
604         mode="lines",
605         line=dict(
606             dash="dot",
607             color=COLORS[-1],
608             width=1),
609         hoverinfo="none"
610     ))
611     annotations.append(dict(
612         x=1,
613         y=nic_limit,
614         xref="x",
615         yref="y",
616         xanchor="left",
617         yanchor="bottom",
618         text="NIC: {0:.2f}Mpps".format(nic_limit),
619         font=dict(
620             size=14,
621             color=COLORS[-1],
622         ),
623         align="left",
624         showarrow=False
625     ))
626     y_max.append(nic_limit)
627
628     lnk_limit /= 1000000.0
629     if lnk_limit < threshold:
630         traces.append(plgo.Scatter(
631             x=x_vals,
632             y=[lnk_limit, ] * len(x_vals),
633             name="Link: {0:.2f}Mpps".format(lnk_limit),
634             showlegend=False,
635             mode="lines",
636             line=dict(
637                 dash="dot",
638                 color=COLORS[-2],
639                 width=1),
640             hoverinfo="none"
641         ))
642         annotations.append(dict(
643             x=1,
644             y=lnk_limit,
645             xref="x",
646             yref="y",
647             xanchor="left",
648             yanchor="bottom",
649             text="Link: {0:.2f}Mpps".format(lnk_limit),
650             font=dict(
651                 size=14,
652                 color=COLORS[-2],
653             ),
654             align="left",
655             showarrow=False
656         ))
657         y_max.append(lnk_limit)
658
659     pci_limit /= 1000000.0
660     if (pci_limit < threshold and
661         (pci_limit < lnk_limit * 0.95 or lnk_limit > lnk_limit * 1.05)):
662         traces.append(plgo.Scatter(
663             x=x_vals,
664             y=[pci_limit, ] * len(x_vals),
665             name="PCIe: {0:.2f}Mpps".format(pci_limit),
666             showlegend=False,
667             mode="lines",
668             line=dict(
669                 dash="dot",
670                 color=COLORS[-3],
671                 width=1),
672             hoverinfo="none"
673         ))
674         annotations.append(dict(
675             x=1,
676             y=pci_limit,
677             xref="x",
678             yref="y",
679             xanchor="left",
680             yanchor="bottom",
681             text="PCIe: {0:.2f}Mpps".format(pci_limit),
682             font=dict(
683                 size=14,
684                 color=COLORS[-3],
685             ),
686             align="left",
687             showarrow=False
688         ))
689         y_max.append(pci_limit)
690
691     # Perfect and measured:
692     cidx = 0
693     for name, val in vals.iteritems():
694         hovertext = list()
695         try:
696             for idx in range(len(val["val"])):
697                 htext = ""
698                 if isinstance(val["val"][idx], float):
699                     htext += "No. of Runs: {1}<br>" \
700                              "Mean: {0:.2f}Mpps<br>".format(val["val"][idx],
701                                                             val["count"][idx])
702                 if isinstance(val["diff"][idx], float):
703                     htext += "Diff: {0:.0f}%<br>".format(
704                         round(val["diff"][idx]))
705                 if isinstance(val["rel"][idx], float):
706                     htext += "Speedup: {0:.2f}".format(val["rel"][idx])
707                 hovertext.append(htext)
708             traces.append(plgo.Scatter(x=x_vals,
709                                        y=val["val"],
710                                        name=name,
711                                        legendgroup=name,
712                                        mode="lines+markers",
713                                        line=dict(
714                                            color=COLORS[cidx],
715                                            width=2),
716                                        marker=dict(
717                                            symbol="circle",
718                                            size=10
719                                        ),
720                                        text=hovertext,
721                                        hoverinfo="text+name"
722                                        ))
723             traces.append(plgo.Scatter(x=x_vals,
724                                        y=val["ideal"],
725                                        name="{0} perfect".format(name),
726                                        legendgroup=name,
727                                        showlegend=False,
728                                        mode="lines",
729                                        line=dict(
730                                            color=COLORS[cidx],
731                                            width=2,
732                                            dash="dash"),
733                                        text=["Perfect: {0:.2f}Mpps".format(y)
734                                              for y in val["ideal"]],
735                                        hoverinfo="text"
736                                        ))
737             cidx += 1
738         except (IndexError, ValueError, KeyError) as err:
739             logging.warning("No data for '{0}'".format(name))
740             logging.warning(repr(err))
741
742     try:
743         # Create plot
744         file_type = plot.get("output-file-type", ".html")
745         logging.info("    Writing file '{0}{1}'.".
746                      format(plot["output-file"], file_type))
747         layout = deepcopy(plot["layout"])
748         if layout.get("title", None):
749             layout["title"] = "<b>Speedup Multi-core:</b> {0}". \
750                 format(layout["title"])
751         layout["yaxis"]["range"] = [0, int(max(y_max) * 1.1)]
752         layout["annotations"].extend(annotations)
753         plpl = plgo.Figure(data=traces, layout=layout)
754
755         # Export Plot
756         ploff.plot(plpl,
757                    show_link=False, auto_open=False,
758                    filename='{0}{1}'.format(plot["output-file"], file_type))
759     except PlotlyError as err:
760         logging.error("   Finished with error: {}".
761                       format(repr(err).replace("\n", " ")))
762         return
763
764
765 def plot_performance_box(plot, input_data):
766     """Generate the plot(s) with algorithm: plot_performance_box
767     specified in the specification file.
768
769     TODO: Remove when not needed.
770
771     :param plot: Plot to generate.
772     :param input_data: Data to process.
773     :type plot: pandas.Series
774     :type input_data: InputData
775     """
776
777     # Transform the data
778     plot_title = plot.get("title", "")
779     logging.info("    Creating the data set for the {0} '{1}'.".
780                  format(plot.get("type", ""), plot_title))
781     data = input_data.filter_data(plot)
782     if data is None:
783         logging.error("No data.")
784         return
785
786     # Prepare the data for the plot
787     y_vals = dict()
788     y_tags = dict()
789     for job in data:
790         for build in job:
791             for test in build:
792                 if y_vals.get(test["parent"], None) is None:
793                     y_vals[test["parent"]] = list()
794                     y_tags[test["parent"]] = test.get("tags", None)
795                 try:
796                     if test["type"] in ("NDRPDR", ):
797                         if "-pdr" in plot_title.lower():
798                             y_vals[test["parent"]].\
799                                 append(test["throughput"]["PDR"]["LOWER"])
800                         elif "-ndr" in plot_title.lower():
801                             y_vals[test["parent"]]. \
802                                 append(test["throughput"]["NDR"]["LOWER"])
803                         else:
804                             continue
805                     elif test["type"] in ("SOAK", ):
806                         y_vals[test["parent"]].\
807                             append(test["throughput"]["LOWER"])
808                     else:
809                         continue
810                 except (KeyError, TypeError):
811                     y_vals[test["parent"]].append(None)
812
813     # Sort the tests
814     order = plot.get("sort", None)
815     if order and y_tags:
816         y_sorted = OrderedDict()
817         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
818         for tag in order:
819             logging.debug(tag)
820             for suite, tags in y_tags_l.items():
821                 if "not " in tag:
822                     tag = tag.split(" ")[-1]
823                     if tag.lower() in tags:
824                         continue
825                 else:
826                     if tag.lower() not in tags:
827                         continue
828                 try:
829                     y_sorted[suite] = y_vals.pop(suite)
830                     y_tags_l.pop(suite)
831                     logging.debug(suite)
832                 except KeyError as err:
833                     logging.error("Not found: {0}".format(repr(err)))
834                 finally:
835                     break
836     else:
837         y_sorted = y_vals
838
839     # Add None to the lists with missing data
840     max_len = 0
841     nr_of_samples = list()
842     for val in y_sorted.values():
843         if len(val) > max_len:
844             max_len = len(val)
845         nr_of_samples.append(len(val))
846     for key, val in y_sorted.items():
847         if len(val) < max_len:
848             val.extend([None for _ in range(max_len - len(val))])
849
850     # Add plot traces
851     traces = list()
852     df = pd.DataFrame(y_sorted)
853     df.head()
854     y_max = list()
855     for i, col in enumerate(df.columns):
856         tst_name = re.sub(REGEX_NIC, "",
857                           col.lower().replace('-ndrpdr', '').
858                           replace('2n1l-', ''))
859         name = "{nr}. ({samples:02d} run{plural}) {name}".\
860             format(nr=(i + 1),
861                    samples=nr_of_samples[i],
862                    plural='s' if nr_of_samples[i] > 1 else '',
863                    name=tst_name)
864
865         logging.debug(name)
866         traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
867                                y=[y / 1000000 if y else None for y in df[col]],
868                                name=name,
869                                **plot["traces"]))
870         try:
871             val_max = max(df[col])
872         except ValueError as err:
873             logging.error(repr(err))
874             continue
875         if val_max:
876             y_max.append(int(val_max / 1000000) + 2)
877
878     try:
879         # Create plot
880         layout = deepcopy(plot["layout"])
881         if layout.get("title", None):
882             layout["title"] = "<b>Throughput:</b> {0}". \
883                 format(layout["title"])
884         if y_max:
885             layout["yaxis"]["range"] = [0, max(y_max)]
886         plpl = plgo.Figure(data=traces, layout=layout)
887
888         # Export Plot
889         logging.info("    Writing file '{0}{1}'.".
890                      format(plot["output-file"], plot["output-file-type"]))
891         ploff.plot(plpl, show_link=False, auto_open=False,
892                    filename='{0}{1}'.format(plot["output-file"],
893                                             plot["output-file-type"]))
894     except PlotlyError as err:
895         logging.error("   Finished with error: {}".
896                       format(repr(err).replace("\n", " ")))
897         return
898
899
900 def plot_soak_bars(plot, input_data):
901     """Generate the plot(s) with algorithm: plot_soak_bars
902     specified in the specification file.
903
904     :param plot: Plot to generate.
905     :param input_data: Data to process.
906     :type plot: pandas.Series
907     :type input_data: InputData
908     """
909
910     # Transform the data
911     plot_title = plot.get("title", "")
912     logging.info("    Creating the data set for the {0} '{1}'.".
913                  format(plot.get("type", ""), plot_title))
914     data = input_data.filter_data(plot)
915     if data is None:
916         logging.error("No data.")
917         return
918
919     # Prepare the data for the plot
920     y_vals = dict()
921     y_tags = dict()
922     for job in data:
923         for build in job:
924             for test in build:
925                 if y_vals.get(test["parent"], None) is None:
926                     y_tags[test["parent"]] = test.get("tags", None)
927                 try:
928                     if test["type"] in ("SOAK", ):
929                         y_vals[test["parent"]] = test["throughput"]
930                     else:
931                         continue
932                 except (KeyError, TypeError):
933                     y_vals[test["parent"]] = dict()
934
935     # Sort the tests
936     order = plot.get("sort", None)
937     if order and y_tags:
938         y_sorted = OrderedDict()
939         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
940         for tag in order:
941             logging.debug(tag)
942             for suite, tags in y_tags_l.items():
943                 if "not " in tag:
944                     tag = tag.split(" ")[-1]
945                     if tag.lower() in tags:
946                         continue
947                 else:
948                     if tag.lower() not in tags:
949                         continue
950                 try:
951                     y_sorted[suite] = y_vals.pop(suite)
952                     y_tags_l.pop(suite)
953                     logging.debug(suite)
954                 except KeyError as err:
955                     logging.error("Not found: {0}".format(repr(err)))
956                 finally:
957                     break
958     else:
959         y_sorted = y_vals
960
961     idx = 0
962     y_max = 0
963     traces = list()
964     for test_name, test_data in y_sorted.items():
965         idx += 1
966         name = "{nr}. {name}".\
967             format(nr=idx, name=test_name.lower().replace('-soak', ''))
968         if len(name) > 50:
969             name_lst = name.split('-')
970             name = ""
971             split_name = True
972             for segment in name_lst:
973                 if (len(name) + len(segment) + 1) > 50 and split_name:
974                     name += "<br>    "
975                     split_name = False
976                 name += segment + '-'
977             name = name[:-1]
978
979         y_val = test_data.get("LOWER", None)
980         if y_val:
981             y_val /= 1000000
982             if y_val > y_max:
983                 y_max = y_val
984
985         time = "No Information"
986         result = "No Information"
987         hovertext = ("{name}<br>"
988                      "Packet Throughput: {val:.2f}Mpps<br>"
989                      "Final Duration: {time}<br>"
990                      "Result: {result}".format(name=name,
991                                                val=y_val,
992                                                time=time,
993                                                result=result))
994         traces.append(plgo.Bar(x=[str(idx) + '.', ],
995                                y=[y_val, ],
996                                name=name,
997                                text=hovertext,
998                                hoverinfo="text"))
999     try:
1000         # Create plot
1001         layout = deepcopy(plot["layout"])
1002         if layout.get("title", None):
1003             layout["title"] = "<b>Packet Throughput:</b> {0}". \
1004                 format(layout["title"])
1005         if y_max:
1006             layout["yaxis"]["range"] = [0, y_max + 1]
1007         plpl = plgo.Figure(data=traces, layout=layout)
1008         # Export Plot
1009         logging.info("    Writing file '{0}{1}'.".
1010                      format(plot["output-file"], plot["output-file-type"]))
1011         ploff.plot(plpl, show_link=False, auto_open=False,
1012                    filename='{0}{1}'.format(plot["output-file"],
1013                                             plot["output-file-type"]))
1014     except PlotlyError as err:
1015         logging.error("   Finished with error: {}".
1016                       format(repr(err).replace("\n", " ")))
1017         return
1018
1019
1020 def plot_soak_boxes(plot, input_data):
1021     """Generate the plot(s) with algorithm: plot_soak_boxes
1022     specified in the specification file.
1023
1024     :param plot: Plot to generate.
1025     :param input_data: Data to process.
1026     :type plot: pandas.Series
1027     :type input_data: InputData
1028     """
1029
1030     # Transform the data
1031     plot_title = plot.get("title", "")
1032     logging.info("    Creating the data set for the {0} '{1}'.".
1033                  format(plot.get("type", ""), plot_title))
1034     data = input_data.filter_data(plot)
1035     if data is None:
1036         logging.error("No data.")
1037         return
1038
1039     # Prepare the data for the plot
1040     y_vals = dict()
1041     y_tags = dict()
1042     for job in data:
1043         for build in job:
1044             for test in build:
1045                 if y_vals.get(test["parent"], None) is None:
1046                     y_tags[test["parent"]] = test.get("tags", None)
1047                 try:
1048                     if test["type"] in ("SOAK", ):
1049                         y_vals[test["parent"]] = test["throughput"]
1050                     else:
1051                         continue
1052                 except (KeyError, TypeError):
1053                     y_vals[test["parent"]] = dict()
1054
1055     # Sort the tests
1056     order = plot.get("sort", None)
1057     if order and y_tags:
1058         y_sorted = OrderedDict()
1059         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
1060         for tag in order:
1061             logging.debug(tag)
1062             for suite, tags in y_tags_l.items():
1063                 if "not " in tag:
1064                     tag = tag.split(" ")[-1]
1065                     if tag.lower() in tags:
1066                         continue
1067                 else:
1068                     if tag.lower() not in tags:
1069                         continue
1070                 try:
1071                     y_sorted[suite] = y_vals.pop(suite)
1072                     y_tags_l.pop(suite)
1073                     logging.debug(suite)
1074                 except KeyError as err:
1075                     logging.error("Not found: {0}".format(repr(err)))
1076                 finally:
1077                     break
1078     else:
1079         y_sorted = y_vals
1080
1081     idx = 0
1082     y_max = 0
1083     traces = list()
1084     for test_name, test_data in y_sorted.items():
1085         idx += 1
1086         name = "{nr}. {name}".\
1087             format(nr=idx, name=test_name.lower().replace('-soak', '').
1088                    replace('2n1l-', ''))
1089         if len(name) > 55:
1090             name_lst = name.split('-')
1091             name = ""
1092             split_name = True
1093             for segment in name_lst:
1094                 if (len(name) + len(segment) + 1) > 55 and split_name:
1095                     name += "<br>    "
1096                     split_name = False
1097                 name += segment + '-'
1098             name = name[:-1]
1099
1100         y_val = test_data.get("UPPER", None)
1101         if y_val:
1102             y_val /= 1000000
1103             if y_val > y_max:
1104                 y_max = y_val
1105
1106         y_base = test_data.get("LOWER", None)
1107         if y_base:
1108             y_base /= 1000000
1109
1110         hovertext = ("Upper bound: {upper:.2f}<br>"
1111                      "Lower bound: {lower:.2f}".format(upper=y_val,
1112                                                            lower=y_base))
1113         traces.append(plgo.Bar(x=[str(idx) + '.', ],
1114                                # +0.05 to see the value in case lower == upper
1115                                y=[y_val - y_base + 0.05, ],
1116                                base=y_base,
1117                                name=name,
1118                                text=hovertext,
1119                                hoverinfo="text"))
1120     try:
1121         # Create plot
1122         layout = deepcopy(plot["layout"])
1123         if layout.get("title", None):
1124             layout["title"] = "<b>Throughput:</b> {0}". \
1125                 format(layout["title"])
1126         if y_max:
1127             layout["yaxis"]["range"] = [0, y_max + 1]
1128         plpl = plgo.Figure(data=traces, layout=layout)
1129         # Export Plot
1130         logging.info("    Writing file '{0}{1}'.".
1131                      format(plot["output-file"], plot["output-file-type"]))
1132         ploff.plot(plpl, show_link=False, auto_open=False,
1133                    filename='{0}{1}'.format(plot["output-file"],
1134                                             plot["output-file-type"]))
1135     except PlotlyError as err:
1136         logging.error("   Finished with error: {}".
1137                       format(repr(err).replace("\n", " ")))
1138         return
1139
1140
1141 def plot_latency_error_bars(plot, input_data):
1142     """Generate the plot(s) with algorithm: plot_latency_error_bars
1143     specified in the specification file.
1144
1145     TODO: Remove when not needed.
1146
1147     :param plot: Plot to generate.
1148     :param input_data: Data to process.
1149     :type plot: pandas.Series
1150     :type input_data: InputData
1151     """
1152
1153     # Transform the data
1154     plot_title = plot.get("title", "")
1155     logging.info("    Creating the data set for the {0} '{1}'.".
1156                  format(plot.get("type", ""), plot_title))
1157     data = input_data.filter_data(plot)
1158     if data is None:
1159         logging.error("No data.")
1160         return
1161
1162     # Prepare the data for the plot
1163     y_tmp_vals = dict()
1164     y_tags = dict()
1165     for job in data:
1166         for build in job:
1167             for test in build:
1168                 try:
1169                     logging.debug("test['latency']: {0}\n".
1170                                  format(test["latency"]))
1171                 except ValueError as err:
1172                     logging.warning(repr(err))
1173                 if y_tmp_vals.get(test["parent"], None) is None:
1174                     y_tmp_vals[test["parent"]] = [
1175                         list(),  # direction1, min
1176                         list(),  # direction1, avg
1177                         list(),  # direction1, max
1178                         list(),  # direction2, min
1179                         list(),  # direction2, avg
1180                         list()   # direction2, max
1181                     ]
1182                     y_tags[test["parent"]] = test.get("tags", None)
1183                 try:
1184                     if test["type"] in ("NDRPDR", ):
1185                         if "-pdr" in plot_title.lower():
1186                             ttype = "PDR"
1187                         elif "-ndr" in plot_title.lower():
1188                             ttype = "NDR"
1189                         else:
1190                             logging.warning("Invalid test type: {0}".
1191                                             format(test["type"]))
1192                             continue
1193                         y_tmp_vals[test["parent"]][0].append(
1194                             test["latency"][ttype]["direction1"]["min"])
1195                         y_tmp_vals[test["parent"]][1].append(
1196                             test["latency"][ttype]["direction1"]["avg"])
1197                         y_tmp_vals[test["parent"]][2].append(
1198                             test["latency"][ttype]["direction1"]["max"])
1199                         y_tmp_vals[test["parent"]][3].append(
1200                             test["latency"][ttype]["direction2"]["min"])
1201                         y_tmp_vals[test["parent"]][4].append(
1202                             test["latency"][ttype]["direction2"]["avg"])
1203                         y_tmp_vals[test["parent"]][5].append(
1204                             test["latency"][ttype]["direction2"]["max"])
1205                     else:
1206                         logging.warning("Invalid test type: {0}".
1207                                         format(test["type"]))
1208                         continue
1209                 except (KeyError, TypeError) as err:
1210                     logging.warning(repr(err))
1211     logging.debug("y_tmp_vals: {0}\n".format(y_tmp_vals))
1212
1213     # Sort the tests
1214     order = plot.get("sort", None)
1215     if order and y_tags:
1216         y_sorted = OrderedDict()
1217         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
1218         for tag in order:
1219             logging.debug(tag)
1220             for suite, tags in y_tags_l.items():
1221                 if "not " in tag:
1222                     tag = tag.split(" ")[-1]
1223                     if tag.lower() in tags:
1224                         continue
1225                 else:
1226                     if tag.lower() not in tags:
1227                         continue
1228                 try:
1229                     y_sorted[suite] = y_tmp_vals.pop(suite)
1230                     y_tags_l.pop(suite)
1231                     logging.debug(suite)
1232                 except KeyError as err:
1233                     logging.error("Not found: {0}".format(repr(err)))
1234                 finally:
1235                     break
1236     else:
1237         y_sorted = y_tmp_vals
1238
1239     logging.debug("y_sorted: {0}\n".format(y_sorted))
1240     x_vals = list()
1241     y_vals = list()
1242     y_mins = list()
1243     y_maxs = list()
1244     nr_of_samples = list()
1245     for key, val in y_sorted.items():
1246         name = re.sub(REGEX_NIC, "", key.replace('-ndrpdr', '').
1247                       replace('2n1l-', ''))
1248         x_vals.append(name)  # dir 1
1249         y_vals.append(mean(val[1]) if val[1] else None)
1250         y_mins.append(mean(val[0]) if val[0] else None)
1251         y_maxs.append(mean(val[2]) if val[2] else None)
1252         nr_of_samples.append(len(val[1]) if val[1] else 0)
1253         x_vals.append(name)  # dir 2
1254         y_vals.append(mean(val[4]) if val[4] else None)
1255         y_mins.append(mean(val[3]) if val[3] else None)
1256         y_maxs.append(mean(val[5]) if val[5] else None)
1257         nr_of_samples.append(len(val[3]) if val[3] else 0)
1258
1259     logging.debug("x_vals :{0}\n".format(x_vals))
1260     logging.debug("y_vals :{0}\n".format(y_vals))
1261     logging.debug("y_mins :{0}\n".format(y_mins))
1262     logging.debug("y_maxs :{0}\n".format(y_maxs))
1263     logging.debug("nr_of_samples :{0}\n".format(nr_of_samples))
1264     traces = list()
1265     annotations = list()
1266
1267     for idx in range(len(x_vals)):
1268         if not bool(int(idx % 2)):
1269             direction = "West-East"
1270         else:
1271             direction = "East-West"
1272         hovertext = ("No. of Runs: {nr}<br>"
1273                      "Test: {test}<br>"
1274                      "Direction: {dir}<br>".format(test=x_vals[idx],
1275                                                    dir=direction,
1276                                                    nr=nr_of_samples[idx]))
1277         if isinstance(y_maxs[idx], float):
1278             hovertext += "Max: {max:.2f}uSec<br>".format(max=y_maxs[idx])
1279         if isinstance(y_vals[idx], float):
1280             hovertext += "Mean: {avg:.2f}uSec<br>".format(avg=y_vals[idx])
1281         if isinstance(y_mins[idx], float):
1282             hovertext += "Min: {min:.2f}uSec".format(min=y_mins[idx])
1283
1284         if isinstance(y_maxs[idx], float) and isinstance(y_vals[idx], float):
1285             array = [y_maxs[idx] - y_vals[idx], ]
1286         else:
1287             array = [None, ]
1288         if isinstance(y_mins[idx], float) and isinstance(y_vals[idx], float):
1289             arrayminus = [y_vals[idx] - y_mins[idx], ]
1290         else:
1291             arrayminus = [None, ]
1292         logging.debug("y_vals[{1}] :{0}\n".format(y_vals[idx], idx))
1293         logging.debug("array :{0}\n".format(array))
1294         logging.debug("arrayminus :{0}\n".format(arrayminus))
1295         traces.append(plgo.Scatter(
1296             x=[idx, ],
1297             y=[y_vals[idx], ],
1298             name=x_vals[idx],
1299             legendgroup=x_vals[idx],
1300             showlegend=bool(int(idx % 2)),
1301             mode="markers",
1302             error_y=dict(
1303                 type='data',
1304                 symmetric=False,
1305                 array=array,
1306                 arrayminus=arrayminus,
1307                 color=COLORS[int(idx / 2)]
1308             ),
1309             marker=dict(
1310                 size=10,
1311                 color=COLORS[int(idx / 2)],
1312             ),
1313             text=hovertext,
1314             hoverinfo="text",
1315         ))
1316         annotations.append(dict(
1317             x=idx,
1318             y=0,
1319             xref="x",
1320             yref="y",
1321             xanchor="center",
1322             yanchor="top",
1323             text="E-W" if bool(int(idx % 2)) else "W-E",
1324             font=dict(
1325                 size=16,
1326             ),
1327             align="center",
1328             showarrow=False
1329         ))
1330
1331     try:
1332         # Create plot
1333         logging.info("    Writing file '{0}{1}'.".
1334                      format(plot["output-file"], plot["output-file-type"]))
1335         layout = deepcopy(plot["layout"])
1336         if layout.get("title", None):
1337             layout["title"] = "<b>Latency:</b> {0}".\
1338                 format(layout["title"])
1339         layout["annotations"] = annotations
1340         plpl = plgo.Figure(data=traces, layout=layout)
1341
1342         # Export Plot
1343         ploff.plot(plpl,
1344                    show_link=False, auto_open=False,
1345                    filename='{0}{1}'.format(plot["output-file"],
1346                                             plot["output-file-type"]))
1347     except PlotlyError as err:
1348         logging.error("   Finished with error: {}".
1349                       format(str(err).replace("\n", " ")))
1350         return
1351
1352
1353 def plot_throughput_speedup_analysis(plot, input_data):
1354     """Generate the plot(s) with algorithm:
1355     plot_throughput_speedup_analysis
1356     specified in the specification file.
1357
1358     TODO: Remove when not needed.
1359
1360     :param plot: Plot to generate.
1361     :param input_data: Data to process.
1362     :type plot: pandas.Series
1363     :type input_data: InputData
1364     """
1365
1366     # Transform the data
1367     plot_title = plot.get("title", "")
1368     logging.info("    Creating the data set for the {0} '{1}'.".
1369                  format(plot.get("type", ""), plot_title))
1370     data = input_data.filter_data(plot)
1371     if data is None:
1372         logging.error("No data.")
1373         return
1374
1375     y_vals = dict()
1376     y_tags = dict()
1377     for job in data:
1378         for build in job:
1379             for test in build:
1380                 if y_vals.get(test["parent"], None) is None:
1381                     y_vals[test["parent"]] = {"1": list(),
1382                                               "2": list(),
1383                                               "4": list()}
1384                     y_tags[test["parent"]] = test.get("tags", None)
1385                 try:
1386                     if test["type"] in ("NDRPDR",):
1387                         if "-pdr" in plot_title.lower():
1388                             ttype = "PDR"
1389                         elif "-ndr" in plot_title.lower():
1390                             ttype = "NDR"
1391                         else:
1392                             continue
1393                         if "1C" in test["tags"]:
1394                             y_vals[test["parent"]]["1"]. \
1395                                 append(test["throughput"][ttype]["LOWER"])
1396                         elif "2C" in test["tags"]:
1397                             y_vals[test["parent"]]["2"]. \
1398                                 append(test["throughput"][ttype]["LOWER"])
1399                         elif "4C" in test["tags"]:
1400                             y_vals[test["parent"]]["4"]. \
1401                                 append(test["throughput"][ttype]["LOWER"])
1402                 except (KeyError, TypeError):
1403                     pass
1404
1405     if not y_vals:
1406         logging.warning("No data for the plot '{}'".
1407                         format(plot.get("title", "")))
1408         return
1409
1410     y_1c_max = dict()
1411     for test_name, test_vals in y_vals.items():
1412         for key, test_val in test_vals.items():
1413             if test_val:
1414                 avg_val = sum(test_val) / len(test_val)
1415                 y_vals[test_name][key] = (avg_val, len(test_val))
1416                 ideal = avg_val / (int(key) * 1000000.0)
1417                 if test_name not in y_1c_max or ideal > y_1c_max[test_name]:
1418                     y_1c_max[test_name] = ideal
1419
1420     vals = dict()
1421     y_max = list()
1422     nic_limit = 0
1423     lnk_limit = 0
1424     pci_limit = plot["limits"]["pci"]["pci-g3-x8"]
1425     for test_name, test_vals in y_vals.items():
1426         try:
1427             if test_vals["1"][1]:
1428                 name = re.sub(REGEX_NIC, "", test_name.replace('-ndrpdr', '').
1429                               replace('2n1l-', ''))
1430                 vals[name] = dict()
1431                 y_val_1 = test_vals["1"][0] / 1000000.0
1432                 y_val_2 = test_vals["2"][0] / 1000000.0 if test_vals["2"][0] \
1433                     else None
1434                 y_val_4 = test_vals["4"][0] / 1000000.0 if test_vals["4"][0] \
1435                     else None
1436
1437                 vals[name]["val"] = [y_val_1, y_val_2, y_val_4]
1438                 vals[name]["rel"] = [1.0, None, None]
1439                 vals[name]["ideal"] = [y_1c_max[test_name],
1440                                        y_1c_max[test_name] * 2,
1441                                        y_1c_max[test_name] * 4]
1442                 vals[name]["diff"] = [(y_val_1 - y_1c_max[test_name]) * 100 /
1443                                       y_val_1, None, None]
1444                 vals[name]["count"] = [test_vals["1"][1],
1445                                        test_vals["2"][1],
1446                                        test_vals["4"][1]]
1447
1448                 try:
1449                     # val_max = max(max(vals[name]["val"], vals[name]["ideal"]))
1450                     val_max = max(vals[name]["val"])
1451                 except ValueError as err:
1452                     logging.error(err)
1453                     continue
1454                 if val_max:
1455                     # y_max.append(int((val_max / 10) + 1) * 10)
1456                     y_max.append(val_max)
1457
1458                 if y_val_2:
1459                     vals[name]["rel"][1] = round(y_val_2 / y_val_1, 2)
1460                     vals[name]["diff"][1] = \
1461                         (y_val_2 - vals[name]["ideal"][1]) * 100 / y_val_2
1462                 if y_val_4:
1463                     vals[name]["rel"][2] = round(y_val_4 / y_val_1, 2)
1464                     vals[name]["diff"][2] = \
1465                         (y_val_4 - vals[name]["ideal"][2]) * 100 / y_val_4
1466         except IndexError as err:
1467             logging.warning("No data for '{0}'".format(test_name))
1468             logging.warning(repr(err))
1469
1470         # Limits:
1471         if "x520" in test_name:
1472             limit = plot["limits"]["nic"]["x520"]
1473         elif "x710" in test_name:
1474             limit = plot["limits"]["nic"]["x710"]
1475         elif "xxv710" in test_name:
1476             limit = plot["limits"]["nic"]["xxv710"]
1477         elif "xl710" in test_name:
1478             limit = plot["limits"]["nic"]["xl710"]
1479         elif "x553" in test_name:
1480             limit = plot["limits"]["nic"]["x553"]
1481         else:
1482             limit = 0
1483         if limit > nic_limit:
1484             nic_limit = limit
1485
1486         mul = 2 if "ge2p" in test_name else 1
1487         if "10ge" in test_name:
1488             limit = plot["limits"]["link"]["10ge"] * mul
1489         elif "25ge" in test_name:
1490             limit = plot["limits"]["link"]["25ge"] * mul
1491         elif "40ge" in test_name:
1492             limit = plot["limits"]["link"]["40ge"] * mul
1493         elif "100ge" in test_name:
1494             limit = plot["limits"]["link"]["100ge"] * mul
1495         else:
1496             limit = 0
1497         if limit > lnk_limit:
1498             lnk_limit = limit
1499
1500     # Sort the tests
1501     order = plot.get("sort", None)
1502     if order and y_tags:
1503         y_sorted = OrderedDict()
1504         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
1505         for tag in order:
1506             for test, tags in y_tags_l.items():
1507                 if tag.lower() in tags:
1508                     name = re.sub(REGEX_NIC, "",
1509                                   test.replace('-ndrpdr', '').
1510                                   replace('2n1l-', ''))
1511                     try:
1512                         y_sorted[name] = vals.pop(name)
1513                         y_tags_l.pop(test)
1514                     except KeyError as err:
1515                         logging.error("Not found: {0}".format(err))
1516                     finally:
1517                         break
1518     else:
1519         y_sorted = vals
1520
1521     traces = list()
1522     annotations = list()
1523     x_vals = [1, 2, 4]
1524
1525     # Limits:
1526     try:
1527         threshold = 1.1 * max(y_max)  # 10%
1528     except ValueError as err:
1529         logging.error(err)
1530         return
1531     nic_limit /= 1000000.0
1532     # if nic_limit < threshold:
1533     traces.append(plgo.Scatter(
1534         x=x_vals,
1535         y=[nic_limit, ] * len(x_vals),
1536         name="NIC: {0:.2f}Mpps".format(nic_limit),
1537         showlegend=False,
1538         mode="lines",
1539         line=dict(
1540             dash="dot",
1541             color=COLORS[-1],
1542             width=1),
1543         hoverinfo="none"
1544     ))
1545     annotations.append(dict(
1546         x=1,
1547         y=nic_limit,
1548         xref="x",
1549         yref="y",
1550         xanchor="left",
1551         yanchor="bottom",
1552         text="NIC: {0:.2f}Mpps".format(nic_limit),
1553         font=dict(
1554             size=14,
1555             color=COLORS[-1],
1556         ),
1557         align="left",
1558         showarrow=False
1559     ))
1560     # y_max.append(int((nic_limit / 10) + 1) * 10)
1561     y_max.append(nic_limit)
1562
1563     lnk_limit /= 1000000.0
1564     if lnk_limit < threshold:
1565         traces.append(plgo.Scatter(
1566             x=x_vals,
1567             y=[lnk_limit, ] * len(x_vals),
1568             name="Link: {0:.2f}Mpps".format(lnk_limit),
1569             showlegend=False,
1570             mode="lines",
1571             line=dict(
1572                 dash="dot",
1573                 color=COLORS[-2],
1574                 width=1),
1575             hoverinfo="none"
1576         ))
1577         annotations.append(dict(
1578             x=1,
1579             y=lnk_limit,
1580             xref="x",
1581             yref="y",
1582             xanchor="left",
1583             yanchor="bottom",
1584             text="Link: {0:.2f}Mpps".format(lnk_limit),
1585             font=dict(
1586                 size=14,
1587                 color=COLORS[-2],
1588             ),
1589             align="left",
1590             showarrow=False
1591         ))
1592         # y_max.append(int((lnk_limit / 10) + 1) * 10)
1593         y_max.append(lnk_limit)
1594
1595     pci_limit /= 1000000.0
1596     if (pci_limit < threshold and
1597         (pci_limit < lnk_limit * 0.95 or lnk_limit > lnk_limit * 1.05)):
1598         traces.append(plgo.Scatter(
1599             x=x_vals,
1600             y=[pci_limit, ] * len(x_vals),
1601             name="PCIe: {0:.2f}Mpps".format(pci_limit),
1602             showlegend=False,
1603             mode="lines",
1604             line=dict(
1605                 dash="dot",
1606                 color=COLORS[-3],
1607                 width=1),
1608             hoverinfo="none"
1609         ))
1610         annotations.append(dict(
1611             x=1,
1612             y=pci_limit,
1613             xref="x",
1614             yref="y",
1615             xanchor="left",
1616             yanchor="bottom",
1617             text="PCIe: {0:.2f}Mpps".format(pci_limit),
1618             font=dict(
1619                 size=14,
1620                 color=COLORS[-3],
1621             ),
1622             align="left",
1623             showarrow=False
1624         ))
1625         # y_max.append(int((pci_limit / 10) + 1) * 10)
1626         y_max.append(pci_limit)
1627
1628     # Perfect and measured:
1629     cidx = 0
1630     for name, val in y_sorted.iteritems():
1631         hovertext = list()
1632         try:
1633             for idx in range(len(val["val"])):
1634                 htext = ""
1635                 if isinstance(val["val"][idx], float):
1636                     htext += "No. of Runs: {1}<br>" \
1637                              "Mean: {0:.2f}Mpps<br>".format(val["val"][idx],
1638                                                             val["count"][idx])
1639                 if isinstance(val["diff"][idx], float):
1640                     htext += "Diff: {0:.0f}%<br>".format(round(val["diff"][idx]))
1641                 if isinstance(val["rel"][idx], float):
1642                     htext += "Speedup: {0:.2f}".format(val["rel"][idx])
1643                 hovertext.append(htext)
1644             traces.append(plgo.Scatter(x=x_vals,
1645                                        y=val["val"],
1646                                        name=name,
1647                                        legendgroup=name,
1648                                        mode="lines+markers",
1649                                        line=dict(
1650                                            color=COLORS[cidx],
1651                                            width=2),
1652                                        marker=dict(
1653                                            symbol="circle",
1654                                            size=10
1655                                        ),
1656                                        text=hovertext,
1657                                        hoverinfo="text+name"
1658                                        ))
1659             traces.append(plgo.Scatter(x=x_vals,
1660                                        y=val["ideal"],
1661                                        name="{0} perfect".format(name),
1662                                        legendgroup=name,
1663                                        showlegend=False,
1664                                        mode="lines",
1665                                        line=dict(
1666                                            color=COLORS[cidx],
1667                                            width=2,
1668                                            dash="dash"),
1669                                        text=["Perfect: {0:.2f}Mpps".format(y)
1670                                              for y in val["ideal"]],
1671                                        hoverinfo="text"
1672                                        ))
1673             cidx += 1
1674         except (IndexError, ValueError, KeyError) as err:
1675             logging.warning("No data for '{0}'".format(name))
1676             logging.warning(repr(err))
1677
1678     try:
1679         # Create plot
1680         logging.info("    Writing file '{0}{1}'.".
1681                      format(plot["output-file"], plot["output-file-type"]))
1682         layout = deepcopy(plot["layout"])
1683         if layout.get("title", None):
1684             layout["title"] = "<b>Speedup Multi-core:</b> {0}". \
1685                 format(layout["title"])
1686         # layout["yaxis"]["range"] = [0, int((max(y_max) / 10) + 1) * 10]
1687         layout["yaxis"]["range"] = [0, int(max(y_max) * 1.1)]
1688         layout["annotations"].extend(annotations)
1689         plpl = plgo.Figure(data=traces, layout=layout)
1690
1691         # Export Plot
1692         ploff.plot(plpl,
1693                    show_link=False, auto_open=False,
1694                    filename='{0}{1}'.format(plot["output-file"],
1695                                             plot["output-file-type"]))
1696     except PlotlyError as err:
1697         logging.error("   Finished with error: {}".
1698                       format(str(err).replace("\n", " ")))
1699         return
1700
1701
1702 def plot_http_server_performance_box(plot, input_data):
1703     """Generate the plot(s) with algorithm: plot_http_server_performance_box
1704     specified in the specification file.
1705
1706     :param plot: Plot to generate.
1707     :param input_data: Data to process.
1708     :type plot: pandas.Series
1709     :type input_data: InputData
1710     """
1711
1712     # Transform the data
1713     logging.info("    Creating the data set for the {0} '{1}'.".
1714                  format(plot.get("type", ""), plot.get("title", "")))
1715     data = input_data.filter_data(plot)
1716     if data is None:
1717         logging.error("No data.")
1718         return
1719
1720     # Prepare the data for the plot
1721     y_vals = dict()
1722     for job in data:
1723         for build in job:
1724             for test in build:
1725                 if y_vals.get(test["name"], None) is None:
1726                     y_vals[test["name"]] = list()
1727                 try:
1728                     y_vals[test["name"]].append(test["result"])
1729                 except (KeyError, TypeError):
1730                     y_vals[test["name"]].append(None)
1731
1732     # Add None to the lists with missing data
1733     max_len = 0
1734     nr_of_samples = list()
1735     for val in y_vals.values():
1736         if len(val) > max_len:
1737             max_len = len(val)
1738         nr_of_samples.append(len(val))
1739     for key, val in y_vals.items():
1740         if len(val) < max_len:
1741             val.extend([None for _ in range(max_len - len(val))])
1742
1743     # Add plot traces
1744     traces = list()
1745     df = pd.DataFrame(y_vals)
1746     df.head()
1747     for i, col in enumerate(df.columns):
1748         name = "{nr}. ({samples:02d} run{plural}) {name}".\
1749             format(nr=(i + 1),
1750                    samples=nr_of_samples[i],
1751                    plural='s' if nr_of_samples[i] > 1 else '',
1752                    name=col.lower().replace('-ndrpdr', ''))
1753         if len(name) > 50:
1754             name_lst = name.split('-')
1755             name = ""
1756             split_name = True
1757             for segment in name_lst:
1758                 if (len(name) + len(segment) + 1) > 50 and split_name:
1759                     name += "<br>    "
1760                     split_name = False
1761                 name += segment + '-'
1762             name = name[:-1]
1763
1764         traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
1765                                y=df[col],
1766                                name=name,
1767                                **plot["traces"]))
1768     try:
1769         # Create plot
1770         plpl = plgo.Figure(data=traces, layout=plot["layout"])
1771
1772         # Export Plot
1773         logging.info("    Writing file '{0}{1}'.".
1774                      format(plot["output-file"], plot["output-file-type"]))
1775         ploff.plot(plpl, show_link=False, auto_open=False,
1776                    filename='{0}{1}'.format(plot["output-file"],
1777                                             plot["output-file-type"]))
1778     except PlotlyError as err:
1779         logging.error("   Finished with error: {}".
1780                       format(str(err).replace("\n", " ")))
1781         return
1782
1783
1784 def plot_service_density_heatmap(plot, input_data):
1785     """Generate the plot(s) with algorithm: plot_service_density_heatmap
1786     specified in the specification file.
1787
1788     :param plot: Plot to generate.
1789     :param input_data: Data to process.
1790     :type plot: pandas.Series
1791     :type input_data: InputData
1792     """
1793
1794     REGEX_CN = re.compile(r'^(\d*)R(\d*)C$')
1795     REGEX_TEST_NAME = re.compile(r'^.*-(\d+vhost|\d+memif)-'
1796                                  r'(\d+chain|\d+pipe)-'
1797                                  r'(\d+vm|\d+dcr|\d+drc).*$')
1798
1799     txt_chains = list()
1800     txt_nodes = list()
1801     vals = dict()
1802
1803     # Transform the data
1804     logging.info("    Creating the data set for the {0} '{1}'.".
1805                  format(plot.get("type", ""), plot.get("title", "")))
1806     data = input_data.filter_data(plot, continue_on_error=True)
1807     if data is None or data.empty:
1808         logging.error("No data.")
1809         return
1810
1811     for job in data:
1812         for build in job:
1813             for test in build:
1814                 for tag in test['tags']:
1815                     groups = re.search(REGEX_CN, tag)
1816                     if groups:
1817                         c = str(groups.group(1))
1818                         n = str(groups.group(2))
1819                         break
1820                 else:
1821                     continue
1822                 groups = re.search(REGEX_TEST_NAME, test["name"])
1823                 if groups and len(groups.groups()) == 3:
1824                     hover_name = "{vhost}-{chain}-{vm}".format(
1825                         vhost=str(groups.group(1)),
1826                         chain=str(groups.group(2)),
1827                         vm=str(groups.group(3)))
1828                 else:
1829                     hover_name = ""
1830                 if vals.get(c, None) is None:
1831                     vals[c] = dict()
1832                 if vals[c].get(n, None) is None:
1833                     vals[c][n] = dict(name=hover_name,
1834                                       vals=list(),
1835                                       nr=None,
1836                                       mean=None,
1837                                       stdev=None)
1838                 try:
1839                     if plot["include-tests"] == "MRR":
1840                         result = test["result"]["receive-rate"].avg
1841                     elif plot["include-tests"] == "PDR":
1842                         result = test["throughput"]["PDR"]["LOWER"]
1843                     elif plot["include-tests"] == "NDR":
1844                         result = test["throughput"]["NDR"]["LOWER"]
1845                     else:
1846                         result = None
1847                 except TypeError:
1848                     result = None
1849
1850                 if result:
1851                     vals[c][n]["vals"].append(result)
1852
1853     if not vals:
1854         logging.error("No data.")
1855         return
1856
1857     for key_c in vals.keys():
1858         txt_chains.append(key_c)
1859         for key_n in vals[key_c].keys():
1860             txt_nodes.append(key_n)
1861             if vals[key_c][key_n]["vals"]:
1862                 vals[key_c][key_n]["nr"] = len(vals[key_c][key_n]["vals"])
1863                 vals[key_c][key_n]["mean"] = \
1864                     round(mean(vals[key_c][key_n]["vals"]) / 1000000, 1)
1865                 vals[key_c][key_n]["stdev"] = \
1866                     round(stdev(vals[key_c][key_n]["vals"]) / 1000000, 1)
1867     txt_nodes = list(set(txt_nodes))
1868
1869     txt_chains = sorted(txt_chains, key=lambda chain: int(chain))
1870     txt_nodes = sorted(txt_nodes, key=lambda node: int(node))
1871
1872     chains = [i + 1 for i in range(len(txt_chains))]
1873     nodes = [i + 1 for i in range(len(txt_nodes))]
1874
1875     data = [list() for _ in range(len(chains))]
1876     for c in chains:
1877         for n in nodes:
1878             try:
1879                 val = vals[txt_chains[c - 1]][txt_nodes[n - 1]]["mean"]
1880             except (KeyError, IndexError):
1881                 val = None
1882             data[c - 1].append(val)
1883
1884     # Colorscales:
1885     my_green = [[0.0, 'rgb(235, 249, 242)'],
1886                 [1.0, 'rgb(45, 134, 89)']]
1887
1888     my_blue = [[0.0, 'rgb(236, 242, 248)'],
1889                [1.0, 'rgb(57, 115, 172)']]
1890
1891     my_grey = [[0.0, 'rgb(230, 230, 230)'],
1892                [1.0, 'rgb(102, 102, 102)']]
1893
1894     hovertext = list()
1895     annotations = list()
1896
1897     text = ("Test: {name}<br>"
1898             "Runs: {nr}<br>"
1899             "Thput: {val}<br>"
1900             "StDev: {stdev}")
1901
1902     for c in range(len(txt_chains)):
1903         hover_line = list()
1904         for n in range(len(txt_nodes)):
1905             if data[c][n] is not None:
1906                 annotations.append(dict(
1907                     x=n+1,
1908                     y=c+1,
1909                     xref="x",
1910                     yref="y",
1911                     xanchor="center",
1912                     yanchor="middle",
1913                     text=str(data[c][n]),
1914                     font=dict(
1915                         size=14,
1916                     ),
1917                     align="center",
1918                     showarrow=False
1919                 ))
1920                 hover_line.append(text.format(
1921                     name=vals[txt_chains[c]][txt_nodes[n]]["name"],
1922                     nr=vals[txt_chains[c]][txt_nodes[n]]["nr"],
1923                     val=data[c][n],
1924                     stdev=vals[txt_chains[c]][txt_nodes[n]]["stdev"]))
1925         hovertext.append(hover_line)
1926
1927     traces = [
1928         plgo.Heatmap(x=nodes,
1929                      y=chains,
1930                      z=data,
1931                      colorbar=dict(
1932                          title=plot.get("z-axis", ""),
1933                          titleside="right",
1934                          titlefont=dict(
1935                             size=16
1936                          ),
1937                          tickfont=dict(
1938                              size=16,
1939                          ),
1940                          tickformat=".1f",
1941                          yanchor="bottom",
1942                          y=-0.02,
1943                          len=0.925,
1944                      ),
1945                      showscale=True,
1946                      colorscale=my_green,
1947                      text=hovertext,
1948                      hoverinfo="text")
1949     ]
1950
1951     for idx, item in enumerate(txt_nodes):
1952         # X-axis, numbers:
1953         annotations.append(dict(
1954             x=idx+1,
1955             y=0.05,
1956             xref="x",
1957             yref="y",
1958             xanchor="center",
1959             yanchor="top",
1960             text=item,
1961             font=dict(
1962                 size=16,
1963             ),
1964             align="center",
1965             showarrow=False
1966         ))
1967     for idx, item in enumerate(txt_chains):
1968         # Y-axis, numbers:
1969         annotations.append(dict(
1970             x=0.35,
1971             y=idx+1,
1972             xref="x",
1973             yref="y",
1974             xanchor="right",
1975             yanchor="middle",
1976             text=item,
1977             font=dict(
1978                 size=16,
1979             ),
1980             align="center",
1981             showarrow=False
1982         ))
1983     # X-axis, title:
1984     annotations.append(dict(
1985         x=0.55,
1986         y=-0.15,
1987         xref="paper",
1988         yref="y",
1989         xanchor="center",
1990         yanchor="bottom",
1991         text=plot.get("x-axis", ""),
1992         font=dict(
1993             size=16,
1994         ),
1995         align="center",
1996         showarrow=False
1997     ))
1998     # Y-axis, title:
1999     annotations.append(dict(
2000         x=-0.1,
2001         y=0.5,
2002         xref="x",
2003         yref="paper",
2004         xanchor="center",
2005         yanchor="middle",
2006         text=plot.get("y-axis", ""),
2007         font=dict(
2008             size=16,
2009         ),
2010         align="center",
2011         textangle=270,
2012         showarrow=False
2013     ))
2014     updatemenus = list([
2015         dict(
2016             x=1.0,
2017             y=0.0,
2018             xanchor='right',
2019             yanchor='bottom',
2020             direction='up',
2021             buttons=list([
2022                 dict(
2023                     args=[{"colorscale": [my_green, ], "reversescale": False}],
2024                     label="Green",
2025                     method="update"
2026                 ),
2027                 dict(
2028                     args=[{"colorscale": [my_blue, ], "reversescale": False}],
2029                     label="Blue",
2030                     method="update"
2031                 ),
2032                 dict(
2033                     args=[{"colorscale": [my_grey, ], "reversescale": False}],
2034                     label="Grey",
2035                     method="update"
2036                 )
2037             ])
2038         )
2039     ])
2040
2041     try:
2042         layout = deepcopy(plot["layout"])
2043     except KeyError as err:
2044         logging.error("Finished with error: No layout defined")
2045         logging.error(repr(err))
2046         return
2047
2048     layout["annotations"] = annotations
2049     layout['updatemenus'] = updatemenus
2050
2051     try:
2052         # Create plot
2053         plpl = plgo.Figure(data=traces, layout=layout)
2054
2055         # Export Plot
2056         logging.info("    Writing file '{0}{1}'.".
2057                      format(plot["output-file"], plot["output-file-type"]))
2058         ploff.plot(plpl, show_link=False, auto_open=False,
2059                    filename='{0}{1}'.format(plot["output-file"],
2060                                             plot["output-file-type"]))
2061     except PlotlyError as err:
2062         logging.error("   Finished with error: {}".
2063                       format(str(err).replace("\n", " ")))
2064         return
2065
2066
2067 def plot_service_density_heatmap_compare(plot, input_data):
2068     """Generate the plot(s) with algorithm: plot_service_density_heatmap_compare
2069     specified in the specification file.
2070
2071     :param plot: Plot to generate.
2072     :param input_data: Data to process.
2073     :type plot: pandas.Series
2074     :type input_data: InputData
2075     """
2076
2077     REGEX_CN = re.compile(r'^(\d*)R(\d*)C$')
2078     REGEX_TEST_NAME = re.compile(r'^.*-(\d+ch|\d+pl)-'
2079                                  r'(\d+vh|\d+mif)-'
2080                                  r'(\d+vm|\d+dcr).*$')
2081     REGEX_THREADS = re.compile(r'^(\d+)(VM|DCR)(\d+)T$')
2082
2083     txt_chains = list()
2084     txt_nodes = list()
2085     vals = dict()
2086
2087     # Transform the data
2088     logging.info("    Creating the data set for the {0} '{1}'.".
2089                  format(plot.get("type", ""), plot.get("title", "")))
2090     data = input_data.filter_data(plot, continue_on_error=True)
2091     if data is None or data.empty:
2092         logging.error("No data.")
2093         return
2094
2095     for job in data:
2096         for build in job:
2097             for test in build:
2098                 for tag in test['tags']:
2099                     groups = re.search(REGEX_CN, tag)
2100                     if groups:
2101                         c = str(groups.group(1))
2102                         n = str(groups.group(2))
2103                         break
2104                 else:
2105                     continue
2106                 groups = re.search(REGEX_TEST_NAME, test["name"])
2107                 if groups and len(groups.groups()) == 3:
2108                     hover_name = "{chain}-{vhost}-{vm}".format(
2109                         chain=str(groups.group(1)),
2110                         vhost=str(groups.group(2)),
2111                         vm=str(groups.group(3)))
2112                 else:
2113                     hover_name = ""
2114                 if vals.get(c, None) is None:
2115                     vals[c] = dict()
2116                 if vals[c].get(n, None) is None:
2117                     vals[c][n] = dict(name=hover_name,
2118                                       vals_r=list(),
2119                                       vals_c=list(),
2120                                       nr_r=None,
2121                                       nr_c=None,
2122                                       mean_r=None,
2123                                       mean_c=None,
2124                                       stdev_r=None,
2125                                       stdev_c=None)
2126                 try:
2127                     if plot["include-tests"] == "MRR":
2128                         result = test["result"]["receive-rate"].avg
2129                     elif plot["include-tests"] == "PDR":
2130                         result = test["throughput"]["PDR"]["LOWER"]
2131                     elif plot["include-tests"] == "NDR":
2132                         result = test["throughput"]["NDR"]["LOWER"]
2133                     else:
2134                         result = None
2135                 except TypeError:
2136                     result = None
2137
2138                 if result:
2139                     for tag in test['tags']:
2140                         groups = re.search(REGEX_THREADS, tag)
2141                         if groups and len(groups.groups()) == 3:
2142                             if str(groups.group(3)) == \
2143                                     plot["reference"]["include"]:
2144                                 vals[c][n]["vals_r"].append(result)
2145                             elif str(groups.group(3)) == \
2146                                     plot["compare"]["include"]:
2147                                 vals[c][n]["vals_c"].append(result)
2148                             break
2149     if not vals:
2150         logging.error("No data.")
2151         return
2152
2153     for key_c in vals.keys():
2154         txt_chains.append(key_c)
2155         for key_n in vals[key_c].keys():
2156             txt_nodes.append(key_n)
2157             if vals[key_c][key_n]["vals_r"]:
2158                 vals[key_c][key_n]["nr_r"] = len(vals[key_c][key_n]["vals_r"])
2159                 vals[key_c][key_n]["mean_r"] = \
2160                     mean(vals[key_c][key_n]["vals_r"])
2161                 vals[key_c][key_n]["stdev_r"] = \
2162                     round(stdev(vals[key_c][key_n]["vals_r"]) / 1000000, 1)
2163             if vals[key_c][key_n]["vals_c"]:
2164                 vals[key_c][key_n]["nr_c"] = len(vals[key_c][key_n]["vals_c"])
2165                 vals[key_c][key_n]["mean_c"] = \
2166                     mean(vals[key_c][key_n]["vals_c"])
2167                 vals[key_c][key_n]["stdev_c"] = \
2168                     round(stdev(vals[key_c][key_n]["vals_c"]) / 1000000, 1)
2169
2170     txt_nodes = list(set(txt_nodes))
2171
2172     txt_chains = sorted(txt_chains, key=lambda chain: int(chain))
2173     txt_nodes = sorted(txt_nodes, key=lambda node: int(node))
2174
2175     chains = [i + 1 for i in range(len(txt_chains))]
2176     nodes = [i + 1 for i in range(len(txt_nodes))]
2177
2178     data_r = [list() for _ in range(len(chains))]
2179     data_c = [list() for _ in range(len(chains))]
2180     diff = [list() for _ in range(len(chains))]
2181     for c in chains:
2182         for n in nodes:
2183             try:
2184                 val_r = vals[txt_chains[c - 1]][txt_nodes[n - 1]]["mean_r"]
2185             except (KeyError, IndexError):
2186                 val_r = None
2187             try:
2188                 val_c = vals[txt_chains[c - 1]][txt_nodes[n - 1]]["mean_c"]
2189             except (KeyError, IndexError):
2190                 val_c = None
2191             if val_c is not None and val_r:
2192                 val_d = (val_c - val_r) * 100 / val_r
2193             else:
2194                 val_d = None
2195
2196             if val_r is not None:
2197                 val_r = round(val_r / 1000000, 1)
2198             data_r[c - 1].append(val_r)
2199             if val_c is not None:
2200                 val_c = round(val_c / 1000000, 1)
2201             data_c[c - 1].append(val_c)
2202             if val_d is not None:
2203                 val_d = int(round(val_d, 0))
2204             diff[c - 1].append(val_d)
2205
2206     # Colorscales:
2207     my_green = [[0.0, 'rgb(235, 249, 242)'],
2208                 [1.0, 'rgb(45, 134, 89)']]
2209
2210     my_blue = [[0.0, 'rgb(236, 242, 248)'],
2211                [1.0, 'rgb(57, 115, 172)']]
2212
2213     my_grey = [[0.0, 'rgb(230, 230, 230)'],
2214                [1.0, 'rgb(102, 102, 102)']]
2215
2216     hovertext = list()
2217
2218     annotations = list()
2219     annotations_r = list()
2220     annotations_c = list()
2221     annotations_diff = list()
2222
2223     text = ("Test: {name}"
2224             "<br>{title_r}: {text_r}"
2225             "<br>{title_c}: {text_c}{text_diff}")
2226     text_r = "Thput: {val_r}; StDev: {stdev_r}; Runs: {nr_r}"
2227     text_c = "Thput: {val_c}; StDev: {stdev_c}; Runs: {nr_c}"
2228     text_diff = "<br>Relative Difference {title_c} vs. {title_r}: {diff}%"
2229
2230     for c in range(len(txt_chains)):
2231         hover_line = list()
2232         for n in range(len(txt_nodes)):
2233             point = dict(
2234                 x=n + 1,
2235                 y=c + 1,
2236                 xref="x",
2237                 yref="y",
2238                 xanchor="center",
2239                 yanchor="middle",
2240                 text="",
2241                 font=dict(
2242                     size=14,
2243                 ),
2244                 align="center",
2245                 showarrow=False
2246             )
2247
2248             point_text_r = "Not present"
2249             point_text_c = "Not present"
2250             point_text_diff = ""
2251             try:
2252                 point_r = data_r[c][n]
2253                 if point_r is not None:
2254                     point_text_r = text_r.format(
2255                         val_r=point_r,
2256                         stdev_r=vals[txt_chains[c]][txt_nodes[n]]["stdev_r"],
2257                         nr_r=vals[txt_chains[c]][txt_nodes[n]]["nr_r"])
2258             except KeyError:
2259                 point_r = None
2260             point["text"] = "" if point_r is None else point_r
2261             annotations_r.append(deepcopy(point))
2262
2263             try:
2264                 point_c = data_c[c][n]
2265                 if point_c is not None:
2266                     point_text_c = text_c.format(
2267                         val_c=point_c,
2268                         stdev_c=vals[txt_chains[c]][txt_nodes[n]]["stdev_c"],
2269                         nr_c=vals[txt_chains[c]][txt_nodes[n]]["nr_c"])
2270             except KeyError:
2271                 point_c = None
2272             point["text"] = "" if point_c is None else point_c
2273             annotations_c.append(deepcopy(point))
2274
2275             try:
2276                 point_d = diff[c][n]
2277                 if point_d is not None:
2278                     point_text_diff = text_diff.format(
2279                         title_r=plot["reference"]["name"],
2280                         title_c=plot["compare"]["name"],
2281                         diff=point_d)
2282             except KeyError:
2283                 point_d = None
2284             point["text"] = "" if point_d is None else point_d
2285             annotations_diff.append(deepcopy(point))
2286
2287             try:
2288                 name = vals[txt_chains[c]][txt_nodes[n]]["name"]
2289             except KeyError:
2290                 continue
2291
2292             hover_line.append(text.format(
2293                 name=name,
2294                 title_r=plot["reference"]["name"],
2295                 text_r=point_text_r,
2296                 title_c=plot["compare"]["name"],
2297                 text_c=point_text_c,
2298                 text_diff=point_text_diff
2299             ))
2300
2301         hovertext.append(hover_line)
2302
2303     traces = [
2304         plgo.Heatmap(x=nodes,
2305                      y=chains,
2306                      z=data_r,
2307                      visible=True,
2308                      colorbar=dict(
2309                          title=plot.get("z-axis", ""),
2310                          titleside="right",
2311                          titlefont=dict(
2312                             size=16
2313                          ),
2314                          tickfont=dict(
2315                              size=16,
2316                          ),
2317                          tickformat=".1f",
2318                          yanchor="bottom",
2319                          y=-0.02,
2320                          len=0.925,
2321                      ),
2322                      showscale=True,
2323                      colorscale=my_green,
2324                      reversescale=False,
2325                      text=hovertext,
2326                      hoverinfo="text"),
2327         plgo.Heatmap(x=nodes,
2328                      y=chains,
2329                      z=data_c,
2330                      visible=False,
2331                      colorbar=dict(
2332                          title=plot.get("z-axis", ""),
2333                          titleside="right",
2334                          titlefont=dict(
2335                              size=16
2336                          ),
2337                          tickfont=dict(
2338                              size=16,
2339                          ),
2340                          tickformat=".1f",
2341                          yanchor="bottom",
2342                          y=-0.02,
2343                          len=0.925,
2344                      ),
2345                      showscale=True,
2346                      colorscale=my_blue,
2347                      reversescale=False,
2348                      text=hovertext,
2349                      hoverinfo="text"),
2350         plgo.Heatmap(x=nodes,
2351                      y=chains,
2352                      z=diff,
2353                      name="Diff",
2354                      visible=False,
2355                      colorbar=dict(
2356                          title="Relative Difference {name_c} vs. {name_r} [%]".
2357                              format(name_c=plot["compare"]["name"],
2358                                     name_r=plot["reference"]["name"]),
2359                          titleside="right",
2360                          titlefont=dict(
2361                              size=16
2362                          ),
2363                          tickfont=dict(
2364                              size=16,
2365                          ),
2366                          tickformat=".1f",
2367                          yanchor="bottom",
2368                          y=-0.02,
2369                          len=0.925,
2370                      ),
2371                      showscale=True,
2372                      colorscale=my_grey,
2373                      reversescale=False,
2374                      text=hovertext,
2375                      hoverinfo="text")
2376     ]
2377
2378     for idx, item in enumerate(txt_nodes):
2379         # X-axis, numbers:
2380         annotations.append(dict(
2381             x=idx+1,
2382             y=0.05,
2383             xref="x",
2384             yref="y",
2385             xanchor="center",
2386             yanchor="top",
2387             text=item,
2388             font=dict(
2389                 size=16,
2390             ),
2391             align="center",
2392             showarrow=False
2393         ))
2394     for idx, item in enumerate(txt_chains):
2395         # Y-axis, numbers:
2396         annotations.append(dict(
2397             x=0.35,
2398             y=idx+1,
2399             xref="x",
2400             yref="y",
2401             xanchor="right",
2402             yanchor="middle",
2403             text=item,
2404             font=dict(
2405                 size=16,
2406             ),
2407             align="center",
2408             showarrow=False
2409         ))
2410     # X-axis, title:
2411     annotations.append(dict(
2412         x=0.55,
2413         y=-0.15,
2414         xref="paper",
2415         yref="y",
2416         xanchor="center",
2417         yanchor="bottom",
2418         text=plot.get("x-axis", ""),
2419         font=dict(
2420             size=16,
2421         ),
2422         align="center",
2423         showarrow=False
2424     ))
2425     # Y-axis, title:
2426     annotations.append(dict(
2427         x=-0.1,
2428         y=0.5,
2429         xref="x",
2430         yref="paper",
2431         xanchor="center",
2432         yanchor="middle",
2433         text=plot.get("y-axis", ""),
2434         font=dict(
2435             size=16,
2436         ),
2437         align="center",
2438         textangle=270,
2439         showarrow=False
2440     ))
2441     updatemenus = list([
2442         dict(
2443             active=0,
2444             x=1.0,
2445             y=0.0,
2446             xanchor='right',
2447             yanchor='bottom',
2448             direction='up',
2449             buttons=list([
2450                 dict(
2451                     label=plot["reference"]["name"],
2452                     method="update",
2453                     args=[
2454                         {
2455                             "visible": [True, False, False]
2456                         },
2457                         {
2458                             "colorscale": [my_green, ],
2459                             "reversescale": False,
2460                             "annotations": annotations + annotations_r,
2461                         },
2462                     ]
2463                 ),
2464                 dict(
2465                     label=plot["compare"]["name"],
2466                     method="update",
2467                     args=[
2468                         {
2469                             "visible": [False, True, False]
2470                         },
2471                         {
2472                             "colorscale": [my_blue, ],
2473                             "reversescale": False,
2474                             "annotations": annotations + annotations_c,
2475                         },
2476                     ]
2477                 ),
2478                 dict(
2479                     label="Diff",
2480                     method="update",
2481                     args=[
2482                         {
2483                             "visible": [False, False, True]
2484                         },
2485                         {
2486                             "colorscale": [my_grey, ],
2487                             "reversescale": False,
2488                             "annotations": annotations + annotations_diff,
2489                         },
2490                     ]
2491                 ),
2492             ])
2493         )
2494     ])
2495
2496     try:
2497         layout = deepcopy(plot["layout"])
2498     except KeyError as err:
2499         logging.error("Finished with error: No layout defined")
2500         logging.error(repr(err))
2501         return
2502
2503     layout["annotations"] = annotations + annotations_r
2504     layout['updatemenus'] = updatemenus
2505
2506     try:
2507         # Create plot
2508         plpl = plgo.Figure(data=traces, layout=layout)
2509
2510         # Export Plot
2511         logging.info("    Writing file '{0}{1}'.".
2512                      format(plot["output-file"], plot["output-file-type"]))
2513         ploff.plot(plpl, show_link=False, auto_open=False,
2514                    filename='{0}{1}'.format(plot["output-file"],
2515                                             plot["output-file-type"]))
2516     except PlotlyError as err:
2517         logging.error("   Finished with error: {}".
2518                       format(str(err).replace("\n", " ")))
2519         return