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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """Algorithms to generate plots.
20 import plotly.offline as ploff
21 import plotly.graph_objs as plgo
23 from plotly.exceptions import PlotlyError
24 from collections import OrderedDict
25 from copy import deepcopy
27 from utils import mean
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"]
38 def generate_plots(spec, data):
39 """Generate all plots specified in the specification file.
41 :param spec: Specification read from the specification file.
42 :param data: Data to process.
43 :type spec: Specification
47 logging.info("Generating the plots ...")
48 for index, plot in enumerate(spec.plots):
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)))
61 def plot_performance_box(plot, input_data):
62 """Generate the plot(s) with algorithm: plot_performance_box
63 specified in the specification file.
65 :param plot: Plot to generate.
66 :param input_data: Data to process.
67 :type plot: pandas.Series
68 :type input_data: InputData
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)
77 logging.error("No data.")
80 # Prepare the data for the plot
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)
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"])
101 except (KeyError, TypeError):
102 y_vals[test["parent"]].append(None)
105 order = plot.get("sort", None)
107 y_sorted = OrderedDict()
108 y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
111 for suite, tags in y_tags_l.items():
113 tag = tag.split(" ")[-1]
114 if tag.lower() in tags:
117 if tag.lower() not in tags:
120 y_sorted[suite] = y_vals.pop(suite)
123 except KeyError as err:
124 logging.error("Not found: {0}".format(repr(err)))
130 # Add None to the lists with missing data
132 nr_of_samples = list()
133 for val in y_sorted.values():
134 if len(val) > max_len:
136 nr_of_samples.append(len(val))
137 for key, val in y_sorted.items():
138 if len(val) < max_len:
139 val.extend([None for _ in range(max_len - len(val))])
143 df = pd.DataFrame(y_sorted)
146 for i, col in enumerate(df.columns):
147 name = "{nr}. ({samples:02d} run{plural}) {name}".\
149 samples=nr_of_samples[i],
150 plural='s' if nr_of_samples[i] > 1 else '',
151 name=col.lower().replace('-ndrpdr', ''))
153 name_lst = name.split('-')
156 for segment in name_lst:
157 if (len(name) + len(segment) + 1) > 50 and split_name:
160 name += segment + '-'
164 traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
165 y=[y / 1000000 if y else None for y in df[col]],
169 val_max = max(df[col])
170 except ValueError as err:
171 logging.error(repr(err))
174 y_max.append(int(val_max / 1000000) + 1)
178 layout = deepcopy(plot["layout"])
179 if layout.get("title", None):
180 layout["title"] = "<b>Packet Throughput:</b> {0}". \
181 format(layout["title"])
183 layout["yaxis"]["range"] = [0, max(y_max)]
184 plpl = plgo.Figure(data=traces, layout=layout)
187 logging.info(" Writing file '{0}{1}'.".
188 format(plot["output-file"], plot["output-file-type"]))
189 ploff.plot(plpl, show_link=False, auto_open=False,
190 filename='{0}{1}'.format(plot["output-file"],
191 plot["output-file-type"]))
192 except PlotlyError as err:
193 logging.error(" Finished with error: {}".
194 format(repr(err).replace("\n", " ")))
198 def plot_soak_bars(plot, input_data):
199 """Generate the plot(s) with algorithm: plot_soak_bars
200 specified in the specification file.
202 :param plot: Plot to generate.
203 :param input_data: Data to process.
204 :type plot: pandas.Series
205 :type input_data: InputData
209 plot_title = plot.get("title", "")
210 logging.info(" Creating the data set for the {0} '{1}'.".
211 format(plot.get("type", ""), plot_title))
212 data = input_data.filter_data(plot)
214 logging.error("No data.")
217 # Prepare the data for the plot
223 if y_vals.get(test["parent"], None) is None:
224 y_tags[test["parent"]] = test.get("tags", None)
226 if test["type"] in ("SOAK", ):
227 y_vals[test["parent"]] = test["throughput"]
230 except (KeyError, TypeError):
231 y_vals[test["parent"]] = dict()
234 order = plot.get("sort", None)
236 y_sorted = OrderedDict()
237 y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
240 for suite, tags in y_tags_l.items():
242 tag = tag.split(" ")[-1]
243 if tag.lower() in tags:
246 if tag.lower() not in tags:
249 y_sorted[suite] = y_vals.pop(suite)
252 except KeyError as err:
253 logging.error("Not found: {0}".format(repr(err)))
262 for test_name, test_data in y_sorted.items():
264 name = "{nr}. {name}".\
265 format(nr=idx, name=test_name.lower().replace('-soak', ''))
267 name_lst = name.split('-')
270 for segment in name_lst:
271 if (len(name) + len(segment) + 1) > 50 and split_name:
274 name += segment + '-'
277 y_val = test_data.get("LOWER", None)
283 time = "No Information"
284 result = "No Information"
285 hovertext = ("{name}<br>"
286 "Packet Throughput: {val:.2f}Mpps<br>"
287 "Final Duration: {time}<br>"
288 "Result: {result}".format(name=name,
292 traces.append(plgo.Bar(x=[str(idx) + '.', ],
299 layout = deepcopy(plot["layout"])
300 if layout.get("title", None):
301 layout["title"] = "<b>Packet Throughput:</b> {0}". \
302 format(layout["title"])
304 layout["yaxis"]["range"] = [0, y_max + 1]
305 plpl = plgo.Figure(data=traces, layout=layout)
307 logging.info(" Writing file '{0}{1}'.".
308 format(plot["output-file"], plot["output-file-type"]))
309 ploff.plot(plpl, show_link=False, auto_open=False,
310 filename='{0}{1}'.format(plot["output-file"],
311 plot["output-file-type"]))
312 except PlotlyError as err:
313 logging.error(" Finished with error: {}".
314 format(repr(err).replace("\n", " ")))
318 def plot_soak_boxes(plot, input_data):
319 """Generate the plot(s) with algorithm: plot_soak_boxes
320 specified in the specification file.
322 :param plot: Plot to generate.
323 :param input_data: Data to process.
324 :type plot: pandas.Series
325 :type input_data: InputData
329 plot_title = plot.get("title", "")
330 logging.info(" Creating the data set for the {0} '{1}'.".
331 format(plot.get("type", ""), plot_title))
332 data = input_data.filter_data(plot)
334 logging.error("No data.")
337 # Prepare the data for the plot
343 if y_vals.get(test["parent"], None) is None:
344 y_tags[test["parent"]] = test.get("tags", None)
346 if test["type"] in ("SOAK", ):
347 y_vals[test["parent"]] = test["throughput"]
350 except (KeyError, TypeError):
351 y_vals[test["parent"]] = dict()
354 order = plot.get("sort", None)
356 y_sorted = OrderedDict()
357 y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
360 for suite, tags in y_tags_l.items():
362 tag = tag.split(" ")[-1]
363 if tag.lower() in tags:
366 if tag.lower() not in tags:
369 y_sorted[suite] = y_vals.pop(suite)
372 except KeyError as err:
373 logging.error("Not found: {0}".format(repr(err)))
382 for test_name, test_data in y_sorted.items():
384 name = "{nr}. {name}".\
385 format(nr=idx, name=test_name.lower().replace('-soak', ''))
387 name_lst = name.split('-')
390 for segment in name_lst:
391 if (len(name) + len(segment) + 1) > 50 and split_name:
394 name += segment + '-'
397 y_val = test_data.get("UPPER", None)
403 y_base = test_data.get("LOWER", None)
407 hovertext = ("{name}<br>"
408 "Upper bound: {upper:.2f}Mpps<br>"
409 "Lower bound: {lower:.2f}Mpps".format(name=name,
412 traces.append(plgo.Bar(x=[str(idx) + '.', ],
413 # +0.05 to see the value in case lower == upper
414 y=[y_val - y_base + 0.05, ],
421 layout = deepcopy(plot["layout"])
422 if layout.get("title", None):
423 layout["title"] = "<b>Soak Tests:</b> {0}". \
424 format(layout["title"])
426 layout["yaxis"]["range"] = [0, y_max + 1]
427 plpl = plgo.Figure(data=traces, layout=layout)
429 logging.info(" Writing file '{0}{1}'.".
430 format(plot["output-file"], plot["output-file-type"]))
431 ploff.plot(plpl, show_link=False, auto_open=False,
432 filename='{0}{1}'.format(plot["output-file"],
433 plot["output-file-type"]))
434 except PlotlyError as err:
435 logging.error(" Finished with error: {}".
436 format(repr(err).replace("\n", " ")))
440 def plot_latency_error_bars(plot, input_data):
441 """Generate the plot(s) with algorithm: plot_latency_error_bars
442 specified in the specification file.
444 :param plot: Plot to generate.
445 :param input_data: Data to process.
446 :type plot: pandas.Series
447 :type input_data: InputData
451 plot_title = plot.get("title", "")
452 logging.info(" Creating the data set for the {0} '{1}'.".
453 format(plot.get("type", ""), plot_title))
454 data = input_data.filter_data(plot)
456 logging.error("No data.")
459 # Prepare the data for the plot
466 logging.debug("test['latency']: {0}\n".
467 format(test["latency"]))
468 except ValueError as err:
469 logging.warning(repr(err))
470 if y_tmp_vals.get(test["parent"], None) is None:
471 y_tmp_vals[test["parent"]] = [
472 list(), # direction1, min
473 list(), # direction1, avg
474 list(), # direction1, max
475 list(), # direction2, min
476 list(), # direction2, avg
477 list() # direction2, max
479 y_tags[test["parent"]] = test.get("tags", None)
481 if test["type"] in ("NDRPDR", ):
482 if "-pdr" in plot_title.lower():
484 elif "-ndr" in plot_title.lower():
487 logging.warning("Invalid test type: {0}".
488 format(test["type"]))
490 y_tmp_vals[test["parent"]][0].append(
491 test["latency"][ttype]["direction1"]["min"])
492 y_tmp_vals[test["parent"]][1].append(
493 test["latency"][ttype]["direction1"]["avg"])
494 y_tmp_vals[test["parent"]][2].append(
495 test["latency"][ttype]["direction1"]["max"])
496 y_tmp_vals[test["parent"]][3].append(
497 test["latency"][ttype]["direction2"]["min"])
498 y_tmp_vals[test["parent"]][4].append(
499 test["latency"][ttype]["direction2"]["avg"])
500 y_tmp_vals[test["parent"]][5].append(
501 test["latency"][ttype]["direction2"]["max"])
503 logging.warning("Invalid test type: {0}".
504 format(test["type"]))
506 except (KeyError, TypeError) as err:
507 logging.warning(repr(err))
508 logging.debug("y_tmp_vals: {0}\n".format(y_tmp_vals))
511 order = plot.get("sort", None)
513 y_sorted = OrderedDict()
514 y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
517 for suite, tags in y_tags_l.items():
519 tag = tag.split(" ")[-1]
520 if tag.lower() in tags:
523 if tag.lower() not in tags:
526 y_sorted[suite] = y_tmp_vals.pop(suite)
529 except KeyError as err:
530 logging.error("Not found: {0}".format(repr(err)))
534 y_sorted = y_tmp_vals
536 logging.debug("y_sorted: {0}\n".format(y_sorted))
541 nr_of_samples = list()
542 for key, val in y_sorted.items():
543 name = "-".join(key.split("-")[1:-1])
545 name_lst = name.split('-')
548 for segment in name_lst:
549 if (len(name) + len(segment) + 1) > 50 and split_name:
552 name += segment + '-'
554 x_vals.append(name) # dir 1
555 y_vals.append(mean(val[1]) if val[1] else None)
556 y_mins.append(mean(val[0]) if val[0] else None)
557 y_maxs.append(mean(val[2]) if val[2] else None)
558 nr_of_samples.append(len(val[1]) if val[1] else 0)
559 x_vals.append(name) # dir 2
560 y_vals.append(mean(val[4]) if val[4] else None)
561 y_mins.append(mean(val[3]) if val[3] else None)
562 y_maxs.append(mean(val[5]) if val[5] else None)
563 nr_of_samples.append(len(val[3]) if val[3] else 0)
565 logging.debug("x_vals :{0}\n".format(x_vals))
566 logging.debug("y_vals :{0}\n".format(y_vals))
567 logging.debug("y_mins :{0}\n".format(y_mins))
568 logging.debug("y_maxs :{0}\n".format(y_maxs))
569 logging.debug("nr_of_samples :{0}\n".format(nr_of_samples))
573 for idx in range(len(x_vals)):
574 if not bool(int(idx % 2)):
575 direction = "West-East"
577 direction = "East-West"
578 hovertext = ("No. of Runs: {nr}<br>"
580 "Direction: {dir}<br>".format(test=x_vals[idx],
582 nr=nr_of_samples[idx]))
583 if isinstance(y_maxs[idx], float):
584 hovertext += "Max: {max:.2f}uSec<br>".format(max=y_maxs[idx])
585 if isinstance(y_vals[idx], float):
586 hovertext += "Mean: {avg:.2f}uSec<br>".format(avg=y_vals[idx])
587 if isinstance(y_mins[idx], float):
588 hovertext += "Min: {min:.2f}uSec".format(min=y_mins[idx])
590 if isinstance(y_maxs[idx], float) and isinstance(y_vals[idx], float):
591 array = [y_maxs[idx] - y_vals[idx], ]
594 if isinstance(y_mins[idx], float) and isinstance(y_vals[idx], float):
595 arrayminus = [y_vals[idx] - y_mins[idx], ]
597 arrayminus = [None, ]
598 logging.debug("y_vals[{1}] :{0}\n".format(y_vals[idx], idx))
599 logging.debug("array :{0}\n".format(array))
600 logging.debug("arrayminus :{0}\n".format(arrayminus))
601 traces.append(plgo.Scatter(
605 legendgroup=x_vals[idx],
606 showlegend=bool(int(idx % 2)),
612 arrayminus=arrayminus,
613 color=COLORS[int(idx / 2)]
617 color=COLORS[int(idx / 2)],
622 annotations.append(dict(
629 text="E-W" if bool(int(idx % 2)) else "W-E",
639 logging.info(" Writing file '{0}{1}'.".
640 format(plot["output-file"], plot["output-file-type"]))
641 layout = deepcopy(plot["layout"])
642 if layout.get("title", None):
643 layout["title"] = "<b>Packet Latency:</b> {0}".\
644 format(layout["title"])
645 layout["annotations"] = annotations
646 plpl = plgo.Figure(data=traces, layout=layout)
650 show_link=False, auto_open=False,
651 filename='{0}{1}'.format(plot["output-file"],
652 plot["output-file-type"]))
653 except PlotlyError as err:
654 logging.error(" Finished with error: {}".
655 format(str(err).replace("\n", " ")))
659 def plot_throughput_speedup_analysis(plot, input_data):
660 """Generate the plot(s) with algorithm:
661 plot_throughput_speedup_analysis
662 specified in the specification file.
664 :param plot: Plot to generate.
665 :param input_data: Data to process.
666 :type plot: pandas.Series
667 :type input_data: InputData
671 plot_title = plot.get("title", "")
672 logging.info(" Creating the data set for the {0} '{1}'.".
673 format(plot.get("type", ""), plot_title))
674 data = input_data.filter_data(plot)
676 logging.error("No data.")
684 if y_vals.get(test["parent"], None) is None:
685 y_vals[test["parent"]] = {"1": list(),
688 y_tags[test["parent"]] = test.get("tags", None)
690 if test["type"] in ("NDRPDR",):
691 if "-pdr" in plot_title.lower():
693 elif "-ndr" in plot_title.lower():
697 if "1C" in test["tags"]:
698 y_vals[test["parent"]]["1"]. \
699 append(test["throughput"][ttype]["LOWER"])
700 elif "2C" in test["tags"]:
701 y_vals[test["parent"]]["2"]. \
702 append(test["throughput"][ttype]["LOWER"])
703 elif "4C" in test["tags"]:
704 y_vals[test["parent"]]["4"]. \
705 append(test["throughput"][ttype]["LOWER"])
706 except (KeyError, TypeError):
710 logging.warning("No data for the plot '{}'".
711 format(plot.get("title", "")))
715 for test_name, test_vals in y_vals.items():
716 for key, test_val in test_vals.items():
718 avg_val = sum(test_val) / len(test_val)
719 y_vals[test_name][key] = (avg_val, len(test_val))
720 ideal = avg_val / (int(key) * 1000000.0)
721 if test_name not in y_1c_max or ideal > y_1c_max[test_name]:
722 y_1c_max[test_name] = ideal
728 pci_limit = plot["limits"]["pci"]["pci-g3-x8"]
729 for test_name, test_vals in y_vals.items():
731 if test_vals["1"][1]:
732 name = "-".join(test_name.split('-')[1:-1])
734 name_lst = name.split('-')
737 for segment in name_lst:
738 if (len(name) + len(segment) + 1) > 50 and split_name:
741 name += segment + '-'
745 y_val_1 = test_vals["1"][0] / 1000000.0
746 y_val_2 = test_vals["2"][0] / 1000000.0 if test_vals["2"][0] \
748 y_val_4 = test_vals["4"][0] / 1000000.0 if test_vals["4"][0] \
751 vals[name]["val"] = [y_val_1, y_val_2, y_val_4]
752 vals[name]["rel"] = [1.0, None, None]
753 vals[name]["ideal"] = [y_1c_max[test_name],
754 y_1c_max[test_name] * 2,
755 y_1c_max[test_name] * 4]
756 vals[name]["diff"] = [(y_val_1 - y_1c_max[test_name]) * 100 /
758 vals[name]["count"] = [test_vals["1"][1],
763 val_max = max(max(vals[name]["val"], vals[name]["ideal"]))
764 except ValueError as err:
768 y_max.append(int((val_max / 10) + 1) * 10)
771 vals[name]["rel"][1] = round(y_val_2 / y_val_1, 2)
772 vals[name]["diff"][1] = \
773 (y_val_2 - vals[name]["ideal"][1]) * 100 / y_val_2
775 vals[name]["rel"][2] = round(y_val_4 / y_val_1, 2)
776 vals[name]["diff"][2] = \
777 (y_val_4 - vals[name]["ideal"][2]) * 100 / y_val_4
778 except IndexError as err:
779 logging.warning("No data for '{0}'".format(test_name))
780 logging.warning(repr(err))
783 if "x520" in test_name:
784 limit = plot["limits"]["nic"]["x520"]
785 elif "x710" in test_name:
786 limit = plot["limits"]["nic"]["x710"]
787 elif "xxv710" in test_name:
788 limit = plot["limits"]["nic"]["xxv710"]
789 elif "xl710" in test_name:
790 limit = plot["limits"]["nic"]["xl710"]
791 elif "x553" in test_name:
792 limit = plot["limits"]["nic"]["x553"]
795 if limit > nic_limit:
798 mul = 2 if "ge2p" in test_name else 1
799 if "10ge" in test_name:
800 limit = plot["limits"]["link"]["10ge"] * mul
801 elif "25ge" in test_name:
802 limit = plot["limits"]["link"]["25ge"] * mul
803 elif "40ge" in test_name:
804 limit = plot["limits"]["link"]["40ge"] * mul
805 elif "100ge" in test_name:
806 limit = plot["limits"]["link"]["100ge"] * mul
809 if limit > lnk_limit:
813 order = plot.get("sort", None)
815 y_sorted = OrderedDict()
816 y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
818 for test, tags in y_tags_l.items():
819 if tag.lower() in tags:
820 name = "-".join(test.split('-')[1:-1])
822 y_sorted[name] = vals.pop(name)
824 except KeyError as err:
825 logging.error("Not found: {0}".format(err))
837 threshold = 1.1 * max(y_max) # 10%
838 except ValueError as err:
841 nic_limit /= 1000000.0
842 if nic_limit < threshold:
843 traces.append(plgo.Scatter(
845 y=[nic_limit, ] * len(x_vals),
846 name="NIC: {0:.2f}Mpps".format(nic_limit),
855 annotations.append(dict(
862 text="NIC: {0:.2f}Mpps".format(nic_limit),
870 y_max.append(int((nic_limit / 10) + 1) * 10)
872 lnk_limit /= 1000000.0
873 if lnk_limit < threshold:
874 traces.append(plgo.Scatter(
876 y=[lnk_limit, ] * len(x_vals),
877 name="Link: {0:.2f}Mpps".format(lnk_limit),
886 annotations.append(dict(
893 text="Link: {0:.2f}Mpps".format(lnk_limit),
901 y_max.append(int((lnk_limit / 10) + 1) * 10)
903 pci_limit /= 1000000.0
904 if pci_limit < threshold:
905 traces.append(plgo.Scatter(
907 y=[pci_limit, ] * len(x_vals),
908 name="PCIe: {0:.2f}Mpps".format(pci_limit),
917 annotations.append(dict(
924 text="PCIe: {0:.2f}Mpps".format(pci_limit),
932 y_max.append(int((pci_limit / 10) + 1) * 10)
934 # Perfect and measured:
936 for name, val in y_sorted.iteritems():
939 for idx in range(len(val["val"])):
941 if isinstance(val["val"][idx], float):
942 htext += "No. of Runs: {1}<br>" \
943 "Mean: {0:.2f}Mpps<br>".format(val["val"][idx],
945 if isinstance(val["diff"][idx], float):
946 htext += "Diff: {0:.0f}%<br>".format(round(val["diff"][idx]))
947 if isinstance(val["rel"][idx], float):
948 htext += "Speedup: {0:.2f}".format(val["rel"][idx])
949 hovertext.append(htext)
950 traces.append(plgo.Scatter(x=x_vals,
954 mode="lines+markers",
963 hoverinfo="text+name"
965 traces.append(plgo.Scatter(x=x_vals,
967 name="{0} perfect".format(name),
975 text=["Perfect: {0:.2f}Mpps".format(y)
976 for y in val["ideal"]],
980 except (IndexError, ValueError, KeyError) as err:
981 logging.warning("No data for '{0}'".format(name))
982 logging.warning(repr(err))
986 logging.info(" Writing file '{0}{1}'.".
987 format(plot["output-file"], plot["output-file-type"]))
988 layout = deepcopy(plot["layout"])
989 if layout.get("title", None):
990 layout["title"] = "<b>Speedup Multi-core:</b> {0}". \
991 format(layout["title"])
992 layout["annotations"].extend(annotations)
993 plpl = plgo.Figure(data=traces, layout=layout)
997 show_link=False, auto_open=False,
998 filename='{0}{1}'.format(plot["output-file"],
999 plot["output-file-type"]))
1000 except PlotlyError as err:
1001 logging.error(" Finished with error: {}".
1002 format(str(err).replace("\n", " ")))
1006 def plot_http_server_performance_box(plot, input_data):
1007 """Generate the plot(s) with algorithm: plot_http_server_performance_box
1008 specified in the specification file.
1010 :param plot: Plot to generate.
1011 :param input_data: Data to process.
1012 :type plot: pandas.Series
1013 :type input_data: InputData
1016 # Transform the data
1017 logging.info(" Creating the data set for the {0} '{1}'.".
1018 format(plot.get("type", ""), plot.get("title", "")))
1019 data = input_data.filter_data(plot)
1021 logging.error("No data.")
1024 # Prepare the data for the plot
1029 if y_vals.get(test["name"], None) is None:
1030 y_vals[test["name"]] = list()
1032 y_vals[test["name"]].append(test["result"])
1033 except (KeyError, TypeError):
1034 y_vals[test["name"]].append(None)
1036 # Add None to the lists with missing data
1038 nr_of_samples = list()
1039 for val in y_vals.values():
1040 if len(val) > max_len:
1042 nr_of_samples.append(len(val))
1043 for key, val in y_vals.items():
1044 if len(val) < max_len:
1045 val.extend([None for _ in range(max_len - len(val))])
1049 df = pd.DataFrame(y_vals)
1051 for i, col in enumerate(df.columns):
1052 name = "{nr}. ({samples:02d} run{plural}) {name}".\
1054 samples=nr_of_samples[i],
1055 plural='s' if nr_of_samples[i] > 1 else '',
1056 name=col.lower().replace('-ndrpdr', ''))
1058 name_lst = name.split('-')
1061 for segment in name_lst:
1062 if (len(name) + len(segment) + 1) > 50 and split_name:
1065 name += segment + '-'
1068 traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
1074 plpl = plgo.Figure(data=traces, layout=plot["layout"])
1077 logging.info(" Writing file '{0}{1}'.".
1078 format(plot["output-file"], plot["output-file-type"]))
1079 ploff.plot(plpl, show_link=False, auto_open=False,
1080 filename='{0}{1}'.format(plot["output-file"],
1081 plot["output-file-type"]))
1082 except PlotlyError as err:
1083 logging.error(" Finished with error: {}".
1084 format(str(err).replace("\n", " ")))
1088 def plot_service_density_heatmap(plot, input_data):
1089 """Generate the plot(s) with algorithm: plot_service_density_heatmap
1090 specified in the specification file.
1092 :param plot: Plot to generate.
1093 :param input_data: Data to process.
1094 :type plot: pandas.Series
1095 :type input_data: InputData
1098 # Example data in Mpps
1099 txt_chains = ['1', '2', '4', '6', '8', '10']
1100 txt_nodes = ['1', '2', '4', '6', '8', '10']
1101 chains = [i + 1 for i in range(len(txt_chains))]
1102 nodes = [i + 1 for i in range(len(txt_nodes))]
1104 [6.3, 6.3, 6.3, 6.4, 6.5, 6.4],
1105 [5.8, 5.6, 5.6, 5.6, 5.5, None],
1106 [5.6, 5.5, 5.3, None, None, None],
1107 [5.4, 5.3, None, None, None, None],
1108 [5.4, 5.2, None, None, None, None],
1109 [5.3, None, None, None, None, None]
1113 annotations = list()
1117 for c in range(len(txt_chains)):
1119 for n in range(len(txt_nodes)):
1120 if data[c][n] is not None:
1121 annotations.append(dict(
1128 text=str(data[c][n]),
1135 hover_line.append(text.format(name="Testcase Name"))
1136 hovertext.append(hover_line)
1139 plgo.Heatmap(x=nodes,
1143 title="Packet Throughput [Mpps]",
1155 for idx, item in enumerate(txt_nodes):
1156 annotations.append(dict(
1170 for idx, item in enumerate(txt_chains):
1171 annotations.append(dict(
1186 annotations.append(dict(
1193 text="<b>No. of Network Functions per Service Instance</b>",
1201 annotations.append(dict(
1208 text="<b>No. of Service Instances</b>",
1216 updatemenus = list([
1225 args=[{"colorscale": "Reds", "reversescale": False}],
1230 args=[{"colorscale": "Blues", "reversescale": True}],
1235 args=[{"colorscale": "Greys", "reversescale": True}],
1240 args=[{"colorscale": "Greens", "reversescale": True}],
1245 args=[{"colorscale": "RdBu", "reversescale": False}],
1250 args=[{"colorscale": "Picnic", "reversescale": False}],
1255 args=[{"colorscale": "Rainbow", "reversescale": False}],
1260 args=[{"colorscale": "Portland", "reversescale": False}],
1265 args=[{"colorscale": "Jet", "reversescale": False}],
1270 args=[{"colorscale": "Hot", "reversescale": True}],
1275 args=[{"colorscale": "Blackbody", "reversescale": True}],
1280 args=[{"colorscale": "Earth", "reversescale": True}],
1285 args=[{"colorscale": "Electric", "reversescale": True}],
1290 args=[{"colorscale": "Viridis", "reversescale": True}],
1295 args=[{"colorscale": "Cividis", "reversescale": True}],
1304 layout = deepcopy(plot["layout"])
1305 except KeyError as err:
1306 logging.error("Finished with error: No layout defined")
1307 logging.error(repr(err))
1310 layout["annotations"] = annotations
1311 layout['updatemenus'] = updatemenus
1315 plpl = plgo.Figure(data=traces, layout=layout)
1318 logging.info(" Writing file '{0}{1}'.".
1319 format(plot["output-file"], plot["output-file-type"]))
1320 ploff.plot(plpl, show_link=False, auto_open=False,
1321 filename='{0}{1}'.format(plot["output-file"],
1322 plot["output-file-type"]))
1323 except PlotlyError as err:
1324 logging.error(" Finished with error: {}".
1325 format(str(err).replace("\n", " ")))