1 # Copyright (c) 2021 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
6 # http://www.apache.org/licenses/LICENSE-2.0
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
14 """Algorithms to generate plots.
21 from collections import OrderedDict
22 from datetime import datetime
23 from copy import deepcopy
29 import plotly.offline as ploff
30 import plotly.graph_objs as plgo
31 import plotly.exceptions as plerr
33 from plotly.exceptions import PlotlyError
35 from pal_utils import mean, stdev
64 REGEX_NIC = re.compile(r'(\d*ge\dp\d\D*\d*[a-z]*)-')
66 # This value depends on latency stream rate (9001 pps) and duration (5s).
67 # Keep it slightly higher to ensure rounding errors to not remove tick mark.
68 PERCENTILE_MAX = 99.999501
71 def generate_plots(spec, data):
72 """Generate all plots specified in the specification file.
74 :param spec: Specification read from the specification file.
75 :param data: Data to process.
76 :type spec: Specification
81 u"plot_nf_reconf_box_name": plot_nf_reconf_box_name,
82 u"plot_perf_box_name": plot_perf_box_name,
83 u"plot_tsa_name": plot_tsa_name,
84 u"plot_http_server_perf_box": plot_http_server_perf_box,
85 u"plot_nf_heatmap": plot_nf_heatmap,
86 u"plot_hdrh_lat_by_percentile": plot_hdrh_lat_by_percentile,
87 u"plot_hdrh_lat_by_percentile_x_log": plot_hdrh_lat_by_percentile_x_log,
88 u"plot_mrr_box_name": plot_mrr_box_name,
89 u"plot_ndrpdr_box_name": plot_ndrpdr_box_name,
90 u"plot_statistics": plot_statistics
93 logging.info(u"Generating the plots ...")
94 for index, plot in enumerate(spec.plots):
96 logging.info(f" Plot nr {index + 1}: {plot.get(u'title', u'')}")
97 plot[u"limits"] = spec.environment[u"limits"]
98 generator[plot[u"algorithm"]](plot, data)
99 logging.info(u" Done.")
100 except NameError as err:
102 f"Probably algorithm {plot[u'algorithm']} is not defined: "
105 logging.info(u"Done.")
108 def plot_statistics(plot, input_data):
109 """Generate the plot(s) with algorithm: plot_statistics
110 specified in the specification file.
112 :param plot: Plot to generate.
113 :param input_data: Data to process.
114 :type plot: pandas.Series
115 :type input_data: InputData
121 data_y_duration = list()
125 u"passed: {passed}<br>"
126 u"failed: {failed}<br>"
127 u"duration: {duration}<br>"
128 u"{sut}-ref: {build}<br>"
129 u"csit-ref: {test}-{period}-build-{build_nr}<br>"
130 u"testbed: {testbed}"
132 for job, builds in plot[u"data"].items():
133 for build_nr in builds:
135 meta = input_data.metadata(job, str(build_nr))
136 generated = meta[u"generated"]
141 int(generated[9:11]),
144 d_y_pass = meta[u"tests_passed"]
145 d_y_fail = meta[u"tests_failed"]
146 minutes = meta[u"elapsedtime"] // 60000
147 duration = f"{(minutes // 60):02d}:{(minutes % 60):02d}"
148 version = meta[u"version"]
149 except (KeyError, IndexError, ValueError, AttributeError):
152 data_y_pass.append(d_y_pass)
153 data_y_fail.append(d_y_fail)
154 data_y_duration.append(minutes)
155 hover_text.append(hover_str.format(
160 sut=u"vpp" if u"vpp" in job else u"dpdk",
162 test=u"mrr" if u"mrr" in job else u"ndrpdr",
163 period=u"daily" if u"daily" in job else u"weekly",
165 testbed=meta[u"testbed"]
192 name_file = f"{plot[u'output-file']}.html"
194 logging.info(f" Writing the file {name_file}")
195 plpl = plgo.Figure(data=traces, layout=plot[u"layout"])
196 tickvals = [0, (max(data_y_duration) // 60) * 60]
197 step = tickvals[1] / 5
199 tickvals.append(int(tickvals[0] + step * (i + 1)))
202 title=u"Duration [hh:mm]",
209 ticktext=[f"{(val // 60):02d}:{(val % 60):02d}" for val in tickvals]
212 plpl.update_layout(barmode=u"stack")
220 except plerr.PlotlyEmptyDataError:
221 logging.warning(u"No data for the plot. Skipped.")
224 def plot_hdrh_lat_by_percentile(plot, input_data):
225 """Generate the plot(s) with algorithm: plot_hdrh_lat_by_percentile
226 specified in the specification file.
228 :param plot: Plot to generate.
229 :param input_data: Data to process.
230 :type plot: pandas.Series
231 :type input_data: InputData
236 f" Creating the data set for the {plot.get(u'type', u'')} "
237 f"{plot.get(u'title', u'')}."
239 if plot.get(u"include", None):
240 data = input_data.filter_tests_by_name(
242 params=[u"name", u"latency", u"parent", u"tags", u"type"]
244 elif plot.get(u"filter", None):
245 data = input_data.filter_data(
247 params=[u"name", u"latency", u"parent", u"tags", u"type"],
248 continue_on_error=True
251 job = list(plot[u"data"].keys())[0]
252 build = str(plot[u"data"][job][0])
253 data = input_data.tests(job, build)
255 if data is None or len(data) == 0:
256 logging.error(u"No data.")
260 u"LAT0": u"No-load.",
261 u"PDR10": u"Low-load, 10% PDR.",
262 u"PDR50": u"Mid-load, 50% PDR.",
263 u"PDR90": u"High-load, 90% PDR.",
264 u"PDR": u"Full-load, 100% PDR.",
265 u"NDR10": u"Low-load, 10% NDR.",
266 u"NDR50": u"Mid-load, 50% NDR.",
267 u"NDR90": u"High-load, 90% NDR.",
268 u"NDR": u"Full-load, 100% NDR."
278 file_links = plot.get(u"output-file-links", None)
279 target_links = plot.get(u"target-links", None)
283 if test[u"type"] not in (u"NDRPDR",):
284 logging.warning(f"Invalid test type: {test[u'type']}")
286 name = re.sub(REGEX_NIC, u"", test[u"parent"].
287 replace(u'-ndrpdr', u'').replace(u'2n1l-', u''))
289 nic = re.search(REGEX_NIC, test[u"parent"]).group(1)
290 except (IndexError, AttributeError, KeyError, ValueError):
292 name_link = f"{nic}-{test[u'name']}".replace(u'-ndrpdr', u'')
294 logging.info(f" Generating the graph: {name_link}")
297 layout = deepcopy(plot[u"layout"])
299 for color, graph in enumerate(graphs):
300 for idx, direction in enumerate((u"direction1", u"direction2")):
306 decoded = hdrh.histogram.HdrHistogram.decode(
307 test[u"latency"][graph][direction][u"hdrh"]
309 except hdrh.codec.HdrLengthException:
311 f"No data for direction {(u'W-E', u'E-W')[idx % 2]}"
315 for item in decoded.get_recorded_iterator():
316 percentile = item.percentile_level_iterated_to
317 xaxis.append(previous_x)
318 yaxis.append(item.value_iterated_to)
320 f"<b>{desc[graph]}</b><br>"
321 f"Direction: {(u'W-E', u'E-W')[idx % 2]}<br>"
323 f"{previous_x:.5f}-{percentile:.5f}%<br>"
324 f"Latency: {item.value_iterated_to}uSec"
326 xaxis.append(percentile)
327 yaxis.append(item.value_iterated_to)
329 f"<b>{desc[graph]}</b><br>"
330 f"Direction: {(u'W-E', u'E-W')[idx % 2]}<br>"
332 f"{previous_x:.5f}-{percentile:.5f}%<br>"
333 f"Latency: {item.value_iterated_to}uSec"
335 previous_x = percentile
342 legendgroup=desc[graph],
343 showlegend=bool(idx),
347 width=1 if idx % 2 else 2
354 layout[u"title"][u"text"] = f"<b>Latency:</b> {name}"
355 fig.update_layout(layout)
358 file_name = f"{plot[u'output-file']}-{name_link}.html"
359 logging.info(f" Writing file {file_name}")
363 ploff.plot(fig, show_link=False, auto_open=False,
365 # Add link to the file:
366 if file_links and target_links:
367 with open(file_links, u"a") as file_handler:
370 f"<{target_links}/{file_name.split(u'/')[-1]}>`_\n"
372 except FileNotFoundError as err:
374 f"Not possible to write the link to the file "
375 f"{file_links}\n{err}"
377 except PlotlyError as err:
378 logging.error(f" Finished with error: {repr(err)}")
380 except hdrh.codec.HdrLengthException as err:
381 logging.warning(repr(err))
384 except (ValueError, KeyError) as err:
385 logging.warning(repr(err))
389 def plot_hdrh_lat_by_percentile_x_log(plot, input_data):
390 """Generate the plot(s) with algorithm: plot_hdrh_lat_by_percentile_x_log
391 specified in the specification file.
393 :param plot: Plot to generate.
394 :param input_data: Data to process.
395 :type plot: pandas.Series
396 :type input_data: InputData
401 f" Creating the data set for the {plot.get(u'type', u'')} "
402 f"{plot.get(u'title', u'')}."
404 if plot.get(u"include", None):
405 data = input_data.filter_tests_by_name(
407 params=[u"name", u"latency", u"parent", u"tags", u"type"]
409 elif plot.get(u"filter", None):
410 data = input_data.filter_data(
412 params=[u"name", u"latency", u"parent", u"tags", u"type"],
413 continue_on_error=True
416 job = list(plot[u"data"].keys())[0]
417 build = str(plot[u"data"][job][0])
418 data = input_data.tests(job, build)
420 if data is None or len(data) == 0:
421 logging.error(u"No data.")
425 u"LAT0": u"No-load.",
426 u"PDR10": u"Low-load, 10% PDR.",
427 u"PDR50": u"Mid-load, 50% PDR.",
428 u"PDR90": u"High-load, 90% PDR.",
429 u"PDR": u"Full-load, 100% PDR.",
430 u"NDR10": u"Low-load, 10% NDR.",
431 u"NDR50": u"Mid-load, 50% NDR.",
432 u"NDR90": u"High-load, 90% NDR.",
433 u"NDR": u"Full-load, 100% NDR."
443 file_links = plot.get(u"output-file-links", None)
444 target_links = plot.get(u"target-links", None)
448 if test[u"type"] not in (u"NDRPDR",):
449 logging.warning(f"Invalid test type: {test[u'type']}")
451 name = re.sub(REGEX_NIC, u"", test[u"parent"].
452 replace(u'-ndrpdr', u'').replace(u'2n1l-', u''))
454 nic = re.search(REGEX_NIC, test[u"parent"]).group(1)
455 except (IndexError, AttributeError, KeyError, ValueError):
457 name_link = f"{nic}-{test[u'name']}".replace(u'-ndrpdr', u'')
459 logging.info(f" Generating the graph: {name_link}")
462 layout = deepcopy(plot[u"layout"])
464 for color, graph in enumerate(graphs):
465 for idx, direction in enumerate((u"direction1", u"direction2")):
472 decoded = hdrh.histogram.HdrHistogram.decode(
473 test[u"latency"][graph][direction][u"hdrh"]
475 except (hdrh.codec.HdrLengthException, TypeError):
477 f"No data for direction {(u'W-E', u'E-W')[idx % 2]}"
481 for item in decoded.get_recorded_iterator():
482 # The real value is "percentile".
483 # For 100%, we cut that down to "x_perc" to avoid
485 percentile = item.percentile_level_iterated_to
486 x_perc = min(percentile, PERCENTILE_MAX)
487 xaxis.append(previous_x)
488 yaxis.append(item.value_iterated_to)
490 f"<b>{desc[graph]}</b><br>"
491 f"Direction: {(u'W-E', u'E-W')[idx % 2]}<br>"
492 f"Percentile: {prev_perc:.5f}-{percentile:.5f}%<br>"
493 f"Latency: {item.value_iterated_to}uSec"
495 next_x = 100.0 / (100.0 - x_perc)
497 yaxis.append(item.value_iterated_to)
499 f"<b>{desc[graph]}</b><br>"
500 f"Direction: {(u'W-E', u'E-W')[idx % 2]}<br>"
501 f"Percentile: {prev_perc:.5f}-{percentile:.5f}%<br>"
502 f"Latency: {item.value_iterated_to}uSec"
505 prev_perc = percentile
512 legendgroup=desc[graph],
513 showlegend=not(bool(idx)),
517 width=1 if idx % 2 else 2
524 layout[u"title"][u"text"] = f"<b>Latency:</b> {name}"
525 x_max = log(100.0 / (100.0 - PERCENTILE_MAX), 10)
526 layout[u"xaxis"][u"range"] = [0, x_max]
527 fig.update_layout(layout)
530 file_name = f"{plot[u'output-file']}-{name_link}.html"
531 logging.info(f" Writing file {file_name}")
535 ploff.plot(fig, show_link=False, auto_open=False,
537 # Add link to the file:
538 if file_links and target_links:
539 with open(file_links, u"a") as file_handler:
542 f"<{target_links}/{file_name.split(u'/')[-1]}>`_\n"
544 except FileNotFoundError as err:
546 f"Not possible to write the link to the file "
547 f"{file_links}\n{err}"
549 except PlotlyError as err:
550 logging.error(f" Finished with error: {repr(err)}")
552 except hdrh.codec.HdrLengthException as err:
553 logging.warning(repr(err))
556 except (ValueError, KeyError) as err:
557 logging.warning(repr(err))
561 def plot_nf_reconf_box_name(plot, input_data):
562 """Generate the plot(s) with algorithm: plot_nf_reconf_box_name
563 specified in the specification file.
565 :param plot: Plot to generate.
566 :param input_data: Data to process.
567 :type plot: pandas.Series
568 :type input_data: InputData
573 f" Creating the data set for the {plot.get(u'type', u'')} "
574 f"{plot.get(u'title', u'')}."
576 data = input_data.filter_tests_by_name(
577 plot, params=[u"result", u"parent", u"tags", u"type"]
580 logging.error(u"No data.")
583 for core in plot.get(u"core", tuple()):
584 # Prepare the data for the plot
585 y_vals = OrderedDict()
587 for item in plot.get(u"include", tuple()):
588 reg_ex = re.compile(str(item.format(core=core)).lower())
591 for test_id, test in build.iteritems():
592 if not re.match(reg_ex, str(test_id).lower()):
594 if y_vals.get(test[u"parent"], None) is None:
595 y_vals[test[u"parent"]] = list()
596 loss[test[u"parent"]] = list()
598 y_vals[test[u"parent"]].append(
599 test[u"result"][u"time"]
601 loss[test[u"parent"]].append(
602 test[u"result"][u"loss"]
604 except (KeyError, TypeError):
605 y_vals[test[u"parent"]].append(None)
607 # Add None to the lists with missing data
609 nr_of_samples = list()
610 for val in y_vals.values():
611 if len(val) > max_len:
613 nr_of_samples.append(len(val))
614 for val in y_vals.values():
615 if len(val) < max_len:
616 val.extend([None for _ in range(max_len - len(val))])
620 df_y = pd.DataFrame(y_vals)
622 for i, col in enumerate(df_y.columns):
625 col.lower().replace(u'-reconf', u'').replace(u'2n1l-', u'').
626 replace(u'2n-', u'').replace(u'-testpmd', u'')
628 traces.append(plgo.Box(
629 x=[str(i + 1) + u'.'] * len(df_y[col]),
633 f"({nr_of_samples[i]:02d} "
634 f"run{u's' if nr_of_samples[i] > 1 else u''}, "
635 f"packets lost average: {mean(loss[col]):.1f}) "
636 f"{u'-'.join(tst_name.split(u'-')[2:])}"
642 layout = deepcopy(plot[u"layout"])
643 layout[u"title"] = f"<b>Time Lost:</b> {layout[u'title']}"
644 layout[u"yaxis"][u"title"] = u"<b>Effective Blocked Time [s]</b>"
645 layout[u"legend"][u"font"][u"size"] = 14
646 layout[u"yaxis"].pop(u"range")
647 plpl = plgo.Figure(data=traces, layout=layout)
650 file_name = f"{plot[u'output-file'].format(core=core)}.html"
651 logging.info(f" Writing file {file_name}")
658 except PlotlyError as err:
660 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
664 def plot_perf_box_name(plot, input_data):
665 """Generate the plot(s) with algorithm: plot_perf_box_name
666 specified in the specification file.
668 Use only for soak and hoststack tests.
670 :param plot: Plot to generate.
671 :param input_data: Data to process.
672 :type plot: pandas.Series
673 :type input_data: InputData
678 f" Creating data set for the {plot.get(u'type', u'')} "
679 f"{plot.get(u'title', u'')}."
681 data = input_data.filter_tests_by_name(
683 params=[u"throughput", u"gbps", u"result", u"parent", u"tags", u"type"])
685 logging.error(u"No data.")
688 # Prepare the data for the plot
689 y_vals = OrderedDict()
692 for item in plot.get(u"include", tuple()):
693 reg_ex = re.compile(str(item).lower())
696 for test_id, test in build.iteritems():
697 if not re.match(reg_ex, str(test_id).lower()):
699 if y_vals.get(test[u"parent"], None) is None:
700 y_vals[test[u"parent"]] = list()
702 if test[u"type"] in (u"SOAK",):
703 y_vals[test[u"parent"]]. \
704 append(test[u"throughput"][u"LOWER"])
707 elif test[u"type"] in (u"HOSTSTACK",):
708 if u"LDPRELOAD" in test[u"tags"]:
709 y_vals[test[u"parent"]].append(
711 test[u"result"][u"bits_per_second"]
714 elif u"VPPECHO" in test[u"tags"]:
715 y_vals[test[u"parent"]].append(
717 test[u"result"][u"client"][u"tx_data"]
720 test[u"result"][u"client"][u"time"]
723 test[u"result"][u"server"][u"time"])
726 test_type = u"HOSTSTACK"
728 elif test[u"type"] in (u"LDP_NGINX",):
729 if u"TCP_CPS" in test[u"tags"]:
730 test_type = u"VSAP_CPS"
731 y_vals[test[u"parent"]].append(
732 test[u"result"][u"cps"]
734 elif u"TCP_RPS" in test[u"tags"]:
735 test_type = u"VSAP_RPS"
736 y_vals[test[u"parent"]].append(
737 test[u"result"][u"rps"]
744 except (KeyError, TypeError):
745 y_vals[test[u"parent"]].append(None)
747 # Add None to the lists with missing data
749 nr_of_samples = list()
750 for val in y_vals.values():
751 if len(val) > max_len:
753 nr_of_samples.append(len(val))
754 for val in y_vals.values():
755 if len(val) < max_len:
756 val.extend([None for _ in range(max_len - len(val))])
760 df_y = pd.DataFrame(y_vals)
763 for i, col in enumerate(df_y.columns):
764 tst_name = re.sub(REGEX_NIC, u"",
765 col.lower().replace(u'-ndrpdr', u'').
766 replace(u'2n1l-', u''))
767 if test_type in (u"VSAP_CPS", u"VSAP_RPS"):
768 data_y = [y if y else None for y in df_y[col]]
770 data_y = [y / 1e6 if y else None for y in df_y[col]]
772 x=[str(i + 1) + u'.'] * len(df_y[col]),
776 f"({nr_of_samples[i]:02d} "
777 f"run{u's' if nr_of_samples[i] > 1 else u''}) "
782 if test_type in (u"SOAK", ):
783 kwargs[u"boxpoints"] = u"all"
785 traces.append(plgo.Box(**kwargs))
788 val_max = max(df_y[col])
790 y_max.append(int(val_max / 1e6))
791 except (ValueError, TypeError) as err:
792 logging.error(repr(err))
797 layout = deepcopy(plot[u"layout"])
798 if layout.get(u"title", None):
799 if test_type in (u"HOSTSTACK", ):
800 layout[u"title"] = f"<b>Bandwidth:</b> {layout[u'title']}"
801 elif test_type == u"VSAP_CPS":
802 layout[u"title"] = f"<b>CPS:</b> {layout[u'title']}"
803 layout[u"yaxis"][u"title"] = u"<b>Connection Rate [cps]</b>"
804 elif test_type == u"VSAP_RPS":
805 layout[u"title"] = f"<b>RPS:</b> {layout[u'title']}"
806 layout[u"yaxis"][u"title"] = u"<b>Connection Rate [rps]</b>"
808 layout[u"title"] = f"<b>Tput:</b> {layout[u'title']}"
809 if y_max and max(y_max) > 1:
810 layout[u"yaxis"][u"range"] = [0, max(y_max) + 2]
811 plpl = plgo.Figure(data=traces, layout=layout)
814 logging.info(f" Writing file {plot[u'output-file']}.html.")
819 filename=f"{plot[u'output-file']}.html"
821 except PlotlyError as err:
823 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
828 def plot_ndrpdr_box_name(plot, input_data):
829 """Generate the plot(s) with algorithm: plot_ndrpdr_box_name
830 specified in the specification file.
832 :param plot: Plot to generate.
833 :param input_data: Data to process.
834 :type plot: pandas.Series
835 :type input_data: InputData
840 f" Creating data set for the {plot.get(u'type', u'')} "
841 f"{plot.get(u'title', u'')}."
843 data = input_data.filter_tests_by_name(
845 params=[u"throughput", u"gbps", u"parent", u"tags", u"type"]
848 logging.error(u"No data.")
851 if u"-gbps" in plot.get(u"title", u"").lower():
855 value = u"throughput"
860 for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
861 for core in plot.get(u"core", tuple()):
862 # Prepare the data for the plot
864 data_y = OrderedDict()
867 for item in plot.get(u"include", tuple()):
868 reg_ex = re.compile(str(item.format(core=core)).lower())
871 for test_id, test in build.iteritems():
872 if not re.match(reg_ex, str(test_id).lower()):
874 if data_y.get(test[u"parent"], None) is None:
875 data_y[test[u"parent"]] = list()
876 test_type = test[u"type"]
880 data_y[test[u"parent"]].append(
881 test[value][ttype.upper()][u"LOWER"] *
884 except (KeyError, TypeError):
889 for idx, (key, vals) in enumerate(data_y.items()):
891 REGEX_NIC, u'', key.lower().replace(u'-ndrpdr', u'').
892 replace(u'2n1l-', u'')
896 x=[data_x[idx], ] * len(data_x),
897 y=[y / 1e6 if y else None for y in vals],
902 f"{u's' if len(vals) > 1 else u''}) "
909 data_y_max.append(max(vals))
910 except ValueError as err:
911 logging.warning(f"No values to use.\n{err!r}")
914 layout = deepcopy(plot[u"layout"])
915 if layout.get(u"title", None):
917 layout[u'title'].format(core=core, test_type=ttype)
918 if test_type in (u"CPS", ):
919 layout[u"title"] = f"<b>CPS:</b> {layout[u'title']}"
922 f"<b>Tput:</b> {layout[u'title']}"
924 layout[u"yaxis"][u"range"] = [0, max(data_y_max) / 1e6 + 1]
925 plpl = plgo.Figure(data=traces, layout=layout)
929 f"{plot[u'output-file'].format(core=core, test_type=ttype)}"
932 logging.info(f" Writing file {file_name}")
939 except PlotlyError as err:
941 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
945 def plot_mrr_box_name(plot, input_data):
946 """Generate the plot(s) with algorithm: plot_mrr_box_name
947 specified in the specification file.
949 :param plot: Plot to generate.
950 :param input_data: Data to process.
951 :type plot: pandas.Series
952 :type input_data: InputData
957 f" Creating data set for the {plot.get(u'type', u'')} "
958 f"{plot.get(u'title', u'')}."
960 data = input_data.filter_tests_by_name(
962 params=[u"result", u"parent", u"tags", u"type"]
965 logging.error(u"No data.")
968 for core in plot.get(u"core", tuple()):
969 # Prepare the data for the plot
975 for item in plot.get(u"include", tuple()):
976 reg_ex = re.compile(str(item.format(core=core)).lower())
979 for test_id, test in build.iteritems():
980 if not re.match(reg_ex, str(test_id).lower()):
985 REGEX_NIC, u'', test[u'parent'].lower().
986 replace(u'-mrr', u'').replace(u'2n1l-', u'')
988 data_y.append(test[u"result"][u"samples"])
991 f"({len(data_y[-1]):02d} "
992 f"run{u's' if len(data_y[-1]) > 1 else u''}) "
995 data_y_max.append(max(data_y[-1]))
997 except (KeyError, TypeError):
1002 for idx, x_item in enumerate(data_x):
1005 x=[x_item, ] * len(data_y[idx]),
1007 name=data_names[idx],
1014 layout = deepcopy(plot[u"layout"])
1015 if layout.get(u"title", None):
1016 layout[u"title"] = (
1017 f"<b>Tput:</b> {layout[u'title'].format(core=core)}"
1020 layout[u"yaxis"][u"range"] = [0, max(data_y_max) + 1]
1021 plpl = plgo.Figure(data=traces, layout=layout)
1024 file_name = f"{plot[u'output-file'].format(core=core)}.html"
1025 logging.info(f" Writing file {file_name}")
1032 except PlotlyError as err:
1034 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
1038 def plot_tsa_name(plot, input_data):
1039 """Generate the plot(s) with algorithm:
1041 specified in the specification file.
1043 :param plot: Plot to generate.
1044 :param input_data: Data to process.
1045 :type plot: pandas.Series
1046 :type input_data: InputData
1049 # Transform the data
1050 plot_title = plot.get(u"title", u"")
1052 f" Creating data set for the {plot.get(u'type', u'')} {plot_title}."
1054 data = input_data.filter_tests_by_name(
1056 params=[u"throughput", u"gbps", u"parent", u"tags", u"type"]
1059 logging.error(u"No data.")
1062 plot_title = plot_title.lower()
1064 if u"-gbps" in plot_title:
1069 value = u"throughput"
1073 for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
1074 y_vals = OrderedDict()
1075 for item in plot.get(u"include", tuple()):
1076 reg_ex = re.compile(str(item).lower())
1079 for test_id, test in build.iteritems():
1080 if re.match(reg_ex, str(test_id).lower()):
1081 if y_vals.get(test[u"parent"], None) is None:
1082 y_vals[test[u"parent"]] = {
1088 if test[u"type"] not in (u"NDRPDR", u"CPS"):
1091 if u"1C" in test[u"tags"]:
1092 y_vals[test[u"parent"]][u"1"].append(
1093 test[value][ttype.upper()][u"LOWER"] *
1096 elif u"2C" in test[u"tags"]:
1097 y_vals[test[u"parent"]][u"2"].append(
1098 test[value][ttype.upper()][u"LOWER"] *
1101 elif u"4C" in test[u"tags"]:
1102 y_vals[test[u"parent"]][u"4"].append(
1103 test[value][ttype.upper()][u"LOWER"] *
1106 except (KeyError, TypeError):
1110 logging.warning(f"No data for the plot {plot.get(u'title', u'')}")
1114 for test_name, test_vals in y_vals.items():
1115 for key, test_val in test_vals.items():
1117 avg_val = sum(test_val) / len(test_val)
1118 y_vals[test_name][key] = [avg_val, len(test_val)]
1119 ideal = avg_val / (int(key) * 1e6)
1120 if test_name not in y_1c_max or ideal > y_1c_max[test_name]:
1121 y_1c_max[test_name] = ideal
1123 vals = OrderedDict()
1128 for test_name, test_vals in y_vals.items():
1130 if test_vals[u"1"][1]:
1134 test_name.replace(u'-ndrpdr', u'').
1135 replace(u'2n1l-', u'')
1137 vals[name] = OrderedDict()
1138 y_val_1 = test_vals[u"1"][0] / 1e6
1139 y_val_2 = test_vals[u"2"][0] / 1e6 if test_vals[u"2"][0] \
1141 y_val_4 = test_vals[u"4"][0] / 1e6 if test_vals[u"4"][0] \
1144 vals[name][u"val"] = [y_val_1, y_val_2, y_val_4]
1145 vals[name][u"rel"] = [1.0, None, None]
1146 vals[name][u"ideal"] = [
1147 y_1c_max[test_name],
1148 y_1c_max[test_name] * 2,
1149 y_1c_max[test_name] * 4
1151 vals[name][u"diff"] = [
1152 (y_val_1 - y_1c_max[test_name]) * 100 / y_val_1,
1156 vals[name][u"count"] = [
1163 val_max = max(vals[name][u"val"])
1164 except ValueError as err:
1165 logging.error(repr(err))
1168 y_max.append(val_max)
1171 vals[name][u"rel"][1] = round(y_val_2 / y_val_1, 2)
1172 vals[name][u"diff"][1] = \
1173 (y_val_2 - vals[name][u"ideal"][1]) * 100 / y_val_2
1175 vals[name][u"rel"][2] = round(y_val_4 / y_val_1, 2)
1176 vals[name][u"diff"][2] = \
1177 (y_val_4 - vals[name][u"ideal"][2]) * 100 / y_val_4
1178 except IndexError as err:
1179 logging.warning(f"No data for {test_name}")
1180 logging.warning(repr(err))
1183 if u"x520" in test_name:
1184 limit = plot[u"limits"][u"nic"][u"x520"]
1185 elif u"x710" in test_name:
1186 limit = plot[u"limits"][u"nic"][u"x710"]
1187 elif u"xxv710" in test_name:
1188 limit = plot[u"limits"][u"nic"][u"xxv710"]
1189 elif u"xl710" in test_name:
1190 limit = plot[u"limits"][u"nic"][u"xl710"]
1191 elif u"x553" in test_name:
1192 limit = plot[u"limits"][u"nic"][u"x553"]
1193 elif u"cx556a" in test_name:
1194 limit = plot[u"limits"][u"nic"][u"cx556a"]
1195 elif u"e810cq" in test_name:
1196 limit = plot[u"limits"][u"nic"][u"e810cq"]
1199 if limit > nic_limit:
1202 mul = 2 if u"ge2p" in test_name else 1
1203 if u"10ge" in test_name:
1204 limit = plot[u"limits"][u"link"][u"10ge"] * mul
1205 elif u"25ge" in test_name:
1206 limit = plot[u"limits"][u"link"][u"25ge"] * mul
1207 elif u"40ge" in test_name:
1208 limit = plot[u"limits"][u"link"][u"40ge"] * mul
1209 elif u"100ge" in test_name:
1210 limit = plot[u"limits"][u"link"][u"100ge"] * mul
1213 if limit > lnk_limit:
1216 if u"cx556a" in test_name:
1217 limit = plot[u"limits"][u"pci"][u"pci-g3-x8"]
1219 limit = plot[u"limits"][u"pci"][u"pci-g3-x16"]
1220 if limit > pci_limit:
1224 annotations = list()
1228 if u"-gbps" not in plot_title and u"-cps-" not in plot_title:
1232 min_limit = min((nic_limit, lnk_limit, pci_limit))
1233 if nic_limit == min_limit:
1234 traces.append(plgo.Scatter(
1236 y=[nic_limit, ] * len(x_vals),
1237 name=f"NIC: {nic_limit:.2f}Mpps",
1246 annotations.append(dict(
1253 text=f"NIC: {nic_limit:.2f}Mpps",
1261 y_max.append(nic_limit)
1262 elif lnk_limit == min_limit:
1263 traces.append(plgo.Scatter(
1265 y=[lnk_limit, ] * len(x_vals),
1266 name=f"Link: {lnk_limit:.2f}Mpps",
1275 annotations.append(dict(
1282 text=f"Link: {lnk_limit:.2f}Mpps",
1290 y_max.append(lnk_limit)
1291 elif pci_limit == min_limit:
1292 traces.append(plgo.Scatter(
1294 y=[pci_limit, ] * len(x_vals),
1295 name=f"PCIe: {pci_limit:.2f}Mpps",
1304 annotations.append(dict(
1311 text=f"PCIe: {pci_limit:.2f}Mpps",
1319 y_max.append(pci_limit)
1321 # Perfect and measured:
1323 for name, val in vals.items():
1326 for idx in range(len(val[u"val"])):
1328 if isinstance(val[u"val"][idx], float):
1330 f"No. of Runs: {val[u'count'][idx]}<br>"
1331 f"Mean: {val[u'val'][idx]:.2f}{h_unit}<br>"
1333 if isinstance(val[u"diff"][idx], float):
1334 htext += f"Diff: {round(val[u'diff'][idx]):.0f}%<br>"
1335 if isinstance(val[u"rel"][idx], float):
1336 htext += f"Speedup: {val[u'rel'][idx]:.2f}"
1337 hovertext.append(htext)
1344 mode=u"lines+markers",
1353 hoverinfo=u"text+name"
1360 name=f"{name} perfect",
1368 text=[f"Perfect: {y:.2f}Mpps" for y in val[u"ideal"]],
1373 except (IndexError, ValueError, KeyError) as err:
1374 logging.warning(f"No data for {name}\n{repr(err)}")
1378 file_name = f"{plot[u'output-file'].format(test_type=ttype)}.html"
1379 logging.info(f" Writing file {file_name}")
1380 layout = deepcopy(plot[u"layout"])
1381 if layout.get(u"title", None):
1382 layout[u"title"] = (
1383 f"<b>Speedup Multi-core:</b> "
1384 f"{layout[u'title'].format(test_type=ttype)}"
1386 layout[u"yaxis"][u"range"] = [0, int(max(y_max) * 1.1)]
1387 layout[u"annotations"].extend(annotations)
1388 plpl = plgo.Figure(data=traces, layout=layout)
1397 except PlotlyError as err:
1399 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
1403 def plot_http_server_perf_box(plot, input_data):
1404 """Generate the plot(s) with algorithm: plot_http_server_perf_box
1405 specified in the specification file.
1407 :param plot: Plot to generate.
1408 :param input_data: Data to process.
1409 :type plot: pandas.Series
1410 :type input_data: InputData
1413 # Transform the data
1415 f" Creating the data set for the {plot.get(u'type', u'')} "
1416 f"{plot.get(u'title', u'')}."
1418 data = input_data.filter_data(plot)
1420 logging.error(u"No data.")
1423 # Prepare the data for the plot
1428 if y_vals.get(test[u"name"], None) is None:
1429 y_vals[test[u"name"]] = list()
1431 y_vals[test[u"name"]].append(test[u"result"])
1432 except (KeyError, TypeError):
1433 y_vals[test[u"name"]].append(None)
1435 # Add None to the lists with missing data
1437 nr_of_samples = list()
1438 for val in y_vals.values():
1439 if len(val) > max_len:
1441 nr_of_samples.append(len(val))
1442 for val in y_vals.values():
1443 if len(val) < max_len:
1444 val.extend([None for _ in range(max_len - len(val))])
1448 df_y = pd.DataFrame(y_vals)
1450 for i, col in enumerate(df_y.columns):
1453 f"({nr_of_samples[i]:02d} " \
1454 f"run{u's' if nr_of_samples[i] > 1 else u''}) " \
1455 f"{col.lower().replace(u'-ndrpdr', u'')}"
1457 name_lst = name.split(u'-')
1460 for segment in name_lst:
1461 if (len(name) + len(segment) + 1) > 50 and split_name:
1464 name += segment + u'-'
1467 traces.append(plgo.Box(x=[str(i + 1) + u'.'] * len(df_y[col]),
1473 plpl = plgo.Figure(data=traces, layout=plot[u"layout"])
1477 f" Writing file {plot[u'output-file']}"
1478 f"{plot[u'output-file-type']}."
1484 filename=f"{plot[u'output-file']}{plot[u'output-file-type']}"
1486 except PlotlyError as err:
1488 f" Finished with error: {repr(err)}".replace(u"\n", u" ")
1493 def plot_nf_heatmap(plot, input_data):
1494 """Generate the plot(s) with algorithm: plot_nf_heatmap
1495 specified in the specification file.
1497 :param plot: Plot to generate.
1498 :param input_data: Data to process.
1499 :type plot: pandas.Series
1500 :type input_data: InputData
1503 def sort_by_int(value):
1504 """Makes possible to sort a list of strings which represent integers.
1506 :param value: Integer as a string.
1508 :returns: Integer representation of input parameter 'value'.
1513 regex_cn = re.compile(r'^(\d*)R(\d*)C$')
1514 regex_test_name = re.compile(r'^.*-(\d+ch|\d+pl)-'
1516 r'(\d+vm\d+t|\d+dcr\d+t|\d+dcr\d+c).*$')
1519 # Transform the data
1521 f" Creating the data set for the {plot.get(u'type', u'')} "
1522 f"{plot.get(u'title', u'')}."
1524 in_data = input_data.filter_tests_by_name(
1526 continue_on_error=True,
1527 params=[u"throughput", u"result", u"name", u"tags", u"type"]
1529 if in_data is None or in_data.empty:
1530 logging.error(u"No data.")
1533 for ttype in plot.get(u"test-type", (u"ndr", u"pdr")):
1534 for core in plot.get(u"core", tuple()):
1535 for item in plot.get(u"include", tuple()):
1536 reg_ex = re.compile(str(item.format(core=core)).lower())
1539 for test_id, test in build.iteritems():
1540 if not re.match(reg_ex, str(test_id).lower()):
1542 for tag in test[u"tags"]:
1543 groups = re.search(regex_cn, tag)
1545 chain = str(groups.group(1))
1546 node = str(groups.group(2))
1550 groups = re.search(regex_test_name, test[u"name"])
1551 if groups and len(groups.groups()) == 3:
1553 f"{str(groups.group(1))}-"
1554 f"{str(groups.group(2))}-"
1555 f"{str(groups.group(3))}"
1559 if vals.get(chain, None) is None:
1560 vals[chain] = dict()
1561 if vals[chain].get(node, None) is None:
1562 vals[chain][node] = dict(
1571 result = test[u"result"][u"receive-rate"]
1572 elif ttype == u"pdr":
1574 test[u"throughput"][u"PDR"][u"LOWER"]
1575 elif ttype == u"ndr":
1577 test[u"throughput"][u"NDR"][u"LOWER"]
1584 vals[chain][node][u"vals"].append(result)
1587 logging.error(u"No data.")
1593 txt_chains.append(key_c)
1594 for key_n in vals[key_c].keys():
1595 txt_nodes.append(key_n)
1596 if vals[key_c][key_n][u"vals"]:
1597 vals[key_c][key_n][u"nr"] = \
1598 len(vals[key_c][key_n][u"vals"])
1599 vals[key_c][key_n][u"mean"] = \
1600 round(mean(vals[key_c][key_n][u"vals"]) / 1e6, 1)
1601 vals[key_c][key_n][u"stdev"] = \
1602 round(stdev(vals[key_c][key_n][u"vals"]) / 1e6, 1)
1603 txt_nodes = list(set(txt_nodes))
1605 txt_chains = sorted(txt_chains, key=sort_by_int)
1606 txt_nodes = sorted(txt_nodes, key=sort_by_int)
1608 chains = [i + 1 for i in range(len(txt_chains))]
1609 nodes = [i + 1 for i in range(len(txt_nodes))]
1611 data = [list() for _ in range(len(chains))]
1612 for chain in chains:
1615 val = vals[txt_chains[chain - 1]] \
1616 [txt_nodes[node - 1]][u"mean"]
1617 except (KeyError, IndexError):
1619 data[chain - 1].append(val)
1622 my_green = [[0.0, u"rgb(235, 249, 242)"],
1623 [1.0, u"rgb(45, 134, 89)"]]
1625 my_blue = [[0.0, u"rgb(236, 242, 248)"],
1626 [1.0, u"rgb(57, 115, 172)"]]
1628 my_grey = [[0.0, u"rgb(230, 230, 230)"],
1629 [1.0, u"rgb(102, 102, 102)"]]
1632 annotations = list()
1634 text = (u"Test: {name}<br>"
1639 for chain, _ in enumerate(txt_chains):
1641 for node, _ in enumerate(txt_nodes):
1642 if data[chain][node] is not None:
1651 text=str(data[chain][node]),
1659 hover_line.append(text.format(
1660 name=vals[txt_chains[chain]][txt_nodes[node]]
1662 nr=vals[txt_chains[chain]][txt_nodes[node]][u"nr"],
1663 val=data[chain][node],
1664 stdev=vals[txt_chains[chain]][txt_nodes[node]]
1667 hovertext.append(hover_line)
1675 title=plot.get(u"z-axis", u"{test_type}").
1676 format(test_type=ttype.upper()),
1690 colorscale=my_green,
1696 for idx, item in enumerate(txt_nodes):
1714 for idx, item in enumerate(txt_chains):
1741 text=plot.get(u"x-axis", u""),
1758 text=plot.get(u"y-axis", u""),
1767 updatemenus = list([
1778 u"colorscale": [my_green, ],
1779 u"reversescale": False
1788 u"colorscale": [my_blue, ],
1789 u"reversescale": False
1798 u"colorscale": [my_grey, ],
1799 u"reversescale": False
1810 layout = deepcopy(plot[u"layout"])
1811 except KeyError as err:
1813 f"Finished with error: No layout defined\n{repr(err)}"
1817 layout[u"annotations"] = annotations
1818 layout[u'updatemenus'] = updatemenus
1819 if layout.get(u"title", None):
1820 layout[u"title"] = layout[u'title'].replace(u"test_type", ttype)
1824 plpl = plgo.Figure(data=traces, layout=layout)
1828 f"{plot[u'output-file'].format(core=core, test_type=ttype)}"
1831 logging.info(f" Writing file {file_name}")
1838 except PlotlyError as err:
1840 f" Finished with error: {repr(err)}".replace(u"\n", u" ")