Trending: Remove unneeded tests from Dashboard
[csit.git] / resources / tools / presentation / generator_tables.py
index 6d81f43..3ad1e3c 100644 (file)
 
 import logging
 import csv
+import re
 
 from string import replace
 from collections import OrderedDict
 from numpy import nan, isnan
 from xml.etree import ElementTree as ET
+from datetime import datetime as dt
+from datetime import timedelta
 
-from errors import PresentationError
 from utils import mean, stdev, relative_change, classify_anomalies, \
     convert_csv_to_pretty_txt
 
 
+REGEX_NIC = re.compile(r'\d*ge\dp\d\D*\d*')
+
+
 def generate_tables(spec, data):
     """Generate all tables specified in the specification file.
 
@@ -180,179 +185,6 @@ def table_merged_details(table, input_data):
     logging.info("  Done.")
 
 
-def table_performance_improvements(table, input_data):
-    """Generate the table(s) with algorithm: table_performance_improvements
-    specified in the specification file.
-
-    # FIXME: Not used now.
-
-    :param table: Table to generate.
-    :param input_data: Data to process.
-    :type table: pandas.Series
-    :type input_data: InputData
-    """
-
-    def _write_line_to_file(file_handler, data):
-        """Write a line to the .csv file.
-
-        :param file_handler: File handler for the csv file. It must be open for
-         writing text.
-        :param data: Item to be written to the file.
-        :type file_handler: BinaryIO
-        :type data: list
-        """
-
-        line_lst = list()
-        for item in data:
-            if isinstance(item["data"], str):
-                # Remove -?drdisc from the end
-                if item["data"].endswith("drdisc"):
-                    item["data"] = item["data"][:-8]
-                line_lst.append(item["data"])
-            elif isinstance(item["data"], float):
-                line_lst.append("{:.1f}".format(item["data"]))
-            elif item["data"] is None:
-                line_lst.append("")
-        file_handler.write(",".join(line_lst) + "\n")
-
-    logging.info("  Generating the table {0} ...".
-                 format(table.get("title", "")))
-
-    # Read the template
-    file_name = table.get("template", None)
-    if file_name:
-        try:
-            tmpl = _read_csv_template(file_name)
-        except PresentationError:
-            logging.error("  The template '{0}' does not exist. Skipping the "
-                          "table.".format(file_name))
-            return None
-    else:
-        logging.error("The template is not defined. Skipping the table.")
-        return None
-
-    # Transform the data
-    logging.info("    Creating the data set for the {0} '{1}'.".
-                 format(table.get("type", ""), table.get("title", "")))
-    data = input_data.filter_data(table)
-
-    # Prepare the header of the tables
-    header = list()
-    for column in table["columns"]:
-        header.append(column["title"])
-
-    # Generate the data for the table according to the model in the table
-    # specification
-    tbl_lst = list()
-    for tmpl_item in tmpl:
-        tbl_item = list()
-        for column in table["columns"]:
-            cmd = column["data"].split(" ")[0]
-            args = column["data"].split(" ")[1:]
-            if cmd == "template":
-                try:
-                    val = float(tmpl_item[int(args[0])])
-                except ValueError:
-                    val = tmpl_item[int(args[0])]
-                tbl_item.append({"data": val})
-            elif cmd == "data":
-                jobs = args[0:-1]
-                operation = args[-1]
-                data_lst = list()
-                for job in jobs:
-                    for build in data[job]:
-                        try:
-                            data_lst.append(float(build[tmpl_item[0]]
-                                                  ["throughput"]["value"]))
-                        except (KeyError, TypeError):
-                            # No data, ignore
-                            continue
-                if data_lst:
-                    tbl_item.append({"data": (eval(operation)(data_lst)) /
-                                             1000000})
-                else:
-                    tbl_item.append({"data": None})
-            elif cmd == "operation":
-                operation = args[0]
-                try:
-                    nr1 = float(tbl_item[int(args[1])]["data"])
-                    nr2 = float(tbl_item[int(args[2])]["data"])
-                    if nr1 and nr2:
-                        tbl_item.append({"data": eval(operation)(nr1, nr2)})
-                    else:
-                        tbl_item.append({"data": None})
-                except (IndexError, ValueError, TypeError):
-                    logging.error("No data for {0}".format(tbl_item[0]["data"]))
-                    tbl_item.append({"data": None})
-                    continue
-            else:
-                logging.error("Not supported command {0}. Skipping the table.".
-                              format(cmd))
-                return None
-        tbl_lst.append(tbl_item)
-
-    # Sort the table according to the relative change
-    tbl_lst.sort(key=lambda rel: rel[-1]["data"], reverse=True)
-
-    # Create the tables and write them to the files
-    file_names = [
-        "{0}_ndr_top{1}".format(table["output-file"], table["output-file-ext"]),
-        "{0}_pdr_top{1}".format(table["output-file"], table["output-file-ext"]),
-        "{0}_ndr_low{1}".format(table["output-file"], table["output-file-ext"]),
-        "{0}_pdr_low{1}".format(table["output-file"], table["output-file-ext"])
-    ]
-
-    for file_name in file_names:
-        logging.info("    Writing the file '{0}'".format(file_name))
-        with open(file_name, "w") as file_handler:
-            file_handler.write(",".join(header) + "\n")
-            for item in tbl_lst:
-                if isinstance(item[-1]["data"], float):
-                    rel_change = round(item[-1]["data"], 1)
-                else:
-                    rel_change = item[-1]["data"]
-                if "ndr_top" in file_name \
-                        and "ndr" in item[0]["data"] \
-                        and rel_change >= 10.0:
-                    _write_line_to_file(file_handler, item)
-                elif "pdr_top" in file_name \
-                        and "pdr" in item[0]["data"] \
-                        and rel_change >= 10.0:
-                    _write_line_to_file(file_handler, item)
-                elif "ndr_low" in file_name \
-                        and "ndr" in item[0]["data"] \
-                        and rel_change < 10.0:
-                    _write_line_to_file(file_handler, item)
-                elif "pdr_low" in file_name \
-                        and "pdr" in item[0]["data"] \
-                        and rel_change < 10.0:
-                    _write_line_to_file(file_handler, item)
-
-    logging.info("  Done.")
-
-
-def _read_csv_template(file_name):
-    """Read the template from a .csv file.
-
-    # FIXME: Not used now.
-
-    :param file_name: Name / full path / relative path of the file to read.
-    :type file_name: str
-    :returns: Data from the template as list (lines) of lists (items on line).
-    :rtype: list
-    :raises: PresentationError if it is not possible to read the file.
-    """
-
-    try:
-        with open(file_name, 'r') as csv_file:
-            tmpl_data = list()
-            for line in csv_file:
-                tmpl_data.append(line[:-1].split(","))
-        return tmpl_data
-    except IOError as err:
-        raise PresentationError(str(err), level="ERROR")
-
-
 def table_performance_comparison(table, input_data):
     """Generate the table(s) with algorithm: table_performance_comparison
     specified in the specification file.
@@ -406,11 +238,22 @@ def table_performance_comparison(table, input_data):
                 tst_name_mod = tst_name.replace("-ndrpdrdisc", "").\
                     replace("-ndrpdr", "").replace("-pdrdisc", "").\
                     replace("-ndrdisc", "").replace("-pdr", "").\
-                    replace("-ndr", "")
+                    replace("-ndr", "").\
+                    replace("1t1c", "1c").replace("2t1c", "1c").\
+                    replace("2t2c", "2c").replace("4t2c", "2c").\
+                    replace("4t4c", "4c").replace("8t4c", "4c")
+                if "across topologies" in table["title"].lower():
+                    tst_name_mod = tst_name_mod.replace("2n1l-", "")
                 if tbl_dict.get(tst_name_mod, None) is None:
                     name = "{0}-{1}".format(tst_data["parent"].split("-")[0],
                                             "-".join(tst_data["name"].
                                                      split("-")[:-1]))
+                    if "across testbeds" in table["title"].lower() or \
+                            "across topologies" in table["title"].lower():
+                        name = name.\
+                            replace("1t1c", "1c").replace("2t1c", "1c").\
+                            replace("2t2c", "2c").replace("4t2c", "2c").\
+                            replace("4t4c", "4c").replace("8t4c", "4c")
                     tbl_dict[tst_name_mod] = {"name": name,
                                               "ref-data": list(),
                                               "cmp-data": list()}
@@ -444,7 +287,12 @@ def table_performance_comparison(table, input_data):
                 tst_name_mod = tst_name.replace("-ndrpdrdisc", ""). \
                     replace("-ndrpdr", "").replace("-pdrdisc", ""). \
                     replace("-ndrdisc", "").replace("-pdr", ""). \
-                    replace("-ndr", "")
+                    replace("-ndr", "").\
+                    replace("1t1c", "1c").replace("2t1c", "1c").\
+                    replace("2t2c", "2c").replace("4t2c", "2c").\
+                    replace("4t4c", "4c").replace("8t4c", "4c")
+                if "across topologies" in table["title"].lower():
+                    tst_name_mod = tst_name_mod.replace("2n1l-", "")
                 try:
                     # TODO: Re-work when NDRPDRDISC tests are not used
                     if table["include-tests"] == "MRR":
@@ -478,7 +326,12 @@ def table_performance_comparison(table, input_data):
                         tst_name_mod = tst_name.replace("-ndrpdrdisc", ""). \
                             replace("-ndrpdr", "").replace("-pdrdisc", ""). \
                             replace("-ndrdisc", "").replace("-pdr", ""). \
-                            replace("-ndr", "")
+                            replace("-ndr", "").\
+                            replace("1t1c", "1c").replace("2t1c", "1c").\
+                            replace("2t2c", "2c").replace("4t2c", "2c").\
+                            replace("4t4c", "4c").replace("8t4c", "4c")
+                        if "across topologies" in table["title"].lower():
+                            tst_name_mod = tst_name_mod.replace("2n1l-", "")
                         if tbl_dict.get(tst_name_mod, None) is None:
                             continue
                         if tbl_dict[tst_name_mod].get("history", None) is None:
@@ -596,10 +449,13 @@ def table_performance_trending_dashboard(table, input_data):
                 if tst_name.lower() in table["ignore-list"]:
                     continue
                 if tbl_dict.get(tst_name, None) is None:
-                    name = "{0}-{1}".format(tst_data["parent"].split("-")[0],
-                                            tst_data["name"])
-                    tbl_dict[tst_name] = {"name": name,
-                                          "data": OrderedDict()}
+                    groups = re.search(REGEX_NIC, tst_data["parent"])
+                    if not groups:
+                        continue
+                    nic = groups.group(0)
+                    tbl_dict[tst_name] = {
+                        "name": "{0}-{1}".format(nic, tst_data["name"]),
+                        "data": OrderedDict()}
                 try:
                     tbl_dict[tst_name]["data"][str(build)] = \
                         tst_data["result"]["receive-rate"]
@@ -641,12 +497,15 @@ def table_performance_trending_dashboard(table, input_data):
         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]["name"],
-                 '-' if isnan(last_avg) else
                  round(last_avg / 1000000, 2),
-                 '-' if isnan(rel_change_last) else rel_change_last,
-                 '-' if isnan(rel_change_long) else rel_change_long,
+                 rel_change_last,
+                 rel_change_long,
                  classification_lst[-win_size:].count("regression"),
                  classification_lst[-win_size:].count("progression")])
 
@@ -673,12 +532,14 @@ def table_performance_trending_dashboard(table, input_data):
     convert_csv_to_pretty_txt(file_name, txt_file_name)
 
 
-def _generate_url(base, test_name):
+def _generate_url(base, testbed, test_name):
     """Generate URL to a trending plot from the name of the test case.
 
     :param base: The base part of URL common to all test cases.
+    :param testbed: The testbed used for testing.
     :param test_name: The name of the test case.
     :type base: str
+    :type testbed: str
     :type test_name: str
     :returns: The URL to the plot with the trending data for the given test
         case.
@@ -687,65 +548,105 @@ def _generate_url(base, test_name):
 
     url = base
     file_name = ""
-    anchor = "#"
+    anchor = ".html#"
     feature = ""
 
     if "lbdpdk" in test_name or "lbvpp" in test_name:
-        file_name = "link_bonding.html"
+        file_name = "link_bonding"
+
+    elif "114b" in test_name and "vhost" in test_name:
+        file_name = "vts"
 
     elif "testpmd" in test_name or "l3fwd" in test_name:
-        file_name = "dpdk.html"
+        file_name = "dpdk"
 
     elif "memif" in test_name:
-        file_name = "container_memif.html"
+        file_name = "container_memif"
+        feature = "-base"
 
     elif "srv6" in test_name:
-        file_name = "srv6.html"
+        file_name = "srv6"
 
     elif "vhost" in test_name:
         if "l2xcbase" in test_name or "l2bdbasemaclrn" in test_name:
-            file_name = "vm_vhost_l2.html"
+            file_name = "vm_vhost_l2"
+            if "114b" in test_name:
+                feature = ""
+            elif "l2xcbase" in test_name:
+                feature = "-base-l2xc"
+            elif "l2bdbasemaclrn" in test_name:
+                feature = "-base-l2bd"
+            else:
+                feature = "-base"
         elif "ip4base" in test_name:
-            file_name = "vm_vhost_ip4.html"
+            file_name = "vm_vhost_ip4"
+            feature = "-base"
 
     elif "ipsec" in test_name:
-        file_name = "ipsec.html"
+        file_name = "ipsec"
+        feature = "-base-scale"
 
     elif "ethip4lispip" in test_name or "ethip4vxlan" in test_name:
-        file_name = "ip4_tunnels.html"
+        file_name = "ip4_tunnels"
+        feature = "-base"
 
     elif "ip4base" in test_name or "ip4scale" in test_name:
-        file_name = "ip4.html"
-        if "iacl" in test_name or "snat" in test_name or "cop" in test_name:
+        file_name = "ip4"
+        if "xl710" in test_name:
+            feature = "-base-scale-features"
+        elif "iacl" in test_name:
+            feature = "-features-iacl"
+        elif "oacl" in test_name:
+            feature = "-features-oacl"
+        elif "snat" in test_name or "cop" in test_name:
             feature = "-features"
+        else:
+            feature = "-base-scale"
 
     elif "ip6base" in test_name or "ip6scale" in test_name:
-        file_name = "ip6.html"
+        file_name = "ip6"
+        feature = "-base-scale"
 
     elif "l2xcbase" in test_name or "l2xcscale" in test_name \
             or "l2bdbasemaclrn" in test_name or "l2bdscale" in test_name \
             or "l2dbbasemaclrn" in test_name or "l2dbscale" in test_name:
-        file_name = "l2.html"
-        if "iacl" in test_name:
-            feature = "-features"
+        file_name = "l2"
+        if "macip" in test_name:
+            feature = "-features-macip"
+        elif "iacl" in test_name:
+            feature = "-features-iacl"
+        elif "oacl" in test_name:
+            feature = "-features-oacl"
+        else:
+            feature = "-base-scale"
 
     if "x520" in test_name:
-        anchor += "x520-"
+        nic = "x520-"
     elif "x710" in test_name:
-        anchor += "x710-"
+        nic = "x710-"
     elif "xl710" in test_name:
-        anchor += "xl710-"
+        nic = "xl710-"
+    elif "xxv710" in test_name:
+        nic = "xxv710-"
+    else:
+        nic = ""
+    anchor += nic
 
     if "64b" in test_name:
-        anchor += "64b-"
+        framesize = "64b"
     elif "78b" in test_name:
-        anchor += "78b-"
+        framesize = "78b"
     elif "imix" in test_name:
-        anchor += "imix-"
+        framesize = "imix"
     elif "9000b" in test_name:
-        anchor += "9000b-"
-    elif "1518" in test_name:
-        anchor += "1518b-"
+        framesize = "9000b"
+    elif "1518b" in test_name:
+        framesize = "1518b"
+    elif "114b" in test_name:
+        framesize = "114b"
+    else:
+        framesize = ""
+    anchor += framesize + '-'
 
     if "1t1c" in test_name:
         anchor += "1t1c"
@@ -753,8 +654,15 @@ def _generate_url(base, test_name):
         anchor += "2t2c"
     elif "4t4c" in test_name:
         anchor += "4t4c"
+    elif "2t1c" in test_name:
+        anchor += "2t1c"
+    elif "4t2c" in test_name:
+        anchor += "4t2c"
+    elif "8t4c" in test_name:
+        anchor += "8t4c"
 
-    return url + file_name + anchor + feature
+    return url + file_name + '-' + testbed + '-' + nic + framesize + feature + \
+           anchor + feature
 
 
 def table_performance_trending_dashboard_html(table, input_data):
@@ -764,10 +672,16 @@ def table_performance_trending_dashboard_html(table, input_data):
 
     :param table: Table to generate.
     :param input_data: Data to process.
-    :type table: pandas.Series
+    :type table: dict
     :type input_data: InputData
     """
 
+    testbed = table.get("testbed", None)
+    if testbed is None:
+        logging.error("The testbed is not defined for the table '{0}'.".
+                      format(table.get("title", "")))
+        return
+
     logging.info("  Generating the table {0} ...".
                  format(table.get("title", "")))
 
@@ -813,7 +727,7 @@ def table_performance_trending_dashboard_html(table, input_data):
             td = ET.SubElement(tr, "td", attrib=dict(align=alignment))
             # Name:
             if c_idx == 0:
-                url = _generate_url("../trending/", item)
+                url = _generate_url("../trending/", testbed, item)
                 ref = ET.SubElement(td, "a", attrib=dict(href=url))
                 ref.text = item
             else:
@@ -856,6 +770,10 @@ def table_failed_tests(table, input_data):
 
     # Generate the data for the table according to the model in the table
     # specification
+
+    now = dt.utcnow()
+    timeperiod = timedelta(int(table.get("window", 7)))
+
     tbl_dict = dict()
     for job, builds in table["data"].items():
         for build in builds:
@@ -864,30 +782,41 @@ def table_failed_tests(table, input_data):
                 if tst_name.lower() in table["ignore-list"]:
                     continue
                 if tbl_dict.get(tst_name, None) is None:
-                    name = "{0}-{1}".format(tst_data["parent"].split("-")[0],
-                                            tst_data["name"])
-                    tbl_dict[tst_name] = {"name": name,
-                                          "data": OrderedDict()}
+                    groups = re.search(REGEX_NIC, tst_data["parent"])
+                    if not groups:
+                        continue
+                    nic = groups.group(0)
+                    tbl_dict[tst_name] = {
+                        "name": "{0}-{1}".format(nic, tst_data["name"]),
+                        "data": OrderedDict()}
                 try:
-                    tbl_dict[tst_name]["data"][build] = (
-                        tst_data["status"],
-                        input_data.metadata(job, build).get("generated", ""),
-                        input_data.metadata(job, build).get("version", ""),
-                        build)
-                except (TypeError, KeyError):
-                    pass  # No data in output.xml for this test
-
+                    generated = input_data.metadata(job, build).\
+                        get("generated", "")
+                    if not generated:
+                        continue
+                    then = dt.strptime(generated, "%Y%m%d %H:%M")
+                    if (now - then) <= timeperiod:
+                        tbl_dict[tst_name]["data"][build] = (
+                            tst_data["status"],
+                            generated,
+                            input_data.metadata(job, build).get("version", ""),
+                            build)
+                except (TypeError, KeyError) as err:
+                    logging.warning("tst_name: {} - err: {}".
+                                    format(tst_name, repr(err)))
+
+    max_fails = 0
     tbl_lst = list()
     for tst_data in tbl_dict.values():
-        win_size = min(len(tst_data["data"]), table["window"])
         fails_nr = 0
-        for val in tst_data["data"].values()[-win_size:]:
+        for val in tst_data["data"].values():
             if val[0] == "FAIL":
                 fails_nr += 1
                 fails_last_date = val[1]
                 fails_last_vpp = val[2]
                 fails_last_csit = val[3]
         if fails_nr:
+            max_fails = fails_nr if fails_nr > max_fails else max_fails
             tbl_lst.append([tst_data["name"],
                             fails_nr,
                             fails_last_date,
@@ -896,7 +825,7 @@ def table_failed_tests(table, input_data):
 
     tbl_lst.sort(key=lambda rel: rel[2], reverse=True)
     tbl_sorted = list()
-    for nrf in range(table["window"], -1, -1):
+    for nrf in range(max_fails, -1, -1):
         tbl_fails = [item for item in tbl_lst if item[1] == nrf]
         tbl_sorted.extend(tbl_fails)
     file_name = "{0}{1}".format(table["output-file"], table["output-file-ext"])
@@ -922,6 +851,12 @@ def table_failed_tests_html(table, input_data):
     :type input_data: InputData
     """
 
+    testbed = table.get("testbed", None)
+    if testbed is None:
+        logging.error("The testbed is not defined for the table '{0}'.".
+                      format(table.get("title", "")))
+        return
+
     logging.info("  Generating the table {0} ...".
                  format(table.get("title", "")))
 
@@ -959,7 +894,7 @@ def table_failed_tests_html(table, input_data):
             td = ET.SubElement(tr, "td", attrib=dict(align=alignment))
             # Name:
             if c_idx == 0:
-                url = _generate_url("../trending/", item)
+                url = _generate_url("../trending/", testbed, item)
                 ref = ET.SubElement(td, "a", attrib=dict(href=url))
                 ref.text = item
             else: