Report: Use best value for ideal line
[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.debug(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.debug(suite)
123                 except KeyError as err:
124                     logging.error("Not found: {0}".format(repr(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.debug(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         try:
153             val_max = max(df[col])
154         except ValueError as err:
155             logging.error(repr(err))
156             continue
157         if val_max:
158             y_max.append(int(val_max / 1000000) + 1)
159
160     try:
161         # Create plot
162         layout = deepcopy(plot["layout"])
163         if layout.get("title", None):
164             layout["title"] = "<b>Packet Throughput:</b> {0}". \
165                 format(layout["title"])
166         if y_max:
167             layout["yaxis"]["range"] = [0, max(y_max)]
168         plpl = plgo.Figure(data=traces, layout=layout)
169
170         # Export Plot
171         logging.info("    Writing file '{0}{1}'.".
172                      format(plot["output-file"], plot["output-file-type"]))
173         ploff.plot(plpl, show_link=False, auto_open=False,
174                    filename='{0}{1}'.format(plot["output-file"],
175                                             plot["output-file-type"]))
176     except PlotlyError as err:
177         logging.error("   Finished with error: {}".
178                       format(repr(err).replace("\n", " ")))
179         return
180
181
182 def plot_latency_error_bars(plot, input_data):
183     """Generate the plot(s) with algorithm: plot_latency_error_bars
184     specified in the specification file.
185
186     :param plot: Plot to generate.
187     :param input_data: Data to process.
188     :type plot: pandas.Series
189     :type input_data: InputData
190     """
191
192     # Transform the data
193     plot_title = plot.get("title", "")
194     logging.info("    Creating the data set for the {0} '{1}'.".
195                  format(plot.get("type", ""), plot_title))
196     data = input_data.filter_data(plot)
197     if data is None:
198         logging.error("No data.")
199         return
200
201     # Prepare the data for the plot
202     y_tmp_vals = dict()
203     y_tags = dict()
204     for job in data:
205         for build in job:
206             for test in build:
207                 try:
208                     logging.debug("test['latency']: {0}\n".
209                                  format(test["latency"]))
210                 except ValueError as err:
211                     logging.warning(repr(err))
212                 if y_tmp_vals.get(test["parent"], None) is None:
213                     y_tmp_vals[test["parent"]] = [
214                         list(),  # direction1, min
215                         list(),  # direction1, avg
216                         list(),  # direction1, max
217                         list(),  # direction2, min
218                         list(),  # direction2, avg
219                         list()   # direction2, max
220                     ]
221                     y_tags[test["parent"]] = test.get("tags", None)
222                 try:
223                     if test["type"] in ("NDRPDR", ):
224                         if "-pdr" in plot_title.lower():
225                             ttype = "PDR"
226                         elif "-ndr" in plot_title.lower():
227                             ttype = "NDR"
228                         else:
229                             logging.warning("Invalid test type: {0}".
230                                             format(test["type"]))
231                             continue
232                         y_tmp_vals[test["parent"]][0].append(
233                             test["latency"][ttype]["direction1"]["min"])
234                         y_tmp_vals[test["parent"]][1].append(
235                             test["latency"][ttype]["direction1"]["avg"])
236                         y_tmp_vals[test["parent"]][2].append(
237                             test["latency"][ttype]["direction1"]["max"])
238                         y_tmp_vals[test["parent"]][3].append(
239                             test["latency"][ttype]["direction2"]["min"])
240                         y_tmp_vals[test["parent"]][4].append(
241                             test["latency"][ttype]["direction2"]["avg"])
242                         y_tmp_vals[test["parent"]][5].append(
243                             test["latency"][ttype]["direction2"]["max"])
244                     else:
245                         logging.warning("Invalid test type: {0}".
246                                         format(test["type"]))
247                         continue
248                 except (KeyError, TypeError) as err:
249                     logging.warning(repr(err))
250     logging.debug("y_tmp_vals: {0}\n".format(y_tmp_vals))
251     # Sort the tests
252     order = plot.get("sort", None)
253     if order and y_tags:
254         y_sorted = OrderedDict()
255         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
256         for tag in order:
257             logging.debug(tag)
258             for suite, tags in y_tags_l.items():
259                 if "not " in tag:
260                     tag = tag.split(" ")[-1]
261                     if tag.lower() in tags:
262                         continue
263                 else:
264                     if tag.lower() not in tags:
265                         continue
266                 try:
267                     y_sorted[suite] = y_tmp_vals.pop(suite)
268                     y_tags_l.pop(suite)
269                     logging.debug(suite)
270                 except KeyError as err:
271                     logging.error("Not found: {0}".format(repr(err)))
272                 finally:
273                     break
274     else:
275         y_sorted = y_tmp_vals
276
277     logging.debug("y_sorted: {0}\n".format(y_sorted))
278     x_vals = list()
279     y_vals = list()
280     y_mins = list()
281     y_maxs = list()
282     for key, val in y_sorted.items():
283         key = "-".join(key.split("-")[1:-1])
284         x_vals.append(key)  # dir 1
285         y_vals.append(mean(val[1]) if val[1] else None)
286         y_mins.append(mean(val[0]) if val[0] else None)
287         y_maxs.append(mean(val[2]) if val[2] else None)
288         x_vals.append(key)  # dir 2
289         y_vals.append(mean(val[4]) if val[4] else None)
290         y_mins.append(mean(val[3]) if val[3] else None)
291         y_maxs.append(mean(val[5]) if val[5] else None)
292
293     logging.debug("x_vals :{0}\n".format(x_vals))
294     logging.debug("y_vals :{0}\n".format(y_vals))
295     logging.debug("y_mins :{0}\n".format(y_mins))
296     logging.debug("y_maxs :{0}\n".format(y_maxs))
297     traces = list()
298     annotations = list()
299
300     for idx in range(len(x_vals)):
301         if not bool(int(idx % 2)):
302             direction = "West - East"
303         else:
304             direction = "East - West"
305         hovertext = ("Test: {test}<br>"
306                      "Direction: {dir}<br>".format(test=x_vals[idx],
307                                                    dir=direction))
308         if isinstance(y_maxs[idx], float):
309             hovertext += "Max: {max:.2f}uSec<br>".format(max=y_maxs[idx])
310         if isinstance(y_vals[idx], float):
311             hovertext += "Avg: {avg:.2f}uSec<br>".format(avg=y_vals[idx])
312         if isinstance(y_mins[idx], float):
313             hovertext += "Min: {min:.2f}uSec".format(min=y_mins[idx])
314
315         if isinstance(y_maxs[idx], float) and isinstance(y_vals[idx], float):
316             array = [y_maxs[idx] - y_vals[idx], ]
317         else:
318             array = [None, ]
319         if isinstance(y_mins[idx], float) and isinstance(y_vals[idx], float):
320             arrayminus = [y_vals[idx] - y_mins[idx], ]
321         else:
322             arrayminus = [None, ]
323         logging.debug("y_vals[{1}] :{0}\n".format(y_vals[idx], idx))
324         logging.debug("array :{0}\n".format(array))
325         logging.debug("arrayminus :{0}\n".format(arrayminus))
326         traces.append(plgo.Scatter(
327             x=[idx, ],
328             y=[y_vals[idx], ],
329             name=x_vals[idx],
330             legendgroup=x_vals[idx],
331             showlegend=bool(int(idx % 2)),
332             mode="markers",
333             error_y=dict(
334                 type='data',
335                 symmetric=False,
336                 array=array,
337                 arrayminus=arrayminus,
338                 color=COLORS[int(idx / 2)]
339             ),
340             marker=dict(
341                 size=10,
342                 color=COLORS[int(idx / 2)],
343             ),
344             text=hovertext,
345             hoverinfo="text",
346         ))
347         annotations.append(dict(
348             x=idx,
349             y=0,
350             xref="x",
351             yref="y",
352             xanchor="center",
353             yanchor="top",
354             text="E-W" if bool(int(idx % 2)) else "W-E",
355             font=dict(
356                 size=16,
357             ),
358             align="center",
359             showarrow=False
360         ))
361
362     try:
363         # Create plot
364         logging.info("    Writing file '{0}{1}'.".
365                      format(plot["output-file"], plot["output-file-type"]))
366         layout = deepcopy(plot["layout"])
367         if layout.get("title", None):
368             layout["title"] = "<b>Packet Latency:</b> {0}".\
369                 format(layout["title"])
370         layout["annotations"] = annotations
371         plpl = plgo.Figure(data=traces, layout=layout)
372
373         # Export Plot
374         ploff.plot(plpl,
375                    show_link=False, auto_open=False,
376                    filename='{0}{1}'.format(plot["output-file"],
377                                             plot["output-file-type"]))
378     except PlotlyError as err:
379         logging.error("   Finished with error: {}".
380                       format(str(err).replace("\n", " ")))
381         return
382
383
384 def plot_throughput_speedup_analysis(plot, input_data):
385     """Generate the plot(s) with algorithm:
386     plot_throughput_speedup_analysis
387     specified in the specification file.
388
389     :param plot: Plot to generate.
390     :param input_data: Data to process.
391     :type plot: pandas.Series
392     :type input_data: InputData
393     """
394
395     # Transform the data
396     plot_title = plot.get("title", "")
397     logging.info("    Creating the data set for the {0} '{1}'.".
398                  format(plot.get("type", ""), plot_title))
399     data = input_data.filter_data(plot)
400     if data is None:
401         logging.error("No data.")
402         return
403
404     y_vals = dict()
405     y_tags = dict()
406     for job in data:
407         for build in job:
408             for test in build:
409                 if y_vals.get(test["parent"], None) is None:
410                     y_vals[test["parent"]] = {"1": list(),
411                                               "2": list(),
412                                               "4": list()}
413                     y_tags[test["parent"]] = test.get("tags", None)
414                 try:
415                     if test["type"] in ("NDRPDR",):
416                         if "-pdr" in plot_title.lower():
417                             ttype = "PDR"
418                         elif "-ndr" in plot_title.lower():
419                             ttype = "NDR"
420                         else:
421                             continue
422                         if "1C" in test["tags"]:
423                             y_vals[test["parent"]]["1"]. \
424                                 append(test["throughput"][ttype]["LOWER"])
425                         elif "2C" in test["tags"]:
426                             y_vals[test["parent"]]["2"]. \
427                                 append(test["throughput"][ttype]["LOWER"])
428                         elif "4C" in test["tags"]:
429                             y_vals[test["parent"]]["4"]. \
430                                 append(test["throughput"][ttype]["LOWER"])
431                 except (KeyError, TypeError):
432                     pass
433
434     if not y_vals:
435         logging.warning("No data for the plot '{}'".
436                         format(plot.get("title", "")))
437         return
438
439     y_1c_max = dict()
440     for test_name, test_vals in y_vals.items():
441         for key, test_val in test_vals.items():
442             if test_val:
443                 avg_val = sum(test_val) / len(test_val)
444                 y_vals[test_name][key] =  avg_val
445                 ideal = avg_val / (int(key) * 1000000.0)
446                 if test_name not in y_1c_max or ideal > y_1c_max[test_name]:
447                     y_1c_max[test_name] = ideal
448
449     vals = dict()
450     y_max = list()
451     nic_limit = 0
452     lnk_limit = 0
453     pci_limit = plot["limits"]["pci"]["pci-g3-x8"]
454     for test_name, test_vals in y_vals.items():
455         if test_vals["1"]:
456             name = "-".join(test_name.split('-')[1:-1])
457
458             vals[name] = dict()
459             y_val_1 = test_vals["1"] / 1000000.0
460             y_val_2 = test_vals["2"] / 1000000.0 if test_vals["2"] else None
461             y_val_4 = test_vals["4"] / 1000000.0 if test_vals["4"] else None
462
463             vals[name]["val"] = [y_val_1, y_val_2, y_val_4]
464             vals[name]["rel"] = [1.0, None, None]
465             vals[name]["ideal"] = [y_1c_max[test_name],
466                                    y_1c_max[test_name] * 2,
467                                    y_1c_max[test_name] * 4]
468             vals[name]["diff"] = \
469                 [(y_val_1 - y_1c_max[test_name]) * 100 / y_val_1, None, None]
470
471             try:
472                 val_max = max(max(vals[name]["val"], vals[name]["ideal"]))
473             except ValueError as err:
474                 logging.error(err)
475                 continue
476             if val_max:
477                 y_max.append(int((val_max / 10) + 1) * 10)
478
479             if y_val_2:
480                 vals[name]["rel"][1] = round(y_val_2 / y_val_1, 2)
481                 vals[name]["diff"][1] = \
482                     (y_val_2 - vals[name]["ideal"][1]) * 100 / y_val_2
483             if y_val_4:
484                 vals[name]["rel"][2] = round(y_val_4 / y_val_1, 2)
485                 vals[name]["diff"][2] = \
486                     (y_val_4 - vals[name]["ideal"][2]) * 100 / y_val_4
487
488         # Limits:
489         if "x520" in test_name:
490             limit = plot["limits"]["nic"]["x520"]
491         elif "x710" in test_name:
492             limit = plot["limits"]["nic"]["x710"]
493         elif "xxv710" in test_name:
494             limit = plot["limits"]["nic"]["xxv710"]
495         elif "xl710" in test_name:
496             limit = plot["limits"]["nic"]["xl710"]
497         else:
498             limit = 0
499         if limit > nic_limit:
500             nic_limit = limit
501
502         mul = 2 if "ge2p" in test_name else 1
503         if "10ge" in test_name:
504             limit = plot["limits"]["link"]["10ge"] * mul
505         elif "25ge" in test_name:
506             limit = plot["limits"]["link"]["25ge"] * mul
507         elif "40ge" in test_name:
508             limit = plot["limits"]["link"]["40ge"] * mul
509         elif "100ge" in test_name:
510             limit = plot["limits"]["link"]["100ge"] * mul
511         else:
512             limit = 0
513         if limit > lnk_limit:
514             lnk_limit = limit
515
516     # Sort the tests
517     order = plot.get("sort", None)
518     if order and y_tags:
519         y_sorted = OrderedDict()
520         y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
521         for tag in order:
522             for test, tags in y_tags_l.items():
523                 if tag.lower() in tags:
524                     name = "-".join(test.split('-')[1:-1])
525                     try:
526                         y_sorted[name] = vals.pop(name)
527                         y_tags_l.pop(test)
528                     except KeyError as err:
529                         logging.error("Not found: {0}".format(err))
530                     finally:
531                         break
532     else:
533         y_sorted = vals
534
535     traces = list()
536     annotations = list()
537     x_vals = [1, 2, 4]
538
539     # Limits:
540     try:
541         threshold = 1.1 * max(y_max)  # 10%
542     except ValueError as err:
543         logging.error(err)
544         return
545     nic_limit /= 1000000.0
546     if nic_limit < threshold:
547         traces.append(plgo.Scatter(
548             x=x_vals,
549             y=[nic_limit, ] * len(x_vals),
550             name="NIC: {0:.2f}Mpps".format(nic_limit),
551             showlegend=False,
552             mode="lines",
553             line=dict(
554                 dash="dot",
555                 color=COLORS[-1],
556                 width=1),
557             hoverinfo="none"
558         ))
559         annotations.append(dict(
560             x=1,
561             y=nic_limit,
562             xref="x",
563             yref="y",
564             xanchor="left",
565             yanchor="bottom",
566             text="NIC: {0:.2f}Mpps".format(nic_limit),
567             font=dict(
568                 size=14,
569                 color=COLORS[-1],
570             ),
571             align="left",
572             showarrow=False
573         ))
574         y_max.append(int((nic_limit / 10) + 1) * 10)
575
576     lnk_limit /= 1000000.0
577     if lnk_limit < threshold:
578         traces.append(plgo.Scatter(
579             x=x_vals,
580             y=[lnk_limit, ] * len(x_vals),
581             name="Link: {0:.2f}Mpps".format(lnk_limit),
582             showlegend=False,
583             mode="lines",
584             line=dict(
585                 dash="dot",
586                 color=COLORS[-2],
587                 width=1),
588             hoverinfo="none"
589         ))
590         annotations.append(dict(
591             x=1,
592             y=lnk_limit,
593             xref="x",
594             yref="y",
595             xanchor="left",
596             yanchor="bottom",
597             text="Link: {0:.2f}Mpps".format(lnk_limit),
598             font=dict(
599                 size=14,
600                 color=COLORS[-2],
601             ),
602             align="left",
603             showarrow=False
604         ))
605         y_max.append(int((lnk_limit / 10) + 1) * 10)
606
607     pci_limit /= 1000000.0
608     if pci_limit < threshold:
609         traces.append(plgo.Scatter(
610             x=x_vals,
611             y=[pci_limit, ] * len(x_vals),
612             name="PCIe: {0:.2f}Mpps".format(pci_limit),
613             showlegend=False,
614             mode="lines",
615             line=dict(
616                 dash="dot",
617                 color=COLORS[-3],
618                 width=1),
619             hoverinfo="none"
620         ))
621         annotations.append(dict(
622             x=1,
623             y=pci_limit,
624             xref="x",
625             yref="y",
626             xanchor="left",
627             yanchor="bottom",
628             text="PCIe: {0:.2f}Mpps".format(pci_limit),
629             font=dict(
630                 size=14,
631                 color=COLORS[-3],
632             ),
633             align="left",
634             showarrow=False
635         ))
636         y_max.append(int((pci_limit / 10) + 1) * 10)
637
638     # Perfect and measured:
639     cidx = 0
640     for name, val in y_sorted.iteritems():
641         hovertext = list()
642         for idx in range(len(val["val"])):
643             htext = ""
644             if isinstance(val["val"][idx], float):
645                 htext += "value: {0:.2f}Mpps<br>".format(val["val"][idx])
646             if isinstance(val["diff"][idx], float):
647                 htext += "diff: {0:.0f}%<br>".format(round(val["diff"][idx]))
648             if isinstance(val["rel"][idx], float):
649                 htext += "speedup: {0:.2f}".format(val["rel"][idx])
650             hovertext.append(htext)
651         traces.append(plgo.Scatter(x=x_vals,
652                                    y=val["val"],
653                                    name=name,
654                                    legendgroup=name,
655                                    mode="lines+markers",
656                                    line=dict(
657                                        color=COLORS[cidx],
658                                        width=2),
659                                    marker=dict(
660                                        symbol="circle",
661                                        size=10
662                                    ),
663                                    text=hovertext,
664                                    hoverinfo="text+name"
665                                    ))
666         traces.append(plgo.Scatter(x=x_vals,
667                                    y=val["ideal"],
668                                    name="{0} perfect".format(name),
669                                    legendgroup=name,
670                                    showlegend=False,
671                                    mode="lines",
672                                    line=dict(
673                                        color=COLORS[cidx],
674                                        width=2,
675                                        dash="dash"),
676                                    text=["perfect: {0:.2f}Mpps".format(y)
677                                          for y in val["ideal"]],
678                                    hoverinfo="text"
679                                    ))
680         cidx += 1
681
682     try:
683         # Create plot
684         logging.info("    Writing file '{0}{1}'.".
685                      format(plot["output-file"], plot["output-file-type"]))
686         layout = deepcopy(plot["layout"])
687         if layout.get("title", None):
688             layout["title"] = "<b>Speedup Multi-core:</b> {0}". \
689                 format(layout["title"])
690         layout["annotations"].extend(annotations)
691         plpl = plgo.Figure(data=traces, layout=layout)
692
693         # Export Plot
694         ploff.plot(plpl,
695                    show_link=False, auto_open=False,
696                    filename='{0}{1}'.format(plot["output-file"],
697                                             plot["output-file-type"]))
698     except PlotlyError as err:
699         logging.error("   Finished with error: {}".
700                       format(str(err).replace("\n", " ")))
701         return
702
703
704 def plot_http_server_performance_box(plot, input_data):
705     """Generate the plot(s) with algorithm: plot_http_server_performance_box
706     specified in the specification file.
707
708     :param plot: Plot to generate.
709     :param input_data: Data to process.
710     :type plot: pandas.Series
711     :type input_data: InputData
712     """
713
714     # Transform the data
715     logging.info("    Creating the data set for the {0} '{1}'.".
716                  format(plot.get("type", ""), plot.get("title", "")))
717     data = input_data.filter_data(plot)
718     if data is None:
719         logging.error("No data.")
720         return
721
722     # Prepare the data for the plot
723     y_vals = dict()
724     for job in data:
725         for build in job:
726             for test in build:
727                 if y_vals.get(test["name"], None) is None:
728                     y_vals[test["name"]] = list()
729                 try:
730                     y_vals[test["name"]].append(test["result"])
731                 except (KeyError, TypeError):
732                     y_vals[test["name"]].append(None)
733
734     # Add None to the lists with missing data
735     max_len = 0
736     for val in y_vals.values():
737         if len(val) > max_len:
738             max_len = len(val)
739     for key, val in y_vals.items():
740         if len(val) < max_len:
741             val.extend([None for _ in range(max_len - len(val))])
742
743     # Add plot traces
744     traces = list()
745     df = pd.DataFrame(y_vals)
746     df.head()
747     for i, col in enumerate(df.columns):
748         name = "{0}. {1}".format(i + 1, col.lower().replace('-cps', '').
749                                  replace('-rps', ''))
750         traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
751                                y=df[col],
752                                name=name,
753                                **plot["traces"]))
754     try:
755         # Create plot
756         plpl = plgo.Figure(data=traces, layout=plot["layout"])
757
758         # Export Plot
759         logging.info("    Writing file '{0}{1}'.".
760                      format(plot["output-file"], plot["output-file-type"]))
761         ploff.plot(plpl, show_link=False, auto_open=False,
762                    filename='{0}{1}'.format(plot["output-file"],
763                                             plot["output-file-type"]))
764     except PlotlyError as err:
765         logging.error("   Finished with error: {}".
766                       format(str(err).replace("\n", " ")))
767         return