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