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"]
38 REGEX_NIC = re.compile(r'\d*ge\dp\d\D*\d*-')
41 def generate_plots(spec, data):
42 """Generate all plots specified in the specification file.
44 :param spec: Specification read from the specification file.
45 :param data: Data to process.
46 :type spec: Specification
50 logging.info("Generating the plots ...")
51 for index, plot in enumerate(spec.plots):
53 logging.info(" Plot nr {0}: {1}".format(index + 1,
54 plot.get("title", "")))
55 plot["limits"] = spec.configuration["limits"]
56 eval(plot["algorithm"])(plot, data)
57 logging.info(" Done.")
58 except NameError as err:
59 logging.error("Probably algorithm '{alg}' is not defined: {err}".
60 format(alg=plot["algorithm"], err=repr(err)))
64 def plot_performance_box(plot, input_data):
65 """Generate the plot(s) with algorithm: plot_performance_box
66 specified in the specification file.
68 :param plot: Plot to generate.
69 :param input_data: Data to process.
70 :type plot: pandas.Series
71 :type input_data: InputData
75 plot_title = plot.get("title", "")
76 logging.info(" Creating the data set for the {0} '{1}'.".
77 format(plot.get("type", ""), plot_title))
78 data = input_data.filter_data(plot)
80 logging.error("No data.")
83 # Prepare the data for the plot
89 if y_vals.get(test["parent"], None) is None:
90 y_vals[test["parent"]] = list()
91 y_tags[test["parent"]] = test.get("tags", None)
93 if test["type"] in ("NDRPDR", ):
94 if "-pdr" in plot_title.lower():
95 y_vals[test["parent"]].\
96 append(test["throughput"]["PDR"]["LOWER"])
97 elif "-ndr" in plot_title.lower():
98 y_vals[test["parent"]]. \
99 append(test["throughput"]["NDR"]["LOWER"])
104 except (KeyError, TypeError):
105 y_vals[test["parent"]].append(None)
108 order = plot.get("sort", None)
110 y_sorted = OrderedDict()
111 y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
114 for suite, tags in y_tags_l.items():
116 tag = tag.split(" ")[-1]
117 if tag.lower() in tags:
120 if tag.lower() not in tags:
123 y_sorted[suite] = y_vals.pop(suite)
126 except KeyError as err:
127 logging.error("Not found: {0}".format(repr(err)))
133 # Add None to the lists with missing data
135 nr_of_samples = list()
136 for val in y_sorted.values():
137 if len(val) > max_len:
139 nr_of_samples.append(len(val))
140 for key, val in y_sorted.items():
141 if len(val) < max_len:
142 val.extend([None for _ in range(max_len - len(val))])
146 df = pd.DataFrame(y_sorted)
149 for i, col in enumerate(df.columns):
150 tst_name = re.sub(REGEX_NIC, "",
151 col.lower().replace('-ndrpdr', '').
152 replace('2n1l-', ''))
153 name = "{nr}. ({samples:02d} run{plural}) {name}".\
155 samples=nr_of_samples[i],
156 plural='s' if nr_of_samples[i] > 1 else '',
160 traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
161 y=[y / 1000000 if y else None for y in df[col]],
165 val_max = max(df[col])
166 except ValueError as err:
167 logging.error(repr(err))
170 y_max.append(int(val_max / 1000000) + 1)
174 layout = deepcopy(plot["layout"])
175 if layout.get("title", None):
176 layout["title"] = "<b>Throughput:</b> {0}". \
177 format(layout["title"])
179 layout["yaxis"]["range"] = [0, max(y_max)]
180 plpl = plgo.Figure(data=traces, layout=layout)
183 logging.info(" Writing file '{0}{1}'.".
184 format(plot["output-file"], plot["output-file-type"]))
185 ploff.plot(plpl, show_link=False, auto_open=False,
186 filename='{0}{1}'.format(plot["output-file"],
187 plot["output-file-type"]))
188 except PlotlyError as err:
189 logging.error(" Finished with error: {}".
190 format(repr(err).replace("\n", " ")))
194 def plot_soak_bars(plot, input_data):
195 """Generate the plot(s) with algorithm: plot_soak_bars
196 specified in the specification file.
198 :param plot: Plot to generate.
199 :param input_data: Data to process.
200 :type plot: pandas.Series
201 :type input_data: InputData
205 plot_title = plot.get("title", "")
206 logging.info(" Creating the data set for the {0} '{1}'.".
207 format(plot.get("type", ""), plot_title))
208 data = input_data.filter_data(plot)
210 logging.error("No data.")
213 # Prepare the data for the plot
219 if y_vals.get(test["parent"], None) is None:
220 y_tags[test["parent"]] = test.get("tags", None)
222 if test["type"] in ("SOAK", ):
223 y_vals[test["parent"]] = test["throughput"]
226 except (KeyError, TypeError):
227 y_vals[test["parent"]] = dict()
230 order = plot.get("sort", None)
232 y_sorted = OrderedDict()
233 y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
236 for suite, tags in y_tags_l.items():
238 tag = tag.split(" ")[-1]
239 if tag.lower() in tags:
242 if tag.lower() not in tags:
245 y_sorted[suite] = y_vals.pop(suite)
248 except KeyError as err:
249 logging.error("Not found: {0}".format(repr(err)))
258 for test_name, test_data in y_sorted.items():
260 name = "{nr}. {name}".\
261 format(nr=idx, name=test_name.lower().replace('-soak', ''))
263 name_lst = name.split('-')
266 for segment in name_lst:
267 if (len(name) + len(segment) + 1) > 50 and split_name:
270 name += segment + '-'
273 y_val = test_data.get("LOWER", None)
279 time = "No Information"
280 result = "No Information"
281 hovertext = ("{name}<br>"
282 "Packet Throughput: {val:.2f}Mpps<br>"
283 "Final Duration: {time}<br>"
284 "Result: {result}".format(name=name,
288 traces.append(plgo.Bar(x=[str(idx) + '.', ],
295 layout = deepcopy(plot["layout"])
296 if layout.get("title", None):
297 layout["title"] = "<b>Packet Throughput:</b> {0}". \
298 format(layout["title"])
300 layout["yaxis"]["range"] = [0, y_max + 1]
301 plpl = plgo.Figure(data=traces, layout=layout)
303 logging.info(" Writing file '{0}{1}'.".
304 format(plot["output-file"], plot["output-file-type"]))
305 ploff.plot(plpl, show_link=False, auto_open=False,
306 filename='{0}{1}'.format(plot["output-file"],
307 plot["output-file-type"]))
308 except PlotlyError as err:
309 logging.error(" Finished with error: {}".
310 format(repr(err).replace("\n", " ")))
314 def plot_soak_boxes(plot, input_data):
315 """Generate the plot(s) with algorithm: plot_soak_boxes
316 specified in the specification file.
318 :param plot: Plot to generate.
319 :param input_data: Data to process.
320 :type plot: pandas.Series
321 :type input_data: InputData
325 plot_title = plot.get("title", "")
326 logging.info(" Creating the data set for the {0} '{1}'.".
327 format(plot.get("type", ""), plot_title))
328 data = input_data.filter_data(plot)
330 logging.error("No data.")
333 # Prepare the data for the plot
339 if y_vals.get(test["parent"], None) is None:
340 y_tags[test["parent"]] = test.get("tags", None)
342 if test["type"] in ("SOAK", ):
343 y_vals[test["parent"]] = test["throughput"]
346 except (KeyError, TypeError):
347 y_vals[test["parent"]] = dict()
350 order = plot.get("sort", None)
352 y_sorted = OrderedDict()
353 y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
356 for suite, tags in y_tags_l.items():
358 tag = tag.split(" ")[-1]
359 if tag.lower() in tags:
362 if tag.lower() not in tags:
365 y_sorted[suite] = y_vals.pop(suite)
368 except KeyError as err:
369 logging.error("Not found: {0}".format(repr(err)))
378 for test_name, test_data in y_sorted.items():
380 name = "{nr}. {name}".\
381 format(nr=idx, name=test_name.lower().replace('-soak', '').
382 replace('2n1l-', ''))
384 name_lst = name.split('-')
387 for segment in name_lst:
388 if (len(name) + len(segment) + 1) > 55 and split_name:
391 name += segment + '-'
394 y_val = test_data.get("UPPER", None)
400 y_base = test_data.get("LOWER", None)
404 hovertext = ("Upper bound: {upper:.2f}<br>"
405 "Lower bound: {lower:.2f}".format(upper=y_val,
407 traces.append(plgo.Bar(x=[str(idx) + '.', ],
408 # +0.05 to see the value in case lower == upper
409 y=[y_val - y_base + 0.05, ],
416 layout = deepcopy(plot["layout"])
417 if layout.get("title", None):
418 layout["title"] = "<b>Throughput:</b> {0}". \
419 format(layout["title"])
421 layout["yaxis"]["range"] = [0, y_max + 1]
422 plpl = plgo.Figure(data=traces, layout=layout)
424 logging.info(" Writing file '{0}{1}'.".
425 format(plot["output-file"], plot["output-file-type"]))
426 ploff.plot(plpl, show_link=False, auto_open=False,
427 filename='{0}{1}'.format(plot["output-file"],
428 plot["output-file-type"]))
429 except PlotlyError as err:
430 logging.error(" Finished with error: {}".
431 format(repr(err).replace("\n", " ")))
435 def plot_latency_error_bars(plot, input_data):
436 """Generate the plot(s) with algorithm: plot_latency_error_bars
437 specified in the specification file.
439 :param plot: Plot to generate.
440 :param input_data: Data to process.
441 :type plot: pandas.Series
442 :type input_data: InputData
446 plot_title = plot.get("title", "")
447 logging.info(" Creating the data set for the {0} '{1}'.".
448 format(plot.get("type", ""), plot_title))
449 data = input_data.filter_data(plot)
451 logging.error("No data.")
454 # Prepare the data for the plot
461 logging.debug("test['latency']: {0}\n".
462 format(test["latency"]))
463 except ValueError as err:
464 logging.warning(repr(err))
465 if y_tmp_vals.get(test["parent"], None) is None:
466 y_tmp_vals[test["parent"]] = [
467 list(), # direction1, min
468 list(), # direction1, avg
469 list(), # direction1, max
470 list(), # direction2, min
471 list(), # direction2, avg
472 list() # direction2, max
474 y_tags[test["parent"]] = test.get("tags", None)
476 if test["type"] in ("NDRPDR", ):
477 if "-pdr" in plot_title.lower():
479 elif "-ndr" in plot_title.lower():
482 logging.warning("Invalid test type: {0}".
483 format(test["type"]))
485 y_tmp_vals[test["parent"]][0].append(
486 test["latency"][ttype]["direction1"]["min"])
487 y_tmp_vals[test["parent"]][1].append(
488 test["latency"][ttype]["direction1"]["avg"])
489 y_tmp_vals[test["parent"]][2].append(
490 test["latency"][ttype]["direction1"]["max"])
491 y_tmp_vals[test["parent"]][3].append(
492 test["latency"][ttype]["direction2"]["min"])
493 y_tmp_vals[test["parent"]][4].append(
494 test["latency"][ttype]["direction2"]["avg"])
495 y_tmp_vals[test["parent"]][5].append(
496 test["latency"][ttype]["direction2"]["max"])
498 logging.warning("Invalid test type: {0}".
499 format(test["type"]))
501 except (KeyError, TypeError) as err:
502 logging.warning(repr(err))
503 logging.debug("y_tmp_vals: {0}\n".format(y_tmp_vals))
506 order = plot.get("sort", None)
508 y_sorted = OrderedDict()
509 y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
512 for suite, tags in y_tags_l.items():
514 tag = tag.split(" ")[-1]
515 if tag.lower() in tags:
518 if tag.lower() not in tags:
521 y_sorted[suite] = y_tmp_vals.pop(suite)
524 except KeyError as err:
525 logging.error("Not found: {0}".format(repr(err)))
529 y_sorted = y_tmp_vals
531 logging.debug("y_sorted: {0}\n".format(y_sorted))
536 nr_of_samples = list()
537 for key, val in y_sorted.items():
538 name = re.sub(REGEX_NIC, "", key.replace('-ndrpdr', '').
539 replace('2n1l-', ''))
540 x_vals.append(name) # dir 1
541 y_vals.append(mean(val[1]) if val[1] else None)
542 y_mins.append(mean(val[0]) if val[0] else None)
543 y_maxs.append(mean(val[2]) if val[2] else None)
544 nr_of_samples.append(len(val[1]) if val[1] else 0)
545 x_vals.append(name) # dir 2
546 y_vals.append(mean(val[4]) if val[4] else None)
547 y_mins.append(mean(val[3]) if val[3] else None)
548 y_maxs.append(mean(val[5]) if val[5] else None)
549 nr_of_samples.append(len(val[3]) if val[3] else 0)
551 logging.debug("x_vals :{0}\n".format(x_vals))
552 logging.debug("y_vals :{0}\n".format(y_vals))
553 logging.debug("y_mins :{0}\n".format(y_mins))
554 logging.debug("y_maxs :{0}\n".format(y_maxs))
555 logging.debug("nr_of_samples :{0}\n".format(nr_of_samples))
559 for idx in range(len(x_vals)):
560 if not bool(int(idx % 2)):
561 direction = "West-East"
563 direction = "East-West"
564 hovertext = ("No. of Runs: {nr}<br>"
566 "Direction: {dir}<br>".format(test=x_vals[idx],
568 nr=nr_of_samples[idx]))
569 if isinstance(y_maxs[idx], float):
570 hovertext += "Max: {max:.2f}uSec<br>".format(max=y_maxs[idx])
571 if isinstance(y_vals[idx], float):
572 hovertext += "Mean: {avg:.2f}uSec<br>".format(avg=y_vals[idx])
573 if isinstance(y_mins[idx], float):
574 hovertext += "Min: {min:.2f}uSec".format(min=y_mins[idx])
576 if isinstance(y_maxs[idx], float) and isinstance(y_vals[idx], float):
577 array = [y_maxs[idx] - y_vals[idx], ]
580 if isinstance(y_mins[idx], float) and isinstance(y_vals[idx], float):
581 arrayminus = [y_vals[idx] - y_mins[idx], ]
583 arrayminus = [None, ]
584 logging.debug("y_vals[{1}] :{0}\n".format(y_vals[idx], idx))
585 logging.debug("array :{0}\n".format(array))
586 logging.debug("arrayminus :{0}\n".format(arrayminus))
587 traces.append(plgo.Scatter(
591 legendgroup=x_vals[idx],
592 showlegend=bool(int(idx % 2)),
598 arrayminus=arrayminus,
599 color=COLORS[int(idx / 2)]
603 color=COLORS[int(idx / 2)],
608 annotations.append(dict(
615 text="E-W" if bool(int(idx % 2)) else "W-E",
625 logging.info(" Writing file '{0}{1}'.".
626 format(plot["output-file"], plot["output-file-type"]))
627 layout = deepcopy(plot["layout"])
628 if layout.get("title", None):
629 layout["title"] = "<b>Latency:</b> {0}".\
630 format(layout["title"])
631 layout["annotations"] = annotations
632 plpl = plgo.Figure(data=traces, layout=layout)
636 show_link=False, auto_open=False,
637 filename='{0}{1}'.format(plot["output-file"],
638 plot["output-file-type"]))
639 except PlotlyError as err:
640 logging.error(" Finished with error: {}".
641 format(str(err).replace("\n", " ")))
645 def plot_throughput_speedup_analysis(plot, input_data):
646 """Generate the plot(s) with algorithm:
647 plot_throughput_speedup_analysis
648 specified in the specification file.
650 :param plot: Plot to generate.
651 :param input_data: Data to process.
652 :type plot: pandas.Series
653 :type input_data: InputData
657 plot_title = plot.get("title", "")
658 logging.info(" Creating the data set for the {0} '{1}'.".
659 format(plot.get("type", ""), plot_title))
660 data = input_data.filter_data(plot)
662 logging.error("No data.")
670 if y_vals.get(test["parent"], None) is None:
671 y_vals[test["parent"]] = {"1": list(),
674 y_tags[test["parent"]] = test.get("tags", None)
676 if test["type"] in ("NDRPDR",):
677 if "-pdr" in plot_title.lower():
679 elif "-ndr" in plot_title.lower():
683 if "1C" in test["tags"]:
684 y_vals[test["parent"]]["1"]. \
685 append(test["throughput"][ttype]["LOWER"])
686 elif "2C" in test["tags"]:
687 y_vals[test["parent"]]["2"]. \
688 append(test["throughput"][ttype]["LOWER"])
689 elif "4C" in test["tags"]:
690 y_vals[test["parent"]]["4"]. \
691 append(test["throughput"][ttype]["LOWER"])
692 except (KeyError, TypeError):
696 logging.warning("No data for the plot '{}'".
697 format(plot.get("title", "")))
701 for test_name, test_vals in y_vals.items():
702 for key, test_val in test_vals.items():
704 avg_val = sum(test_val) / len(test_val)
705 y_vals[test_name][key] = (avg_val, len(test_val))
706 ideal = avg_val / (int(key) * 1000000.0)
707 if test_name not in y_1c_max or ideal > y_1c_max[test_name]:
708 y_1c_max[test_name] = ideal
714 pci_limit = plot["limits"]["pci"]["pci-g3-x8"]
715 for test_name, test_vals in y_vals.items():
717 if test_vals["1"][1]:
718 name = re.sub(REGEX_NIC, "", test_name.replace('-ndrpdr', '').
719 replace('2n1l-', ''))
721 y_val_1 = test_vals["1"][0] / 1000000.0
722 y_val_2 = test_vals["2"][0] / 1000000.0 if test_vals["2"][0] \
724 y_val_4 = test_vals["4"][0] / 1000000.0 if test_vals["4"][0] \
727 vals[name]["val"] = [y_val_1, y_val_2, y_val_4]
728 vals[name]["rel"] = [1.0, None, None]
729 vals[name]["ideal"] = [y_1c_max[test_name],
730 y_1c_max[test_name] * 2,
731 y_1c_max[test_name] * 4]
732 vals[name]["diff"] = [(y_val_1 - y_1c_max[test_name]) * 100 /
734 vals[name]["count"] = [test_vals["1"][1],
739 # val_max = max(max(vals[name]["val"], vals[name]["ideal"]))
740 val_max = max(vals[name]["val"])
741 except ValueError as err:
745 # y_max.append(int((val_max / 10) + 1) * 10)
746 y_max.append(val_max)
749 vals[name]["rel"][1] = round(y_val_2 / y_val_1, 2)
750 vals[name]["diff"][1] = \
751 (y_val_2 - vals[name]["ideal"][1]) * 100 / y_val_2
753 vals[name]["rel"][2] = round(y_val_4 / y_val_1, 2)
754 vals[name]["diff"][2] = \
755 (y_val_4 - vals[name]["ideal"][2]) * 100 / y_val_4
756 except IndexError as err:
757 logging.warning("No data for '{0}'".format(test_name))
758 logging.warning(repr(err))
761 if "x520" in test_name:
762 limit = plot["limits"]["nic"]["x520"]
763 elif "x710" in test_name:
764 limit = plot["limits"]["nic"]["x710"]
765 elif "xxv710" in test_name:
766 limit = plot["limits"]["nic"]["xxv710"]
767 elif "xl710" in test_name:
768 limit = plot["limits"]["nic"]["xl710"]
769 elif "x553" in test_name:
770 limit = plot["limits"]["nic"]["x553"]
773 if limit > nic_limit:
776 mul = 2 if "ge2p" in test_name else 1
777 if "10ge" in test_name:
778 limit = plot["limits"]["link"]["10ge"] * mul
779 elif "25ge" in test_name:
780 limit = plot["limits"]["link"]["25ge"] * mul
781 elif "40ge" in test_name:
782 limit = plot["limits"]["link"]["40ge"] * mul
783 elif "100ge" in test_name:
784 limit = plot["limits"]["link"]["100ge"] * mul
787 if limit > lnk_limit:
791 order = plot.get("sort", None)
793 y_sorted = OrderedDict()
794 y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
796 for test, tags in y_tags_l.items():
797 if tag.lower() in tags:
798 name = re.sub(REGEX_NIC, "",
799 test.replace('-ndrpdr', '').
800 replace('2n1l-', ''))
802 y_sorted[name] = vals.pop(name)
804 except KeyError as err:
805 logging.error("Not found: {0}".format(err))
817 threshold = 1.1 * max(y_max) # 10%
818 except ValueError as err:
821 nic_limit /= 1000000.0
822 # if nic_limit < threshold:
823 traces.append(plgo.Scatter(
825 y=[nic_limit, ] * len(x_vals),
826 name="NIC: {0:.2f}Mpps".format(nic_limit),
835 annotations.append(dict(
842 text="NIC: {0:.2f}Mpps".format(nic_limit),
850 # y_max.append(int((nic_limit / 10) + 1) * 10)
851 y_max.append(nic_limit)
853 lnk_limit /= 1000000.0
854 if lnk_limit < threshold:
855 traces.append(plgo.Scatter(
857 y=[lnk_limit, ] * len(x_vals),
858 name="Link: {0:.2f}Mpps".format(lnk_limit),
867 annotations.append(dict(
874 text="Link: {0:.2f}Mpps".format(lnk_limit),
882 # y_max.append(int((lnk_limit / 10) + 1) * 10)
883 y_max.append(lnk_limit)
885 pci_limit /= 1000000.0
886 if (pci_limit < threshold and
887 (pci_limit < lnk_limit * 0.95 or lnk_limit > lnk_limit * 1.05)):
888 traces.append(plgo.Scatter(
890 y=[pci_limit, ] * len(x_vals),
891 name="PCIe: {0:.2f}Mpps".format(pci_limit),
900 annotations.append(dict(
907 text="PCIe: {0:.2f}Mpps".format(pci_limit),
915 # y_max.append(int((pci_limit / 10) + 1) * 10)
916 y_max.append(pci_limit)
918 # Perfect and measured:
920 for name, val in y_sorted.iteritems():
923 for idx in range(len(val["val"])):
925 if isinstance(val["val"][idx], float):
926 htext += "No. of Runs: {1}<br>" \
927 "Mean: {0:.2f}Mpps<br>".format(val["val"][idx],
929 if isinstance(val["diff"][idx], float):
930 htext += "Diff: {0:.0f}%<br>".format(round(val["diff"][idx]))
931 if isinstance(val["rel"][idx], float):
932 htext += "Speedup: {0:.2f}".format(val["rel"][idx])
933 hovertext.append(htext)
934 traces.append(plgo.Scatter(x=x_vals,
938 mode="lines+markers",
947 hoverinfo="text+name"
949 traces.append(plgo.Scatter(x=x_vals,
951 name="{0} perfect".format(name),
959 text=["Perfect: {0:.2f}Mpps".format(y)
960 for y in val["ideal"]],
964 except (IndexError, ValueError, KeyError) as err:
965 logging.warning("No data for '{0}'".format(name))
966 logging.warning(repr(err))
970 logging.info(" Writing file '{0}{1}'.".
971 format(plot["output-file"], plot["output-file-type"]))
972 layout = deepcopy(plot["layout"])
973 if layout.get("title", None):
974 layout["title"] = "<b>Speedup Multi-core:</b> {0}". \
975 format(layout["title"])
976 # layout["yaxis"]["range"] = [0, int((max(y_max) / 10) + 1) * 10]
977 layout["yaxis"]["range"] = [0, int(max(y_max) * 1.1)]
978 layout["annotations"].extend(annotations)
979 plpl = plgo.Figure(data=traces, layout=layout)
983 show_link=False, auto_open=False,
984 filename='{0}{1}'.format(plot["output-file"],
985 plot["output-file-type"]))
986 except PlotlyError as err:
987 logging.error(" Finished with error: {}".
988 format(str(err).replace("\n", " ")))
992 def plot_http_server_performance_box(plot, input_data):
993 """Generate the plot(s) with algorithm: plot_http_server_performance_box
994 specified in the specification file.
996 :param plot: Plot to generate.
997 :param input_data: Data to process.
998 :type plot: pandas.Series
999 :type input_data: InputData
1002 # Transform the data
1003 logging.info(" Creating the data set for the {0} '{1}'.".
1004 format(plot.get("type", ""), plot.get("title", "")))
1005 data = input_data.filter_data(plot)
1007 logging.error("No data.")
1010 # Prepare the data for the plot
1015 if y_vals.get(test["name"], None) is None:
1016 y_vals[test["name"]] = list()
1018 y_vals[test["name"]].append(test["result"])
1019 except (KeyError, TypeError):
1020 y_vals[test["name"]].append(None)
1022 # Add None to the lists with missing data
1024 nr_of_samples = list()
1025 for val in y_vals.values():
1026 if len(val) > max_len:
1028 nr_of_samples.append(len(val))
1029 for key, val in y_vals.items():
1030 if len(val) < max_len:
1031 val.extend([None for _ in range(max_len - len(val))])
1035 df = pd.DataFrame(y_vals)
1037 for i, col in enumerate(df.columns):
1038 name = "{nr}. ({samples:02d} run{plural}) {name}".\
1040 samples=nr_of_samples[i],
1041 plural='s' if nr_of_samples[i] > 1 else '',
1042 name=col.lower().replace('-ndrpdr', ''))
1044 name_lst = name.split('-')
1047 for segment in name_lst:
1048 if (len(name) + len(segment) + 1) > 50 and split_name:
1051 name += segment + '-'
1054 traces.append(plgo.Box(x=[str(i + 1) + '.'] * len(df[col]),
1060 plpl = plgo.Figure(data=traces, layout=plot["layout"])
1063 logging.info(" Writing file '{0}{1}'.".
1064 format(plot["output-file"], plot["output-file-type"]))
1065 ploff.plot(plpl, show_link=False, auto_open=False,
1066 filename='{0}{1}'.format(plot["output-file"],
1067 plot["output-file-type"]))
1068 except PlotlyError as err:
1069 logging.error(" Finished with error: {}".
1070 format(str(err).replace("\n", " ")))
1074 def plot_service_density_heatmap(plot, input_data):
1075 """Generate the plot(s) with algorithm: plot_service_density_heatmap
1076 specified in the specification file.
1078 :param plot: Plot to generate.
1079 :param input_data: Data to process.
1080 :type plot: pandas.Series
1081 :type input_data: InputData
1084 REGEX_CN = re.compile(r'^(\d*)R(\d*)C$')
1085 REGEX_TEST_NAME = re.compile(r'^.*-(\d+vhost|\d+memif)-'
1086 r'(\d+chain|\d+pipe)-'
1087 r'(\d+vm|\d+dcr|\d+drc).*$')
1093 # Transform the data
1094 logging.info(" Creating the data set for the {0} '{1}'.".
1095 format(plot.get("type", ""), plot.get("title", "")))
1096 data = input_data.filter_data(plot, continue_on_error=True)
1097 if data is None or data.empty:
1098 logging.error("No data.")
1104 for tag in test['tags']:
1105 groups = re.search(REGEX_CN, tag)
1107 c = str(groups.group(1))
1108 n = str(groups.group(2))
1112 groups = re.search(REGEX_TEST_NAME, test["name"])
1113 if groups and len(groups.groups()) == 3:
1114 hover_name = "{vhost}-{chain}-{vm}".format(
1115 vhost=str(groups.group(1)),
1116 chain=str(groups.group(2)),
1117 vm=str(groups.group(3)))
1120 if vals.get(c, None) is None:
1122 if vals[c].get(n, None) is None:
1123 vals[c][n] = dict(name=hover_name,
1129 if plot["include-tests"] == "MRR":
1130 result = test["result"]["receive-rate"].avg
1131 elif plot["include-tests"] == "PDR":
1132 result = test["throughput"]["PDR"]["LOWER"]
1133 elif plot["include-tests"] == "NDR":
1134 result = test["throughput"]["NDR"]["LOWER"]
1141 vals[c][n]["vals"].append(result)
1144 logging.error("No data.")
1147 for key_c in vals.keys():
1148 txt_chains.append(key_c)
1149 for key_n in vals[key_c].keys():
1150 txt_nodes.append(key_n)
1151 if vals[key_c][key_n]["vals"]:
1152 vals[key_c][key_n]["nr"] = len(vals[key_c][key_n]["vals"])
1153 vals[key_c][key_n]["mean"] = \
1154 round(mean(vals[key_c][key_n]["vals"]) / 1000000, 1)
1155 vals[key_c][key_n]["stdev"] = \
1156 round(stdev(vals[key_c][key_n]["vals"]) / 1000000, 1)
1157 txt_nodes = list(set(txt_nodes))
1159 txt_chains = sorted(txt_chains, key=lambda chain: int(chain))
1160 txt_nodes = sorted(txt_nodes, key=lambda node: int(node))
1162 chains = [i + 1 for i in range(len(txt_chains))]
1163 nodes = [i + 1 for i in range(len(txt_nodes))]
1165 data = [list() for _ in range(len(chains))]
1169 val = vals[txt_chains[c - 1]][txt_nodes[n - 1]]["mean"]
1170 except (KeyError, IndexError):
1172 data[c - 1].append(val)
1175 my_green = [[0.0, 'rgb(235, 249, 242)'],
1176 [1.0, 'rgb(45, 134, 89)']]
1178 my_blue = [[0.0, 'rgb(236, 242, 248)'],
1179 [1.0, 'rgb(57, 115, 172)']]
1181 my_grey = [[0.0, 'rgb(230, 230, 230)'],
1182 [1.0, 'rgb(102, 102, 102)']]
1185 annotations = list()
1187 text = ("Test: {name}<br>"
1192 for c in range(len(txt_chains)):
1194 for n in range(len(txt_nodes)):
1195 if data[c][n] is not None:
1196 annotations.append(dict(
1203 text=str(data[c][n]),
1210 hover_line.append(text.format(
1211 name=vals[txt_chains[c]][txt_nodes[n]]["name"],
1212 nr=vals[txt_chains[c]][txt_nodes[n]]["nr"],
1214 stdev=vals[txt_chains[c]][txt_nodes[n]]["stdev"]))
1215 hovertext.append(hover_line)
1218 plgo.Heatmap(x=nodes,
1222 title=plot.get("z-axis", ""),
1236 colorscale=my_green,
1241 for idx, item in enumerate(txt_nodes):
1243 annotations.append(dict(
1257 for idx, item in enumerate(txt_chains):
1259 annotations.append(dict(
1274 annotations.append(dict(
1281 text=plot.get("x-axis", ""),
1289 annotations.append(dict(
1296 text=plot.get("y-axis", ""),
1304 updatemenus = list([
1313 args=[{"colorscale": [my_green, ], "reversescale": False}],
1318 args=[{"colorscale": [my_blue, ], "reversescale": False}],
1323 args=[{"colorscale": [my_grey, ], "reversescale": False}],
1332 layout = deepcopy(plot["layout"])
1333 except KeyError as err:
1334 logging.error("Finished with error: No layout defined")
1335 logging.error(repr(err))
1338 layout["annotations"] = annotations
1339 layout['updatemenus'] = updatemenus
1343 plpl = plgo.Figure(data=traces, layout=layout)
1346 logging.info(" Writing file '{0}{1}'.".
1347 format(plot["output-file"], plot["output-file-type"]))
1348 ploff.plot(plpl, show_link=False, auto_open=False,
1349 filename='{0}{1}'.format(plot["output-file"],
1350 plot["output-file-type"]))
1351 except PlotlyError as err:
1352 logging.error(" Finished with error: {}".
1353 format(str(err).replace("\n", " ")))