1 # Copyright (c) 2021 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 from collections import OrderedDict
22 from datetime import datetime
23 from copy import deepcopy
29 import plotly.offline as ploff
30 import plotly.graph_objs as plgo
31 import plotly.exceptions as plerr
33 from plotly.exceptions import PlotlyError
35 from pal_utils import mean, stdev
64 REGEX_NIC = re.compile(r'(\d*ge\dp\d\D*\d*[a-z]*)-')
66 # This value depends on latency stream rate (9001 pps) and duration (5s).
67 # Keep it slightly higher to ensure rounding errors to not remove tick mark.
68 PERCENTILE_MAX = 99.999501
71 def generate_plots(spec, data):
72 """Generate all plots specified in the specification file.
74 :param spec: Specification read from the specification file.
75 :param data: Data to process.
76 :type spec: Specification
81 u"plot_nf_reconf_box_name": plot_nf_reconf_box_name,
82 u"plot_perf_box_name": plot_perf_box_name,
83 u"plot_tsa_name": plot_tsa_name,
84 u"plot_http_server_perf_box": plot_http_server_perf_box,
85 u"plot_nf_heatmap": plot_nf_heatmap,
86 u"plot_hdrh_lat_by_percentile": plot_hdrh_lat_by_percentile,
87 u"plot_hdrh_lat_by_percentile_x_log": plot_hdrh_lat_by_percentile_x_log,
88 u"plot_mrr_box_name": plot_mrr_box_name,
89 u"plot_ndrpdr_box_name": plot_ndrpdr_box_name,
90 u"plot_statistics": plot_statistics
93 logging.info(u"Generating the plots ...")
94 for index, plot in enumerate(spec.plots):
96 logging.info(f" Plot nr {index + 1}: {plot.get(u'title', u'')}")
97 plot[u"limits"] = spec.environment[u"limits"]
98 generator[plot[u"algorithm"]](plot, data)
99 logging.info(u" Done.")
100 except NameError as err:
102 f"Probably algorithm {plot[u'algorithm']} is not defined: "
105 logging.info(u"Done.")
108 def plot_statistics(plot, input_data):
109 """Generate the plot(s) with algorithm: plot_statistics
110 specified in the specification file.
112 :param plot: Plot to generate.
113 :param input_data: Data to process.
114 :type plot: pandas.Series
115 :type input_data: InputData
121 data_y_duration = list()
125 u"passed: {passed}<br>"
126 u"failed: {failed}<br>"
127 u"duration: {duration}<br>"
128 u"{sut}-ref: {build}<br>"
129 u"csit-ref: {test}-{period}-build-{build_nr}<br>"
130 u"testbed: {testbed}"
132 for job, builds in plot[u"data"].items():
133 for build_nr in builds:
135 meta = input_data.metadata(job, str(build_nr))
136 generated = meta[u"generated"]
141 int(generated[9:11]),
144 d_y_pass = meta[u"tests_passed"]
145 d_y_fail = meta[u"tests_failed"]
146 minutes = meta[u"elapsedtime"] // 60000
147 duration = f"{(minutes // 60):02d}:{(minutes % 60):02d}"
148 version = meta[u"version"]
149 except (KeyError, IndexError, ValueError, AttributeError):
152 data_y_pass.append(d_y_pass)
153 data_y_fail.append(d_y_fail)
154 data_y_duration.append(minutes)
155 hover_text.append(hover_str.format(
160 sut=u"vpp" if u"vpp" in job else u"dpdk",
162 test=u"mrr" if u"mrr" in job else u"ndrpdr",
163 period=u"daily" if u"daily" in job else u"weekly",
165 testbed=meta[u"testbed"]
192 name_file = f"{plot[u'output-file']}.html"
194 logging.info(f" Writing the file {name_file}")
195 plpl = plgo.Figure(data=traces, layout=plot[u"layout"])
196 tickvals = [min(data_y_duration), max(data_y_duration)]
197 step = (tickvals[1] - tickvals[0]) / 6
199 tickvals.append(int(tickvals[0] + step * (i + 1)))
202 title=u"Duration [hh:mm]",
208 ticktext=[f"{(val // 60):02d}:{(val % 60):02d}" for val in tickvals]
211 plpl.update_layout(barmode=u"stack")
219 except plerr.PlotlyEmptyDataError:
220 logging.warning(u"No data for the plot. Skipped.")
223 def plot_hdrh_lat_by_percentile(plot, input_data):
224 """Generate the plot(s) with algorithm: plot_hdrh_lat_by_percentile
225 specified in the specification file.
227 :param plot: Plot to generate.
228 :param input_data: Data to process.
229 :type plot: pandas.Series
230 :type input_data: InputData
235 f" Creating the data set for the {plot.get(u'type', u'')} "
236 f"{plot.get(u'title', u'')}."
238 if plot.get(u"include", None):
239 data = input_data.filter_tests_by_name(
241 params=[u"name", u"latency", u"parent", u"tags", u"type"]
243 elif plot.get(u"filter", None):
244 data = input_data.filter_data(
246 params=[u"name", u"latency", u"parent", u"tags", u"type"],
247 continue_on_error=True
250 job = list(plot[u"data"].keys())[0]
251 build = str(plot[u"data"][job][0])
252 data = input_data.tests(job, build)
254 if data is None or len(data) == 0:
255 logging.error(u"No data.")
259 u"LAT0": u"No-load.",
260 u"PDR10": u"Low-load, 10% PDR.",
261 u"PDR50": u"Mid-load, 50% PDR.",
262 u"PDR90": u"High-load, 90% PDR.",
263 u"PDR": u"Full-load, 100% PDR.",
264 u"NDR10": u"Low-load, 10% NDR.",
265 u"NDR50": u"Mid-load, 50% NDR.",
266 u"NDR90": u"High-load, 90% NDR.",
267 u"NDR": u"Full-load, 100% NDR."
277 file_links = plot.get(u"output-file-links", None)
278 target_links = plot.get(u"target-links", None)
282 if test[u"type"] not in (u"NDRPDR",):
283 logging.warning(f"Invalid test type: {test[u'type']}")
285 name = re.sub(REGEX_NIC, u"", test[u"parent"].
286 replace(u'-ndrpdr', u'').replace(u'2n1l-', u''))
288 nic = re.search(REGEX_NIC, test[u"parent"]).group(1)
289 except (IndexError, AttributeError, KeyError, ValueError):
291 name_link = f"{nic}-{test[u'name']}".replace(u'-ndrpdr', u'')
293 logging.info(f" Generating the graph: {name_link}")
296 layout = deepcopy(plot[u"layout"])
298 for color, graph in enumerate(graphs):
299 for idx, direction in enumerate((u"direction1", u"direction2")):
305 decoded = hdrh.histogram.HdrHistogram.decode(
306 test[u"latency"][graph][direction][u"hdrh"]
308 except hdrh.codec.HdrLengthException:
310 f"No data for direction {(u'W-E', u'E-W')[idx % 2]}"
314 for item in decoded.get_recorded_iterator():
315 percentile = item.percentile_level_iterated_to
316 xaxis.append(previous_x)
317 yaxis.append(item.value_iterated_to)
319 f"<b>{desc[graph]}</b><br>"
320 f"Direction: {(u'W-E', u'E-W')[idx % 2]}<br>"
322 f"{previous_x:.5f}-{percentile:.5f}%<br>"
323 f"Latency: {item.value_iterated_to}uSec"
325 xaxis.append(percentile)
326 yaxis.append(item.value_iterated_to)
328 f"<b>{desc[graph]}</b><br>"
329 f"Direction: {(u'W-E', u'E-W')[idx % 2]}<br>"
331 f"{previous_x:.5f}-{percentile:.5f}%<br>"
332 f"Latency: {item.value_iterated_to}uSec"
334 previous_x = percentile
341 legendgroup=desc[graph],
342 showlegend=bool(idx),
346 width=1 if idx % 2 else 2
353 layout[u"title"][u"text"] = f"<b>Latency:</b> {name}"
354 fig.update_layout(layout)
357 file_name = f"{plot[u'output-file']}-{name_link}.html"
358 logging.info(f" Writing file {file_name}")
362 ploff.plot(fig, show_link=False, auto_open=False,
364 # Add link to the file:
365 if file_links and target_links:
366 with open(file_links, u"a") as file_handler:
369 f"<{target_links}/{file_name.split(u'/')[-1]}>`_\n"
371 except FileNotFoundError as err:
373 f"Not possible to write the link to the file "
374 f"{file_links}\n{err}"
376 except PlotlyError as err:
377 logging.error(f" Finished with error: {repr(err)}")
379 except hdrh.codec.HdrLengthException as err:
380 logging.warning(repr(err))
383 except (ValueError, KeyError) as err:
384 logging.warning(repr(err))
388 def plot_hdrh_lat_by_percentile_x_log(plot, input_data):
389 """Generate the plot(s) with algorithm: plot_hdrh_lat_by_percentile_x_log
390 specified in the specification file.
392 :param plot: Plot to generate.
393 :param input_data: Data to process.
394 :type plot: pandas.Series
395 :type input_data: InputData
400 f" Creating the data set for the {plot.get(u'type', u'')} "
401 f"{plot.get(u'title', u'')}."
403 if plot.get(u"include", None):
404 data = input_data.filter_tests_by_name(
406 params=[u"name", u"latency", u"parent", u"tags", u"type"]
408 elif plot.get(u"filter", None):
409 data = input_data.filter_data(
411 params=[u"name", u"latency", u"parent", u"tags", u"type"],
412 continue_on_error=True
415 job = list(plot[u"data"].keys())[0]
416 build = str(plot[u"data"][job][0])
417 data = input_data.tests(job, build)
419 if data is None or len(data) == 0:
420 logging.error(u"No data.")
424 u"LAT0": u"No-load.",
425 u"PDR10": u"Low-load, 10% PDR.",
426 u"PDR50": u"Mid-load, 50% PDR.",
427 u"PDR90": u"High-load, 90% PDR.",
428 u"PDR": u"Full-load, 100% PDR.",
429 u"NDR10": u"Low-load, 10% NDR.",
430 u"NDR50": u"Mid-load, 50% NDR.",
431 u"NDR90": u"High-load, 90% NDR.",
432 u"NDR": u"Full-load, 100% NDR."
442 file_links = plot.get(u"output-file-links", None)
443 target_links = plot.get(u"target-links", None)
447 if test[u"type"] not in (u"NDRPDR",):
448 logging.warning(f"Invalid test type: {test[u'type']}")
450 name = re.sub(REGEX_NIC, u"", test[u"parent"].
451 replace(u'-ndrpdr', u'').replace(u'2n1l-', u''))
453 nic = re.search(REGEX_NIC, test[u"parent"]).group(1)
454 except (IndexError, AttributeError, KeyError, ValueError):
456 name_link = f"{nic}-{test[u'name']}".replace(u'-ndrpdr', u'')
458 logging.info(f" Generating the graph: {name_link}")
461 layout = deepcopy(plot[u"layout"])
463 for color, graph in enumerate(graphs):
464 for idx, direction in enumerate((u"direction1", u"direction2")):
471 decoded = hdrh.histogram.HdrHistogram.decode(
472 test[u"latency"][graph][direction][u"hdrh"]
474 except (hdrh.codec.HdrLengthException, TypeError):
476 f"No data for direction {(u'W-E', u'E-W')[idx % 2]}"
480 for item in decoded.get_recorded_iterator():
481 # The real value is "percentile".
482 # For 100%, we cut that down to "x_perc" to avoid
484 percentile = item.percentile_level_iterated_to
485 x_perc = min(percentile, PERCENTILE_MAX)
486 xaxis.append(previous_x)
487 yaxis.append(item.value_iterated_to)
489 f"<b>{desc[graph]}</b><br>"
490 f"Direction: {(u'W-E', u'E-W')[idx % 2]}<br>"
491 f"Percentile: {prev_perc:.5f}-{percentile:.5f}%<br>"
492 f"Latency: {item.value_iterated_to}uSec"
494 next_x = 100.0 / (100.0 - x_perc)
496 yaxis.append(item.value_iterated_to)
498 f"<b>{desc[graph]}</b><br>"
499 f"Direction: {(u'W-E', u'E-W')[idx % 2]}<br>"
500 f"Percentile: {prev_perc:.5f}-{percentile:.5f}%<br>"
501 f"Latency: {item.value_iterated_to}uSec"
504 prev_perc = percentile
511 legendgroup=desc[graph],
512 showlegend=not(bool(idx)),
516 width=1 if idx % 2 else 2
523 layout[u"title"][u"text"] = f"<b>Latency:</b> {name}"
524 x_max = log(100.0 / (100.0 - PERCENTILE_MAX), 10)
525 layout[u"xaxis"][u"range"] = [0, x_max]
526 fig.update_layout(layout)
529 file_name = f"{plot[u'output-file']}-{name_link}.html"
530 logging.info(f" Writing file {file_name}")
534 ploff.plot(fig, show_link=False, auto_open=False,
536 # Add link to the file:
537 if file_links and target_links:
538 with open(file_links, u"a") as file_handler:
541 f"<{target_links}/{file_name.split(u'/')[-1]}>`_\n"
543 except FileNotFoundError as err:
545 f"Not possible to write the link to the file "
546 f"{file_links}\n{err}"
548 except PlotlyError as err:
549 logging.error(f" Finished with error: {repr(err)}")
551 except hdrh.codec.HdrLengthException as err:
552 logging.warning(repr(err))
555 except (ValueError, KeyError) as err:
556 logging.warning(repr(err))
560 def plot_nf_reconf_box_name(plot, input_data):
561 """Generate the plot(s) with algorithm: plot_nf_reconf_box_name
562 specified in the specification file.
564 :param plot: Plot to generate.
565 :param input_data: Data to process.
566 :type plot: pandas.Series
567 :type input_data: InputData
572 f" Creating the data set for the {plot.get(u'type', u'')} "
573 f"{plot.get(u'title', u'')}."
575 data = input_data.filter_tests_by_name(
576 plot, params=[u"result", u"parent", u"tags", u"type"]
579 logging.error(u"No data.")
582 for core in plot.get(u"core", tuple()):
583 # Prepare the data for the plot
584 y_vals = OrderedDict()
586 for item in plot.get(u"include", tuple()):
587 reg_ex = re.compile(str(item.format(core=core)).lower())
590 for test_id, test in build.iteritems():
591 if not re.match(reg_ex, str(test_id).lower()):
593 if y_vals.get(test[u"parent"], None) is None:
594 y_vals[test[u"parent"]] = list()
595 loss[test[u"parent"]] = list()
597 y_vals[test[u"parent"]].append(
598 test[u"result"][u"time"]
600 loss[test[u"parent"]].append(
601 test[u"result"][u"loss"]
603 except (KeyError, TypeError):
604 y_vals[test[u"parent"]].append(None)
606 # Add None to the lists with missing data
608 nr_of_samples = list()
609 for val in y_vals.values():
610 if len(val) > max_len:
612 nr_of_samples.append(len(val))
613 for val in y_vals.values():
614 if len(val) < max_len:
615 val.extend([None for _ in range(max_len - len(val))])
619 df_y = pd.DataFrame(y_vals)
621 for i, col in enumerate(df_y.columns):
624 col.lower().replace(u'-reconf', u'').replace(u'2n1l-', u'').
625 replace(u'2n-', u'').replace(u'-testpmd', u'')
627 traces.append(plgo.Box(
628 x=[str(i + 1) + u'.'] * len(df_y[col]),
632 f"({nr_of_samples[i]:02d} "
633 f"run{u's' if nr_of_samples[i] > 1 else u''}, "
634 f"packets lost average: {mean(loss[col]):.1f}) "
635 f"{u'-'.join(tst_name.split(u'-')[2:])}"
641 layout = deepcopy(plot[u"layout"])
642 layout[u"title"] = f"<b>Time Lost:</b> {layout[u'title']}"
643 layout[u"yaxis"][u"title"] = u"<b>Effective Blocked Time [s]</b>"
644 layout[u"legend"][u"font"][u"size"] = 14
645 layout[u"yaxis"].pop(u"range")
646 plpl = plgo.Figure(data=traces, layout=layout)
649 file_name = f"{plot[u'output-file'].format(core=core)}.html"
650 logging.info(f" Writing file {file_name}")
657 except PlotlyError as err:
659 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
663 def plot_perf_box_name(plot, input_data):
664 """Generate the plot(s) with algorithm: plot_perf_box_name
665 specified in the specification file.
667 Use only for soak and hoststack tests.
669 :param plot: Plot to generate.
670 :param input_data: Data to process.
671 :type plot: pandas.Series
672 :type input_data: InputData
677 f" Creating data set for the {plot.get(u'type', u'')} "
678 f"{plot.get(u'title', u'')}."
680 data = input_data.filter_tests_by_name(
682 params=[u"throughput", u"gbps", u"result", u"parent", u"tags", u"type"])
684 logging.error(u"No data.")
687 # Prepare the data for the plot
688 y_vals = OrderedDict()
691 for item in plot.get(u"include", tuple()):
692 reg_ex = re.compile(str(item).lower())
695 for test_id, test in build.iteritems():
696 if not re.match(reg_ex, str(test_id).lower()):
698 if y_vals.get(test[u"parent"], None) is None:
699 y_vals[test[u"parent"]] = list()
701 if test[u"type"] in (u"SOAK",):
702 y_vals[test[u"parent"]]. \
703 append(test[u"throughput"][u"LOWER"])
706 elif test[u"type"] in (u"HOSTSTACK",):
707 if u"LDPRELOAD" in test[u"tags"]:
708 y_vals[test[u"parent"]].append(
710 test[u"result"][u"bits_per_second"]
713 elif u"VPPECHO" in test[u"tags"]:
714 y_vals[test[u"parent"]].append(
716 test[u"result"][u"client"][u"tx_data"]
719 test[u"result"][u"client"][u"time"]
722 test[u"result"][u"server"][u"time"])
725 test_type = u"HOSTSTACK"
727 elif test[u"type"] in (u"LDP_NGINX",):
728 if u"TCP_CPS" in test[u"tags"]:
729 test_type = u"VSAP_CPS"
730 y_vals[test[u"parent"]].append(
731 test[u"result"][u"cps"]
733 elif u"TCP_RPS" in test[u"tags"]:
734 test_type = u"VSAP_RPS"
735 y_vals[test[u"parent"]].append(
736 test[u"result"][u"rps"]
743 except (KeyError, TypeError):
744 y_vals[test[u"parent"]].append(None)
746 # Add None to the lists with missing data
748 nr_of_samples = list()
749 for val in y_vals.values():
750 if len(val) > max_len:
752 nr_of_samples.append(len(val))
753 for val in y_vals.values():
754 if len(val) < max_len:
755 val.extend([None for _ in range(max_len - len(val))])
759 df_y = pd.DataFrame(y_vals)
762 for i, col in enumerate(df_y.columns):
763 tst_name = re.sub(REGEX_NIC, u"",
764 col.lower().replace(u'-ndrpdr', u'').
765 replace(u'2n1l-', u''))
766 if test_type in (u"VSAP_CPS", u"VSAP_RPS"):
767 data_y = [y if y else None for y in df_y[col]]
769 data_y = [y / 1e6 if y else None for y in df_y[col]]
771 x=[str(i + 1) + u'.'] * len(df_y[col]),
775 f"({nr_of_samples[i]:02d} "
776 f"run{u's' if nr_of_samples[i] > 1 else u''}) "
781 if test_type in (u"SOAK", ):
782 kwargs[u"boxpoints"] = u"all"
784 traces.append(plgo.Box(**kwargs))
787 val_max = max(df_y[col])
789 y_max.append(int(val_max / 1e6))
790 except (ValueError, TypeError) as err:
791 logging.error(repr(err))
796 layout = deepcopy(plot[u"layout"])
797 if layout.get(u"title", None):
798 if test_type in (u"HOSTSTACK", ):
799 layout[u"title"] = f"<b>Bandwidth:</b> {layout[u'title']}"
800 elif test_type == u"VSAP_CPS":
801 layout[u"title"] = f"<b>CPS:</b> {layout[u'title']}"
802 layout[u"yaxis"][u"title"] = u"<b>Connection Rate [cps]</b>"
803 elif test_type == u"VSAP_RPS":
804 layout[u"title"] = f"<b>RPS:</b> {layout[u'title']}"
805 layout[u"yaxis"][u"title"] = u"<b>Connection Rate [rps]</b>"
807 layout[u"title"] = f"<b>Tput:</b> {layout[u'title']}"
808 if y_max and max(y_max) > 1:
809 layout[u"yaxis"][u"range"] = [0, max(y_max) + 2]
810 plpl = plgo.Figure(data=traces, layout=layout)
813 logging.info(f" Writing file {plot[u'output-file']}.html.")
818 filename=f"{plot[u'output-file']}.html"
820 except PlotlyError as err:
822 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
827 def plot_ndrpdr_box_name(plot, input_data):
828 """Generate the plot(s) with algorithm: plot_ndrpdr_box_name
829 specified in the specification file.
831 :param plot: Plot to generate.
832 :param input_data: Data to process.
833 :type plot: pandas.Series
834 :type input_data: InputData
839 f" Creating data set for the {plot.get(u'type', u'')} "
840 f"{plot.get(u'title', u'')}."
842 data = input_data.filter_tests_by_name(
844 params=[u"throughput", u"gbps", u"parent", u"tags", u"type"]
847 logging.error(u"No data.")
850 if u"-gbps" in plot.get(u"title", u"").lower():
854 value = u"throughput"
859 for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
860 for core in plot.get(u"core", tuple()):
861 # Prepare the data for the plot
863 data_y = OrderedDict()
866 for item in plot.get(u"include", tuple()):
867 reg_ex = re.compile(str(item.format(core=core)).lower())
870 for test_id, test in build.iteritems():
871 if not re.match(reg_ex, str(test_id).lower()):
873 if data_y.get(test[u"parent"], None) is None:
874 data_y[test[u"parent"]] = list()
875 test_type = test[u"type"]
879 data_y[test[u"parent"]].append(
880 test[value][ttype.upper()][u"LOWER"] *
883 except (KeyError, TypeError):
888 for idx, (key, vals) in enumerate(data_y.items()):
890 REGEX_NIC, u'', key.lower().replace(u'-ndrpdr', u'').
891 replace(u'2n1l-', u'')
895 x=[data_x[idx], ] * len(data_x),
896 y=[y / 1e6 if y else None for y in vals],
901 f"{u's' if len(vals) > 1 else u''}) "
908 data_y_max.append(max(vals))
909 except ValueError as err:
910 logging.warning(f"No values to use.\n{err!r}")
913 layout = deepcopy(plot[u"layout"])
914 if layout.get(u"title", None):
916 layout[u'title'].format(core=core, test_type=ttype)
917 if test_type in (u"CPS", ):
918 layout[u"title"] = f"<b>CPS:</b> {layout[u'title']}"
921 f"<b>Tput:</b> {layout[u'title']}"
923 layout[u"yaxis"][u"range"] = [0, max(data_y_max) / 1e6 + 1]
924 plpl = plgo.Figure(data=traces, layout=layout)
928 f"{plot[u'output-file'].format(core=core, test_type=ttype)}"
931 logging.info(f" Writing file {file_name}")
938 except PlotlyError as err:
940 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
944 def plot_mrr_box_name(plot, input_data):
945 """Generate the plot(s) with algorithm: plot_mrr_box_name
946 specified in the specification file.
948 :param plot: Plot to generate.
949 :param input_data: Data to process.
950 :type plot: pandas.Series
951 :type input_data: InputData
956 f" Creating data set for the {plot.get(u'type', u'')} "
957 f"{plot.get(u'title', u'')}."
959 data = input_data.filter_tests_by_name(
961 params=[u"result", u"parent", u"tags", u"type"]
964 logging.error(u"No data.")
967 for core in plot.get(u"core", tuple()):
968 # Prepare the data for the plot
974 for item in plot.get(u"include", tuple()):
975 reg_ex = re.compile(str(item.format(core=core)).lower())
978 for test_id, test in build.iteritems():
979 if not re.match(reg_ex, str(test_id).lower()):
984 REGEX_NIC, u'', test[u'parent'].lower().
985 replace(u'-mrr', u'').replace(u'2n1l-', u'')
987 data_y.append(test[u"result"][u"samples"])
990 f"({len(data_y[-1]):02d} "
991 f"run{u's' if len(data_y[-1]) > 1 else u''}) "
994 data_y_max.append(max(data_y[-1]))
996 except (KeyError, TypeError):
1001 for idx, x_item in enumerate(data_x):
1004 x=[x_item, ] * len(data_y[idx]),
1006 name=data_names[idx],
1013 layout = deepcopy(plot[u"layout"])
1014 if layout.get(u"title", None):
1015 layout[u"title"] = (
1016 f"<b>Tput:</b> {layout[u'title'].format(core=core)}"
1019 layout[u"yaxis"][u"range"] = [0, max(data_y_max) + 1]
1020 plpl = plgo.Figure(data=traces, layout=layout)
1023 file_name = f"{plot[u'output-file'].format(core=core)}.html"
1024 logging.info(f" Writing file {file_name}")
1031 except PlotlyError as err:
1033 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
1037 def plot_tsa_name(plot, input_data):
1038 """Generate the plot(s) with algorithm:
1040 specified in the specification file.
1042 :param plot: Plot to generate.
1043 :param input_data: Data to process.
1044 :type plot: pandas.Series
1045 :type input_data: InputData
1048 # Transform the data
1049 plot_title = plot.get(u"title", u"")
1051 f" Creating data set for the {plot.get(u'type', u'')} {plot_title}."
1053 data = input_data.filter_tests_by_name(
1055 params=[u"throughput", u"gbps", u"parent", u"tags", u"type"]
1058 logging.error(u"No data.")
1061 plot_title = plot_title.lower()
1063 if u"-gbps" in plot_title:
1068 value = u"throughput"
1072 for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
1073 y_vals = OrderedDict()
1074 for item in plot.get(u"include", tuple()):
1075 reg_ex = re.compile(str(item).lower())
1078 for test_id, test in build.iteritems():
1079 if re.match(reg_ex, str(test_id).lower()):
1080 if y_vals.get(test[u"parent"], None) is None:
1081 y_vals[test[u"parent"]] = {
1087 if test[u"type"] not in (u"NDRPDR", u"CPS"):
1090 if u"1C" in test[u"tags"]:
1091 y_vals[test[u"parent"]][u"1"].append(
1092 test[value][ttype.upper()][u"LOWER"] *
1095 elif u"2C" in test[u"tags"]:
1096 y_vals[test[u"parent"]][u"2"].append(
1097 test[value][ttype.upper()][u"LOWER"] *
1100 elif u"4C" in test[u"tags"]:
1101 y_vals[test[u"parent"]][u"4"].append(
1102 test[value][ttype.upper()][u"LOWER"] *
1105 except (KeyError, TypeError):
1109 logging.warning(f"No data for the plot {plot.get(u'title', u'')}")
1113 for test_name, test_vals in y_vals.items():
1114 for key, test_val in test_vals.items():
1116 avg_val = sum(test_val) / len(test_val)
1117 y_vals[test_name][key] = [avg_val, len(test_val)]
1118 ideal = avg_val / (int(key) * 1e6)
1119 if test_name not in y_1c_max or ideal > y_1c_max[test_name]:
1120 y_1c_max[test_name] = ideal
1122 vals = OrderedDict()
1127 for test_name, test_vals in y_vals.items():
1129 if test_vals[u"1"][1]:
1133 test_name.replace(u'-ndrpdr', u'').
1134 replace(u'2n1l-', u'')
1136 vals[name] = OrderedDict()
1137 y_val_1 = test_vals[u"1"][0] / 1e6
1138 y_val_2 = test_vals[u"2"][0] / 1e6 if test_vals[u"2"][0] \
1140 y_val_4 = test_vals[u"4"][0] / 1e6 if test_vals[u"4"][0] \
1143 vals[name][u"val"] = [y_val_1, y_val_2, y_val_4]
1144 vals[name][u"rel"] = [1.0, None, None]
1145 vals[name][u"ideal"] = [
1146 y_1c_max[test_name],
1147 y_1c_max[test_name] * 2,
1148 y_1c_max[test_name] * 4
1150 vals[name][u"diff"] = [
1151 (y_val_1 - y_1c_max[test_name]) * 100 / y_val_1,
1155 vals[name][u"count"] = [
1162 val_max = max(vals[name][u"val"])
1163 except ValueError as err:
1164 logging.error(repr(err))
1167 y_max.append(val_max)
1170 vals[name][u"rel"][1] = round(y_val_2 / y_val_1, 2)
1171 vals[name][u"diff"][1] = \
1172 (y_val_2 - vals[name][u"ideal"][1]) * 100 / y_val_2
1174 vals[name][u"rel"][2] = round(y_val_4 / y_val_1, 2)
1175 vals[name][u"diff"][2] = \
1176 (y_val_4 - vals[name][u"ideal"][2]) * 100 / y_val_4
1177 except IndexError as err:
1178 logging.warning(f"No data for {test_name}")
1179 logging.warning(repr(err))
1182 if u"x520" in test_name:
1183 limit = plot[u"limits"][u"nic"][u"x520"]
1184 elif u"x710" in test_name:
1185 limit = plot[u"limits"][u"nic"][u"x710"]
1186 elif u"xxv710" in test_name:
1187 limit = plot[u"limits"][u"nic"][u"xxv710"]
1188 elif u"xl710" in test_name:
1189 limit = plot[u"limits"][u"nic"][u"xl710"]
1190 elif u"x553" in test_name:
1191 limit = plot[u"limits"][u"nic"][u"x553"]
1192 elif u"cx556a" in test_name:
1193 limit = plot[u"limits"][u"nic"][u"cx556a"]
1194 elif u"e810cq" in test_name:
1195 limit = plot[u"limits"][u"nic"][u"e810cq"]
1198 if limit > nic_limit:
1201 mul = 2 if u"ge2p" in test_name else 1
1202 if u"10ge" in test_name:
1203 limit = plot[u"limits"][u"link"][u"10ge"] * mul
1204 elif u"25ge" in test_name:
1205 limit = plot[u"limits"][u"link"][u"25ge"] * mul
1206 elif u"40ge" in test_name:
1207 limit = plot[u"limits"][u"link"][u"40ge"] * mul
1208 elif u"100ge" in test_name:
1209 limit = plot[u"limits"][u"link"][u"100ge"] * mul
1212 if limit > lnk_limit:
1215 if u"cx556a" in test_name:
1216 limit = plot[u"limits"][u"pci"][u"pci-g3-x8"]
1218 limit = plot[u"limits"][u"pci"][u"pci-g3-x16"]
1219 if limit > pci_limit:
1223 annotations = list()
1227 if u"-gbps" not in plot_title and u"-cps-" not in plot_title:
1231 min_limit = min((nic_limit, lnk_limit, pci_limit))
1232 if nic_limit == min_limit:
1233 traces.append(plgo.Scatter(
1235 y=[nic_limit, ] * len(x_vals),
1236 name=f"NIC: {nic_limit:.2f}Mpps",
1245 annotations.append(dict(
1252 text=f"NIC: {nic_limit:.2f}Mpps",
1260 y_max.append(nic_limit)
1261 elif lnk_limit == min_limit:
1262 traces.append(plgo.Scatter(
1264 y=[lnk_limit, ] * len(x_vals),
1265 name=f"Link: {lnk_limit:.2f}Mpps",
1274 annotations.append(dict(
1281 text=f"Link: {lnk_limit:.2f}Mpps",
1289 y_max.append(lnk_limit)
1290 elif pci_limit == min_limit:
1291 traces.append(plgo.Scatter(
1293 y=[pci_limit, ] * len(x_vals),
1294 name=f"PCIe: {pci_limit:.2f}Mpps",
1303 annotations.append(dict(
1310 text=f"PCIe: {pci_limit:.2f}Mpps",
1318 y_max.append(pci_limit)
1320 # Perfect and measured:
1322 for name, val in vals.items():
1325 for idx in range(len(val[u"val"])):
1327 if isinstance(val[u"val"][idx], float):
1329 f"No. of Runs: {val[u'count'][idx]}<br>"
1330 f"Mean: {val[u'val'][idx]:.2f}{h_unit}<br>"
1332 if isinstance(val[u"diff"][idx], float):
1333 htext += f"Diff: {round(val[u'diff'][idx]):.0f}%<br>"
1334 if isinstance(val[u"rel"][idx], float):
1335 htext += f"Speedup: {val[u'rel'][idx]:.2f}"
1336 hovertext.append(htext)
1343 mode=u"lines+markers",
1352 hoverinfo=u"text+name"
1359 name=f"{name} perfect",
1367 text=[f"Perfect: {y:.2f}Mpps" for y in val[u"ideal"]],
1372 except (IndexError, ValueError, KeyError) as err:
1373 logging.warning(f"No data for {name}\n{repr(err)}")
1377 file_name = f"{plot[u'output-file'].format(test_type=ttype)}.html"
1378 logging.info(f" Writing file {file_name}")
1379 layout = deepcopy(plot[u"layout"])
1380 if layout.get(u"title", None):
1381 layout[u"title"] = (
1382 f"<b>Speedup Multi-core:</b> "
1383 f"{layout[u'title'].format(test_type=ttype)}"
1385 layout[u"yaxis"][u"range"] = [0, int(max(y_max) * 1.1)]
1386 layout[u"annotations"].extend(annotations)
1387 plpl = plgo.Figure(data=traces, layout=layout)
1396 except PlotlyError as err:
1398 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
1402 def plot_http_server_perf_box(plot, input_data):
1403 """Generate the plot(s) with algorithm: plot_http_server_perf_box
1404 specified in the specification file.
1406 :param plot: Plot to generate.
1407 :param input_data: Data to process.
1408 :type plot: pandas.Series
1409 :type input_data: InputData
1412 # Transform the data
1414 f" Creating the data set for the {plot.get(u'type', u'')} "
1415 f"{plot.get(u'title', u'')}."
1417 data = input_data.filter_data(plot)
1419 logging.error(u"No data.")
1422 # Prepare the data for the plot
1427 if y_vals.get(test[u"name"], None) is None:
1428 y_vals[test[u"name"]] = list()
1430 y_vals[test[u"name"]].append(test[u"result"])
1431 except (KeyError, TypeError):
1432 y_vals[test[u"name"]].append(None)
1434 # Add None to the lists with missing data
1436 nr_of_samples = list()
1437 for val in y_vals.values():
1438 if len(val) > max_len:
1440 nr_of_samples.append(len(val))
1441 for val in y_vals.values():
1442 if len(val) < max_len:
1443 val.extend([None for _ in range(max_len - len(val))])
1447 df_y = pd.DataFrame(y_vals)
1449 for i, col in enumerate(df_y.columns):
1452 f"({nr_of_samples[i]:02d} " \
1453 f"run{u's' if nr_of_samples[i] > 1 else u''}) " \
1454 f"{col.lower().replace(u'-ndrpdr', u'')}"
1456 name_lst = name.split(u'-')
1459 for segment in name_lst:
1460 if (len(name) + len(segment) + 1) > 50 and split_name:
1463 name += segment + u'-'
1466 traces.append(plgo.Box(x=[str(i + 1) + u'.'] * len(df_y[col]),
1472 plpl = plgo.Figure(data=traces, layout=plot[u"layout"])
1476 f" Writing file {plot[u'output-file']}"
1477 f"{plot[u'output-file-type']}."
1483 filename=f"{plot[u'output-file']}{plot[u'output-file-type']}"
1485 except PlotlyError as err:
1487 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
1492 def plot_nf_heatmap(plot, input_data):
1493 """Generate the plot(s) with algorithm: plot_nf_heatmap
1494 specified in the specification file.
1496 :param plot: Plot to generate.
1497 :param input_data: Data to process.
1498 :type plot: pandas.Series
1499 :type input_data: InputData
1502 def sort_by_int(value):
1503 """Makes possible to sort a list of strings which represent integers.
1505 :param value: Integer as a string.
1507 :returns: Integer representation of input parameter 'value'.
1512 regex_cn = re.compile(r'^(\d*)R(\d*)C$')
1513 regex_test_name = re.compile(r'^.*-(\d+ch|\d+pl)-'
1515 r'(\d+vm\d+t|\d+dcr\d+t|\d+dcr\d+c).*$')
1518 # Transform the data
1520 f" Creating the data set for the {plot.get(u'type', u'')} "
1521 f"{plot.get(u'title', u'')}."
1523 in_data = input_data.filter_tests_by_name(
1525 continue_on_error=True,
1526 params=[u"throughput", u"result", u"name", u"tags", u"type"]
1528 if in_data is None or in_data.empty:
1529 logging.error(u"No data.")
1532 for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
1533 for core in plot.get(u"core", tuple()):
1534 for item in plot.get(u"include", tuple()):
1535 reg_ex = re.compile(str(item.format(core=core)).lower())
1538 for test_id, test in build.iteritems():
1539 if not re.match(reg_ex, str(test_id).lower()):
1541 for tag in test[u"tags"]:
1542 groups = re.search(regex_cn, tag)
1544 chain = str(groups.group(1))
1545 node = str(groups.group(2))
1549 groups = re.search(regex_test_name, test[u"name"])
1550 if groups and len(groups.groups()) == 3:
1552 f"{str(groups.group(1))}-"
1553 f"{str(groups.group(2))}-"
1554 f"{str(groups.group(3))}"
1558 if vals.get(chain, None) is None:
1559 vals[chain] = dict()
1560 if vals[chain].get(node, None) is None:
1561 vals[chain][node] = dict(
1570 result = test[u"result"][u"receive-rate"]
1571 elif ttype == u"pdr":
1573 test[u"throughput"][u"PDR"][u"LOWER"]
1574 elif ttype == u"ndr":
1576 test[u"throughput"][u"NDR"][u"LOWER"]
1583 vals[chain][node][u"vals"].append(result)
1586 logging.error(u"No data.")
1592 txt_chains.append(key_c)
1593 for key_n in vals[key_c].keys():
1594 txt_nodes.append(key_n)
1595 if vals[key_c][key_n][u"vals"]:
1596 vals[key_c][key_n][u"nr"] = \
1597 len(vals[key_c][key_n][u"vals"])
1598 vals[key_c][key_n][u"mean"] = \
1599 round(mean(vals[key_c][key_n][u"vals"]) / 1e6, 1)
1600 vals[key_c][key_n][u"stdev"] = \
1601 round(stdev(vals[key_c][key_n][u"vals"]) / 1e6, 1)
1602 txt_nodes = list(set(txt_nodes))
1604 txt_chains = sorted(txt_chains, key=sort_by_int)
1605 txt_nodes = sorted(txt_nodes, key=sort_by_int)
1607 chains = [i + 1 for i in range(len(txt_chains))]
1608 nodes = [i + 1 for i in range(len(txt_nodes))]
1610 data = [list() for _ in range(len(chains))]
1611 for chain in chains:
1614 val = vals[txt_chains[chain - 1]] \
1615 [txt_nodes[node - 1]][u"mean"]
1616 except (KeyError, IndexError):
1618 data[chain - 1].append(val)
1621 my_green = [[0.0, u"rgb(235, 249, 242)"],
1622 [1.0, u"rgb(45, 134, 89)"]]
1624 my_blue = [[0.0, u"rgb(236, 242, 248)"],
1625 [1.0, u"rgb(57, 115, 172)"]]
1627 my_grey = [[0.0, u"rgb(230, 230, 230)"],
1628 [1.0, u"rgb(102, 102, 102)"]]
1631 annotations = list()
1633 text = (u"Test: {name}<br>"
1638 for chain, _ in enumerate(txt_chains):
1640 for node, _ in enumerate(txt_nodes):
1641 if data[chain][node] is not None:
1650 text=str(data[chain][node]),
1658 hover_line.append(text.format(
1659 name=vals[txt_chains[chain]][txt_nodes[node]]
1661 nr=vals[txt_chains[chain]][txt_nodes[node]][u"nr"],
1662 val=data[chain][node],
1663 stdev=vals[txt_chains[chain]][txt_nodes[node]]
1666 hovertext.append(hover_line)
1674 title=plot.get(u"z-axis", u"{test_type}").
1675 format(test_type=ttype.upper()),
1689 colorscale=my_green,
1695 for idx, item in enumerate(txt_nodes):
1713 for idx, item in enumerate(txt_chains):
1740 text=plot.get(u"x-axis", u""),
1757 text=plot.get(u"y-axis", u""),
1766 updatemenus = list([
1777 u"colorscale": [my_green, ],
1778 u"reversescale": False
1787 u"colorscale": [my_blue, ],
1788 u"reversescale": False
1797 u"colorscale": [my_grey, ],
1798 u"reversescale": False
1809 layout = deepcopy(plot[u"layout"])
1810 except KeyError as err:
1812 f"Finished with error: No layout defined\n{repr(err)}"
1816 layout[u"annotations"] = annotations
1817 layout[u'updatemenus'] = updatemenus
1818 if layout.get(u"title", None):
1819 layout[u"title"] = layout[u'title'].replace(u"test_type", ttype)
1823 plpl = plgo.Figure(data=traces, layout=layout)
1827 f"{plot[u'output-file'].format(core=core, test_type=ttype)}"
1830 logging.info(f" Writing file {file_name}")
1837 except PlotlyError as err:
1839 f" Finished with error: {repr(err)}".replace(u"\n", u" ")