+ # 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:
+ tbl_dict[tst_name][u"data"][str(build)] = \
+ tst_data[u"result"][u"receive-rate"]
+ 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"])
+
+ try:
+ max_long_avg = max(
+ [x for x in avgs[-long_win_size:-win_size]
+ if not isnan(x)])
+ except ValueError:
+ max_long_avg = nan
+ last_avg = avgs[-1]
+ avg_week_ago = avgs[max(-win_size, -len(avgs))]
+
+ if isnan(last_avg) or isnan(avg_week_ago) or avg_week_ago == 0.0:
+ rel_change_last = nan
+ else:
+ rel_change_last = round(
+ ((last_avg - avg_week_ago) / avg_week_ago) * 100, 2)
+
+ if isnan(max_long_avg) or isnan(last_avg) or max_long_avg == 0.0:
+ rel_change_long = nan
+ else:
+ rel_change_long = round(
+ ((last_avg - max_long_avg) / max_long_avg) * 100, 2)
+
+ if classification_lst:
+ if isnan(rel_change_last) and isnan(rel_change_long):
+ continue
+ if isnan(last_avg) or isnan(rel_change_last) or \
+ isnan(rel_change_long):
+ continue
+ tbl_lst.append(
+ [tbl_dict[tst_name][u"name"],
+ round(last_avg / 1000000, 2),
+ rel_change_last,
+ rel_change_long,
+ classification_lst[-win_size:].count(u"regression"),
+ classification_lst[-win_size:].count(u"progression")])
+
+ tbl_lst.sort(key=lambda rel: rel[0])
+
+ tbl_sorted = list()
+ for nrr in range(table[u"window"], -1, -1):
+ tbl_reg = [item for item in tbl_lst if item[4] == nrr]
+ for nrp in range(table[u"window"], -1, -1):
+ tbl_out = [item for item in tbl_reg if item[5] == nrp]
+ tbl_out.sort(key=lambda rel: rel[2])
+ tbl_sorted.extend(tbl_out)
+
+ file_name = f"{table[u'output-file']}{table[u'output-file-ext']}"
+
+ logging.info(f" Writing file: {file_name}")
+ with open(file_name, u"wt") as file_handler:
+ file_handler.write(header_str)
+ for test in tbl_sorted:
+ file_handler.write(u",".join([str(item) for item in test]) + u'\n')
+
+ logging.info(f" Writing file: {table[u'output-file']}.txt")
+ convert_csv_to_pretty_txt(file_name, f"{table[u'output-file']}.txt")
+
+
+def _generate_url(testbed, test_name):
+ """Generate URL to a trending plot from the name of the test case.
+
+ :param testbed: The testbed used for testing.
+ :param test_name: The name of the test case.
+ :type testbed: str
+ :type test_name: str
+ :returns: The URL to the plot with the trending data for the given test
+ case.
+ :rtype str
+ """
+
+ if u"x520" in test_name:
+ nic = u"x520"
+ elif u"x710" in test_name:
+ nic = u"x710"
+ elif u"xl710" in test_name:
+ nic = u"xl710"
+ elif u"xxv710" in test_name:
+ nic = u"xxv710"
+ elif u"vic1227" in test_name:
+ nic = u"vic1227"
+ elif u"vic1385" in test_name:
+ nic = u"vic1385"
+ elif u"x553" in test_name:
+ nic = u"x553"
+ elif u"cx556" in test_name or u"cx556a" in test_name:
+ nic = u"cx556a"
+ else:
+ nic = u""
+
+ if u"64b" in test_name:
+ frame_size = u"64b"
+ elif u"78b" in test_name:
+ frame_size = u"78b"
+ elif u"imix" in test_name:
+ frame_size = u"imix"
+ elif u"9000b" in test_name:
+ frame_size = u"9000b"
+ elif u"1518b" in test_name:
+ frame_size = u"1518b"
+ elif u"114b" in test_name:
+ frame_size = u"114b"
+ else:
+ frame_size = u""
+
+ if u"1t1c" in test_name or \
+ (u"-1c-" in test_name and
+ testbed in (u"3n-hsw", u"3n-tsh", u"2n-dnv", u"3n-dnv")):
+ cores = u"1t1c"
+ elif u"2t2c" in test_name or \
+ (u"-2c-" in test_name and
+ testbed in (u"3n-hsw", u"3n-tsh", u"2n-dnv", u"3n-dnv")):
+ cores = u"2t2c"
+ elif u"4t4c" in test_name or \
+ (u"-4c-" in test_name and
+ testbed in (u"3n-hsw", u"3n-tsh", u"2n-dnv", u"3n-dnv")):
+ cores = u"4t4c"
+ elif u"2t1c" in test_name or \
+ (u"-1c-" in test_name and
+ testbed in (u"2n-skx", u"3n-skx", u"2n-clx")):
+ cores = u"2t1c"
+ elif u"4t2c" in test_name or \
+ (u"-2c-" in test_name and
+ testbed in (u"2n-skx", u"3n-skx", u"2n-clx")):
+ cores = u"4t2c"
+ elif u"8t4c" in test_name or \
+ (u"-4c-" in test_name and
+ testbed in (u"2n-skx", u"3n-skx", u"2n-clx")):
+ cores = u"8t4c"
+ else:
+ cores = u""
+
+ if u"testpmd" in test_name:
+ driver = u"testpmd"
+ elif u"l3fwd" in test_name:
+ driver = u"l3fwd"
+ elif u"avf" in test_name:
+ driver = u"avf"
+ elif u"rdma" in test_name:
+ driver = u"rdma"
+ elif u"dnv" in testbed or u"tsh" in testbed:
+ driver = u"ixgbe"
+ else:
+ driver = u"i40e"
+
+ if u"acl" in test_name or \
+ u"macip" in test_name or \
+ u"nat" in test_name or \
+ u"policer" in test_name or \
+ u"cop" in test_name:
+ bsf = u"features"
+ elif u"scale" in test_name:
+ bsf = u"scale"
+ elif u"base" in test_name:
+ bsf = u"base"
+ else:
+ bsf = u"base"
+
+ if u"114b" in test_name and u"vhost" in test_name:
+ domain = u"vts"
+ elif u"testpmd" in test_name or u"l3fwd" in test_name:
+ domain = u"dpdk"
+ elif u"memif" in test_name:
+ domain = u"container_memif"
+ elif u"srv6" in test_name:
+ domain = u"srv6"
+ elif u"vhost" in test_name:
+ domain = u"vhost"
+ if u"vppl2xc" in test_name:
+ driver += u"-vpp"
+ else:
+ driver += u"-testpmd"
+ if u"lbvpplacp" in test_name:
+ bsf += u"-link-bonding"
+ elif u"ch" in test_name and u"vh" in test_name and u"vm" in test_name:
+ domain = u"nf_service_density_vnfc"
+ elif u"ch" in test_name and u"mif" in test_name and u"dcr" in test_name:
+ domain = u"nf_service_density_cnfc"
+ elif u"pl" in test_name and u"mif" in test_name and u"dcr" in test_name:
+ domain = u"nf_service_density_cnfp"
+ elif u"ipsec" in test_name:
+ domain = u"ipsec"
+ if u"sw" in test_name:
+ bsf += u"-sw"
+ elif u"hw" in test_name:
+ bsf += u"-hw"
+ elif u"ethip4vxlan" in test_name:
+ domain = u"ip4_tunnels"
+ elif u"ip4base" in test_name or u"ip4scale" in test_name:
+ domain = u"ip4"
+ elif u"ip6base" in test_name or u"ip6scale" in test_name:
+ domain = u"ip6"
+ elif u"l2xcbase" in test_name or \
+ u"l2xcscale" in test_name or \
+ u"l2bdbasemaclrn" in test_name or \
+ u"l2bdscale" in test_name or \
+ u"l2patch" in test_name:
+ domain = u"l2"
+ else:
+ domain = u""
+
+ file_name = u"-".join((domain, testbed, nic)) + u".html#"
+ anchor_name = u"-".join((frame_size, cores, bsf, driver))
+
+ return file_name + anchor_name
+
+
+def table_perf_trending_dash_html(table, input_data):
+ """Generate the table(s) with algorithm:
+ table_perf_trending_dash_html specified in the specification
+ file.
+
+ :param table: Table to generate.
+ :param input_data: Data to process.
+ :type table: dict
+ :type input_data: InputData
+ """
+
+ _ = input_data
+
+ if not table.get(u"testbed", None):
+ logging.error(
+ f"The testbed is not defined for the table "
+ f"{table.get(u'title', u'')}."
+ )
+ return
+
+ logging.info(f" Generating the table {table.get(u'title', u'')} ...")
+
+ try:
+ with open(table[u"input-file"], u'rt') as csv_file:
+ csv_lst = list(csv.reader(csv_file, delimiter=u',', quotechar=u'"'))
+ except KeyError:
+ logging.warning(u"The input file is not defined.")
+ return
+ except csv.Error as err:
+ logging.warning(
+ f"Not possible to process the file {table[u'input-file']}.\n"
+ f"{repr(err)}"
+ )
+ return
+
+ # Table:
+ dashboard = ET.Element(u"table", attrib=dict(width=u"100%", border=u'0'))
+
+ # Table header:
+ trow = ET.SubElement(dashboard, u"tr", attrib=dict(bgcolor=u"#7eade7"))
+ for idx, item in enumerate(csv_lst[0]):
+ alignment = u"left" if idx == 0 else u"center"
+ thead = ET.SubElement(trow, u"th", attrib=dict(align=alignment))
+ thead.text = item
+
+ # Rows:
+ colors = {
+ u"regression": (
+ u"#ffcccc",
+ u"#ff9999"
+ ),
+ u"progression": (
+ u"#c6ecc6",
+ u"#9fdf9f"
+ ),
+ u"normal": (
+ u"#e9f1fb",
+ u"#d4e4f7"
+ )
+ }
+ for r_idx, row in enumerate(csv_lst[1:]):
+ if int(row[4]):
+ color = u"regression"
+ elif int(row[5]):
+ color = u"progression"
+ else:
+ color = u"normal"
+ trow = ET.SubElement(
+ dashboard, u"tr", attrib=dict(bgcolor=colors[color][r_idx % 2])
+ )
+
+ # Columns:
+ for c_idx, item in enumerate(row):
+ tdata = ET.SubElement(
+ trow,
+ u"td",
+ attrib=dict(align=u"left" if c_idx == 0 else u"center")
+ )
+ # Name:
+ if c_idx == 0:
+ ref = ET.SubElement(
+ tdata,
+ u"a",
+ attrib=dict(
+ href=f"../trending/"
+ f"{_generate_url(table.get(u'testbed', ''), item)}"
+ )
+ )
+ ref.text = item
+ else:
+ tdata.text = item
+ try:
+ with open(table[u"output-file"], u'w') as html_file:
+ logging.info(f" Writing file: {table[u'output-file']}")
+ html_file.write(u".. raw:: html\n\n\t")
+ html_file.write(str(ET.tostring(dashboard, encoding=u"unicode")))
+ html_file.write(u"\n\t<p><br><br></p>\n")
+ except KeyError:
+ logging.warning(u"The output file is not defined.")
+ return
+
+
+def table_last_failed_tests(table, input_data):
+ """Generate the table(s) with algorithm: table_last_failed_tests
+ 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)
+
+ if data is None or data.empty:
+ logging.warning(
+ f" No data for the {table.get(u'type', u'')} "
+ f"{table.get(u'title', u'')}."
+ )
+ return
+
+ tbl_list = list()
+ for job, builds in table[u"data"].items():
+ for build in builds:
+ build = str(build)
+ try:
+ version = input_data.metadata(job, build).get(u"version", u"")
+ except KeyError:
+ logging.error(f"Data for {job}: {build} is not present.")
+ return
+ tbl_list.append(build)
+ tbl_list.append(version)
+ failed_tests = list()
+ passed = 0
+ failed = 0
+ for tst_data in data[job][build].values:
+ if tst_data[u"status"] != u"FAIL":
+ passed += 1
+ continue
+ failed += 1
+ groups = re.search(REGEX_NIC, tst_data[u"parent"])
+ if not groups:
+ continue
+ nic = groups.group(0)
+ failed_tests.append(f"{nic}-{tst_data[u'name']}")
+ tbl_list.append(str(passed))
+ tbl_list.append(str(failed))
+ tbl_list.extend(failed_tests)
+
+ file_name = f"{table[u'output-file']}{table[u'output-file-ext']}"
+ logging.info(f" Writing file: {file_name}")
+ with open(file_name, u"wt") as file_handler:
+ for test in tbl_list:
+ file_handler.write(test + u'\n')
+
+
+def table_failed_tests(table, input_data):
+ """Generate the table(s) with algorithm: table_failed_tests
+ specified in the specification file.
+
+ :param table: Table to generate.
+ :param input_data: Data to process.
+ :type table: pandas.Series
+ :type input_data: InputData