1 # Copyright (c) 2023 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.get(u"version", u"")
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)
163 hover_text.append(hover_str.format(
170 test=u"mrr" if u"mrr" in job else u"ndrpdr",
171 period=u"daily" if u"daily" in job else u"weekly",
173 testbed=meta.get(u"testbed", u"")
200 name_file = f"{plot[u'output-file']}.html"
202 logging.info(f" Writing the file {name_file}")
203 plpl = plgo.Figure(data=traces, layout=plot[u"layout"])
204 tickvals = [0, (max(data_y_duration) // 60) * 60]
205 step = tickvals[1] / 5
207 tickvals.append(int(tickvals[0] + step * (i + 1)))
210 title=u"Duration [hh:mm]",
217 ticktext=[f"{(val // 60):02d}:{(val % 60):02d}" for val in tickvals]
220 plpl.update_layout(barmode=u"stack")
228 except plerr.PlotlyEmptyDataError:
229 logging.warning(u"No data for the plot. Skipped.")
232 def plot_hdrh_lat_by_percentile(plot, input_data):
233 """Generate the plot(s) with algorithm: plot_hdrh_lat_by_percentile
234 specified in the specification file.
236 :param plot: Plot to generate.
237 :param input_data: Data to process.
238 :type plot: pandas.Series
239 :type input_data: InputData
244 f" Creating the data set for the {plot.get(u'type', u'')} "
245 f"{plot.get(u'title', u'')}."
247 if plot.get(u"include", None):
248 data = input_data.filter_tests_by_name(
250 params=[u"name", u"latency", u"parent", u"tags", u"type"]
252 elif plot.get(u"filter", None):
253 data = input_data.filter_data(
255 params=[u"name", u"latency", u"parent", u"tags", u"type"],
256 continue_on_error=True
259 job = list(plot[u"data"].keys())[0]
260 build = str(plot[u"data"][job][0])
261 data = input_data.tests(job, build)
263 if data is None or len(data) == 0:
264 logging.error(u"No data.")
268 u"LAT0": u"No-load.",
269 u"PDR10": u"Low-load, 10% PDR.",
270 u"PDR50": u"Mid-load, 50% PDR.",
271 u"PDR90": u"High-load, 90% PDR.",
272 u"PDR": u"Full-load, 100% PDR.",
273 u"NDR10": u"Low-load, 10% NDR.",
274 u"NDR50": u"Mid-load, 50% NDR.",
275 u"NDR90": u"High-load, 90% NDR.",
276 u"NDR": u"Full-load, 100% NDR."
286 file_links = plot.get(u"output-file-links", None)
287 target_links = plot.get(u"target-links", None)
291 if test[u"type"] not in (u"NDRPDR",):
292 logging.warning(f"Invalid test type: {test[u'type']}")
294 name = re.sub(REGEX_NIC, u"", test[u"parent"].
295 replace(u'-ndrpdr', u'').replace(u'2n1l-', u''))
297 nic = re.search(REGEX_NIC, test[u"parent"]).group(1)
298 except (IndexError, AttributeError, KeyError, ValueError):
300 name_link = f"{nic}-{test[u'name']}".replace(u'-ndrpdr', u'')
302 logging.info(f" Generating the graph: {name_link}")
305 layout = deepcopy(plot[u"layout"])
307 for color, graph in enumerate(graphs):
308 for idx, direction in enumerate((u"direction1", u"direction2")):
314 decoded = hdrh.histogram.HdrHistogram.decode(
315 test[u"latency"][graph][direction][u"hdrh"]
317 except hdrh.codec.HdrLengthException:
319 f"No data for direction {(u'W-E', u'E-W')[idx % 2]}"
323 for item in decoded.get_recorded_iterator():
324 percentile = item.percentile_level_iterated_to
325 xaxis.append(previous_x)
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 xaxis.append(percentile)
335 yaxis.append(item.value_iterated_to)
337 f"<b>{desc[graph]}</b><br>"
338 f"Direction: {(u'W-E', u'E-W')[idx % 2]}<br>"
340 f"{previous_x:.5f}-{percentile:.5f}%<br>"
341 f"Latency: {item.value_iterated_to}uSec"
343 previous_x = percentile
350 legendgroup=desc[graph],
351 showlegend=bool(idx),
355 width=1 if idx % 2 else 2
362 layout[u"title"][u"text"] = f"<b>Latency:</b> {name}"
363 fig.update_layout(layout)
366 file_name = f"{plot[u'output-file']}-{name_link}.html"
367 logging.info(f" Writing file {file_name}")
371 ploff.plot(fig, show_link=False, auto_open=False,
373 # Add link to the file:
374 if file_links and target_links:
375 with open(file_links, u"a") as file_handler:
378 f"<{target_links}/{file_name.split(u'/')[-1]}>`_\n"
380 except FileNotFoundError as err:
382 f"Not possible to write the link to the file "
383 f"{file_links}\n{err}"
385 except PlotlyError as err:
386 logging.error(f" Finished with error: {repr(err)}")
388 except hdrh.codec.HdrLengthException as err:
389 logging.warning(repr(err))
392 except (ValueError, KeyError) as err:
393 logging.warning(repr(err))
397 def plot_hdrh_lat_by_percentile_x_log(plot, input_data):
398 """Generate the plot(s) with algorithm: plot_hdrh_lat_by_percentile_x_log
399 specified in the specification file.
401 :param plot: Plot to generate.
402 :param input_data: Data to process.
403 :type plot: pandas.Series
404 :type input_data: InputData
409 f" Creating the data set for the {plot.get(u'type', u'')} "
410 f"{plot.get(u'title', u'')}."
412 if plot.get(u"include", None):
413 data = input_data.filter_tests_by_name(
415 params=[u"name", u"latency", u"parent", u"tags", u"type"]
417 elif plot.get(u"filter", None):
418 data = input_data.filter_data(
420 params=[u"name", u"latency", u"parent", u"tags", u"type"],
421 continue_on_error=True
424 job = list(plot[u"data"].keys())[0]
425 build = str(plot[u"data"][job][0])
426 data = input_data.tests(job, build)
428 if data is None or len(data) == 0:
429 logging.error(u"No data.")
433 u"LAT0": u"No-load.",
434 u"PDR10": u"Low-load, 10% PDR.",
435 u"PDR50": u"Mid-load, 50% PDR.",
436 u"PDR90": u"High-load, 90% PDR.",
437 u"PDR": u"Full-load, 100% PDR.",
438 u"NDR10": u"Low-load, 10% NDR.",
439 u"NDR50": u"Mid-load, 50% NDR.",
440 u"NDR90": u"High-load, 90% NDR.",
441 u"NDR": u"Full-load, 100% NDR."
451 file_links = plot.get(u"output-file-links", None)
452 target_links = plot.get(u"target-links", None)
456 if test[u"type"] not in (u"NDRPDR",):
457 logging.warning(f"Invalid test type: {test[u'type']}")
459 name = re.sub(REGEX_NIC, u"", test[u"parent"].
460 replace(u'-ndrpdr', u'').replace(u'2n1l-', u''))
462 nic = re.search(REGEX_NIC, test[u"parent"]).group(1)
463 except (IndexError, AttributeError, KeyError, ValueError):
465 name_link = f"{nic}-{test[u'name']}".replace(u'-ndrpdr', u'')
467 logging.info(f" Generating the graph: {name_link}")
470 layout = deepcopy(plot[u"layout"])
472 for color, graph in enumerate(graphs):
473 for idx, direction in enumerate((u"direction1", u"direction2")):
480 decoded = hdrh.histogram.HdrHistogram.decode(
481 test[u"latency"][graph][direction][u"hdrh"]
483 except (hdrh.codec.HdrLengthException, TypeError):
485 f"No data for direction {(u'W-E', u'E-W')[idx % 2]}"
489 for item in decoded.get_recorded_iterator():
490 # The real value is "percentile".
491 # For 100%, we cut that down to "x_perc" to avoid
493 percentile = item.percentile_level_iterated_to
494 x_perc = min(percentile, PERCENTILE_MAX)
495 xaxis.append(previous_x)
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"
503 next_x = 100.0 / (100.0 - x_perc)
505 yaxis.append(item.value_iterated_to)
507 f"<b>{desc[graph]}</b><br>"
508 f"Direction: {(u'W-E', u'E-W')[idx % 2]}<br>"
509 f"Percentile: {prev_perc:.5f}-{percentile:.5f}%<br>"
510 f"Latency: {item.value_iterated_to}uSec"
513 prev_perc = percentile
520 legendgroup=desc[graph],
521 showlegend=not(bool(idx)),
525 width=1 if idx % 2 else 2
532 layout[u"title"][u"text"] = f"<b>Latency:</b> {name}"
533 x_max = log(100.0 / (100.0 - PERCENTILE_MAX), 10)
534 layout[u"xaxis"][u"range"] = [0, x_max]
535 fig.update_layout(layout)
538 file_name = f"{plot[u'output-file']}-{name_link}.html"
539 logging.info(f" Writing file {file_name}")
543 ploff.plot(fig, show_link=False, auto_open=False,
545 # Add link to the file:
546 if file_links and target_links:
547 with open(file_links, u"a") as file_handler:
550 f"<{target_links}/{file_name.split(u'/')[-1]}>`_\n"
552 except FileNotFoundError as err:
554 f"Not possible to write the link to the file "
555 f"{file_links}\n{err}"
557 except PlotlyError as err:
558 logging.error(f" Finished with error: {repr(err)}")
560 except hdrh.codec.HdrLengthException as err:
561 logging.warning(repr(err))
564 except (ValueError, KeyError) as err:
565 logging.warning(repr(err))
569 def plot_nf_reconf_box_name(plot, input_data):
570 """Generate the plot(s) with algorithm: plot_nf_reconf_box_name
571 specified in the specification file.
573 :param plot: Plot to generate.
574 :param input_data: Data to process.
575 :type plot: pandas.Series
576 :type input_data: InputData
581 f" Creating the data set for the {plot.get(u'type', u'')} "
582 f"{plot.get(u'title', u'')}."
584 data = input_data.filter_tests_by_name(
585 plot, params=[u"result", u"parent", u"tags", u"type"]
588 logging.error(u"No data.")
591 for core in plot.get(u"core", tuple()):
592 # Prepare the data for the plot
593 y_vals = OrderedDict()
595 for item in plot.get(u"include", tuple()):
596 reg_ex = re.compile(str(item.format(core=core)).lower())
599 for test_id, test in build.iteritems():
600 if not re.match(reg_ex, str(test_id).lower()):
602 if y_vals.get(test[u"parent"], None) is None:
603 y_vals[test[u"parent"]] = list()
604 loss[test[u"parent"]] = list()
606 y_vals[test[u"parent"]].append(
607 test[u"result"][u"time"]
609 loss[test[u"parent"]].append(
610 test[u"result"][u"loss"]
612 except (KeyError, TypeError):
613 y_vals[test[u"parent"]].append(None)
615 # Add None to the lists with missing data
617 nr_of_samples = list()
618 for val in y_vals.values():
619 if len(val) > max_len:
621 nr_of_samples.append(len(val))
622 for val in y_vals.values():
623 if len(val) < max_len:
624 val.extend([None for _ in range(max_len - len(val))])
628 df_y = pd.DataFrame(y_vals)
630 for i, col in enumerate(df_y.columns):
633 col.lower().replace(u'-reconf', u'').replace(u'2n1l-', u'').
634 replace(u'2n-', u'').replace(u'-testpmd', u'')
636 traces.append(plgo.Box(
637 x=[str(i + 1) + u'.'] * len(df_y[col]),
641 f"({nr_of_samples[i]:02d} "
642 f"run{u's' if nr_of_samples[i] > 1 else u''}, "
643 f"packets lost average: {mean(loss[col]):.1f}) "
644 f"{u'-'.join(tst_name.split(u'-')[2:])}"
650 layout = deepcopy(plot[u"layout"])
651 layout[u"title"] = f"<b>Time Lost:</b> {layout[u'title']}"
652 layout[u"yaxis"][u"title"] = u"<b>Effective Blocked Time [s]</b>"
653 layout[u"legend"][u"font"][u"size"] = 14
654 layout[u"yaxis"].pop(u"range")
655 plpl = plgo.Figure(data=traces, layout=layout)
658 file_name = f"{plot[u'output-file'].format(core=core)}.html"
659 logging.info(f" Writing file {file_name}")
666 except PlotlyError as err:
668 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
672 def plot_perf_box_name(plot, input_data):
673 """Generate the plot(s) with algorithm: plot_perf_box_name
674 specified in the specification file.
676 Use only for soak and hoststack tests.
678 :param plot: Plot to generate.
679 :param input_data: Data to process.
680 :type plot: pandas.Series
681 :type input_data: InputData
686 f" Creating data set for the {plot.get(u'type', u'')} "
687 f"{plot.get(u'title', u'')}."
689 data = input_data.filter_tests_by_name(
691 params=[u"throughput", u"gbps", u"result", u"parent", u"tags", u"type"])
693 logging.error(u"No data.")
696 # Prepare the data for the plot
697 y_vals = OrderedDict()
700 for item in plot.get(u"include", tuple()):
701 reg_ex = re.compile(str(item).lower())
704 for test_id, test in build.iteritems():
705 if not re.match(reg_ex, str(test_id).lower()):
707 if y_vals.get(test[u"parent"], None) is None:
708 y_vals[test[u"parent"]] = list()
710 if test[u"type"] in (u"SOAK",):
711 y_vals[test[u"parent"]]. \
712 append(test[u"throughput"][u"LOWER"])
715 elif test[u"type"] in (u"HOSTSTACK",):
716 if u"LDPRELOAD" in test[u"tags"]:
717 y_vals[test[u"parent"]].append(
719 test[u"result"][u"bits_per_second"]
722 elif u"VPPECHO" in test[u"tags"]:
723 y_vals[test[u"parent"]].append(
725 test[u"result"][u"client"][u"tx_data"]
728 test[u"result"][u"client"][u"time"]
731 test[u"result"][u"server"][u"time"])
734 test_type = u"HOSTSTACK"
736 elif test[u"type"] in (u"LDP_NGINX",):
737 if u"TCP_CPS" in test[u"tags"]:
738 test_type = u"VSAP_CPS"
739 y_vals[test[u"parent"]].append(
740 test[u"result"][u"cps"]
742 elif u"TCP_RPS" in test[u"tags"]:
743 test_type = u"VSAP_RPS"
744 y_vals[test[u"parent"]].append(
745 test[u"result"][u"rps"]
752 except (KeyError, TypeError):
753 y_vals[test[u"parent"]].append(None)
755 # Add None to the lists with missing data
757 nr_of_samples = list()
758 for val in y_vals.values():
759 if len(val) > max_len:
761 nr_of_samples.append(len(val))
762 for val in y_vals.values():
763 if len(val) < max_len:
764 val.extend([None for _ in range(max_len - len(val))])
768 df_y = pd.DataFrame(y_vals)
771 for i, col in enumerate(df_y.columns):
772 tst_name = re.sub(REGEX_NIC, u"",
773 col.lower().replace(u'-ndrpdr', u'').
774 replace(u'2n1l-', u''))
775 if test_type in (u"VSAP_CPS", u"VSAP_RPS"):
776 data_y = [y if y else None for y in df_y[col]]
778 data_y = [y / 1e6 if y else None for y in df_y[col]]
783 f"({nr_of_samples[i]:02d} "
784 f"run{u's' if nr_of_samples[i] > 1 else u''}) "
789 if test_type in (u"SOAK", ):
790 kwargs[u"boxpoints"] = u"all"
791 kwargs[u"jitter"] = 0.3
793 traces.append(plgo.Box(**kwargs))
796 val_max = max(df_y[col])
798 y_max.append(int(val_max / 1e6))
799 except (ValueError, TypeError) as err:
800 logging.error(repr(err))
805 layout = deepcopy(plot[u"layout"])
806 layout[u"xaxis"][u"tickvals"] = [i for i in range(len(y_vals))]
807 layout[u"xaxis"][u"ticktext"] = [str(i + 1) for i in range(len(y_vals))]
808 if layout.get(u"title", None):
809 if test_type in (u"HOSTSTACK", ):
810 layout[u"title"] = f"<b>Bandwidth:</b> {layout[u'title']}"
811 elif test_type == u"VSAP_CPS":
812 layout[u"title"] = f"<b>CPS:</b> {layout[u'title']}"
813 layout[u"yaxis"][u"title"] = u"<b>Connection Rate [cps]</b>"
814 elif test_type == u"VSAP_RPS":
815 layout[u"title"] = f"<b>RPS:</b> {layout[u'title']}"
816 layout[u"yaxis"][u"title"] = u"<b>Connection Rate [rps]</b>"
818 layout[u"title"] = f"<b>Tput:</b> {layout[u'title']}"
819 if y_max and max(y_max) > 1:
820 layout[u"yaxis"][u"range"] = [0, max(y_max) + 2]
821 plpl = plgo.Figure(data=traces, layout=layout)
824 logging.info(f" Writing file {plot[u'output-file']}.html.")
829 filename=f"{plot[u'output-file']}.html"
831 except PlotlyError as err:
833 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
838 def plot_ndrpdr_box_name(plot, input_data):
839 """Generate the plot(s) with algorithm: plot_ndrpdr_box_name
840 specified in the specification file.
842 :param plot: Plot to generate.
843 :param input_data: Data to process.
844 :type plot: pandas.Series
845 :type input_data: InputData
850 f" Creating data set for the {plot.get(u'type', u'')} "
851 f"{plot.get(u'title', u'')}."
853 data = input_data.filter_tests_by_name(
855 params=[u"throughput", u"gbps", u"parent", u"tags", u"type"]
858 logging.error(u"No data.")
861 if u"-gbps" in plot.get(u"title", u"").lower():
865 value = u"throughput"
870 for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
871 for core in plot.get(u"core", tuple()):
872 # Prepare the data for the plot
874 data_y = OrderedDict()
877 for item in plot.get(u"include", tuple()):
878 reg_ex = re.compile(str(item.format(core=core)).lower())
881 for test_id, test in build.iteritems():
882 if not re.match(reg_ex, str(test_id).lower()):
884 if data_y.get(test[u"parent"], None) is None:
885 data_y[test[u"parent"]] = list()
886 test_type = test[u"type"]
890 data_y[test[u"parent"]].append(
891 test[value][ttype.upper()][u"LOWER"] *
894 except (KeyError, TypeError):
899 for idx, (key, vals) in enumerate(data_y.items()):
901 REGEX_NIC, u'', key.lower().replace(u'-ndrpdr', u'').
902 replace(u'2n1l-', u'')
905 y=[y / 1e6 if y else None for y in vals],
910 f"{u's' if len(vals) > 1 else u''}) "
915 box_points = plot.get(u"boxpoints", u"all")
917 (u"all", u"outliers", u"suspectedoutliers", False):
918 kwargs[u"boxpoints"] = box_points
919 kwargs[u"jitter"] = 0.3
920 traces.append(plgo.Box(**kwargs))
922 data_y_max.append(max(vals))
923 except ValueError as err:
924 logging.warning(f"No values to use.\n{err!r}")
927 layout = deepcopy(plot[u"layout"])
928 layout[u"xaxis"][u"tickvals"] = [i for i in range(len(data_y))]
929 layout[u"xaxis"][u"ticktext"] = \
930 [str(i + 1) for i in range(len(data_y))]
931 if layout.get(u"title", None):
933 layout[u'title'].format(core=core, test_type=ttype)
934 if test_type in (u"CPS", ):
935 layout[u"title"] = f"<b>CPS:</b> {layout[u'title']}"
938 f"<b>Tput:</b> {layout[u'title']}"
940 layout[u"yaxis"][u"range"] = [0, max(data_y_max) / 1e6 + 1]
941 plpl = plgo.Figure(data=traces, layout=layout)
945 f"{plot[u'output-file'].format(core=core, test_type=ttype)}"
948 logging.info(f" Writing file {file_name}")
955 except PlotlyError as err:
957 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
961 def plot_mrr_box_name(plot, input_data):
962 """Generate the plot(s) with algorithm: plot_mrr_box_name
963 specified in the specification file.
965 :param plot: Plot to generate.
966 :param input_data: Data to process.
967 :type plot: pandas.Series
968 :type input_data: InputData
973 f" Creating data set for the {plot.get(u'type', u'')} "
974 f"{plot.get(u'title', u'')}."
976 data = input_data.filter_tests_by_name(
978 params=[u"result", u"parent", u"tags", u"type"]
981 logging.error(u"No data.")
984 for core in plot.get(u"core", tuple()):
985 # Prepare the data for the plot
991 for item in plot.get(u"include", tuple()):
992 reg_ex = re.compile(str(item.format(core=core)).lower())
995 for test_id, test in build.iteritems():
996 if not re.match(reg_ex, str(test_id).lower()):
1001 REGEX_NIC, u'', test[u'parent'].lower().
1002 replace(u'-mrr', u'').replace(u'2n1l-', u'')
1004 data_y.append(test[u"result"][u"samples"])
1007 f"({len(data_y[-1]):02d} "
1008 f"run{u's' if len(data_y[-1]) > 1 else u''}) "
1011 data_y_max.append(max(data_y[-1]))
1013 except (KeyError, TypeError):
1018 for idx, x_item in enumerate(data_x):
1021 name=data_names[idx],
1024 box_points = plot.get(u"boxpoints", u"all")
1025 if box_points in (u"all", u"outliers", u"suspectedoutliers", False):
1026 kwargs[u"boxpoints"] = box_points
1027 kwargs["jitter"] = 0.3
1028 traces.append(plgo.Box(**kwargs))
1032 layout = deepcopy(plot[u"layout"])
1033 layout[u"xaxis"][u"tickvals"] = [i for i in range(len(data_y))]
1034 layout[u"xaxis"][u"ticktext"] = \
1035 [str(i + 1) for i in range(len(data_y))]
1036 if layout.get(u"title", None):
1037 layout[u"title"] = (
1038 f"<b>Tput:</b> {layout[u'title'].format(core=core)}"
1041 layout[u"yaxis"][u"range"] = [0, max(data_y_max) + 1]
1042 plpl = plgo.Figure(data=traces, layout=layout)
1045 file_name = f"{plot[u'output-file'].format(core=core)}.html"
1046 logging.info(f" Writing file {file_name}")
1053 except PlotlyError as err:
1055 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
1059 def plot_tsa_name(plot, input_data):
1060 """Generate the plot(s) with algorithm:
1062 specified in the specification file.
1064 :param plot: Plot to generate.
1065 :param input_data: Data to process.
1066 :type plot: pandas.Series
1067 :type input_data: InputData
1070 # Transform the data
1071 plot_title = plot.get(u"title", u"")
1073 f" Creating data set for the {plot.get(u'type', u'')} {plot_title}."
1075 data = input_data.filter_tests_by_name(
1077 params=[u"throughput", u"gbps", u"parent", u"tags", u"type"]
1080 logging.error(u"No data.")
1083 plot_title = plot_title.lower()
1085 if u"-gbps" in plot_title:
1090 value = u"throughput"
1094 for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
1095 y_vals = OrderedDict()
1096 for item in plot.get(u"include", tuple()):
1097 reg_ex = re.compile(str(item).lower())
1100 for test_id, test in build.iteritems():
1101 if re.match(reg_ex, str(test_id).lower()):
1102 if y_vals.get(test[u"parent"], None) is None:
1103 y_vals[test[u"parent"]] = {
1109 if test[u"type"] not in (u"NDRPDR", u"CPS"):
1112 if u"1C" in test[u"tags"]:
1113 y_vals[test[u"parent"]][u"1"].append(
1114 test[value][ttype.upper()][u"LOWER"] *
1117 elif u"2C" in test[u"tags"]:
1118 y_vals[test[u"parent"]][u"2"].append(
1119 test[value][ttype.upper()][u"LOWER"] *
1122 elif u"4C" in test[u"tags"]:
1123 y_vals[test[u"parent"]][u"4"].append(
1124 test[value][ttype.upper()][u"LOWER"] *
1127 except (KeyError, TypeError):
1131 logging.warning(f"No data for the plot {plot.get(u'title', u'')}")
1135 for test_name, test_vals in y_vals.items():
1136 for key, test_val in test_vals.items():
1138 avg_val = sum(test_val) / len(test_val)
1139 y_vals[test_name][key] = [avg_val, len(test_val)]
1140 ideal = avg_val / (int(key) * 1e6)
1141 if test_name not in y_1c_max or ideal > y_1c_max[test_name]:
1142 y_1c_max[test_name] = ideal
1144 vals = OrderedDict()
1149 for test_name, test_vals in y_vals.items():
1151 if test_vals[u"1"][1]:
1155 test_name.replace(u'-ndrpdr', u'').
1156 replace(u'2n1l-', u'')
1158 vals[name] = OrderedDict()
1159 y_val_1 = test_vals[u"1"][0] / 1e6
1160 y_val_2 = test_vals[u"2"][0] / 1e6 if test_vals[u"2"][0] \
1162 y_val_4 = test_vals[u"4"][0] / 1e6 if test_vals[u"4"][0] \
1165 vals[name][u"val"] = [y_val_1, y_val_2, y_val_4]
1166 vals[name][u"rel"] = [1.0, None, None]
1167 vals[name][u"ideal"] = [
1168 y_1c_max[test_name],
1169 y_1c_max[test_name] * 2,
1170 y_1c_max[test_name] * 4
1172 vals[name][u"diff"] = [
1173 (y_val_1 - y_1c_max[test_name]) * 100 / y_val_1,
1177 vals[name][u"count"] = [
1184 val_max = max(vals[name][u"val"])
1185 except ValueError as err:
1186 logging.error(repr(err))
1189 y_max.append(val_max)
1192 vals[name][u"rel"][1] = round(y_val_2 / y_val_1, 2)
1193 vals[name][u"diff"][1] = \
1194 (y_val_2 - vals[name][u"ideal"][1]) * 100 / y_val_2
1196 vals[name][u"rel"][2] = round(y_val_4 / y_val_1, 2)
1197 vals[name][u"diff"][2] = \
1198 (y_val_4 - vals[name][u"ideal"][2]) * 100 / y_val_4
1199 except IndexError as err:
1200 logging.warning(f"No data for {test_name}")
1201 logging.warning(repr(err))
1204 if u"x520" in test_name:
1205 limit = plot[u"limits"][u"nic"][u"x520"]
1206 elif u"x710" in test_name:
1207 limit = plot[u"limits"][u"nic"][u"x710"]
1208 elif u"xxv710" in test_name:
1209 limit = plot[u"limits"][u"nic"][u"xxv710"]
1210 elif u"xl710" in test_name:
1211 limit = plot[u"limits"][u"nic"][u"xl710"]
1212 elif u"x553" in test_name:
1213 limit = plot[u"limits"][u"nic"][u"x553"]
1214 elif u"cx556a" in test_name:
1215 limit = plot[u"limits"][u"nic"][u"cx556a"]
1216 elif u"e810cq" in test_name:
1217 limit = plot[u"limits"][u"nic"][u"e810cq"]
1218 elif u"e810xxv" in test_name:
1219 limit = plot[u"limits"][u"nic"][u"e810xxv"]
1220 elif u"e822cq" in test_name:
1221 limit = plot[u"limits"][u"nic"][u"e822cq"]
1224 if limit > nic_limit:
1227 mul = 2 if u"ge2p" in test_name else 1
1228 if u"10ge" in test_name:
1229 limit = plot[u"limits"][u"link"][u"10ge"] * mul
1230 elif u"25ge" in test_name:
1231 limit = plot[u"limits"][u"link"][u"25ge"] * mul
1232 elif u"40ge" in test_name:
1233 limit = plot[u"limits"][u"link"][u"40ge"] * mul
1234 elif u"100ge" in test_name:
1235 limit = plot[u"limits"][u"link"][u"100ge"] * mul
1238 if limit > lnk_limit:
1241 if u"cx556a" in test_name:
1242 limit = plot[u"limits"][u"pci"][u"pci-g3-x8"]
1244 limit = plot[u"limits"][u"pci"][u"pci-g3-x16"]
1245 if limit > pci_limit:
1249 annotations = list()
1253 if u"-gbps" not in plot_title and u"-cps-" not in plot_title:
1257 min_limit = min((nic_limit, lnk_limit, pci_limit))
1258 if nic_limit == min_limit:
1259 traces.append(plgo.Scatter(
1261 y=[nic_limit, ] * len(x_vals),
1262 name=f"NIC: {nic_limit:.2f}Mpps",
1271 annotations.append(dict(
1278 text=f"NIC: {nic_limit:.2f}Mpps",
1286 y_max.append(nic_limit)
1287 elif lnk_limit == min_limit:
1288 traces.append(plgo.Scatter(
1290 y=[lnk_limit, ] * len(x_vals),
1291 name=f"Link: {lnk_limit:.2f}Mpps",
1300 annotations.append(dict(
1307 text=f"Link: {lnk_limit:.2f}Mpps",
1315 y_max.append(lnk_limit)
1316 elif pci_limit == min_limit:
1317 traces.append(plgo.Scatter(
1319 y=[pci_limit, ] * len(x_vals),
1320 name=f"PCIe: {pci_limit:.2f}Mpps",
1329 annotations.append(dict(
1336 text=f"PCIe: {pci_limit:.2f}Mpps",
1344 y_max.append(pci_limit)
1346 # Perfect and measured:
1348 for name, val in vals.items():
1351 for idx in range(len(val[u"val"])):
1353 if isinstance(val[u"val"][idx], float):
1355 f"No. of Runs: {val[u'count'][idx]}<br>"
1356 f"Mean: {val[u'val'][idx]:.2f}{h_unit}<br>"
1358 if isinstance(val[u"diff"][idx], float):
1359 htext += f"Diff: {round(val[u'diff'][idx]):.0f}%<br>"
1360 if isinstance(val[u"rel"][idx], float):
1361 htext += f"Speedup: {val[u'rel'][idx]:.2f}"
1362 hovertext.append(htext)
1369 mode=u"lines+markers",
1378 hoverinfo=u"text+name"
1385 name=f"{name} perfect",
1393 text=[f"Perfect: {y:.2f}Mpps" for y in val[u"ideal"]],
1398 except (IndexError, ValueError, KeyError) as err:
1399 logging.warning(f"No data for {name}\n{repr(err)}")
1403 file_name = f"{plot[u'output-file'].format(test_type=ttype)}.html"
1404 logging.info(f" Writing file {file_name}")
1405 layout = deepcopy(plot[u"layout"])
1406 if layout.get(u"title", None):
1407 layout[u"title"] = (
1408 f"<b>Speedup Multi-core:</b> "
1409 f"{layout[u'title'].format(test_type=ttype)}"
1411 layout[u"yaxis"][u"range"] = [0, int(max(y_max) * 1.1)]
1412 layout[u"annotations"].extend(annotations)
1413 plpl = plgo.Figure(data=traces, layout=layout)
1422 except PlotlyError as err:
1424 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
1428 def plot_http_server_perf_box(plot, input_data):
1429 """Generate the plot(s) with algorithm: plot_http_server_perf_box
1430 specified in the specification file.
1432 :param plot: Plot to generate.
1433 :param input_data: Data to process.
1434 :type plot: pandas.Series
1435 :type input_data: InputData
1438 # Transform the data
1440 f" Creating the data set for the {plot.get(u'type', u'')} "
1441 f"{plot.get(u'title', u'')}."
1443 data = input_data.filter_data(plot)
1445 logging.error(u"No data.")
1448 # Prepare the data for the plot
1453 if y_vals.get(test[u"name"], None) is None:
1454 y_vals[test[u"name"]] = list()
1456 y_vals[test[u"name"]].append(test[u"result"])
1457 except (KeyError, TypeError):
1458 y_vals[test[u"name"]].append(None)
1460 # Add None to the lists with missing data
1462 nr_of_samples = list()
1463 for val in y_vals.values():
1464 if len(val) > max_len:
1466 nr_of_samples.append(len(val))
1467 for val in y_vals.values():
1468 if len(val) < max_len:
1469 val.extend([None for _ in range(max_len - len(val))])
1473 df_y = pd.DataFrame(y_vals)
1475 for i, col in enumerate(df_y.columns):
1478 f"({nr_of_samples[i]:02d} " \
1479 f"run{u's' if nr_of_samples[i] > 1 else u''}) " \
1480 f"{col.lower().replace(u'-ndrpdr', u'')}"
1482 name_lst = name.split(u'-')
1485 for segment in name_lst:
1486 if (len(name) + len(segment) + 1) > 50 and split_name:
1489 name += segment + u'-'
1492 traces.append(plgo.Box(x=[str(i + 1) + u'.'] * len(df_y[col]),
1498 plpl = plgo.Figure(data=traces, layout=plot[u"layout"])
1502 f" Writing file {plot[u'output-file']}"
1503 f"{plot[u'output-file-type']}."
1509 filename=f"{plot[u'output-file']}{plot[u'output-file-type']}"
1511 except PlotlyError as err:
1513 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
1518 def plot_nf_heatmap(plot, input_data):
1519 """Generate the plot(s) with algorithm: plot_nf_heatmap
1520 specified in the specification file.
1522 :param plot: Plot to generate.
1523 :param input_data: Data to process.
1524 :type plot: pandas.Series
1525 :type input_data: InputData
1528 def sort_by_int(value):
1529 """Makes possible to sort a list of strings which represent integers.
1531 :param value: Integer as a string.
1533 :returns: Integer representation of input parameter 'value'.
1538 regex_cn = re.compile(r'^(\d*)R(\d*)C$')
1539 regex_test_name = re.compile(r'^.*-(\d+ch|\d+pl)-'
1541 r'(\d+vm\d+t|\d+dcr\d+t|\d+dcr\d+c).*$')
1542 # Transform the data
1544 f" Creating the data set for the {plot.get(u'type', u'')} "
1545 f"{plot.get(u'title', u'')}."
1547 in_data = input_data.filter_tests_by_name(
1549 continue_on_error=True,
1550 params=[u"throughput", u"result", u"name", u"tags", u"type"]
1552 if in_data is None or in_data.empty:
1553 logging.error(u"No data.")
1556 for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
1557 for core in plot.get(u"core", tuple()):
1559 for item in plot.get(u"include", tuple()):
1560 reg_ex = re.compile(str(item.format(core=core)).lower())
1563 for test_id, test in build.iteritems():
1564 if not re.match(reg_ex, str(test_id).lower()):
1566 for tag in test[u"tags"]:
1567 groups = re.search(regex_cn, tag)
1569 chain = str(groups.group(1))
1570 node = str(groups.group(2))
1574 groups = re.search(regex_test_name, test[u"name"])
1575 if groups and len(groups.groups()) == 3:
1577 f"{str(groups.group(1))}-"
1578 f"{str(groups.group(2))}-"
1579 f"{str(groups.group(3))}"
1583 if vals.get(chain, None) is None:
1584 vals[chain] = dict()
1585 if vals[chain].get(node, None) is None:
1586 vals[chain][node] = dict(
1595 result = test[u"result"][u"receive-rate"]
1596 elif ttype == u"pdr":
1598 test[u"throughput"][u"PDR"][u"LOWER"]
1599 elif ttype == u"ndr":
1601 test[u"throughput"][u"NDR"][u"LOWER"]
1608 vals[chain][node][u"vals"].append(result)
1611 logging.error(u"No data.")
1617 txt_chains.append(key_c)
1618 for key_n in vals[key_c].keys():
1619 txt_nodes.append(key_n)
1620 if vals[key_c][key_n][u"vals"]:
1621 vals[key_c][key_n][u"nr"] = \
1622 len(vals[key_c][key_n][u"vals"])
1623 vals[key_c][key_n][u"mean"] = \
1624 round(mean(vals[key_c][key_n][u"vals"]) / 1e6, 1)
1625 vals[key_c][key_n][u"stdev"] = \
1626 round(stdev(vals[key_c][key_n][u"vals"]) / 1e6, 1)
1627 txt_nodes = list(set(txt_nodes))
1629 txt_chains = sorted(txt_chains, key=sort_by_int)
1630 txt_nodes = sorted(txt_nodes, key=sort_by_int)
1632 chains = [i + 1 for i in range(len(txt_chains))]
1633 nodes = [i + 1 for i in range(len(txt_nodes))]
1635 data = [list() for _ in range(len(chains))]
1636 for chain in chains:
1639 val = vals[txt_chains[chain - 1]] \
1640 [txt_nodes[node - 1]][u"mean"]
1641 except (KeyError, IndexError):
1643 data[chain - 1].append(val)
1646 my_green = [[0.0, u"rgb(235, 249, 242)"],
1647 [1.0, u"rgb(45, 134, 89)"]]
1649 my_blue = [[0.0, u"rgb(236, 242, 248)"],
1650 [1.0, u"rgb(57, 115, 172)"]]
1652 my_grey = [[0.0, u"rgb(230, 230, 230)"],
1653 [1.0, u"rgb(102, 102, 102)"]]
1656 annotations = list()
1658 text = (u"Test: {name}<br>"
1663 for chain, _ in enumerate(txt_chains):
1665 for node, _ in enumerate(txt_nodes):
1666 if data[chain][node] is not None:
1675 text=str(data[chain][node]),
1683 hover_line.append(text.format(
1684 name=vals[txt_chains[chain]][txt_nodes[node]]
1686 nr=vals[txt_chains[chain]][txt_nodes[node]][u"nr"],
1687 val=data[chain][node],
1688 stdev=vals[txt_chains[chain]][txt_nodes[node]]
1691 hovertext.append(hover_line)
1699 title=plot.get(u"z-axis", u"{test_type}").
1700 format(test_type=ttype.upper()),
1714 colorscale=my_green,
1720 for idx, item in enumerate(txt_nodes):
1738 for idx, item in enumerate(txt_chains):
1765 text=plot.get(u"x-axis", u""),
1782 text=plot.get(u"y-axis", u""),
1791 updatemenus = list([
1802 u"colorscale": [my_green, ],
1803 u"reversescale": False
1812 u"colorscale": [my_blue, ],
1813 u"reversescale": False
1822 u"colorscale": [my_grey, ],
1823 u"reversescale": False
1834 layout = deepcopy(plot[u"layout"])
1835 except KeyError as err:
1837 f"Finished with error: No layout defined\n{repr(err)}"
1841 layout[u"annotations"] = annotations
1842 layout[u'updatemenus'] = updatemenus
1843 if layout.get(u"title", None):
1844 layout[u"title"] = layout[u'title'].replace(u"test_type", ttype)
1848 plpl = plgo.Figure(data=traces, layout=layout)
1852 f"{plot[u'output-file'].format(core=core, test_type=ttype)}"
1855 logging.info(f" Writing file {file_name}")
1862 except PlotlyError as err:
1864 f" Finished with error: {repr(err)}".replace(u"\n", u" ")