+ # Transform the data
+ logging.info(
+ f" Creating the data set for the {table.get(u'type', u'')} "
+ f"{table.get(u'title', u'')}."
+ )
+ data = input_data.filter_data(table, continue_on_error=True)
+
+ # Prepare the header of the table
+ try:
+ header = [
+ u"Test Case",
+ f"Avg({table[u'reference'][u'title']})",
+ f"Stdev({table[u'reference'][u'title']})",
+ f"Avg({table[u'compare'][u'title']})",
+ f"Stdev{table[u'compare'][u'title']})",
+ u"Diff",
+ u"Stdev(Diff)"
+ ]
+ header_str = u";".join(header) + u"\n"
+ legend = (
+ u"\nLegend:\n"
+ f"Avg({table[u'reference'][u'title']}): "
+ f"Mean value of {table[u'reference'][u'title']} [Mpps] computed "
+ f"from a series of runs of the listed tests.\n"
+ f"Stdev({table[u'reference'][u'title']}): "
+ f"Standard deviation value of {table[u'reference'][u'title']} "
+ f"[Mpps] computed from a series of runs of the listed tests.\n"
+ f"Avg({table[u'compare'][u'title']}): "
+ f"Mean value of {table[u'compare'][u'title']} [Mpps] computed from "
+ f"a series of runs of the listed tests.\n"
+ f"Stdev({table[u'compare'][u'title']}): "
+ f"Standard deviation value of {table[u'compare'][u'title']} [Mpps] "
+ f"computed from a series of runs of the listed tests.\n"
+ f"Diff({table[u'reference'][u'title']},"
+ f"{table[u'compare'][u'title']}): "
+ f"Percentage change calculated for mean values.\n"
+ u"Stdev(Diff): "
+ u"Standard deviation of percentage change calculated for mean "
+ u"values."
+ )
+ except (AttributeError, KeyError) as err:
+ logging.error(f"The model is invalid, missing parameter: {repr(err)}")
+ return
+
+ # Create a list of available SOAK test results:
+ tbl_dict = dict()
+ for job, builds in table[u"compare"][u"data"].items():
+ for build in builds:
+ for tst_name, tst_data in data[job][str(build)].items():
+ if tst_data[u"type"] == u"SOAK":
+ tst_name_mod = tst_name.replace(u"-soak", u"")
+ if tbl_dict.get(tst_name_mod, None) is None:
+ groups = re.search(REGEX_NIC, tst_data[u"parent"])
+ nic = groups.group(0) if groups else u""
+ name = (
+ f"{nic}-"
+ f"{u'-'.join(tst_data[u'name'].split(u'-')[:-1])}"
+ )
+ tbl_dict[tst_name_mod] = {
+ u"name": name,
+ u"ref-data": list(),
+ u"cmp-data": list()
+ }
+ try:
+ tbl_dict[tst_name_mod][u"cmp-data"].append(
+ tst_data[u"throughput"][u"LOWER"])
+ except (KeyError, TypeError):
+ pass
+ tests_lst = tbl_dict.keys()
+
+ # Add corresponding NDR test results:
+ for job, builds in table[u"reference"][u"data"].items():
+ for build in builds:
+ for tst_name, tst_data in data[job][str(build)].items():
+ tst_name_mod = tst_name.replace(u"-ndrpdr", u"").\
+ replace(u"-mrr", u"")
+ if tst_name_mod not in tests_lst:
+ continue
+ try:
+ if tst_data[u"type"] not in (u"NDRPDR", u"MRR", u"BMRR"):
+ continue
+ if table[u"include-tests"] == u"MRR":
+ result = (tst_data[u"result"][u"receive-rate"],
+ tst_data[u"result"][u"receive-stdev"])
+ elif table[u"include-tests"] == u"PDR":
+ result = \
+ tst_data[u"throughput"][u"PDR"][u"LOWER"]
+ elif table[u"include-tests"] == u"NDR":
+ result = \
+ tst_data[u"throughput"][u"NDR"][u"LOWER"]
+ else:
+ result = None
+ if result is not None:
+ tbl_dict[tst_name_mod][u"ref-data"].append(
+ result)
+ except (KeyError, TypeError):
+ continue
+
+ tbl_lst = list()
+ for tst_name in tbl_dict:
+ item = [tbl_dict[tst_name][u"name"], ]
+ data_r = tbl_dict[tst_name][u"ref-data"]
+ if data_r:
+ if table[u"include-tests"] == u"MRR":
+ data_r_mean = data_r[0][0]
+ data_r_stdev = data_r[0][1]
+ else:
+ data_r_mean = mean(data_r)
+ data_r_stdev = stdev(data_r)
+ item.append(round(data_r_mean / 1e6, 1))
+ item.append(round(data_r_stdev / 1e6, 1))
+ else:
+ data_r_mean = None
+ data_r_stdev = None
+ item.extend([None, None])
+ data_c = tbl_dict[tst_name][u"cmp-data"]
+ if data_c:
+ if table[u"include-tests"] == u"MRR":
+ data_c_mean = data_c[0][0]
+ data_c_stdev = data_c[0][1]
+ else:
+ data_c_mean = mean(data_c)
+ data_c_stdev = stdev(data_c)
+ item.append(round(data_c_mean / 1e6, 1))
+ item.append(round(data_c_stdev / 1e6, 1))
+ else:
+ data_c_mean = None
+ data_c_stdev = None
+ item.extend([None, None])
+ if data_r_mean is not None and data_c_mean is not None:
+ delta, d_stdev = relative_change_stdev(
+ data_r_mean, data_c_mean, data_r_stdev, data_c_stdev)
+ try:
+ item.append(round(delta))
+ except ValueError:
+ item.append(delta)
+ try:
+ item.append(round(d_stdev))
+ except ValueError:
+ item.append(d_stdev)
+ tbl_lst.append(item)
+
+ # Sort the table according to the relative change
+ tbl_lst.sort(key=lambda rel: rel[-1], reverse=True)
+
+ # Generate csv tables:
+ csv_file = f"{table[u'output-file']}.csv"
+ with open(csv_file, u"wt") as file_handler:
+ file_handler.write(header_str)
+ for test in tbl_lst:
+ file_handler.write(u";".join([str(item) for item in test]) + u"\n")
+
+ convert_csv_to_pretty_txt(
+ csv_file, f"{table[u'output-file']}.txt", delimiter=u";"
+ )
+ with open(f"{table[u'output-file']}.txt", u'a') as txt_file:
+ txt_file.write(legend)
+
+ # Generate html table:
+ _tpc_generate_html_table(
+ header,
+ tbl_lst,
+ table[u'output-file'],
+ legend=legend,
+ title=table.get(u"title", u"")
+ )
+
+
+def table_perf_trending_dash(table, input_data):
+ """Generate the table(s) with algorithm:
+ table_perf_trending_dash
+ specified in the specification file.
+
+ :param table: Table to generate.
+ :param input_data: Data to process.
+ :type table: pandas.Series
+ :type input_data: InputData
+ """
+
+ logging.info(f" Generating the table {table.get(u'title', u'')} ...")
+
+ # Transform the data
+ logging.info(
+ f" Creating the data set for the {table.get(u'type', u'')} "
+ f"{table.get(u'title', u'')}."
+ )
+ data = input_data.filter_data(table, continue_on_error=True)
+
+ # Prepare the header of the tables
+ header = [
+ u"Test Case",
+ u"Trend [Mpps]",
+ u"Short-Term Change [%]",
+ u"Long-Term Change [%]",
+ u"Regressions [#]",
+ u"Progressions [#]"
+ ]
+ header_str = u",".join(header) + u"\n"
+
+ incl_tests = table.get(u"include-tests", u"MRR")
+
+ # Prepare data to the table:
+ tbl_dict = dict()
+ for job, builds in table[u"data"].items():
+ for build in builds:
+ for tst_name, tst_data in data[job][str(build)].items():
+ if tst_name.lower() in table.get(u"ignore-list", list()):
+ continue
+ if tbl_dict.get(tst_name, None) is None:
+ groups = re.search(REGEX_NIC, tst_data[u"parent"])
+ if not groups:
+ continue
+ nic = groups.group(0)
+ tbl_dict[tst_name] = {
+ u"name": f"{nic}-{tst_data[u'name']}",
+ u"data": OrderedDict()
+ }
+ try:
+ if incl_tests == u"MRR":
+ tbl_dict[tst_name][u"data"][str(build)] = \
+ tst_data[u"result"][u"receive-rate"]
+ elif incl_tests == u"NDR":
+ tbl_dict[tst_name][u"data"][str(build)] = \
+ tst_data[u"throughput"][u"NDR"][u"LOWER"]
+ elif incl_tests == u"PDR":
+ tbl_dict[tst_name][u"data"][str(build)] = \
+ tst_data[u"throughput"][u"PDR"][u"LOWER"]
+ except (TypeError, KeyError):
+ pass # No data in output.xml for this test
+
+ tbl_lst = list()
+ for tst_name in tbl_dict:
+ data_t = tbl_dict[tst_name][u"data"]
+ if len(data_t) < 2:
+ continue
+
+ classification_lst, avgs, _ = classify_anomalies(data_t)
+
+ win_size = min(len(data_t), table[u"window"])
+ long_win_size = min(len(data_t), table[u"long-trend-window"])