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