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",):
738 if u"TCP_CPS" in test[u"tags"]:
739 test_type = u"VSAP_CPS"
740 y_vals[test[u"parent"]].append(
741 test[u"result"][u"cps"]
743 elif u"TCP_RPS" in test[u"tags"]:
744 test_type = u"VSAP_RPS"
745 y_vals[test[u"parent"]].append(
746 test[u"result"][u"rps"]
753 except (KeyError, TypeError):
754 y_vals[test[u"parent"]].append(None)
756 # Add None to the lists with missing data
758 nr_of_samples = list()
759 for val in y_vals.values():
760 if len(val) > max_len:
762 nr_of_samples.append(len(val))
763 for val in y_vals.values():
764 if len(val) < max_len:
765 val.extend([None for _ in range(max_len - len(val))])
769 df_y = pd.DataFrame(y_vals)
772 for i, col in enumerate(df_y.columns):
773 tst_name = re.sub(REGEX_NIC, u"",
774 col.lower().replace(u'-ndrpdr', u'').
775 replace(u'2n1l-', u''))
776 if test_type in (u"VSAP_CPS", u"VSAP_RPS"):
777 data_y = [y if y else None for y in df_y[col]]
779 data_y = [y / 1e6 if y else None for y in df_y[col]]
784 f"({nr_of_samples[i]:02d} "
785 f"run{u's' if nr_of_samples[i] > 1 else u''}) "
790 if test_type in (u"SOAK", ):
791 kwargs[u"boxpoints"] = u"all"
792 kwargs[u"jitter"] = 0.3
794 traces.append(plgo.Box(**kwargs))
797 val_max = max(df_y[col])
799 y_max.append(int(val_max / 1e6))
800 except (ValueError, TypeError) as err:
801 logging.error(repr(err))
806 layout = deepcopy(plot[u"layout"])
807 layout[u"xaxis"][u"tickvals"] = [i for i in range(len(y_vals))]
808 layout[u"xaxis"][u"ticktext"] = [str(i + 1) for i in range(len(y_vals))]
809 if layout.get(u"title", None):
810 if test_type in (u"HOSTSTACK", ):
811 layout[u"title"] = f"<b>Bandwidth:</b> {layout[u'title']}"
812 elif test_type == u"VSAP_CPS":
813 layout[u"title"] = f"<b>CPS:</b> {layout[u'title']}"
814 layout[u"yaxis"][u"title"] = u"<b>Connection Rate [cps]</b>"
815 elif test_type == u"VSAP_RPS":
816 layout[u"title"] = f"<b>RPS:</b> {layout[u'title']}"
817 layout[u"yaxis"][u"title"] = u"<b>Connection Rate [rps]</b>"
819 layout[u"title"] = f"<b>Tput:</b> {layout[u'title']}"
820 if y_max and max(y_max) > 1:
821 layout[u"yaxis"][u"range"] = [0, max(y_max) + 2]
822 plpl = plgo.Figure(data=traces, layout=layout)
825 logging.info(f" Writing file {plot[u'output-file']}.html.")
830 filename=f"{plot[u'output-file']}.html"
832 except PlotlyError as err:
834 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
839 def plot_ndrpdr_box_name(plot, input_data):
840 """Generate the plot(s) with algorithm: plot_ndrpdr_box_name
841 specified in the specification file.
843 :param plot: Plot to generate.
844 :param input_data: Data to process.
845 :type plot: pandas.Series
846 :type input_data: InputData
851 f" Creating data set for the {plot.get(u'type', u'')} "
852 f"{plot.get(u'title', u'')}."
854 data = input_data.filter_tests_by_name(
856 params=[u"throughput", u"gbps", u"parent", u"tags", u"type"]
859 logging.error(u"No data.")
862 if u"-gbps" in plot.get(u"title", u"").lower():
866 value = u"throughput"
871 for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
872 for core in plot.get(u"core", tuple()):
873 # Prepare the data for the plot
875 data_y = OrderedDict()
878 for item in plot.get(u"include", tuple()):
879 reg_ex = re.compile(str(item.format(core=core)).lower())
882 for test_id, test in build.iteritems():
883 if not re.match(reg_ex, str(test_id).lower()):
885 if data_y.get(test[u"parent"], None) is None:
886 data_y[test[u"parent"]] = list()
887 test_type = test[u"type"]
891 data_y[test[u"parent"]].append(
892 test[value][ttype.upper()][u"LOWER"] *
895 except (KeyError, TypeError):
900 for idx, (key, vals) in enumerate(data_y.items()):
902 REGEX_NIC, u'', key.lower().replace(u'-ndrpdr', u'').
903 replace(u'2n1l-', u'')
906 y=[y / 1e6 if y else None for y in vals],
911 f"{u's' if len(vals) > 1 else u''}) "
916 box_points = plot.get(u"boxpoints", u"all")
918 (u"all", u"outliers", u"suspectedoutliers", False):
919 kwargs[u"boxpoints"] = box_points
920 kwargs[u"jitter"] = 0.3
921 traces.append(plgo.Box(**kwargs))
923 data_y_max.append(max(vals))
924 except ValueError as err:
925 logging.warning(f"No values to use.\n{err!r}")
928 layout = deepcopy(plot[u"layout"])
929 layout[u"xaxis"][u"tickvals"] = [i for i in range(len(data_y))]
930 layout[u"xaxis"][u"ticktext"] = \
931 [str(i + 1) for i in range(len(data_y))]
932 if layout.get(u"title", None):
934 layout[u'title'].format(core=core, test_type=ttype)
935 if test_type in (u"CPS", ):
936 layout[u"title"] = f"<b>CPS:</b> {layout[u'title']}"
939 f"<b>Tput:</b> {layout[u'title']}"
941 layout[u"yaxis"][u"range"] = [0, max(data_y_max) / 1e6 + 1]
942 plpl = plgo.Figure(data=traces, layout=layout)
946 f"{plot[u'output-file'].format(core=core, test_type=ttype)}"
949 logging.info(f" Writing file {file_name}")
956 except PlotlyError as err:
958 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
962 def plot_mrr_box_name(plot, input_data):
963 """Generate the plot(s) with algorithm: plot_mrr_box_name
964 specified in the specification file.
966 :param plot: Plot to generate.
967 :param input_data: Data to process.
968 :type plot: pandas.Series
969 :type input_data: InputData
974 f" Creating data set for the {plot.get(u'type', u'')} "
975 f"{plot.get(u'title', u'')}."
977 data = input_data.filter_tests_by_name(
979 params=[u"result", u"parent", u"tags", u"type"]
982 logging.error(u"No data.")
985 for core in plot.get(u"core", tuple()):
986 # Prepare the data for the plot
992 for item in plot.get(u"include", tuple()):
993 reg_ex = re.compile(str(item.format(core=core)).lower())
996 for test_id, test in build.iteritems():
997 if not re.match(reg_ex, str(test_id).lower()):
1002 REGEX_NIC, u'', test[u'parent'].lower().
1003 replace(u'-mrr', u'').replace(u'2n1l-', u'')
1005 data_y.append(test[u"result"][u"samples"])
1008 f"({len(data_y[-1]):02d} "
1009 f"run{u's' if len(data_y[-1]) > 1 else u''}) "
1012 data_y_max.append(max(data_y[-1]))
1014 except (KeyError, TypeError):
1019 for idx, x_item in enumerate(data_x):
1022 name=data_names[idx],
1025 box_points = plot.get(u"boxpoints", u"all")
1026 if box_points in (u"all", u"outliers", u"suspectedoutliers", False):
1027 kwargs[u"boxpoints"] = box_points
1028 kwargs["jitter"] = 0.3
1029 traces.append(plgo.Box(**kwargs))
1033 layout = deepcopy(plot[u"layout"])
1034 layout[u"xaxis"][u"tickvals"] = [i for i in range(len(data_y))]
1035 layout[u"xaxis"][u"ticktext"] = \
1036 [str(i + 1) for i in range(len(data_y))]
1037 if layout.get(u"title", None):
1038 layout[u"title"] = (
1039 f"<b>Tput:</b> {layout[u'title'].format(core=core)}"
1042 layout[u"yaxis"][u"range"] = [0, max(data_y_max) + 1]
1043 plpl = plgo.Figure(data=traces, layout=layout)
1046 file_name = f"{plot[u'output-file'].format(core=core)}.html"
1047 logging.info(f" Writing file {file_name}")
1054 except PlotlyError as err:
1056 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
1060 def plot_tsa_name(plot, input_data):
1061 """Generate the plot(s) with algorithm:
1063 specified in the specification file.
1065 :param plot: Plot to generate.
1066 :param input_data: Data to process.
1067 :type plot: pandas.Series
1068 :type input_data: InputData
1071 # Transform the data
1072 plot_title = plot.get(u"title", u"")
1074 f" Creating data set for the {plot.get(u'type', u'')} {plot_title}."
1076 data = input_data.filter_tests_by_name(
1078 params=[u"throughput", u"gbps", u"parent", u"tags", u"type"]
1081 logging.error(u"No data.")
1084 plot_title = plot_title.lower()
1086 if u"-gbps" in plot_title:
1091 value = u"throughput"
1095 for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
1096 y_vals = OrderedDict()
1097 for item in plot.get(u"include", tuple()):
1098 reg_ex = re.compile(str(item).lower())
1101 for test_id, test in build.iteritems():
1102 if re.match(reg_ex, str(test_id).lower()):
1103 if y_vals.get(test[u"parent"], None) is None:
1104 y_vals[test[u"parent"]] = {
1110 if test[u"type"] not in (u"NDRPDR", u"CPS"):
1113 if u"1C" in test[u"tags"]:
1114 y_vals[test[u"parent"]][u"1"].append(
1115 test[value][ttype.upper()][u"LOWER"] *
1118 elif u"2C" in test[u"tags"]:
1119 y_vals[test[u"parent"]][u"2"].append(
1120 test[value][ttype.upper()][u"LOWER"] *
1123 elif u"4C" in test[u"tags"]:
1124 y_vals[test[u"parent"]][u"4"].append(
1125 test[value][ttype.upper()][u"LOWER"] *
1128 except (KeyError, TypeError):
1132 logging.warning(f"No data for the plot {plot.get(u'title', u'')}")
1136 for test_name, test_vals in y_vals.items():
1137 for key, test_val in test_vals.items():
1139 avg_val = sum(test_val) / len(test_val)
1140 y_vals[test_name][key] = [avg_val, len(test_val)]
1141 ideal = avg_val / (int(key) * 1e6)
1142 if test_name not in y_1c_max or ideal > y_1c_max[test_name]:
1143 y_1c_max[test_name] = ideal
1145 vals = OrderedDict()
1150 for test_name, test_vals in y_vals.items():
1152 if test_vals[u"1"][1]:
1156 test_name.replace(u'-ndrpdr', u'').
1157 replace(u'2n1l-', u'')
1159 vals[name] = OrderedDict()
1160 y_val_1 = test_vals[u"1"][0] / 1e6
1161 y_val_2 = test_vals[u"2"][0] / 1e6 if test_vals[u"2"][0] \
1163 y_val_4 = test_vals[u"4"][0] / 1e6 if test_vals[u"4"][0] \
1166 vals[name][u"val"] = [y_val_1, y_val_2, y_val_4]
1167 vals[name][u"rel"] = [1.0, None, None]
1168 vals[name][u"ideal"] = [
1169 y_1c_max[test_name],
1170 y_1c_max[test_name] * 2,
1171 y_1c_max[test_name] * 4
1173 vals[name][u"diff"] = [
1174 (y_val_1 - y_1c_max[test_name]) * 100 / y_val_1,
1178 vals[name][u"count"] = [
1185 val_max = max(vals[name][u"val"])
1186 except ValueError as err:
1187 logging.error(repr(err))
1190 y_max.append(val_max)
1193 vals[name][u"rel"][1] = round(y_val_2 / y_val_1, 2)
1194 vals[name][u"diff"][1] = \
1195 (y_val_2 - vals[name][u"ideal"][1]) * 100 / y_val_2
1197 vals[name][u"rel"][2] = round(y_val_4 / y_val_1, 2)
1198 vals[name][u"diff"][2] = \
1199 (y_val_4 - vals[name][u"ideal"][2]) * 100 / y_val_4
1200 except IndexError as err:
1201 logging.warning(f"No data for {test_name}")
1202 logging.warning(repr(err))
1205 if u"x520" in test_name:
1206 limit = plot[u"limits"][u"nic"][u"x520"]
1207 elif u"x710" in test_name:
1208 limit = plot[u"limits"][u"nic"][u"x710"]
1209 elif u"xxv710" in test_name:
1210 limit = plot[u"limits"][u"nic"][u"xxv710"]
1211 elif u"xl710" in test_name:
1212 limit = plot[u"limits"][u"nic"][u"xl710"]
1213 elif u"x553" in test_name:
1214 limit = plot[u"limits"][u"nic"][u"x553"]
1215 elif u"cx556a" in test_name:
1216 limit = plot[u"limits"][u"nic"][u"cx556a"]
1217 elif u"e810cq" in test_name:
1218 limit = plot[u"limits"][u"nic"][u"e810cq"]
1219 elif u"e810xxv" in test_name:
1220 limit = plot[u"limits"][u"nic"][u"e810xxv"]
1221 elif u"e822cq" in test_name:
1222 limit = plot[u"limits"][u"nic"][u"e822cq"]
1225 if limit > nic_limit:
1228 mul = 2 if u"ge2p" in test_name else 1
1229 if u"10ge" in test_name:
1230 limit = plot[u"limits"][u"link"][u"10ge"] * mul
1231 elif u"25ge" in test_name:
1232 limit = plot[u"limits"][u"link"][u"25ge"] * mul
1233 elif u"40ge" in test_name:
1234 limit = plot[u"limits"][u"link"][u"40ge"] * mul
1235 elif u"100ge" in test_name:
1236 limit = plot[u"limits"][u"link"][u"100ge"] * mul
1239 if limit > lnk_limit:
1242 if u"cx556a" in test_name:
1243 limit = plot[u"limits"][u"pci"][u"pci-g3-x8"]
1245 limit = plot[u"limits"][u"pci"][u"pci-g3-x16"]
1246 if limit > pci_limit:
1250 annotations = list()
1254 if u"-gbps" not in plot_title and u"-cps-" not in plot_title:
1258 min_limit = min((nic_limit, lnk_limit, pci_limit))
1259 if nic_limit == min_limit:
1260 traces.append(plgo.Scatter(
1262 y=[nic_limit, ] * len(x_vals),
1263 name=f"NIC: {nic_limit:.2f}Mpps",
1272 annotations.append(dict(
1279 text=f"NIC: {nic_limit:.2f}Mpps",
1287 y_max.append(nic_limit)
1288 elif lnk_limit == min_limit:
1289 traces.append(plgo.Scatter(
1291 y=[lnk_limit, ] * len(x_vals),
1292 name=f"Link: {lnk_limit:.2f}Mpps",
1301 annotations.append(dict(
1308 text=f"Link: {lnk_limit:.2f}Mpps",
1316 y_max.append(lnk_limit)
1317 elif pci_limit == min_limit:
1318 traces.append(plgo.Scatter(
1320 y=[pci_limit, ] * len(x_vals),
1321 name=f"PCIe: {pci_limit:.2f}Mpps",
1330 annotations.append(dict(
1337 text=f"PCIe: {pci_limit:.2f}Mpps",
1345 y_max.append(pci_limit)
1347 # Perfect and measured:
1349 for name, val in vals.items():
1352 for idx in range(len(val[u"val"])):
1354 if isinstance(val[u"val"][idx], float):
1356 f"No. of Runs: {val[u'count'][idx]}<br>"
1357 f"Mean: {val[u'val'][idx]:.2f}{h_unit}<br>"
1359 if isinstance(val[u"diff"][idx], float):
1360 htext += f"Diff: {round(val[u'diff'][idx]):.0f}%<br>"
1361 if isinstance(val[u"rel"][idx], float):
1362 htext += f"Speedup: {val[u'rel'][idx]:.2f}"
1363 hovertext.append(htext)
1370 mode=u"lines+markers",
1379 hoverinfo=u"text+name"
1386 name=f"{name} perfect",
1394 text=[f"Perfect: {y:.2f}Mpps" for y in val[u"ideal"]],
1399 except (IndexError, ValueError, KeyError) as err:
1400 logging.warning(f"No data for {name}\n{repr(err)}")
1404 file_name = f"{plot[u'output-file'].format(test_type=ttype)}.html"
1405 logging.info(f" Writing file {file_name}")
1406 layout = deepcopy(plot[u"layout"])
1407 if layout.get(u"title", None):
1408 layout[u"title"] = (
1409 f"<b>Speedup Multi-core:</b> "
1410 f"{layout[u'title'].format(test_type=ttype)}"
1412 layout[u"yaxis"][u"range"] = [0, int(max(y_max) * 1.1)]
1413 layout[u"annotations"].extend(annotations)
1414 plpl = plgo.Figure(data=traces, layout=layout)
1423 except PlotlyError as err:
1425 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
1429 def plot_http_server_perf_box(plot, input_data):
1430 """Generate the plot(s) with algorithm: plot_http_server_perf_box
1431 specified in the specification file.
1433 :param plot: Plot to generate.
1434 :param input_data: Data to process.
1435 :type plot: pandas.Series
1436 :type input_data: InputData
1439 # Transform the data
1441 f" Creating the data set for the {plot.get(u'type', u'')} "
1442 f"{plot.get(u'title', u'')}."
1444 data = input_data.filter_data(plot)
1446 logging.error(u"No data.")
1449 # Prepare the data for the plot
1454 if y_vals.get(test[u"name"], None) is None:
1455 y_vals[test[u"name"]] = list()
1457 y_vals[test[u"name"]].append(test[u"result"])
1458 except (KeyError, TypeError):
1459 y_vals[test[u"name"]].append(None)
1461 # Add None to the lists with missing data
1463 nr_of_samples = list()
1464 for val in y_vals.values():
1465 if len(val) > max_len:
1467 nr_of_samples.append(len(val))
1468 for val in y_vals.values():
1469 if len(val) < max_len:
1470 val.extend([None for _ in range(max_len - len(val))])
1474 df_y = pd.DataFrame(y_vals)
1476 for i, col in enumerate(df_y.columns):
1479 f"({nr_of_samples[i]:02d} " \
1480 f"run{u's' if nr_of_samples[i] > 1 else u''}) " \
1481 f"{col.lower().replace(u'-ndrpdr', u'')}"
1483 name_lst = name.split(u'-')
1486 for segment in name_lst:
1487 if (len(name) + len(segment) + 1) > 50 and split_name:
1490 name += segment + u'-'
1493 traces.append(plgo.Box(x=[str(i + 1) + u'.'] * len(df_y[col]),
1499 plpl = plgo.Figure(data=traces, layout=plot[u"layout"])
1503 f" Writing file {plot[u'output-file']}"
1504 f"{plot[u'output-file-type']}."
1510 filename=f"{plot[u'output-file']}{plot[u'output-file-type']}"
1512 except PlotlyError as err:
1514 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
1519 def plot_nf_heatmap(plot, input_data):
1520 """Generate the plot(s) with algorithm: plot_nf_heatmap
1521 specified in the specification file.
1523 :param plot: Plot to generate.
1524 :param input_data: Data to process.
1525 :type plot: pandas.Series
1526 :type input_data: InputData
1529 def sort_by_int(value):
1530 """Makes possible to sort a list of strings which represent integers.
1532 :param value: Integer as a string.
1534 :returns: Integer representation of input parameter 'value'.
1539 regex_cn = re.compile(r'^(\d*)R(\d*)C$')
1540 regex_test_name = re.compile(r'^.*-(\d+ch|\d+pl)-'
1542 r'(\d+vm\d+t|\d+dcr\d+t|\d+dcr\d+c).*$')
1543 # Transform the data
1545 f" Creating the data set for the {plot.get(u'type', u'')} "
1546 f"{plot.get(u'title', u'')}."
1548 in_data = input_data.filter_tests_by_name(
1550 continue_on_error=True,
1551 params=[u"throughput", u"result", u"name", u"tags", u"type"]
1553 if in_data is None or in_data.empty:
1554 logging.error(u"No data.")
1557 for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
1558 for core in plot.get(u"core", tuple()):
1560 for item in plot.get(u"include", tuple()):
1561 reg_ex = re.compile(str(item.format(core=core)).lower())
1564 for test_id, test in build.iteritems():
1565 if not re.match(reg_ex, str(test_id).lower()):
1567 for tag in test[u"tags"]:
1568 groups = re.search(regex_cn, tag)
1570 chain = str(groups.group(1))
1571 node = str(groups.group(2))
1575 groups = re.search(regex_test_name, test[u"name"])
1576 if groups and len(groups.groups()) == 3:
1578 f"{str(groups.group(1))}-"
1579 f"{str(groups.group(2))}-"
1580 f"{str(groups.group(3))}"
1584 if vals.get(chain, None) is None:
1585 vals[chain] = dict()
1586 if vals[chain].get(node, None) is None:
1587 vals[chain][node] = dict(
1596 result = test[u"result"][u"receive-rate"]
1597 elif ttype == u"pdr":
1599 test[u"throughput"][u"PDR"][u"LOWER"]
1600 elif ttype == u"ndr":
1602 test[u"throughput"][u"NDR"][u"LOWER"]
1609 vals[chain][node][u"vals"].append(result)
1612 logging.error(u"No data.")
1618 txt_chains.append(key_c)
1619 for key_n in vals[key_c].keys():
1620 txt_nodes.append(key_n)
1621 if vals[key_c][key_n][u"vals"]:
1622 vals[key_c][key_n][u"nr"] = \
1623 len(vals[key_c][key_n][u"vals"])
1624 vals[key_c][key_n][u"mean"] = \
1625 round(mean(vals[key_c][key_n][u"vals"]) / 1e6, 1)
1626 vals[key_c][key_n][u"stdev"] = \
1627 round(stdev(vals[key_c][key_n][u"vals"]) / 1e6, 1)
1628 txt_nodes = list(set(txt_nodes))
1630 txt_chains = sorted(txt_chains, key=sort_by_int)
1631 txt_nodes = sorted(txt_nodes, key=sort_by_int)
1633 chains = [i + 1 for i in range(len(txt_chains))]
1634 nodes = [i + 1 for i in range(len(txt_nodes))]
1636 data = [list() for _ in range(len(chains))]
1637 for chain in chains:
1640 val = vals[txt_chains[chain - 1]] \
1641 [txt_nodes[node - 1]][u"mean"]
1642 except (KeyError, IndexError):
1644 data[chain - 1].append(val)
1647 my_green = [[0.0, u"rgb(235, 249, 242)"],
1648 [1.0, u"rgb(45, 134, 89)"]]
1650 my_blue = [[0.0, u"rgb(236, 242, 248)"],
1651 [1.0, u"rgb(57, 115, 172)"]]
1653 my_grey = [[0.0, u"rgb(230, 230, 230)"],
1654 [1.0, u"rgb(102, 102, 102)"]]
1657 annotations = list()
1659 text = (u"Test: {name}<br>"
1664 for chain, _ in enumerate(txt_chains):
1666 for node, _ in enumerate(txt_nodes):
1667 if data[chain][node] is not None:
1676 text=str(data[chain][node]),
1684 hover_line.append(text.format(
1685 name=vals[txt_chains[chain]][txt_nodes[node]]
1687 nr=vals[txt_chains[chain]][txt_nodes[node]][u"nr"],
1688 val=data[chain][node],
1689 stdev=vals[txt_chains[chain]][txt_nodes[node]]
1692 hovertext.append(hover_line)
1700 title=plot.get(u"z-axis", u"{test_type}").
1701 format(test_type=ttype.upper()),
1715 colorscale=my_green,
1721 for idx, item in enumerate(txt_nodes):
1739 for idx, item in enumerate(txt_chains):
1766 text=plot.get(u"x-axis", u""),
1783 text=plot.get(u"y-axis", u""),
1792 updatemenus = list([
1803 u"colorscale": [my_green, ],
1804 u"reversescale": False
1813 u"colorscale": [my_blue, ],
1814 u"reversescale": False
1823 u"colorscale": [my_grey, ],
1824 u"reversescale": False
1835 layout = deepcopy(plot[u"layout"])
1836 except KeyError as err:
1838 f"Finished with error: No layout defined\n{repr(err)}"
1842 layout[u"annotations"] = annotations
1843 layout[u'updatemenus'] = updatemenus
1844 if layout.get(u"title", None):
1845 layout[u"title"] = layout[u'title'].replace(u"test_type", ttype)
1849 plpl = plgo.Figure(data=traces, layout=layout)
1853 f"{plot[u'output-file'].format(core=core, test_type=ttype)}"
1856 logging.info(f" Writing file {file_name}")
1863 except PlotlyError as err:
1865 f" Finished with error: {repr(err)}".replace(u"\n", u" ")