51b29407c3c6e1a81327ba5261f325d696ab9ae7
[csit.git] / resources / tools / presentation / generator_plots.py
1 # Copyright (c) 2018 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 logging
19 import pandas as pd
20 import plotly.offline as ploff
21 import plotly.graph_objs as plgo
22
23 from plotly.exceptions import PlotlyError
24 from collections import OrderedDict
25 from copy import deepcopy
26
27 from utils import mean
28
29
30 COLORS = ["SkyBlue", "Olive", "Purple", "Coral", "Indigo", "Pink",
31           "Chocolate", "Brown", "Magenta", "Cyan", "Orange", "Black",
32           "Violet", "Blue", "Yellow", "BurlyWood", "CadetBlue", "Crimson",
33           "DarkBlue", "DarkCyan", "DarkGreen", "Green", "GoldenRod",
34           "LightGreen", "LightSeaGreen", "LightSkyBlue", "Maroon",
35           "MediumSeaGreen", "SeaGreen", "LightSlateGrey"]
36
37
38 def generate_plots(spec, data):
39     """Generate all plots specified in the specification file.
40
41     :param spec: Specification read from the specification file.
42     :param data: Data to process.
43     :type spec: Specification
44     :type data: InputData
45     """
46
47     logging.info("Generating the plots ...")
48     for index, plot in enumerate(spec.plots):
49         try:
50             logging.info("  Plot nr {0}: {1}".format(index + 1,
51                                                      plot.get("title", "")))
52             plot["limits"] = spec.configuration["limits"]
53             eval(plot["algorithm"])(plot, data)
54             logging.info("  Done.")
55         except NameError as err:
56             logging.error("Probably algorithm '{alg}' is not defined: {err}".
57                           format(alg=plot["algorithm"], err=repr(err)))
58     logging.info("Done.")
59
60
61 def plot_performance_box(plot, input_data):
62     """Generate the plot(s) with algorithm: plot_performance_box
63     specified in the specification file.
64
65     :param plot: Plot to generate.
66     :param input_data: Data to process.
67     :type plot: pandas.Series
68     :type input_data: InputData
69     """
70
71     # Transform the data
72     plot_title = plot.get("title", "")
73     logging.info("    Creating the data set for the {0} '{1}'.".
74                  format(plot.get("type", ""), plot_title))
75     data = input_data.filter_data(plot)
76     if data is None:
77         logging.error("No data.")
78         return
79
80     # Prepare the data for the plot
81     y_vals = dict()
82     y_tags = dict()
83     for job in data:
84         for build in job:
85             for test in build:
86                 if y_vals.get(test["parent"], None) is None:
87                     y_vals[test["parent"]] = list()
88                     y_tags[test["parent"]] = test.get("tags", None)
89                 try:
90                     if test["type"] in ("NDRPDR", ):
91                         if "-pdr" in plot_title.lower():
92                             y_vals[test["parent"]].\
93                                 append(test["throughput"]["PDR"]["LOWER"])
94                         elif "-ndr" in plot_title.lower():
95                             y_vals[test["parent"]]. \
96                                 append(test["throughput"]["NDR"]["LOWER"])
97                         else:
98                             continue
99                     else:
100                         continue
101                 except (KeyError, TypeError):
102                     y_vals[test["parent"]].append(None)
103
104     # Sort the tests
105     order = plot.get("sort", None)
106     if order and y_tags:
107         y_sorted = OrderedDict()
108         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
109         for tag in order:
110             logging.info(tag)
111             for suite, tags in y_tags_l.items():
112                 if "not " in tag:
113                     tag = tag.split(" ")[-1]
114                     if tag.lower() in tags:
115                         continue
116                 else:
117                     if tag.lower() not in tags:
118                         continue
119                 try:
120                     y_sorted[suite] = y_vals.pop(suite)
121                     y_tags_l.pop(suite)
122                     logging.info(suite)
123                 except KeyError as err:
124                     logging.error("Not found: {0}".format(err))
125                 finally:
126                     break
127     else:
128         y_sorted = y_vals
129
130     # Add None to the lists with missing data
131     max_len = 0
132     for val in y_sorted.values():
133         if len(val) > max_len:
134             max_len = len(val)
135     for key, val in y_sorted.items():
136         if len(val) < max_len:
137             val.extend([None for _ in range(max_len - len(val))])
138
139     # Add plot traces
140     traces = list()
141     df = pd.DataFrame(y_sorted)
142     df.head()
143     y_max = list()
144     for i, col in enumerate(df.columns):
145         name = "{0}. {1}".format(i + 1, col.lower().replace('-ndrpdrdisc', '').
146                                  replace('-ndrpdr', ''))
147         logging.info(name)
148         traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
149                                y=[y / 1000000 if y else None for y in df[col]],
150                                name=name,
151                                **plot["traces"]))
152         val_max = max(df[col])
153         if val_max:
154             y_max.append(int(val_max / 1000000) + 1)
155
156     try:
157         # Create plot
158         layout = deepcopy(plot["layout"])
159         if layout.get("title", None):
160             layout["title"] = "<b>Packet Throughput:</b> {0}". \
161                 format(layout["title"])
162         if y_max:
163             layout["yaxis"]["range"] = [0, max(y_max)]
164         plpl = plgo.Figure(data=traces, layout=layout)
165
166         # Export Plot
167         logging.info("    Writing file '{0}{1}'.".
168                      format(plot["output-file"], plot["output-file-type"]))
169         ploff.plot(plpl, show_link=False, auto_open=False,
170                    filename='{0}{1}'.format(plot["output-file"],
171                                             plot["output-file-type"]))
172     except PlotlyError as err:
173         logging.error("   Finished with error: {}".
174                       format(str(err).replace("\n", " ")))
175         return
176
177
178 def plot_latency_error_bars(plot, input_data):
179     """Generate the plot(s) with algorithm: plot_latency_error_bars
180     specified in the specification file.
181
182     :param plot: Plot to generate.
183     :param input_data: Data to process.
184     :type plot: pandas.Series
185     :type input_data: InputData
186     """
187
188     # Transform the data
189     plot_title = plot.get("title", "")
190     logging.info("    Creating the data set for the {0} '{1}'.".
191                  format(plot.get("type", ""), plot_title))
192     data = input_data.filter_data(plot)
193     if data is None:
194         logging.error("No data.")
195         return
196
197     # Prepare the data for the plot
198     y_tmp_vals = dict()
199     y_tags = dict()
200     for job in data:
201         for build in job:
202             for test in build:
203                 if y_tmp_vals.get(test["parent"], None) is None:
204                     y_tmp_vals[test["parent"]] = [
205                         list(),  # direction1, min
206                         list(),  # direction1, avg
207                         list(),  # direction1, max
208                         list(),  # direction2, min
209                         list(),  # direction2, avg
210                         list()   # direction2, max
211                     ]
212                     y_tags[test["parent"]] = test.get("tags", None)
213                 try:
214                     if test["type"] in ("NDRPDR", ):
215                         if "-pdr" in plot_title.lower():
216                             ttype = "PDR"
217                         elif "-ndr" in plot_title.lower():
218                             ttype = "NDR"
219                         else:
220                             continue
221                         y_tmp_vals[test["parent"]][0].append(
222                             test["latency"][ttype]["direction1"]["min"])
223                         y_tmp_vals[test["parent"]][1].append(
224                             test["latency"][ttype]["direction1"]["avg"])
225                         y_tmp_vals[test["parent"]][2].append(
226                             test["latency"][ttype]["direction1"]["max"])
227                         y_tmp_vals[test["parent"]][3].append(
228                             test["latency"][ttype]["direction2"]["min"])
229                         y_tmp_vals[test["parent"]][4].append(
230                             test["latency"][ttype]["direction2"]["avg"])
231                         y_tmp_vals[test["parent"]][5].append(
232                             test["latency"][ttype]["direction2"]["max"])
233                     else:
234                         continue
235                 except (KeyError, TypeError):
236                     pass
237
238     # Sort the tests
239     order = plot.get("sort", None)
240     if order and y_tags:
241         y_sorted = OrderedDict()
242         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
243         for tag in order:
244             for suite, tags in y_tags_l.items():
245                 if tag.lower() in tags:
246                     try:
247                         y_sorted[suite] = y_tmp_vals.pop(suite)
248                         y_tags_l.pop(suite)
249                     except KeyError as err:
250                         logging.error("Not found: {0}".format(err))
251                     finally:
252                         break
253     else:
254         y_sorted = y_tmp_vals
255
256     x_vals = list()
257     y_vals = list()
258     y_mins = list()
259     y_maxs = list()
260     for key, val in y_sorted.items():
261         key = "-".join(key.split("-")[1:-1])
262         x_vals.append(key)  # dir 1
263         y_vals.append(mean(val[1]) if val[1] else None)
264         y_mins.append(mean(val[0]) if val[0] else None)
265         y_maxs.append(mean(val[2]) if val[2] else None)
266         x_vals.append(key)  # dir 2
267         y_vals.append(mean(val[4]) if val[4] else None)
268         y_mins.append(mean(val[3]) if val[3] else None)
269         y_maxs.append(mean(val[5]) if val[5] else None)
270
271     traces = list()
272     annotations = list()
273
274     for idx in range(len(x_vals)):
275         if not bool(int(idx % 2)):
276             direction = "West - East"
277         else:
278             direction = "East - West"
279         hovertext = ("Test: {test}<br>"
280                      "Direction: {dir}<br>".format(test=x_vals[idx],
281                                                    dir=direction))
282         if isinstance(y_maxs[idx], float):
283             hovertext += "Max: {max:.2f}uSec<br>".format(max=y_maxs[idx])
284         if isinstance(y_vals[idx], float):
285             hovertext += "Avg: {avg:.2f}uSec<br>".format(avg=y_vals[idx])
286         if isinstance(y_mins[idx], float):
287             hovertext += "Min: {min:.2f}uSec".format(min=y_mins[idx])
288
289         if isinstance(y_maxs[idx], float) and isinstance(y_vals[idx], float):
290             array = [y_maxs[idx] - y_vals[idx], ]
291         else:
292             array = [None, ]
293         if isinstance(y_mins[idx], float) and isinstance(y_vals[idx], float):
294             arrayminus = [y_vals[idx] - y_mins[idx], ]
295         else:
296             arrayminus = [None, ]
297         traces.append(plgo.Scatter(
298             x=[idx, ],
299             y=[y_vals[idx], ],
300             name=x_vals[idx],
301             legendgroup=x_vals[idx],
302             showlegend=bool(int(idx % 2)),
303             mode="markers",
304             error_y=dict(
305                 type='data',
306                 symmetric=False,
307                 array=array,
308                 arrayminus=arrayminus,
309                 color=COLORS[int(idx / 2)]
310             ),
311             marker=dict(
312                 size=10,
313                 color=COLORS[int(idx / 2)],
314             ),
315             text=hovertext,
316             hoverinfo="text",
317         ))
318         annotations.append(dict(
319             x=idx,
320             y=0,
321             xref="x",
322             yref="y",
323             xanchor="center",
324             yanchor="top",
325             text="E-W" if bool(int(idx % 2)) else "W-E",
326             font=dict(
327                 size=16,
328             ),
329             align="center",
330             showarrow=False
331         ))
332
333     try:
334         # Create plot
335         logging.info("    Writing file '{0}{1}'.".
336                      format(plot["output-file"], plot["output-file-type"]))
337         layout = deepcopy(plot["layout"])
338         if layout.get("title", None):
339             layout["title"] = "<b>Packet Latency:</b> {0}".\
340                 format(layout["title"])
341         layout["annotations"] = annotations
342         plpl = plgo.Figure(data=traces, layout=layout)
343
344         # Export Plot
345         ploff.plot(plpl,
346                    show_link=False, auto_open=False,
347                    filename='{0}{1}'.format(plot["output-file"],
348                                             plot["output-file-type"]))
349     except PlotlyError as err:
350         logging.error("   Finished with error: {}".
351                       format(str(err).replace("\n", " ")))
352         return
353
354
355 def plot_throughput_speedup_analysis(plot, input_data):
356     """Generate the plot(s) with algorithm:
357     plot_throughput_speedup_analysis
358     specified in the specification file.
359
360     :param plot: Plot to generate.
361     :param input_data: Data to process.
362     :type plot: pandas.Series
363     :type input_data: InputData
364     """
365
366     # Transform the data
367     plot_title = plot.get("title", "")
368     logging.info("    Creating the data set for the {0} '{1}'.".
369                  format(plot.get("type", ""), plot_title))
370     data = input_data.filter_data(plot)
371     if data is None:
372         logging.error("No data.")
373         return
374
375     y_vals = dict()
376     y_tags = dict()
377     for job in data:
378         for build in job:
379             for test in build:
380                 if y_vals.get(test["parent"], None) is None:
381                     y_vals[test["parent"]] = {"1": list(),
382                                               "2": list(),
383                                               "4": list()}
384                     y_tags[test["parent"]] = test.get("tags", None)
385                 try:
386                     if test["type"] in ("NDRPDR",):
387                         if "-pdr" in plot_title.lower():
388                             ttype = "PDR"
389                         elif "-ndr" in plot_title.lower():
390                             ttype = "NDR"
391                         else:
392                             continue
393                         if "1C" in test["tags"]:
394                             y_vals[test["parent"]]["1"]. \
395                                 append(test["throughput"][ttype]["LOWER"])
396                         elif "2C" in test["tags"]:
397                             y_vals[test["parent"]]["2"]. \
398                                 append(test["throughput"][ttype]["LOWER"])
399                         elif "4C" in test["tags"]:
400                             y_vals[test["parent"]]["4"]. \
401                                 append(test["throughput"][ttype]["LOWER"])
402                 except (KeyError, TypeError):
403                     pass
404
405     if not y_vals:
406         logging.warning("No data for the plot '{}'".
407                         format(plot.get("title", "")))
408         return
409
410     y_1c_max = dict()
411     for test_name, test_vals in y_vals.items():
412         for key, test_val in test_vals.items():
413             if test_val:
414                 y_vals[test_name][key] = sum(test_val) / len(test_val)
415                 if key == "1":
416                     y_1c_max[test_name] = max(test_val) / 1000000.0
417
418     vals = dict()
419     y_max = list()
420     nic_limit = 0
421     lnk_limit = 0
422     pci_limit = plot["limits"]["pci"]["pci-g3-x8"]
423     for test_name, test_vals in y_vals.items():
424         if test_vals["1"]:
425             name = "-".join(test_name.split('-')[1:-1])
426
427             vals[name] = dict()
428             y_val_1 = test_vals["1"] / 1000000.0
429             y_val_2 = test_vals["2"] / 1000000.0 if test_vals["2"] else None
430             y_val_4 = test_vals["4"] / 1000000.0 if test_vals["4"] else None
431
432             vals[name]["val"] = [y_val_1, y_val_2, y_val_4]
433             vals[name]["rel"] = [1.0, None, None]
434             vals[name]["ideal"] = [y_1c_max[test_name],
435                                    y_1c_max[test_name] * 2,
436                                    y_1c_max[test_name] * 4]
437             vals[name]["diff"] = \
438                 [(y_val_1 - y_1c_max[test_name]) * 100 / y_val_1, None, None]
439
440             try:
441                 val_max = max(max(vals[name]["val"], vals[name]["ideal"]))
442             except ValueError as err:
443                 logging.error(err)
444                 continue
445             if val_max:
446                 y_max.append(int((val_max / 10) + 1) * 10)
447
448             if y_val_2:
449                 vals[name]["rel"][1] = round(y_val_2 / y_val_1, 2)
450                 vals[name]["diff"][1] = \
451                     (y_val_2 - vals[name]["ideal"][1]) * 100 / y_val_2
452             if y_val_4:
453                 vals[name]["rel"][2] = round(y_val_4 / y_val_1, 2)
454                 vals[name]["diff"][2] = \
455                     (y_val_4 - vals[name]["ideal"][2]) * 100 / y_val_4
456
457         # Limits:
458         if "x520" in test_name:
459             limit = plot["limits"]["nic"]["x520"]
460         elif "x710" in test_name:
461             limit = plot["limits"]["nic"]["x710"]
462         elif "xxv710" in test_name:
463             limit = plot["limits"]["nic"]["xxv710"]
464         elif "xl710" in test_name:
465             limit = plot["limits"]["nic"]["xl710"]
466         else:
467             limit = 0
468         if limit > nic_limit:
469             nic_limit = limit
470
471         mul = 2 if "ge2p" in test_name else 1
472         if "10ge" in test_name:
473             limit = plot["limits"]["link"]["10ge"] * mul
474         elif "25ge" in test_name:
475             limit = plot["limits"]["link"]["25ge"] * mul
476         elif "40ge" in test_name:
477             limit = plot["limits"]["link"]["40ge"] * mul
478         elif "100ge" in test_name:
479             limit = plot["limits"]["link"]["100ge"] * mul
480         else:
481             limit = 0
482         if limit > lnk_limit:
483             lnk_limit = limit
484
485     # Sort the tests
486     order = plot.get("sort", None)
487     if order and y_tags:
488         y_sorted = OrderedDict()
489         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
490         for tag in order:
491             for test, tags in y_tags_l.items():
492                 if tag.lower() in tags:
493                     name = "-".join(test.split('-')[1:-1])
494                     try:
495                         y_sorted[name] = vals.pop(name)
496                         y_tags_l.pop(test)
497                     except KeyError as err:
498                         logging.error("Not found: {0}".format(err))
499                     finally:
500                         break
501     else:
502         y_sorted = vals
503
504     traces = list()
505     annotations = list()
506     x_vals = [1, 2, 4]
507
508     # Limits:
509     try:
510         threshold = 1.1 * max(y_max)  # 10%
511     except ValueError as err:
512         logging.error(err)
513         return
514     nic_limit /= 1000000.0
515     if nic_limit < threshold:
516         traces.append(plgo.Scatter(
517             x=x_vals,
518             y=[nic_limit, ] * len(x_vals),
519             name="NIC: {0:.2f}Mpps".format(nic_limit),
520             showlegend=False,
521             mode="lines",
522             line=dict(
523                 dash="dot",
524                 color=COLORS[-1],
525                 width=1),
526             hoverinfo="none"
527         ))
528         annotations.append(dict(
529             x=1,
530             y=nic_limit,
531             xref="x",
532             yref="y",
533             xanchor="left",
534             yanchor="bottom",
535             text="NIC: {0:.2f}Mpps".format(nic_limit),
536             font=dict(
537                 size=14,
538                 color=COLORS[-1],
539             ),
540             align="left",
541             showarrow=False
542         ))
543         y_max.append(int((nic_limit / 10) + 1) * 10)
544
545     lnk_limit /= 1000000.0
546     if lnk_limit < threshold:
547         traces.append(plgo.Scatter(
548             x=x_vals,
549             y=[lnk_limit, ] * len(x_vals),
550             name="Link: {0:.2f}Mpps".format(lnk_limit),
551             showlegend=False,
552             mode="lines",
553             line=dict(
554                 dash="dot",
555                 color=COLORS[-2],
556                 width=1),
557             hoverinfo="none"
558         ))
559         annotations.append(dict(
560             x=1,
561             y=lnk_limit,
562             xref="x",
563             yref="y",
564             xanchor="left",
565             yanchor="bottom",
566             text="Link: {0:.2f}Mpps".format(lnk_limit),
567             font=dict(
568                 size=14,
569                 color=COLORS[-2],
570             ),
571             align="left",
572             showarrow=False
573         ))
574         y_max.append(int((lnk_limit / 10) + 1) * 10)
575
576     pci_limit /= 1000000.0
577     if pci_limit < threshold:
578         traces.append(plgo.Scatter(
579             x=x_vals,
580             y=[pci_limit, ] * len(x_vals),
581             name="PCIe: {0:.2f}Mpps".format(pci_limit),
582             showlegend=False,
583             mode="lines",
584             line=dict(
585                 dash="dot",
586                 color=COLORS[-3],
587                 width=1),
588             hoverinfo="none"
589         ))
590         annotations.append(dict(
591             x=1,
592             y=pci_limit,
593             xref="x",
594             yref="y",
595             xanchor="left",
596             yanchor="bottom",
597             text="PCIe: {0:.2f}Mpps".format(pci_limit),
598             font=dict(
599                 size=14,
600                 color=COLORS[-3],
601             ),
602             align="left",
603             showarrow=False
604         ))
605         y_max.append(int((pci_limit / 10) + 1) * 10)
606
607     # Perfect and measured:
608     cidx = 0
609     for name, val in y_sorted.iteritems():
610         hovertext = list()
611         for idx in range(len(val["val"])):
612             htext = ""
613             if isinstance(val["val"][idx], float):
614                 htext += "value: {0:.2f}Mpps<br>".format(val["val"][idx])
615             if isinstance(val["diff"][idx], float):
616                 htext += "diff: {0:.0f}%<br>".format(round(val["diff"][idx]))
617             if isinstance(val["rel"][idx], float):
618                 htext += "speedup: {0:.2f}".format(val["rel"][idx])
619             hovertext.append(htext)
620         traces.append(plgo.Scatter(x=x_vals,
621                                    y=val["val"],
622                                    name=name,
623                                    legendgroup=name,
624                                    mode="lines+markers",
625                                    line=dict(
626                                        color=COLORS[cidx],
627                                        width=2),
628                                    marker=dict(
629                                        symbol="circle",
630                                        size=10
631                                    ),
632                                    text=hovertext,
633                                    hoverinfo="text+name"
634                                    ))
635         traces.append(plgo.Scatter(x=x_vals,
636                                    y=val["ideal"],
637                                    name="{0} perfect".format(name),
638                                    legendgroup=name,
639                                    showlegend=False,
640                                    mode="lines",
641                                    line=dict(
642                                        color=COLORS[cidx],
643                                        width=2,
644                                        dash="dash"),
645                                    text=["perfect: {0:.2f}Mpps".format(y)
646                                          for y in val["ideal"]],
647                                    hoverinfo="text"
648                                    ))
649         cidx += 1
650
651     try:
652         # Create plot
653         logging.info("    Writing file '{0}{1}'.".
654                      format(plot["output-file"], plot["output-file-type"]))
655         layout = deepcopy(plot["layout"])
656         if layout.get("title", None):
657             layout["title"] = "<b>Speedup Multi-core:</b> {0}". \
658                 format(layout["title"])
659         layout["annotations"].extend(annotations)
660         plpl = plgo.Figure(data=traces, layout=layout)
661
662         # Export Plot
663         ploff.plot(plpl,
664                    show_link=False, auto_open=False,
665                    filename='{0}{1}'.format(plot["output-file"],
666                                             plot["output-file-type"]))
667     except PlotlyError as err:
668         logging.error("   Finished with error: {}".
669                       format(str(err).replace("\n", " ")))
670         return
671
672
673 def plot_http_server_performance_box(plot, input_data):
674     """Generate the plot(s) with algorithm: plot_http_server_performance_box
675     specified in the specification file.
676
677     :param plot: Plot to generate.
678     :param input_data: Data to process.
679     :type plot: pandas.Series
680     :type input_data: InputData
681     """
682
683     # Transform the data
684     logging.info("    Creating the data set for the {0} '{1}'.".
685                  format(plot.get("type", ""), plot.get("title", "")))
686     data = input_data.filter_data(plot)
687     if data is None:
688         logging.error("No data.")
689         return
690
691     # Prepare the data for the plot
692     y_vals = dict()
693     for job in data:
694         for build in job:
695             for test in build:
696                 if y_vals.get(test["name"], None) is None:
697                     y_vals[test["name"]] = list()
698                 try:
699                     y_vals[test["name"]].append(test["result"])
700                 except (KeyError, TypeError):
701                     y_vals[test["name"]].append(None)
702
703     # Add None to the lists with missing data
704     max_len = 0
705     for val in y_vals.values():
706         if len(val) > max_len:
707             max_len = len(val)
708     for key, val in y_vals.items():
709         if len(val) < max_len:
710             val.extend([None for _ in range(max_len - len(val))])
711
712     # Add plot traces
713     traces = list()
714     df = pd.DataFrame(y_vals)
715     df.head()
716     for i, col in enumerate(df.columns):
717         name = "{0}. {1}".format(i + 1, col.lower().replace('-cps', '').
718                                  replace('-rps', ''))
719         traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
720                                y=df[col],
721                                name=name,
722                                **plot["traces"]))
723     try:
724         # Create plot
725         plpl = plgo.Figure(data=traces, layout=plot["layout"])
726
727         # Export Plot
728         logging.info("    Writing file '{0}{1}'.".
729                      format(plot["output-file"], plot["output-file-type"]))
730         ploff.plot(plpl, show_link=False, auto_open=False,
731                    filename='{0}{1}'.format(plot["output-file"],
732                                             plot["output-file-type"]))
733     except PlotlyError as err:
734         logging.error("   Finished with error: {}".
735                       format(str(err).replace("\n", " ")))
736         return