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.
21 import plotly.offline as ploff
22 import plotly.graph_objs as plgo
24 from plotly.exceptions import PlotlyError
25 from collections import OrderedDict
26 from copy import deepcopy
28 from utils import mean, stdev
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"]
39 def generate_plots(spec, data):
40 """Generate all plots specified in the specification file.
42 :param spec: Specification read from the specification file.
43 :param data: Data to process.
44 :type spec: Specification
48 logging.info("Generating the plots ...")
49 for index, plot in enumerate(spec.plots):
51 logging.info(" Plot nr {0}: {1}".format(index + 1,
52 plot.get("title", "")))
53 plot["limits"] = spec.configuration["limits"]
54 eval(plot["algorithm"])(plot, data)
55 logging.info(" Done.")
56 except NameError as err:
57 logging.error("Probably algorithm '{alg}' is not defined: {err}".
58 format(alg=plot["algorithm"], err=repr(err)))
62 def plot_performance_box(plot, input_data):
63 """Generate the plot(s) with algorithm: plot_performance_box
64 specified in the specification file.
66 :param plot: Plot to generate.
67 :param input_data: Data to process.
68 :type plot: pandas.Series
69 :type input_data: InputData
73 plot_title = plot.get("title", "")
74 logging.info(" Creating the data set for the {0} '{1}'.".
75 format(plot.get("type", ""), plot_title))
76 data = input_data.filter_data(plot)
78 logging.error("No data.")
81 # Prepare the data for the plot
87 if y_vals.get(test["parent"], None) is None:
88 y_vals[test["parent"]] = list()
89 y_tags[test["parent"]] = test.get("tags", None)
91 if test["type"] in ("NDRPDR", ):
92 if "-pdr" in plot_title.lower():
93 y_vals[test["parent"]].\
94 append(test["throughput"]["PDR"]["LOWER"])
95 elif "-ndr" in plot_title.lower():
96 y_vals[test["parent"]]. \
97 append(test["throughput"]["NDR"]["LOWER"])
102 except (KeyError, TypeError):
103 y_vals[test["parent"]].append(None)
106 order = plot.get("sort", None)
108 y_sorted = OrderedDict()
109 y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
112 for suite, tags in y_tags_l.items():
114 tag = tag.split(" ")[-1]
115 if tag.lower() in tags:
118 if tag.lower() not in tags:
121 y_sorted[suite] = y_vals.pop(suite)
124 except KeyError as err:
125 logging.error("Not found: {0}".format(repr(err)))
131 # Add None to the lists with missing data
133 nr_of_samples = list()
134 for val in y_sorted.values():
135 if len(val) > max_len:
137 nr_of_samples.append(len(val))
138 for key, val in y_sorted.items():
139 if len(val) < max_len:
140 val.extend([None for _ in range(max_len - len(val))])
144 df = pd.DataFrame(y_sorted)
147 for i, col in enumerate(df.columns):
148 name = "{nr}. ({samples:02d} run{plural}) {name}".\
150 samples=nr_of_samples[i],
151 plural='s' if nr_of_samples[i] > 1 else '',
152 name=col.lower().replace('-ndrpdr', ''))
154 name_lst = name.split('-')
157 for segment in name_lst:
158 if (len(name) + len(segment) + 1) > 50 and split_name:
161 name += segment + '-'
165 traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
166 y=[y / 1000000 if y else None for y in df[col]],
170 val_max = max(df[col])
171 except ValueError as err:
172 logging.error(repr(err))
175 y_max.append(int(val_max / 1000000) + 1)
179 layout = deepcopy(plot["layout"])
180 if layout.get("title", None):
181 layout["title"] = "<b>Packet Throughput:</b> {0}". \
182 format(layout["title"])
184 layout["yaxis"]["range"] = [0, max(y_max)]
185 plpl = plgo.Figure(data=traces, layout=layout)
188 logging.info(" Writing file '{0}{1}'.".
189 format(plot["output-file"], plot["output-file-type"]))
190 ploff.plot(plpl, show_link=False, auto_open=False,
191 filename='{0}{1}'.format(plot["output-file"],
192 plot["output-file-type"]))
193 except PlotlyError as err:
194 logging.error(" Finished with error: {}".
195 format(repr(err).replace("\n", " ")))
199 def plot_soak_bars(plot, input_data):
200 """Generate the plot(s) with algorithm: plot_soak_bars
201 specified in the specification file.
203 :param plot: Plot to generate.
204 :param input_data: Data to process.
205 :type plot: pandas.Series
206 :type input_data: InputData
210 plot_title = plot.get("title", "")
211 logging.info(" Creating the data set for the {0} '{1}'.".
212 format(plot.get("type", ""), plot_title))
213 data = input_data.filter_data(plot)
215 logging.error("No data.")
218 # Prepare the data for the plot
224 if y_vals.get(test["parent"], None) is None:
225 y_tags[test["parent"]] = test.get("tags", None)
227 if test["type"] in ("SOAK", ):
228 y_vals[test["parent"]] = test["throughput"]
231 except (KeyError, TypeError):
232 y_vals[test["parent"]] = dict()
235 order = plot.get("sort", None)
237 y_sorted = OrderedDict()
238 y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
241 for suite, tags in y_tags_l.items():
243 tag = tag.split(" ")[-1]
244 if tag.lower() in tags:
247 if tag.lower() not in tags:
250 y_sorted[suite] = y_vals.pop(suite)
253 except KeyError as err:
254 logging.error("Not found: {0}".format(repr(err)))
263 for test_name, test_data in y_sorted.items():
265 name = "{nr}. {name}".\
266 format(nr=idx, name=test_name.lower().replace('-soak', ''))
268 name_lst = name.split('-')
271 for segment in name_lst:
272 if (len(name) + len(segment) + 1) > 50 and split_name:
275 name += segment + '-'
278 y_val = test_data.get("LOWER", None)
284 time = "No Information"
285 result = "No Information"
286 hovertext = ("{name}<br>"
287 "Packet Throughput: {val:.2f}Mpps<br>"
288 "Final Duration: {time}<br>"
289 "Result: {result}".format(name=name,
293 traces.append(plgo.Bar(x=[str(idx) + '.', ],
300 layout = deepcopy(plot["layout"])
301 if layout.get("title", None):
302 layout["title"] = "<b>Packet Throughput:</b> {0}". \
303 format(layout["title"])
305 layout["yaxis"]["range"] = [0, y_max + 1]
306 plpl = plgo.Figure(data=traces, layout=layout)
308 logging.info(" Writing file '{0}{1}'.".
309 format(plot["output-file"], plot["output-file-type"]))
310 ploff.plot(plpl, show_link=False, auto_open=False,
311 filename='{0}{1}'.format(plot["output-file"],
312 plot["output-file-type"]))
313 except PlotlyError as err:
314 logging.error(" Finished with error: {}".
315 format(repr(err).replace("\n", " ")))
319 def plot_soak_boxes(plot, input_data):
320 """Generate the plot(s) with algorithm: plot_soak_boxes
321 specified in the specification file.
323 :param plot: Plot to generate.
324 :param input_data: Data to process.
325 :type plot: pandas.Series
326 :type input_data: InputData
330 plot_title = plot.get("title", "")
331 logging.info(" Creating the data set for the {0} '{1}'.".
332 format(plot.get("type", ""), plot_title))
333 data = input_data.filter_data(plot)
335 logging.error("No data.")
338 # Prepare the data for the plot
344 if y_vals.get(test["parent"], None) is None:
345 y_tags[test["parent"]] = test.get("tags", None)
347 if test["type"] in ("SOAK", ):
348 y_vals[test["parent"]] = test["throughput"]
351 except (KeyError, TypeError):
352 y_vals[test["parent"]] = dict()
355 order = plot.get("sort", None)
357 y_sorted = OrderedDict()
358 y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
361 for suite, tags in y_tags_l.items():
363 tag = tag.split(" ")[-1]
364 if tag.lower() in tags:
367 if tag.lower() not in tags:
370 y_sorted[suite] = y_vals.pop(suite)
373 except KeyError as err:
374 logging.error("Not found: {0}".format(repr(err)))
383 for test_name, test_data in y_sorted.items():
385 name = "{nr}. {name}".\
386 format(nr=idx, name=test_name.lower().replace('-soak', ''))
388 name_lst = name.split('-')
391 for segment in name_lst:
392 if (len(name) + len(segment) + 1) > 50 and split_name:
395 name += segment + '-'
398 y_val = test_data.get("UPPER", None)
404 y_base = test_data.get("LOWER", None)
408 hovertext = ("{name}<br>"
409 "Upper bound: {upper:.2f}Mpps<br>"
410 "Lower bound: {lower:.2f}Mpps".format(name=name,
413 traces.append(plgo.Bar(x=[str(idx) + '.', ],
414 # +0.05 to see the value in case lower == upper
415 y=[y_val - y_base + 0.05, ],
422 layout = deepcopy(plot["layout"])
423 if layout.get("title", None):
424 layout["title"] = "<b>Soak Tests:</b> {0}". \
425 format(layout["title"])
427 layout["yaxis"]["range"] = [0, y_max + 1]
428 plpl = plgo.Figure(data=traces, layout=layout)
430 logging.info(" Writing file '{0}{1}'.".
431 format(plot["output-file"], plot["output-file-type"]))
432 ploff.plot(plpl, show_link=False, auto_open=False,
433 filename='{0}{1}'.format(plot["output-file"],
434 plot["output-file-type"]))
435 except PlotlyError as err:
436 logging.error(" Finished with error: {}".
437 format(repr(err).replace("\n", " ")))
441 def plot_latency_error_bars(plot, input_data):
442 """Generate the plot(s) with algorithm: plot_latency_error_bars
443 specified in the specification file.
445 :param plot: Plot to generate.
446 :param input_data: Data to process.
447 :type plot: pandas.Series
448 :type input_data: InputData
452 plot_title = plot.get("title", "")
453 logging.info(" Creating the data set for the {0} '{1}'.".
454 format(plot.get("type", ""), plot_title))
455 data = input_data.filter_data(plot)
457 logging.error("No data.")
460 # Prepare the data for the plot
467 logging.debug("test['latency']: {0}\n".
468 format(test["latency"]))
469 except ValueError as err:
470 logging.warning(repr(err))
471 if y_tmp_vals.get(test["parent"], None) is None:
472 y_tmp_vals[test["parent"]] = [
473 list(), # direction1, min
474 list(), # direction1, avg
475 list(), # direction1, max
476 list(), # direction2, min
477 list(), # direction2, avg
478 list() # direction2, max
480 y_tags[test["parent"]] = test.get("tags", None)
482 if test["type"] in ("NDRPDR", ):
483 if "-pdr" in plot_title.lower():
485 elif "-ndr" in plot_title.lower():
488 logging.warning("Invalid test type: {0}".
489 format(test["type"]))
491 y_tmp_vals[test["parent"]][0].append(
492 test["latency"][ttype]["direction1"]["min"])
493 y_tmp_vals[test["parent"]][1].append(
494 test["latency"][ttype]["direction1"]["avg"])
495 y_tmp_vals[test["parent"]][2].append(
496 test["latency"][ttype]["direction1"]["max"])
497 y_tmp_vals[test["parent"]][3].append(
498 test["latency"][ttype]["direction2"]["min"])
499 y_tmp_vals[test["parent"]][4].append(
500 test["latency"][ttype]["direction2"]["avg"])
501 y_tmp_vals[test["parent"]][5].append(
502 test["latency"][ttype]["direction2"]["max"])
504 logging.warning("Invalid test type: {0}".
505 format(test["type"]))
507 except (KeyError, TypeError) as err:
508 logging.warning(repr(err))
509 logging.debug("y_tmp_vals: {0}\n".format(y_tmp_vals))
512 order = plot.get("sort", None)
514 y_sorted = OrderedDict()
515 y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
518 for suite, tags in y_tags_l.items():
520 tag = tag.split(" ")[-1]
521 if tag.lower() in tags:
524 if tag.lower() not in tags:
527 y_sorted[suite] = y_tmp_vals.pop(suite)
530 except KeyError as err:
531 logging.error("Not found: {0}".format(repr(err)))
535 y_sorted = y_tmp_vals
537 logging.debug("y_sorted: {0}\n".format(y_sorted))
542 nr_of_samples = list()
543 for key, val in y_sorted.items():
544 name = "-".join(key.split("-")[1:-1])
546 name_lst = name.split('-')
549 for segment in name_lst:
550 if (len(name) + len(segment) + 1) > 50 and split_name:
553 name += segment + '-'
555 x_vals.append(name) # dir 1
556 y_vals.append(mean(val[1]) if val[1] else None)
557 y_mins.append(mean(val[0]) if val[0] else None)
558 y_maxs.append(mean(val[2]) if val[2] else None)
559 nr_of_samples.append(len(val[1]) if val[1] else 0)
560 x_vals.append(name) # dir 2
561 y_vals.append(mean(val[4]) if val[4] else None)
562 y_mins.append(mean(val[3]) if val[3] else None)
563 y_maxs.append(mean(val[5]) if val[5] else None)
564 nr_of_samples.append(len(val[3]) if val[3] else 0)
566 logging.debug("x_vals :{0}\n".format(x_vals))
567 logging.debug("y_vals :{0}\n".format(y_vals))
568 logging.debug("y_mins :{0}\n".format(y_mins))
569 logging.debug("y_maxs :{0}\n".format(y_maxs))
570 logging.debug("nr_of_samples :{0}\n".format(nr_of_samples))
574 for idx in range(len(x_vals)):
575 if not bool(int(idx % 2)):
576 direction = "West-East"
578 direction = "East-West"
579 hovertext = ("No. of Runs: {nr}<br>"
581 "Direction: {dir}<br>".format(test=x_vals[idx],
583 nr=nr_of_samples[idx]))
584 if isinstance(y_maxs[idx], float):
585 hovertext += "Max: {max:.2f}uSec<br>".format(max=y_maxs[idx])
586 if isinstance(y_vals[idx], float):
587 hovertext += "Mean: {avg:.2f}uSec<br>".format(avg=y_vals[idx])
588 if isinstance(y_mins[idx], float):
589 hovertext += "Min: {min:.2f}uSec".format(min=y_mins[idx])
591 if isinstance(y_maxs[idx], float) and isinstance(y_vals[idx], float):
592 array = [y_maxs[idx] - y_vals[idx], ]
595 if isinstance(y_mins[idx], float) and isinstance(y_vals[idx], float):
596 arrayminus = [y_vals[idx] - y_mins[idx], ]
598 arrayminus = [None, ]
599 logging.debug("y_vals[{1}] :{0}\n".format(y_vals[idx], idx))
600 logging.debug("array :{0}\n".format(array))
601 logging.debug("arrayminus :{0}\n".format(arrayminus))
602 traces.append(plgo.Scatter(
606 legendgroup=x_vals[idx],
607 showlegend=bool(int(idx % 2)),
613 arrayminus=arrayminus,
614 color=COLORS[int(idx / 2)]
618 color=COLORS[int(idx / 2)],
623 annotations.append(dict(
630 text="E-W" if bool(int(idx % 2)) else "W-E",
640 logging.info(" Writing file '{0}{1}'.".
641 format(plot["output-file"], plot["output-file-type"]))
642 layout = deepcopy(plot["layout"])
643 if layout.get("title", None):
644 layout["title"] = "<b>Packet Latency:</b> {0}".\
645 format(layout["title"])
646 layout["annotations"] = annotations
647 plpl = plgo.Figure(data=traces, layout=layout)
651 show_link=False, auto_open=False,
652 filename='{0}{1}'.format(plot["output-file"],
653 plot["output-file-type"]))
654 except PlotlyError as err:
655 logging.error(" Finished with error: {}".
656 format(str(err).replace("\n", " ")))
660 def plot_throughput_speedup_analysis(plot, input_data):
661 """Generate the plot(s) with algorithm:
662 plot_throughput_speedup_analysis
663 specified in the specification file.
665 :param plot: Plot to generate.
666 :param input_data: Data to process.
667 :type plot: pandas.Series
668 :type input_data: InputData
672 plot_title = plot.get("title", "")
673 logging.info(" Creating the data set for the {0} '{1}'.".
674 format(plot.get("type", ""), plot_title))
675 data = input_data.filter_data(plot)
677 logging.error("No data.")
685 if y_vals.get(test["parent"], None) is None:
686 y_vals[test["parent"]] = {"1": list(),
689 y_tags[test["parent"]] = test.get("tags", None)
691 if test["type"] in ("NDRPDR",):
692 if "-pdr" in plot_title.lower():
694 elif "-ndr" in plot_title.lower():
698 if "1C" in test["tags"]:
699 y_vals[test["parent"]]["1"]. \
700 append(test["throughput"][ttype]["LOWER"])
701 elif "2C" in test["tags"]:
702 y_vals[test["parent"]]["2"]. \
703 append(test["throughput"][ttype]["LOWER"])
704 elif "4C" in test["tags"]:
705 y_vals[test["parent"]]["4"]. \
706 append(test["throughput"][ttype]["LOWER"])
707 except (KeyError, TypeError):
711 logging.warning("No data for the plot '{}'".
712 format(plot.get("title", "")))
716 for test_name, test_vals in y_vals.items():
717 for key, test_val in test_vals.items():
719 avg_val = sum(test_val) / len(test_val)
720 y_vals[test_name][key] = (avg_val, len(test_val))
721 ideal = avg_val / (int(key) * 1000000.0)
722 if test_name not in y_1c_max or ideal > y_1c_max[test_name]:
723 y_1c_max[test_name] = ideal
729 pci_limit = plot["limits"]["pci"]["pci-g3-x8"]
730 for test_name, test_vals in y_vals.items():
732 if test_vals["1"][1]:
733 name = "-".join(test_name.split('-')[1:-1])
735 name_lst = name.split('-')
738 for segment in name_lst:
739 if (len(name) + len(segment) + 1) > 50 and split_name:
742 name += segment + '-'
746 y_val_1 = test_vals["1"][0] / 1000000.0
747 y_val_2 = test_vals["2"][0] / 1000000.0 if test_vals["2"][0] \
749 y_val_4 = test_vals["4"][0] / 1000000.0 if test_vals["4"][0] \
752 vals[name]["val"] = [y_val_1, y_val_2, y_val_4]
753 vals[name]["rel"] = [1.0, None, None]
754 vals[name]["ideal"] = [y_1c_max[test_name],
755 y_1c_max[test_name] * 2,
756 y_1c_max[test_name] * 4]
757 vals[name]["diff"] = [(y_val_1 - y_1c_max[test_name]) * 100 /
759 vals[name]["count"] = [test_vals["1"][1],
764 val_max = max(max(vals[name]["val"], vals[name]["ideal"]))
765 except ValueError as err:
769 y_max.append(int((val_max / 10) + 1) * 10)
772 vals[name]["rel"][1] = round(y_val_2 / y_val_1, 2)
773 vals[name]["diff"][1] = \
774 (y_val_2 - vals[name]["ideal"][1]) * 100 / y_val_2
776 vals[name]["rel"][2] = round(y_val_4 / y_val_1, 2)
777 vals[name]["diff"][2] = \
778 (y_val_4 - vals[name]["ideal"][2]) * 100 / y_val_4
779 except IndexError as err:
780 logging.warning("No data for '{0}'".format(test_name))
781 logging.warning(repr(err))
784 if "x520" in test_name:
785 limit = plot["limits"]["nic"]["x520"]
786 elif "x710" in test_name:
787 limit = plot["limits"]["nic"]["x710"]
788 elif "xxv710" in test_name:
789 limit = plot["limits"]["nic"]["xxv710"]
790 elif "xl710" in test_name:
791 limit = plot["limits"]["nic"]["xl710"]
792 elif "x553" in test_name:
793 limit = plot["limits"]["nic"]["x553"]
796 if limit > nic_limit:
799 mul = 2 if "ge2p" in test_name else 1
800 if "10ge" in test_name:
801 limit = plot["limits"]["link"]["10ge"] * mul
802 elif "25ge" in test_name:
803 limit = plot["limits"]["link"]["25ge"] * mul
804 elif "40ge" in test_name:
805 limit = plot["limits"]["link"]["40ge"] * mul
806 elif "100ge" in test_name:
807 limit = plot["limits"]["link"]["100ge"] * mul
810 if limit > lnk_limit:
814 order = plot.get("sort", None)
816 y_sorted = OrderedDict()
817 y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
819 for test, tags in y_tags_l.items():
820 if tag.lower() in tags:
821 name = "-".join(test.split('-')[1:-1])
823 y_sorted[name] = vals.pop(name)
825 except KeyError as err:
826 logging.error("Not found: {0}".format(err))
838 threshold = 1.1 * max(y_max) # 10%
839 except ValueError as err:
842 nic_limit /= 1000000.0
843 if nic_limit < threshold:
844 traces.append(plgo.Scatter(
846 y=[nic_limit, ] * len(x_vals),
847 name="NIC: {0:.2f}Mpps".format(nic_limit),
856 annotations.append(dict(
863 text="NIC: {0:.2f}Mpps".format(nic_limit),
871 y_max.append(int((nic_limit / 10) + 1) * 10)
873 lnk_limit /= 1000000.0
874 if lnk_limit < threshold:
875 traces.append(plgo.Scatter(
877 y=[lnk_limit, ] * len(x_vals),
878 name="Link: {0:.2f}Mpps".format(lnk_limit),
887 annotations.append(dict(
894 text="Link: {0:.2f}Mpps".format(lnk_limit),
902 y_max.append(int((lnk_limit / 10) + 1) * 10)
904 pci_limit /= 1000000.0
905 if pci_limit < threshold:
906 traces.append(plgo.Scatter(
908 y=[pci_limit, ] * len(x_vals),
909 name="PCIe: {0:.2f}Mpps".format(pci_limit),
918 annotations.append(dict(
925 text="PCIe: {0:.2f}Mpps".format(pci_limit),
933 y_max.append(int((pci_limit / 10) + 1) * 10)
935 # Perfect and measured:
937 for name, val in y_sorted.iteritems():
940 for idx in range(len(val["val"])):
942 if isinstance(val["val"][idx], float):
943 htext += "No. of Runs: {1}<br>" \
944 "Mean: {0:.2f}Mpps<br>".format(val["val"][idx],
946 if isinstance(val["diff"][idx], float):
947 htext += "Diff: {0:.0f}%<br>".format(round(val["diff"][idx]))
948 if isinstance(val["rel"][idx], float):
949 htext += "Speedup: {0:.2f}".format(val["rel"][idx])
950 hovertext.append(htext)
951 traces.append(plgo.Scatter(x=x_vals,
955 mode="lines+markers",
964 hoverinfo="text+name"
966 traces.append(plgo.Scatter(x=x_vals,
968 name="{0} perfect".format(name),
976 text=["Perfect: {0:.2f}Mpps".format(y)
977 for y in val["ideal"]],
981 except (IndexError, ValueError, KeyError) as err:
982 logging.warning("No data for '{0}'".format(name))
983 logging.warning(repr(err))
987 logging.info(" Writing file '{0}{1}'.".
988 format(plot["output-file"], plot["output-file-type"]))
989 layout = deepcopy(plot["layout"])
990 if layout.get("title", None):
991 layout["title"] = "<b>Speedup Multi-core:</b> {0}". \
992 format(layout["title"])
993 layout["annotations"].extend(annotations)
994 plpl = plgo.Figure(data=traces, layout=layout)
998 show_link=False, auto_open=False,
999 filename='{0}{1}'.format(plot["output-file"],
1000 plot["output-file-type"]))
1001 except PlotlyError as err:
1002 logging.error(" Finished with error: {}".
1003 format(str(err).replace("\n", " ")))
1007 def plot_http_server_performance_box(plot, input_data):
1008 """Generate the plot(s) with algorithm: plot_http_server_performance_box
1009 specified in the specification file.
1011 :param plot: Plot to generate.
1012 :param input_data: Data to process.
1013 :type plot: pandas.Series
1014 :type input_data: InputData
1017 # Transform the data
1018 logging.info(" Creating the data set for the {0} '{1}'.".
1019 format(plot.get("type", ""), plot.get("title", "")))
1020 data = input_data.filter_data(plot)
1022 logging.error("No data.")
1025 # Prepare the data for the plot
1030 if y_vals.get(test["name"], None) is None:
1031 y_vals[test["name"]] = list()
1033 y_vals[test["name"]].append(test["result"])
1034 except (KeyError, TypeError):
1035 y_vals[test["name"]].append(None)
1037 # Add None to the lists with missing data
1039 nr_of_samples = list()
1040 for val in y_vals.values():
1041 if len(val) > max_len:
1043 nr_of_samples.append(len(val))
1044 for key, val in y_vals.items():
1045 if len(val) < max_len:
1046 val.extend([None for _ in range(max_len - len(val))])
1050 df = pd.DataFrame(y_vals)
1052 for i, col in enumerate(df.columns):
1053 name = "{nr}. ({samples:02d} run{plural}) {name}".\
1055 samples=nr_of_samples[i],
1056 plural='s' if nr_of_samples[i] > 1 else '',
1057 name=col.lower().replace('-ndrpdr', ''))
1059 name_lst = name.split('-')
1062 for segment in name_lst:
1063 if (len(name) + len(segment) + 1) > 50 and split_name:
1066 name += segment + '-'
1069 traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
1075 plpl = plgo.Figure(data=traces, layout=plot["layout"])
1078 logging.info(" Writing file '{0}{1}'.".
1079 format(plot["output-file"], plot["output-file-type"]))
1080 ploff.plot(plpl, show_link=False, auto_open=False,
1081 filename='{0}{1}'.format(plot["output-file"],
1082 plot["output-file-type"]))
1083 except PlotlyError as err:
1084 logging.error(" Finished with error: {}".
1085 format(str(err).replace("\n", " ")))
1089 def plot_service_density_heatmap(plot, input_data):
1090 """Generate the plot(s) with algorithm: plot_service_density_heatmap
1091 specified in the specification file.
1093 :param plot: Plot to generate.
1094 :param input_data: Data to process.
1095 :type plot: pandas.Series
1096 :type input_data: InputData
1099 REGEX_CN = re.compile(r'^(\d*)R(\d*)C$')
1105 # Transform the data
1106 logging.info(" Creating the data set for the {0} '{1}'.".
1107 format(plot.get("type", ""), plot.get("title", "")))
1108 data = input_data.filter_data(plot, continue_on_error=True)
1110 logging.error("No data.")
1116 for tag in test['tags']:
1117 groups = re.search(REGEX_CN, tag)
1119 c = str(groups.group(1))
1120 n = str(groups.group(2))
1124 if vals.get(c, None) is None:
1126 if vals[c].get(n, None) is None:
1127 vals[c][n] = dict(name=test["name"],
1132 if plot["include-tests"] == "MRR":
1133 result = test["result"]["receive-rate"].avg
1134 elif plot["include-tests"] == "PDR":
1135 result = test["throughput"]["PDR"]["LOWER"]
1136 elif plot["include-tests"] == "NDR":
1137 result = test["throughput"]["NDR"]["LOWER"]
1142 vals[c][n]["vals"].append(result)
1144 for key_c in vals.keys():
1145 txt_chains.append(key_c)
1146 for key_n in vals[key_c].keys():
1147 txt_nodes.append(key_n)
1148 if vals[key_c][key_n]["vals"]:
1149 vals[key_c][key_n]["nr"] = len(vals[key_c][key_n]["vals"])
1150 vals[key_c][key_n]["mean"] = \
1151 round(mean(vals[key_c][key_n]["vals"]) / 1000000, 2)
1152 vals[key_c][key_n]["stdev"] = \
1153 round(stdev(vals[key_c][key_n]["vals"]) / 1000000, 2)
1154 txt_nodes = list(set(txt_nodes))
1156 txt_chains = sorted(txt_chains, key=lambda chain: int(chain))
1157 txt_nodes = sorted(txt_nodes, key=lambda node: int(node))
1159 chains = [i + 1 for i in range(len(txt_chains))]
1160 nodes = [i + 1 for i in range(len(txt_nodes))]
1162 data = [list() for _ in range(len(chains))]
1166 val = vals[txt_chains[c - 1]][txt_nodes[n - 1]]["mean"]
1167 except (KeyError, IndexError):
1169 data[c - 1].append(val)
1172 annotations = list()
1174 text = ("{name}<br>"
1175 "No. of Samples: {nr}<br>"
1176 "Throughput: {val}<br>"
1179 for c in range(len(txt_chains)):
1181 for n in range(len(txt_nodes)):
1182 if data[c][n] is not None:
1183 annotations.append(dict(
1190 text=str(data[c][n]),
1197 hover_line.append(text.format(
1198 name=vals[txt_chains[c]][txt_nodes[n]]["name"],
1199 nr=vals[txt_chains[c]][txt_nodes[n]]["nr"],
1201 stdev=vals[txt_chains[c]][txt_nodes[n]]["stdev"]))
1202 hovertext.append(hover_line)
1205 plgo.Heatmap(x=nodes,
1209 title="Packet Throughput [Mpps]",
1221 for idx, item in enumerate(txt_nodes):
1222 annotations.append(dict(
1236 for idx, item in enumerate(txt_chains):
1237 annotations.append(dict(
1252 annotations.append(dict(
1259 text="<b>No. of Network Functions per Service Instance</b>",
1267 annotations.append(dict(
1274 text="<b>No. of Service Instances</b>",
1282 updatemenus = list([
1291 args=[{"colorscale": "Reds", "reversescale": False}],
1296 args=[{"colorscale": "Blues", "reversescale": True}],
1301 args=[{"colorscale": "Greys", "reversescale": True}],
1306 args=[{"colorscale": "Greens", "reversescale": True}],
1311 args=[{"colorscale": "RdBu", "reversescale": False}],
1316 args=[{"colorscale": "Picnic", "reversescale": False}],
1321 args=[{"colorscale": "Rainbow", "reversescale": False}],
1326 args=[{"colorscale": "Portland", "reversescale": False}],
1331 args=[{"colorscale": "Jet", "reversescale": False}],
1336 args=[{"colorscale": "Hot", "reversescale": True}],
1341 args=[{"colorscale": "Blackbody", "reversescale": True}],
1346 args=[{"colorscale": "Earth", "reversescale": True}],
1351 args=[{"colorscale": "Electric", "reversescale": True}],
1356 args=[{"colorscale": "Viridis", "reversescale": True}],
1361 args=[{"colorscale": "Cividis", "reversescale": True}],
1370 layout = deepcopy(plot["layout"])
1371 except KeyError as err:
1372 logging.error("Finished with error: No layout defined")
1373 logging.error(repr(err))
1376 layout["annotations"] = annotations
1377 layout['updatemenus'] = updatemenus
1381 plpl = plgo.Figure(data=traces, layout=layout)
1384 logging.info(" Writing file '{0}{1}'.".
1385 format(plot["output-file"], plot["output-file-type"]))
1386 ploff.plot(plpl, show_link=False, auto_open=False,
1387 filename='{0}{1}'.format(plot["output-file"],
1388 plot["output-file-type"]))
1389 except PlotlyError as err:
1390 logging.error(" Finished with error: {}".
1391 format(str(err).replace("\n", " ")))