1 # Copyright (c) 2021 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
6 # http://www.apache.org/licenses/LICENSE-2.0
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
14 """Algorithms to generate plots.
21 from collections import OrderedDict
22 from datetime import datetime
23 from copy import deepcopy
29 import plotly.offline as ploff
30 import plotly.graph_objs as plgo
31 import plotly.exceptions as plerr
33 from plotly.exceptions import PlotlyError
35 from pal_utils import mean, stdev
64 REGEX_NIC = re.compile(r'(\d*ge\dp\d\D*\d*[a-z]*)-')
66 # This value depends on latency stream rate (9001 pps) and duration (5s).
67 # Keep it slightly higher to ensure rounding errors to not remove tick mark.
68 PERCENTILE_MAX = 99.999501
71 def generate_plots(spec, data):
72 """Generate all plots specified in the specification file.
74 :param spec: Specification read from the specification file.
75 :param data: Data to process.
76 :type spec: Specification
81 u"plot_nf_reconf_box_name": plot_nf_reconf_box_name,
82 u"plot_perf_box_name": plot_perf_box_name,
83 u"plot_tsa_name": plot_tsa_name,
84 u"plot_http_server_perf_box": plot_http_server_perf_box,
85 u"plot_nf_heatmap": plot_nf_heatmap,
86 u"plot_hdrh_lat_by_percentile": plot_hdrh_lat_by_percentile,
87 u"plot_hdrh_lat_by_percentile_x_log": plot_hdrh_lat_by_percentile_x_log,
88 u"plot_mrr_box_name": plot_mrr_box_name,
89 u"plot_ndrpdr_box_name": plot_ndrpdr_box_name,
90 u"plot_statistics": plot_statistics
93 logging.info(u"Generating the plots ...")
94 for index, plot in enumerate(spec.plots):
96 logging.info(f" Plot nr {index + 1}: {plot.get(u'title', u'')}")
97 plot[u"limits"] = spec.environment[u"limits"]
98 generator[plot[u"algorithm"]](plot, data)
99 logging.info(u" Done.")
100 except NameError as err:
102 f"Probably algorithm {plot[u'algorithm']} is not defined: "
105 logging.info(u"Done.")
108 def plot_statistics(plot, input_data):
109 """Generate the plot(s) with algorithm: plot_statistics
110 specified in the specification file.
112 :param plot: Plot to generate.
113 :param input_data: Data to process.
114 :type plot: pandas.Series
115 :type input_data: InputData
121 data_y_duration = list()
125 u"passed: {passed}<br>"
126 u"failed: {failed}<br>"
127 u"duration: {duration}<br>"
128 u"{sut}-ref: {build}<br>"
129 u"csit-ref: {test}-{period}-build-{build_nr}<br>"
130 u"testbed: {testbed}"
132 for job, builds in plot[u"data"].items():
133 for build_nr in builds:
135 meta = input_data.metadata(job, str(build_nr))
136 generated = meta[u"generated"]
141 int(generated[9:11]),
144 d_y_pass = meta[u"tests_passed"]
145 d_y_fail = meta[u"tests_failed"]
146 minutes = meta[u"elapsedtime"] // 60000
147 duration = f"{(minutes // 60):02d}:{(minutes % 60):02d}"
148 version = meta.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]]
780 x=[str(i + 1) + u'.'] * len(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"
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 if layout.get(u"title", None):
807 if test_type in (u"HOSTSTACK", ):
808 layout[u"title"] = f"<b>Bandwidth:</b> {layout[u'title']}"
809 elif test_type == u"VSAP_CPS":
810 layout[u"title"] = f"<b>CPS:</b> {layout[u'title']}"
811 layout[u"yaxis"][u"title"] = u"<b>Connection Rate [cps]</b>"
812 elif test_type == u"VSAP_RPS":
813 layout[u"title"] = f"<b>RPS:</b> {layout[u'title']}"
814 layout[u"yaxis"][u"title"] = u"<b>Connection Rate [rps]</b>"
816 layout[u"title"] = f"<b>Tput:</b> {layout[u'title']}"
817 if y_max and max(y_max) > 1:
818 layout[u"yaxis"][u"range"] = [0, max(y_max) + 2]
819 plpl = plgo.Figure(data=traces, layout=layout)
822 logging.info(f" Writing file {plot[u'output-file']}.html.")
827 filename=f"{plot[u'output-file']}.html"
829 except PlotlyError as err:
831 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
836 def plot_ndrpdr_box_name(plot, input_data):
837 """Generate the plot(s) with algorithm: plot_ndrpdr_box_name
838 specified in the specification file.
840 :param plot: Plot to generate.
841 :param input_data: Data to process.
842 :type plot: pandas.Series
843 :type input_data: InputData
848 f" Creating data set for the {plot.get(u'type', u'')} "
849 f"{plot.get(u'title', u'')}."
851 data = input_data.filter_tests_by_name(
853 params=[u"throughput", u"gbps", u"parent", u"tags", u"type"]
856 logging.error(u"No data.")
859 if u"-gbps" in plot.get(u"title", u"").lower():
863 value = u"throughput"
868 for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
869 for core in plot.get(u"core", tuple()):
870 # Prepare the data for the plot
872 data_y = OrderedDict()
875 for item in plot.get(u"include", tuple()):
876 reg_ex = re.compile(str(item.format(core=core)).lower())
879 for test_id, test in build.iteritems():
880 if not re.match(reg_ex, str(test_id).lower()):
882 if data_y.get(test[u"parent"], None) is None:
883 data_y[test[u"parent"]] = list()
884 test_type = test[u"type"]
888 data_y[test[u"parent"]].append(
889 test[value][ttype.upper()][u"LOWER"] *
892 except (KeyError, TypeError):
897 for idx, (key, vals) in enumerate(data_y.items()):
899 REGEX_NIC, u'', key.lower().replace(u'-ndrpdr', u'').
900 replace(u'2n1l-', u'')
903 x=[data_x[idx], ] * len(data_x),
904 y=[y / 1e6 if y else None for y in vals],
909 f"{u's' if len(vals) > 1 else u''}) "
914 box_points = plot.get(u"boxpoints", None)
915 if box_points and box_points in \
916 (u"all", u"outliers", u"suspectedoutliers", False):
917 kwargs[u"boxpoints"] = box_points
918 traces.append(plgo.Box(**kwargs))
920 data_y_max.append(max(vals))
921 except ValueError as err:
922 logging.warning(f"No values to use.\n{err!r}")
925 layout = deepcopy(plot[u"layout"])
926 if layout.get(u"title", None):
928 layout[u'title'].format(core=core, test_type=ttype)
929 if test_type in (u"CPS", ):
930 layout[u"title"] = f"<b>CPS:</b> {layout[u'title']}"
933 f"<b>Tput:</b> {layout[u'title']}"
935 layout[u"yaxis"][u"range"] = [0, max(data_y_max) / 1e6 + 1]
936 plpl = plgo.Figure(data=traces, layout=layout)
940 f"{plot[u'output-file'].format(core=core, test_type=ttype)}"
943 logging.info(f" Writing file {file_name}")
950 except PlotlyError as err:
952 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
956 def plot_mrr_box_name(plot, input_data):
957 """Generate the plot(s) with algorithm: plot_mrr_box_name
958 specified in the specification file.
960 :param plot: Plot to generate.
961 :param input_data: Data to process.
962 :type plot: pandas.Series
963 :type input_data: InputData
968 f" Creating data set for the {plot.get(u'type', u'')} "
969 f"{plot.get(u'title', u'')}."
971 data = input_data.filter_tests_by_name(
973 params=[u"result", u"parent", u"tags", u"type"]
976 logging.error(u"No data.")
979 for core in plot.get(u"core", tuple()):
980 # Prepare the data for the plot
986 for item in plot.get(u"include", tuple()):
987 reg_ex = re.compile(str(item.format(core=core)).lower())
990 for test_id, test in build.iteritems():
991 if not re.match(reg_ex, str(test_id).lower()):
996 REGEX_NIC, u'', test[u'parent'].lower().
997 replace(u'-mrr', u'').replace(u'2n1l-', u'')
999 data_y.append(test[u"result"][u"samples"])
1002 f"({len(data_y[-1]):02d} "
1003 f"run{u's' if len(data_y[-1]) > 1 else u''}) "
1006 data_y_max.append(max(data_y[-1]))
1008 except (KeyError, TypeError):
1013 for idx, x_item in enumerate(data_x):
1016 x=[x_item, ] * len(data_y[idx]),
1018 name=data_names[idx],
1025 layout = deepcopy(plot[u"layout"])
1026 if layout.get(u"title", None):
1027 layout[u"title"] = (
1028 f"<b>Tput:</b> {layout[u'title'].format(core=core)}"
1031 layout[u"yaxis"][u"range"] = [0, max(data_y_max) + 1]
1032 plpl = plgo.Figure(data=traces, layout=layout)
1035 file_name = f"{plot[u'output-file'].format(core=core)}.html"
1036 logging.info(f" Writing file {file_name}")
1043 except PlotlyError as err:
1045 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
1049 def plot_tsa_name(plot, input_data):
1050 """Generate the plot(s) with algorithm:
1052 specified in the specification file.
1054 :param plot: Plot to generate.
1055 :param input_data: Data to process.
1056 :type plot: pandas.Series
1057 :type input_data: InputData
1060 # Transform the data
1061 plot_title = plot.get(u"title", u"")
1063 f" Creating data set for the {plot.get(u'type', u'')} {plot_title}."
1065 data = input_data.filter_tests_by_name(
1067 params=[u"throughput", u"gbps", u"parent", u"tags", u"type"]
1070 logging.error(u"No data.")
1073 plot_title = plot_title.lower()
1075 if u"-gbps" in plot_title:
1080 value = u"throughput"
1084 for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
1085 y_vals = OrderedDict()
1086 for item in plot.get(u"include", tuple()):
1087 reg_ex = re.compile(str(item).lower())
1090 for test_id, test in build.iteritems():
1091 if re.match(reg_ex, str(test_id).lower()):
1092 if y_vals.get(test[u"parent"], None) is None:
1093 y_vals[test[u"parent"]] = {
1099 if test[u"type"] not in (u"NDRPDR", u"CPS"):
1102 if u"1C" in test[u"tags"]:
1103 y_vals[test[u"parent"]][u"1"].append(
1104 test[value][ttype.upper()][u"LOWER"] *
1107 elif u"2C" in test[u"tags"]:
1108 y_vals[test[u"parent"]][u"2"].append(
1109 test[value][ttype.upper()][u"LOWER"] *
1112 elif u"4C" in test[u"tags"]:
1113 y_vals[test[u"parent"]][u"4"].append(
1114 test[value][ttype.upper()][u"LOWER"] *
1117 except (KeyError, TypeError):
1121 logging.warning(f"No data for the plot {plot.get(u'title', u'')}")
1125 for test_name, test_vals in y_vals.items():
1126 for key, test_val in test_vals.items():
1128 avg_val = sum(test_val) / len(test_val)
1129 y_vals[test_name][key] = [avg_val, len(test_val)]
1130 ideal = avg_val / (int(key) * 1e6)
1131 if test_name not in y_1c_max or ideal > y_1c_max[test_name]:
1132 y_1c_max[test_name] = ideal
1134 vals = OrderedDict()
1139 for test_name, test_vals in y_vals.items():
1141 if test_vals[u"1"][1]:
1145 test_name.replace(u'-ndrpdr', u'').
1146 replace(u'2n1l-', u'')
1148 vals[name] = OrderedDict()
1149 y_val_1 = test_vals[u"1"][0] / 1e6
1150 y_val_2 = test_vals[u"2"][0] / 1e6 if test_vals[u"2"][0] \
1152 y_val_4 = test_vals[u"4"][0] / 1e6 if test_vals[u"4"][0] \
1155 vals[name][u"val"] = [y_val_1, y_val_2, y_val_4]
1156 vals[name][u"rel"] = [1.0, None, None]
1157 vals[name][u"ideal"] = [
1158 y_1c_max[test_name],
1159 y_1c_max[test_name] * 2,
1160 y_1c_max[test_name] * 4
1162 vals[name][u"diff"] = [
1163 (y_val_1 - y_1c_max[test_name]) * 100 / y_val_1,
1167 vals[name][u"count"] = [
1174 val_max = max(vals[name][u"val"])
1175 except ValueError as err:
1176 logging.error(repr(err))
1179 y_max.append(val_max)
1182 vals[name][u"rel"][1] = round(y_val_2 / y_val_1, 2)
1183 vals[name][u"diff"][1] = \
1184 (y_val_2 - vals[name][u"ideal"][1]) * 100 / y_val_2
1186 vals[name][u"rel"][2] = round(y_val_4 / y_val_1, 2)
1187 vals[name][u"diff"][2] = \
1188 (y_val_4 - vals[name][u"ideal"][2]) * 100 / y_val_4
1189 except IndexError as err:
1190 logging.warning(f"No data for {test_name}")
1191 logging.warning(repr(err))
1194 if u"x520" in test_name:
1195 limit = plot[u"limits"][u"nic"][u"x520"]
1196 elif u"x710" in test_name:
1197 limit = plot[u"limits"][u"nic"][u"x710"]
1198 elif u"xxv710" in test_name:
1199 limit = plot[u"limits"][u"nic"][u"xxv710"]
1200 elif u"xl710" in test_name:
1201 limit = plot[u"limits"][u"nic"][u"xl710"]
1202 elif u"x553" in test_name:
1203 limit = plot[u"limits"][u"nic"][u"x553"]
1204 elif u"cx556a" in test_name:
1205 limit = plot[u"limits"][u"nic"][u"cx556a"]
1206 elif u"e810cq" in test_name:
1207 limit = plot[u"limits"][u"nic"][u"e810cq"]
1210 if limit > nic_limit:
1213 mul = 2 if u"ge2p" in test_name else 1
1214 if u"10ge" in test_name:
1215 limit = plot[u"limits"][u"link"][u"10ge"] * mul
1216 elif u"25ge" in test_name:
1217 limit = plot[u"limits"][u"link"][u"25ge"] * mul
1218 elif u"40ge" in test_name:
1219 limit = plot[u"limits"][u"link"][u"40ge"] * mul
1220 elif u"100ge" in test_name:
1221 limit = plot[u"limits"][u"link"][u"100ge"] * mul
1224 if limit > lnk_limit:
1227 if u"cx556a" in test_name:
1228 limit = plot[u"limits"][u"pci"][u"pci-g3-x8"]
1230 limit = plot[u"limits"][u"pci"][u"pci-g3-x16"]
1231 if limit > pci_limit:
1235 annotations = list()
1239 if u"-gbps" not in plot_title and u"-cps-" not in plot_title:
1243 min_limit = min((nic_limit, lnk_limit, pci_limit))
1244 if nic_limit == min_limit:
1245 traces.append(plgo.Scatter(
1247 y=[nic_limit, ] * len(x_vals),
1248 name=f"NIC: {nic_limit:.2f}Mpps",
1257 annotations.append(dict(
1264 text=f"NIC: {nic_limit:.2f}Mpps",
1272 y_max.append(nic_limit)
1273 elif lnk_limit == min_limit:
1274 traces.append(plgo.Scatter(
1276 y=[lnk_limit, ] * len(x_vals),
1277 name=f"Link: {lnk_limit:.2f}Mpps",
1286 annotations.append(dict(
1293 text=f"Link: {lnk_limit:.2f}Mpps",
1301 y_max.append(lnk_limit)
1302 elif pci_limit == min_limit:
1303 traces.append(plgo.Scatter(
1305 y=[pci_limit, ] * len(x_vals),
1306 name=f"PCIe: {pci_limit:.2f}Mpps",
1315 annotations.append(dict(
1322 text=f"PCIe: {pci_limit:.2f}Mpps",
1330 y_max.append(pci_limit)
1332 # Perfect and measured:
1334 for name, val in vals.items():
1337 for idx in range(len(val[u"val"])):
1339 if isinstance(val[u"val"][idx], float):
1341 f"No. of Runs: {val[u'count'][idx]}<br>"
1342 f"Mean: {val[u'val'][idx]:.2f}{h_unit}<br>"
1344 if isinstance(val[u"diff"][idx], float):
1345 htext += f"Diff: {round(val[u'diff'][idx]):.0f}%<br>"
1346 if isinstance(val[u"rel"][idx], float):
1347 htext += f"Speedup: {val[u'rel'][idx]:.2f}"
1348 hovertext.append(htext)
1355 mode=u"lines+markers",
1364 hoverinfo=u"text+name"
1371 name=f"{name} perfect",
1379 text=[f"Perfect: {y:.2f}Mpps" for y in val[u"ideal"]],
1384 except (IndexError, ValueError, KeyError) as err:
1385 logging.warning(f"No data for {name}\n{repr(err)}")
1389 file_name = f"{plot[u'output-file'].format(test_type=ttype)}.html"
1390 logging.info(f" Writing file {file_name}")
1391 layout = deepcopy(plot[u"layout"])
1392 if layout.get(u"title", None):
1393 layout[u"title"] = (
1394 f"<b>Speedup Multi-core:</b> "
1395 f"{layout[u'title'].format(test_type=ttype)}"
1397 layout[u"yaxis"][u"range"] = [0, int(max(y_max) * 1.1)]
1398 layout[u"annotations"].extend(annotations)
1399 plpl = plgo.Figure(data=traces, layout=layout)
1408 except PlotlyError as err:
1410 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
1414 def plot_http_server_perf_box(plot, input_data):
1415 """Generate the plot(s) with algorithm: plot_http_server_perf_box
1416 specified in the specification file.
1418 :param plot: Plot to generate.
1419 :param input_data: Data to process.
1420 :type plot: pandas.Series
1421 :type input_data: InputData
1424 # Transform the data
1426 f" Creating the data set for the {plot.get(u'type', u'')} "
1427 f"{plot.get(u'title', u'')}."
1429 data = input_data.filter_data(plot)
1431 logging.error(u"No data.")
1434 # Prepare the data for the plot
1439 if y_vals.get(test[u"name"], None) is None:
1440 y_vals[test[u"name"]] = list()
1442 y_vals[test[u"name"]].append(test[u"result"])
1443 except (KeyError, TypeError):
1444 y_vals[test[u"name"]].append(None)
1446 # Add None to the lists with missing data
1448 nr_of_samples = list()
1449 for val in y_vals.values():
1450 if len(val) > max_len:
1452 nr_of_samples.append(len(val))
1453 for val in y_vals.values():
1454 if len(val) < max_len:
1455 val.extend([None for _ in range(max_len - len(val))])
1459 df_y = pd.DataFrame(y_vals)
1461 for i, col in enumerate(df_y.columns):
1464 f"({nr_of_samples[i]:02d} " \
1465 f"run{u's' if nr_of_samples[i] > 1 else u''}) " \
1466 f"{col.lower().replace(u'-ndrpdr', u'')}"
1468 name_lst = name.split(u'-')
1471 for segment in name_lst:
1472 if (len(name) + len(segment) + 1) > 50 and split_name:
1475 name += segment + u'-'
1478 traces.append(plgo.Box(x=[str(i + 1) + u'.'] * len(df_y[col]),
1484 plpl = plgo.Figure(data=traces, layout=plot[u"layout"])
1488 f" Writing file {plot[u'output-file']}"
1489 f"{plot[u'output-file-type']}."
1495 filename=f"{plot[u'output-file']}{plot[u'output-file-type']}"
1497 except PlotlyError as err:
1499 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
1504 def plot_nf_heatmap(plot, input_data):
1505 """Generate the plot(s) with algorithm: plot_nf_heatmap
1506 specified in the specification file.
1508 :param plot: Plot to generate.
1509 :param input_data: Data to process.
1510 :type plot: pandas.Series
1511 :type input_data: InputData
1514 def sort_by_int(value):
1515 """Makes possible to sort a list of strings which represent integers.
1517 :param value: Integer as a string.
1519 :returns: Integer representation of input parameter 'value'.
1524 regex_cn = re.compile(r'^(\d*)R(\d*)C$')
1525 regex_test_name = re.compile(r'^.*-(\d+ch|\d+pl)-'
1527 r'(\d+vm\d+t|\d+dcr\d+t|\d+dcr\d+c).*$')
1528 # Transform the data
1530 f" Creating the data set for the {plot.get(u'type', u'')} "
1531 f"{plot.get(u'title', u'')}."
1533 in_data = input_data.filter_tests_by_name(
1535 continue_on_error=True,
1536 params=[u"throughput", u"result", u"name", u"tags", u"type"]
1538 if in_data is None or in_data.empty:
1539 logging.error(u"No data.")
1542 for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
1543 for core in plot.get(u"core", tuple()):
1545 for item in plot.get(u"include", tuple()):
1546 reg_ex = re.compile(str(item.format(core=core)).lower())
1549 for test_id, test in build.iteritems():
1550 if not re.match(reg_ex, str(test_id).lower()):
1552 for tag in test[u"tags"]:
1553 groups = re.search(regex_cn, tag)
1555 chain = str(groups.group(1))
1556 node = str(groups.group(2))
1560 groups = re.search(regex_test_name, test[u"name"])
1561 if groups and len(groups.groups()) == 3:
1563 f"{str(groups.group(1))}-"
1564 f"{str(groups.group(2))}-"
1565 f"{str(groups.group(3))}"
1569 if vals.get(chain, None) is None:
1570 vals[chain] = dict()
1571 if vals[chain].get(node, None) is None:
1572 vals[chain][node] = dict(
1581 result = test[u"result"][u"receive-rate"]
1582 elif ttype == u"pdr":
1584 test[u"throughput"][u"PDR"][u"LOWER"]
1585 elif ttype == u"ndr":
1587 test[u"throughput"][u"NDR"][u"LOWER"]
1594 vals[chain][node][u"vals"].append(result)
1597 logging.error(u"No data.")
1603 txt_chains.append(key_c)
1604 for key_n in vals[key_c].keys():
1605 txt_nodes.append(key_n)
1606 if vals[key_c][key_n][u"vals"]:
1607 vals[key_c][key_n][u"nr"] = \
1608 len(vals[key_c][key_n][u"vals"])
1609 vals[key_c][key_n][u"mean"] = \
1610 round(mean(vals[key_c][key_n][u"vals"]) / 1e6, 1)
1611 vals[key_c][key_n][u"stdev"] = \
1612 round(stdev(vals[key_c][key_n][u"vals"]) / 1e6, 1)
1613 txt_nodes = list(set(txt_nodes))
1615 txt_chains = sorted(txt_chains, key=sort_by_int)
1616 txt_nodes = sorted(txt_nodes, key=sort_by_int)
1618 chains = [i + 1 for i in range(len(txt_chains))]
1619 nodes = [i + 1 for i in range(len(txt_nodes))]
1621 data = [list() for _ in range(len(chains))]
1622 for chain in chains:
1625 val = vals[txt_chains[chain - 1]] \
1626 [txt_nodes[node - 1]][u"mean"]
1627 except (KeyError, IndexError):
1629 data[chain - 1].append(val)
1632 my_green = [[0.0, u"rgb(235, 249, 242)"],
1633 [1.0, u"rgb(45, 134, 89)"]]
1635 my_blue = [[0.0, u"rgb(236, 242, 248)"],
1636 [1.0, u"rgb(57, 115, 172)"]]
1638 my_grey = [[0.0, u"rgb(230, 230, 230)"],
1639 [1.0, u"rgb(102, 102, 102)"]]
1642 annotations = list()
1644 text = (u"Test: {name}<br>"
1649 for chain, _ in enumerate(txt_chains):
1651 for node, _ in enumerate(txt_nodes):
1652 if data[chain][node] is not None:
1661 text=str(data[chain][node]),
1669 hover_line.append(text.format(
1670 name=vals[txt_chains[chain]][txt_nodes[node]]
1672 nr=vals[txt_chains[chain]][txt_nodes[node]][u"nr"],
1673 val=data[chain][node],
1674 stdev=vals[txt_chains[chain]][txt_nodes[node]]
1677 hovertext.append(hover_line)
1685 title=plot.get(u"z-axis", u"{test_type}").
1686 format(test_type=ttype.upper()),
1700 colorscale=my_green,
1706 for idx, item in enumerate(txt_nodes):
1724 for idx, item in enumerate(txt_chains):
1751 text=plot.get(u"x-axis", u""),
1768 text=plot.get(u"y-axis", u""),
1777 updatemenus = list([
1788 u"colorscale": [my_green, ],
1789 u"reversescale": False
1798 u"colorscale": [my_blue, ],
1799 u"reversescale": False
1808 u"colorscale": [my_grey, ],
1809 u"reversescale": False
1820 layout = deepcopy(plot[u"layout"])
1821 except KeyError as err:
1823 f"Finished with error: No layout defined\n{repr(err)}"
1827 layout[u"annotations"] = annotations
1828 layout[u'updatemenus'] = updatemenus
1829 if layout.get(u"title", None):
1830 layout[u"title"] = layout[u'title'].replace(u"test_type", ttype)
1834 plpl = plgo.Figure(data=traces, layout=layout)
1838 f"{plot[u'output-file'].format(core=core, test_type=ttype)}"
1841 logging.info(f" Writing file {file_name}")
1848 except PlotlyError as err:
1850 f" Finished with error: {repr(err)}".replace(u"\n", u" ")