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 if test_type in (u"VSAP_CPS", u"VSAP_RPS"):
799 y_max.append(int(val_max))
801 y_max.append(int(val_max / 1e6))
802 except (ValueError, TypeError) as err:
803 logging.error(repr(err))
808 layout = deepcopy(plot[u"layout"])
809 layout[u"xaxis"][u"tickvals"] = [i for i in range(len(y_vals))]
810 layout[u"xaxis"][u"ticktext"] = [str(i + 1) for i in range(len(y_vals))]
811 if layout.get(u"title", None):
812 if test_type in (u"HOSTSTACK", ):
813 layout[u"title"] = f"<b>Bandwidth:</b> {layout[u'title']}"
814 elif test_type == u"VSAP_CPS":
815 layout[u"title"] = f"<b>CPS:</b> {layout[u'title']}"
816 layout[u"yaxis"][u"title"] = u"<b>Connection Rate [cps]</b>"
817 elif test_type == u"VSAP_RPS":
818 layout[u"title"] = f"<b>RPS:</b> {layout[u'title']}"
819 layout[u"yaxis"][u"title"] = u"<b>Connection Rate [rps]</b>"
821 layout[u"title"] = f"<b>Tput:</b> {layout[u'title']}"
822 if y_max and max(y_max) > 1:
823 layout[u"yaxis"][u"range"] = [0, max(y_max) + 2]
824 plpl = plgo.Figure(data=traces, layout=layout)
827 logging.info(f" Writing file {plot[u'output-file']}.html.")
832 filename=f"{plot[u'output-file']}.html"
834 except PlotlyError as err:
836 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
841 def plot_ndrpdr_box_name(plot, input_data):
842 """Generate the plot(s) with algorithm: plot_ndrpdr_box_name
843 specified in the specification file.
845 :param plot: Plot to generate.
846 :param input_data: Data to process.
847 :type plot: pandas.Series
848 :type input_data: InputData
853 f" Creating data set for the {plot.get(u'type', u'')} "
854 f"{plot.get(u'title', u'')}."
856 data = input_data.filter_tests_by_name(
858 params=[u"throughput", u"gbps", u"parent", u"tags", u"type"]
861 logging.error(u"No data.")
864 if u"-gbps" in plot.get(u"title", u"").lower():
868 value = u"throughput"
873 for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
874 for core in plot.get(u"core", tuple()):
875 # Prepare the data for the plot
877 data_y = OrderedDict()
880 for item in plot.get(u"include", tuple()):
881 reg_ex = re.compile(str(item.format(core=core)).lower())
884 for test_id, test in build.iteritems():
885 if not re.match(reg_ex, str(test_id).lower()):
887 if data_y.get(test[u"parent"], None) is None:
888 data_y[test[u"parent"]] = list()
889 test_type = test[u"type"]
893 data_y[test[u"parent"]].append(
894 test[value][ttype.upper()][u"LOWER"] *
897 except (KeyError, TypeError):
902 for idx, (key, vals) in enumerate(data_y.items()):
904 REGEX_NIC, u'', key.lower().replace(u'-ndrpdr', u'').
905 replace(u'2n1l-', u'')
908 y=[y / 1e6 if y else None for y in vals],
913 f"{u's' if len(vals) > 1 else u''}) "
918 box_points = plot.get(u"boxpoints", u"all")
920 (u"all", u"outliers", u"suspectedoutliers", False):
921 kwargs[u"boxpoints"] = box_points
922 kwargs[u"jitter"] = 0.3
923 traces.append(plgo.Box(**kwargs))
925 data_y_max.append(max(vals))
926 except ValueError as err:
927 logging.warning(f"No values to use.\n{err!r}")
930 layout = deepcopy(plot[u"layout"])
931 layout[u"xaxis"][u"tickvals"] = [i for i in range(len(data_y))]
932 layout[u"xaxis"][u"ticktext"] = \
933 [str(i + 1) for i in range(len(data_y))]
934 if layout.get(u"title", None):
936 layout[u'title'].format(core=core, test_type=ttype)
937 if test_type in (u"CPS", ):
938 layout[u"title"] = f"<b>CPS:</b> {layout[u'title']}"
941 f"<b>Tput:</b> {layout[u'title']}"
943 layout[u"yaxis"][u"range"] = [0, max(data_y_max) / 1e6 + 1]
944 plpl = plgo.Figure(data=traces, layout=layout)
948 f"{plot[u'output-file'].format(core=core, test_type=ttype)}"
951 logging.info(f" Writing file {file_name}")
958 except PlotlyError as err:
960 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
964 def plot_mrr_box_name(plot, input_data):
965 """Generate the plot(s) with algorithm: plot_mrr_box_name
966 specified in the specification file.
968 :param plot: Plot to generate.
969 :param input_data: Data to process.
970 :type plot: pandas.Series
971 :type input_data: InputData
976 f" Creating data set for the {plot.get(u'type', u'')} "
977 f"{plot.get(u'title', u'')}."
979 data = input_data.filter_tests_by_name(
981 params=[u"result", u"parent", u"tags", u"type"]
984 logging.error(u"No data.")
987 for core in plot.get(u"core", tuple()):
988 # Prepare the data for the plot
994 for item in plot.get(u"include", tuple()):
995 reg_ex = re.compile(str(item.format(core=core)).lower())
998 for test_id, test in build.iteritems():
999 if not re.match(reg_ex, str(test_id).lower()):
1004 REGEX_NIC, u'', test[u'parent'].lower().
1005 replace(u'-mrr', u'').replace(u'2n1l-', u'')
1007 data_y.append(test[u"result"][u"samples"])
1010 f"({len(data_y[-1]):02d} "
1011 f"run{u's' if len(data_y[-1]) > 1 else u''}) "
1014 data_y_max.append(max(data_y[-1]))
1016 except (KeyError, TypeError):
1021 for idx, x_item in enumerate(data_x):
1024 name=data_names[idx],
1027 box_points = plot.get(u"boxpoints", u"all")
1028 if box_points in (u"all", u"outliers", u"suspectedoutliers", False):
1029 kwargs[u"boxpoints"] = box_points
1030 kwargs["jitter"] = 0.3
1031 traces.append(plgo.Box(**kwargs))
1035 layout = deepcopy(plot[u"layout"])
1036 layout[u"xaxis"][u"tickvals"] = [i for i in range(len(data_y))]
1037 layout[u"xaxis"][u"ticktext"] = \
1038 [str(i + 1) for i in range(len(data_y))]
1039 if layout.get(u"title", None):
1040 layout[u"title"] = (
1041 f"<b>Tput:</b> {layout[u'title'].format(core=core)}"
1044 layout[u"yaxis"][u"range"] = [0, max(data_y_max) + 1]
1045 plpl = plgo.Figure(data=traces, layout=layout)
1048 file_name = f"{plot[u'output-file'].format(core=core)}.html"
1049 logging.info(f" Writing file {file_name}")
1056 except PlotlyError as err:
1058 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
1062 def plot_tsa_name(plot, input_data):
1063 """Generate the plot(s) with algorithm:
1065 specified in the specification file.
1067 :param plot: Plot to generate.
1068 :param input_data: Data to process.
1069 :type plot: pandas.Series
1070 :type input_data: InputData
1073 # Transform the data
1074 plot_title = plot.get(u"title", u"")
1076 f" Creating data set for the {plot.get(u'type', u'')} {plot_title}."
1078 data = input_data.filter_tests_by_name(
1080 params=[u"throughput", u"gbps", u"parent", u"tags", u"type"]
1083 logging.error(u"No data.")
1086 plot_title = plot_title.lower()
1088 if u"-gbps" in plot_title:
1093 value = u"throughput"
1097 for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
1098 y_vals = OrderedDict()
1099 for item in plot.get(u"include", tuple()):
1100 reg_ex = re.compile(str(item).lower())
1103 for test_id, test in build.iteritems():
1104 if re.match(reg_ex, str(test_id).lower()):
1105 if y_vals.get(test[u"parent"], None) is None:
1106 y_vals[test[u"parent"]] = {
1112 if test[u"type"] not in (u"NDRPDR", u"CPS"):
1115 if u"1C" in test[u"tags"]:
1116 y_vals[test[u"parent"]][u"1"].append(
1117 test[value][ttype.upper()][u"LOWER"] *
1120 elif u"2C" in test[u"tags"]:
1121 y_vals[test[u"parent"]][u"2"].append(
1122 test[value][ttype.upper()][u"LOWER"] *
1125 elif u"4C" in test[u"tags"]:
1126 y_vals[test[u"parent"]][u"4"].append(
1127 test[value][ttype.upper()][u"LOWER"] *
1130 except (KeyError, TypeError):
1134 logging.warning(f"No data for the plot {plot.get(u'title', u'')}")
1138 for test_name, test_vals in y_vals.items():
1139 for key, test_val in test_vals.items():
1141 avg_val = sum(test_val) / len(test_val)
1142 y_vals[test_name][key] = [avg_val, len(test_val)]
1143 ideal = avg_val / (int(key) * 1e6)
1144 if test_name not in y_1c_max or ideal > y_1c_max[test_name]:
1145 y_1c_max[test_name] = ideal
1147 vals = OrderedDict()
1152 for test_name, test_vals in y_vals.items():
1154 if test_vals[u"1"][1]:
1158 test_name.replace(u'-ndrpdr', u'').
1159 replace(u'2n1l-', u'')
1161 vals[name] = OrderedDict()
1162 y_val_1 = test_vals[u"1"][0] / 1e6
1163 y_val_2 = test_vals[u"2"][0] / 1e6 if test_vals[u"2"][0] \
1165 y_val_4 = test_vals[u"4"][0] / 1e6 if test_vals[u"4"][0] \
1168 vals[name][u"val"] = [y_val_1, y_val_2, y_val_4]
1169 vals[name][u"rel"] = [1.0, None, None]
1170 vals[name][u"ideal"] = [
1171 y_1c_max[test_name],
1172 y_1c_max[test_name] * 2,
1173 y_1c_max[test_name] * 4
1175 vals[name][u"diff"] = [
1176 (y_val_1 - y_1c_max[test_name]) * 100 / y_val_1,
1180 vals[name][u"count"] = [
1187 val_max = max(vals[name][u"val"])
1188 except ValueError as err:
1189 logging.error(repr(err))
1192 y_max.append(val_max)
1195 vals[name][u"rel"][1] = round(y_val_2 / y_val_1, 2)
1196 vals[name][u"diff"][1] = \
1197 (y_val_2 - vals[name][u"ideal"][1]) * 100 / y_val_2
1199 vals[name][u"rel"][2] = round(y_val_4 / y_val_1, 2)
1200 vals[name][u"diff"][2] = \
1201 (y_val_4 - vals[name][u"ideal"][2]) * 100 / y_val_4
1202 except IndexError as err:
1203 logging.warning(f"No data for {test_name}")
1204 logging.warning(repr(err))
1207 if u"x520" in test_name:
1208 limit = plot[u"limits"][u"nic"][u"x520"]
1209 elif u"x710" in test_name:
1210 limit = plot[u"limits"][u"nic"][u"x710"]
1211 elif u"xxv710" in test_name:
1212 limit = plot[u"limits"][u"nic"][u"xxv710"]
1213 elif u"xl710" in test_name:
1214 limit = plot[u"limits"][u"nic"][u"xl710"]
1215 elif u"x553" in test_name:
1216 limit = plot[u"limits"][u"nic"][u"x553"]
1217 elif u"cx556a" in test_name:
1218 limit = plot[u"limits"][u"nic"][u"cx556a"]
1219 elif u"e810cq" in test_name:
1220 limit = plot[u"limits"][u"nic"][u"e810cq"]
1221 elif u"e810xxv" in test_name:
1222 limit = plot[u"limits"][u"nic"][u"e810xxv"]
1223 elif u"e822cq" in test_name:
1224 limit = plot[u"limits"][u"nic"][u"e822cq"]
1227 if limit > nic_limit:
1230 mul = 2 if u"ge2p" in test_name else 1
1231 if u"10ge" in test_name:
1232 limit = plot[u"limits"][u"link"][u"10ge"] * mul
1233 elif u"25ge" in test_name:
1234 limit = plot[u"limits"][u"link"][u"25ge"] * mul
1235 elif u"40ge" in test_name:
1236 limit = plot[u"limits"][u"link"][u"40ge"] * mul
1237 elif u"100ge" in test_name:
1238 limit = plot[u"limits"][u"link"][u"100ge"] * mul
1241 if limit > lnk_limit:
1244 if u"cx556a" in test_name:
1245 limit = plot[u"limits"][u"pci"][u"pci-g3-x8"]
1247 limit = plot[u"limits"][u"pci"][u"pci-g3-x16"]
1248 if limit > pci_limit:
1252 annotations = list()
1256 if u"-gbps" not in plot_title and u"-cps-" not in plot_title:
1260 min_limit = min((nic_limit, lnk_limit, pci_limit))
1261 if nic_limit == min_limit:
1262 traces.append(plgo.Scatter(
1264 y=[nic_limit, ] * len(x_vals),
1265 name=f"NIC: {nic_limit:.2f}Mpps",
1274 annotations.append(dict(
1281 text=f"NIC: {nic_limit:.2f}Mpps",
1289 y_max.append(nic_limit)
1290 elif lnk_limit == min_limit:
1291 traces.append(plgo.Scatter(
1293 y=[lnk_limit, ] * len(x_vals),
1294 name=f"Link: {lnk_limit:.2f}Mpps",
1303 annotations.append(dict(
1310 text=f"Link: {lnk_limit:.2f}Mpps",
1318 y_max.append(lnk_limit)
1319 elif pci_limit == min_limit:
1320 traces.append(plgo.Scatter(
1322 y=[pci_limit, ] * len(x_vals),
1323 name=f"PCIe: {pci_limit:.2f}Mpps",
1332 annotations.append(dict(
1339 text=f"PCIe: {pci_limit:.2f}Mpps",
1347 y_max.append(pci_limit)
1349 # Perfect and measured:
1351 for name, val in vals.items():
1354 for idx in range(len(val[u"val"])):
1356 if isinstance(val[u"val"][idx], float):
1358 f"No. of Runs: {val[u'count'][idx]}<br>"
1359 f"Mean: {val[u'val'][idx]:.2f}{h_unit}<br>"
1361 if isinstance(val[u"diff"][idx], float):
1362 htext += f"Diff: {round(val[u'diff'][idx]):.0f}%<br>"
1363 if isinstance(val[u"rel"][idx], float):
1364 htext += f"Speedup: {val[u'rel'][idx]:.2f}"
1365 hovertext.append(htext)
1372 mode=u"lines+markers",
1381 hoverinfo=u"text+name"
1388 name=f"{name} perfect",
1396 text=[f"Perfect: {y:.2f}Mpps" for y in val[u"ideal"]],
1401 except (IndexError, ValueError, KeyError) as err:
1402 logging.warning(f"No data for {name}\n{repr(err)}")
1406 file_name = f"{plot[u'output-file'].format(test_type=ttype)}.html"
1407 logging.info(f" Writing file {file_name}")
1408 layout = deepcopy(plot[u"layout"])
1409 if layout.get(u"title", None):
1410 layout[u"title"] = (
1411 f"<b>Speedup Multi-core:</b> "
1412 f"{layout[u'title'].format(test_type=ttype)}"
1414 layout[u"yaxis"][u"range"] = [0, int(max(y_max) * 1.1)]
1415 layout[u"annotations"].extend(annotations)
1416 plpl = plgo.Figure(data=traces, layout=layout)
1425 except PlotlyError as err:
1427 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
1431 def plot_http_server_perf_box(plot, input_data):
1432 """Generate the plot(s) with algorithm: plot_http_server_perf_box
1433 specified in the specification file.
1435 :param plot: Plot to generate.
1436 :param input_data: Data to process.
1437 :type plot: pandas.Series
1438 :type input_data: InputData
1441 # Transform the data
1443 f" Creating the data set for the {plot.get(u'type', u'')} "
1444 f"{plot.get(u'title', u'')}."
1446 data = input_data.filter_data(plot)
1448 logging.error(u"No data.")
1451 # Prepare the data for the plot
1456 if y_vals.get(test[u"name"], None) is None:
1457 y_vals[test[u"name"]] = list()
1459 y_vals[test[u"name"]].append(test[u"result"])
1460 except (KeyError, TypeError):
1461 y_vals[test[u"name"]].append(None)
1463 # Add None to the lists with missing data
1465 nr_of_samples = list()
1466 for val in y_vals.values():
1467 if len(val) > max_len:
1469 nr_of_samples.append(len(val))
1470 for val in y_vals.values():
1471 if len(val) < max_len:
1472 val.extend([None for _ in range(max_len - len(val))])
1476 df_y = pd.DataFrame(y_vals)
1478 for i, col in enumerate(df_y.columns):
1481 f"({nr_of_samples[i]:02d} " \
1482 f"run{u's' if nr_of_samples[i] > 1 else u''}) " \
1483 f"{col.lower().replace(u'-ndrpdr', u'')}"
1485 name_lst = name.split(u'-')
1488 for segment in name_lst:
1489 if (len(name) + len(segment) + 1) > 50 and split_name:
1492 name += segment + u'-'
1495 traces.append(plgo.Box(x=[str(i + 1) + u'.'] * len(df_y[col]),
1501 plpl = plgo.Figure(data=traces, layout=plot[u"layout"])
1505 f" Writing file {plot[u'output-file']}"
1506 f"{plot[u'output-file-type']}."
1512 filename=f"{plot[u'output-file']}{plot[u'output-file-type']}"
1514 except PlotlyError as err:
1516 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
1521 def plot_nf_heatmap(plot, input_data):
1522 """Generate the plot(s) with algorithm: plot_nf_heatmap
1523 specified in the specification file.
1525 :param plot: Plot to generate.
1526 :param input_data: Data to process.
1527 :type plot: pandas.Series
1528 :type input_data: InputData
1531 def sort_by_int(value):
1532 """Makes possible to sort a list of strings which represent integers.
1534 :param value: Integer as a string.
1536 :returns: Integer representation of input parameter 'value'.
1541 regex_cn = re.compile(r'^(\d*)R(\d*)C$')
1542 regex_test_name = re.compile(r'^.*-(\d+ch|\d+pl)-'
1544 r'(\d+vm\d+t|\d+dcr\d+t|\d+dcr\d+c).*$')
1545 # Transform the data
1547 f" Creating the data set for the {plot.get(u'type', u'')} "
1548 f"{plot.get(u'title', u'')}."
1550 in_data = input_data.filter_tests_by_name(
1552 continue_on_error=True,
1553 params=[u"throughput", u"result", u"name", u"tags", u"type"]
1555 if in_data is None or in_data.empty:
1556 logging.error(u"No data.")
1559 for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
1560 for core in plot.get(u"core", tuple()):
1562 for item in plot.get(u"include", tuple()):
1563 reg_ex = re.compile(str(item.format(core=core)).lower())
1566 for test_id, test in build.iteritems():
1567 if not re.match(reg_ex, str(test_id).lower()):
1569 for tag in test[u"tags"]:
1570 groups = re.search(regex_cn, tag)
1572 chain = str(groups.group(1))
1573 node = str(groups.group(2))
1577 groups = re.search(regex_test_name, test[u"name"])
1578 if groups and len(groups.groups()) == 3:
1580 f"{str(groups.group(1))}-"
1581 f"{str(groups.group(2))}-"
1582 f"{str(groups.group(3))}"
1586 if vals.get(chain, None) is None:
1587 vals[chain] = dict()
1588 if vals[chain].get(node, None) is None:
1589 vals[chain][node] = dict(
1598 result = test[u"result"][u"receive-rate"]
1599 elif ttype == u"pdr":
1601 test[u"throughput"][u"PDR"][u"LOWER"]
1602 elif ttype == u"ndr":
1604 test[u"throughput"][u"NDR"][u"LOWER"]
1611 vals[chain][node][u"vals"].append(result)
1614 logging.error(u"No data.")
1620 txt_chains.append(key_c)
1621 for key_n in vals[key_c].keys():
1622 txt_nodes.append(key_n)
1623 if vals[key_c][key_n][u"vals"]:
1624 vals[key_c][key_n][u"nr"] = \
1625 len(vals[key_c][key_n][u"vals"])
1626 vals[key_c][key_n][u"mean"] = \
1627 round(mean(vals[key_c][key_n][u"vals"]) / 1e6, 1)
1628 vals[key_c][key_n][u"stdev"] = \
1629 round(stdev(vals[key_c][key_n][u"vals"]) / 1e6, 1)
1630 txt_nodes = list(set(txt_nodes))
1632 txt_chains = sorted(txt_chains, key=sort_by_int)
1633 txt_nodes = sorted(txt_nodes, key=sort_by_int)
1635 chains = [i + 1 for i in range(len(txt_chains))]
1636 nodes = [i + 1 for i in range(len(txt_nodes))]
1638 data = [list() for _ in range(len(chains))]
1639 for chain in chains:
1642 val = vals[txt_chains[chain - 1]] \
1643 [txt_nodes[node - 1]][u"mean"]
1644 except (KeyError, IndexError):
1646 data[chain - 1].append(val)
1649 my_green = [[0.0, u"rgb(235, 249, 242)"],
1650 [1.0, u"rgb(45, 134, 89)"]]
1652 my_blue = [[0.0, u"rgb(236, 242, 248)"],
1653 [1.0, u"rgb(57, 115, 172)"]]
1655 my_grey = [[0.0, u"rgb(230, 230, 230)"],
1656 [1.0, u"rgb(102, 102, 102)"]]
1659 annotations = list()
1661 text = (u"Test: {name}<br>"
1666 for chain, _ in enumerate(txt_chains):
1668 for node, _ in enumerate(txt_nodes):
1669 if data[chain][node] is not None:
1678 text=str(data[chain][node]),
1686 hover_line.append(text.format(
1687 name=vals[txt_chains[chain]][txt_nodes[node]]
1689 nr=vals[txt_chains[chain]][txt_nodes[node]][u"nr"],
1690 val=data[chain][node],
1691 stdev=vals[txt_chains[chain]][txt_nodes[node]]
1694 hovertext.append(hover_line)
1702 title=plot.get(u"z-axis", u"{test_type}").
1703 format(test_type=ttype.upper()),
1717 colorscale=my_green,
1723 for idx, item in enumerate(txt_nodes):
1741 for idx, item in enumerate(txt_chains):
1768 text=plot.get(u"x-axis", u""),
1785 text=plot.get(u"y-axis", u""),
1794 updatemenus = list([
1805 u"colorscale": [my_green, ],
1806 u"reversescale": False
1815 u"colorscale": [my_blue, ],
1816 u"reversescale": False
1825 u"colorscale": [my_grey, ],
1826 u"reversescale": False
1837 layout = deepcopy(plot[u"layout"])
1838 except KeyError as err:
1840 f"Finished with error: No layout defined\n{repr(err)}"
1844 layout[u"annotations"] = annotations
1845 layout[u'updatemenus'] = updatemenus
1846 if layout.get(u"title", None):
1847 layout[u"title"] = layout[u'title'].replace(u"test_type", ttype)
1851 plpl = plgo.Figure(data=traces, layout=layout)
1855 f"{plot[u'output-file'].format(core=core, test_type=ttype)}"
1858 logging.info(f" Writing file {file_name}")
1865 except PlotlyError as err:
1867 f" Finished with error: {repr(err)}".replace(u"\n", u" ")