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