Report: Add gso tests
[csit.git] / resources / tools / presentation / input_data_parser.py
index 9e460f9..6742439 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2019 Cisco and/or its affiliates.
+# Copyright (c) 2020 Cisco and/or its affiliates.
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at:
@@ -216,6 +216,12 @@ class ExecutionChecker(ResultVisitor):
         r'PDR_LOWER:\s(\d+.\d+).*\n.*\n'
         r'PDR_UPPER:\s(\d+.\d+)'
     )
+    REGEX_NDRPDR_GBPS = re.compile(
+        r'NDR_LOWER:.*,\s(\d+.\d+).*\n.*\n'
+        r'NDR_UPPER:.*,\s(\d+.\d+).*\n'
+        r'PDR_LOWER:.*,\s(\d+.\d+).*\n.*\n'
+        r'PDR_UPPER:.*,\s(\d+.\d+)'
+    )
     REGEX_PERF_MSG_INFO = re.compile(
         r'NDR_LOWER:\s(\d+.\d+)\s.*\s(\d+.\d+)\s.*\n.*\n.*\n'
         r'PDR_LOWER:\s(\d+.\d+)\s.*\s(\d+.\d+)\s.*\n.*\n.*\n'
@@ -223,9 +229,17 @@ class ExecutionChecker(ResultVisitor):
         r'Latency at 50% PDR:.*\[\'(.*)\', \'(.*)\'\].*\n'
         r'Latency at 10% PDR:.*\[\'(.*)\', \'(.*)\'\].*\n'
     )
+    REGEX_CPS_MSG_INFO = re.compile(
+        r'NDR_LOWER:\s(\d+.\d+)\s.*\s.*\n.*\n.*\n'
+        r'PDR_LOWER:\s(\d+.\d+)\s.*\s.*\n.*\n.*'
+    )
+    REGEX_PPS_MSG_INFO = re.compile(
+        r'NDR_LOWER:\s(\d+.\d+)\s.*\s(\d+.\d+)\s.*\n.*\n.*\n'
+        r'PDR_LOWER:\s(\d+.\d+)\s.*\s(\d+.\d+)\s.*\n.*\n.*'
+    )
     REGEX_MRR_MSG_INFO = re.compile(r'.*\[(.*)\]')
 
-    # TODO: Remove when not needed
+    # Needed for CPS and PPS tests
     REGEX_NDRPDR_LAT_BASE = re.compile(
         r'LATENCY.*\[\'(.*)\', \'(.*)\'\]\s\n.*\n.*\n'
         r'LATENCY.*\[\'(.*)\', \'(.*)\'\]'
@@ -238,18 +252,7 @@ class ExecutionChecker(ResultVisitor):
         r'Latency.*\[\'(.*)\', \'(.*)\'\]\s\n'
         r'Latency.*\[\'(.*)\', \'(.*)\'\]'
     )
-    # TODO: Remove when not needed
-    REGEX_NDRPDR_LAT_LONG = re.compile(
-        r'LATENCY.*\[\'(.*)\', \'(.*)\'\]\s\n.*\n.*\n'
-        r'LATENCY.*\[\'(.*)\', \'(.*)\'\]\s\n.*\n'
-        r'Latency.*\[\'(.*)\', \'(.*)\'\]\s\n'
-        r'Latency.*\[\'(.*)\', \'(.*)\'\]\s\n'
-        r'Latency.*\[\'(.*)\', \'(.*)\'\]\s\n'
-        r'Latency.*\[\'(.*)\', \'(.*)\'\]\s\n'
-        r'Latency.*\[\'(.*)\', \'(.*)\'\]\s\n'
-        r'Latency.*\[\'(.*)\', \'(.*)\'\]\s\n'
-        r'Latency.*\[\'(.*)\', \'(.*)\'\]'
-    )
+
     REGEX_VERSION_VPP = re.compile(
         r"(return STDOUT Version:\s*|"
         r"VPP Version:\s*|VPP version:\s*)(.*)"
@@ -265,8 +268,7 @@ class ExecutionChecker(ResultVisitor):
         r'tx\s(\d*),\srx\s(\d*)'
     )
     REGEX_BMRR = re.compile(
-        r'Maximum Receive Rate trial results'
-        r' in packets per second: \[(.*)\]'
+        r'.*trial results.*: \[(.*)\]'
     )
     REGEX_RECONF_LOSS = re.compile(
         r'Packets lost due to reconfig: (\d*)'
@@ -386,12 +388,56 @@ class ExecutionChecker(ResultVisitor):
         except (AttributeError, IndexError, ValueError, KeyError):
             return u"Test Failed."
 
+    def _get_data_from_cps_test_msg(self, msg):
+        """Get info from message of NDRPDR CPS tests.
+
+        :param msg: Message to be processed.
+        :type msg: str
+        :returns: Processed message or "Test Failed." if a problem occurs.
+        :rtype: str
+        """
+
+        groups = re.search(self.REGEX_CPS_MSG_INFO, msg)
+        if not groups or groups.lastindex != 2:
+            return u"Test Failed."
+
+        try:
+            return (
+                f"1. {(float(groups.group(1)) / 1e6):5.2f}\n"
+                f"2. {(float(groups.group(2)) / 1e6):5.2f}"
+            )
+        except (AttributeError, IndexError, ValueError, KeyError):
+            return u"Test Failed."
+
+    def _get_data_from_pps_test_msg(self, msg):
+        """Get info from message of NDRPDR PPS tests.
+
+        :param msg: Message to be processed.
+        :type msg: str
+        :returns: Processed message or "Test Failed." if a problem occurs.
+        :rtype: str
+        """
+
+        groups = re.search(self.REGEX_PPS_MSG_INFO, msg)
+        if not groups or groups.lastindex != 4:
+            return u"Test Failed."
+
+        try:
+            return (
+                f"1. {(float(groups.group(1)) / 1e6):5.2f}      "
+                f"{float(groups.group(2)):5.2f}\n"
+                f"2. {(float(groups.group(3)) / 1e6):5.2f}      "
+                f"{float(groups.group(4)):5.2f}"
+            )
+        except (AttributeError, IndexError, ValueError, KeyError):
+            return u"Test Failed."
+
     def _get_data_from_perf_test_msg(self, msg):
         """Get info from message of NDRPDR performance tests.
 
         :param msg: Message to be processed.
         :type msg: str
-        :returns: Processed message or original message if a problem occurs.
+        :returns: Processed message or "Test Failed." if a problem occurs.
         :rtype: str
         """
 
@@ -524,8 +570,8 @@ class ExecutionChecker(ResultVisitor):
         """
 
         if msg.message.count(u"return STDOUT Version:") or \
-            msg.message.count(u"VPP Version:") or \
-            msg.message.count(u"VPP version:"):
+                msg.message.count(u"VPP Version:") or \
+                msg.message.count(u"VPP version:"):
             self._version = str(re.search(self.REGEX_VERSION_VPP, msg.message).
                                 group(2))
             self._data[u"metadata"][u"version"] = self._version
@@ -713,6 +759,35 @@ class ExecutionChecker(ResultVisitor):
 
         return throughput, status
 
+    def _get_ndrpdr_throughput_gbps(self, msg):
+        """Get NDR_LOWER, NDR_UPPER, PDR_LOWER and PDR_UPPER in Gbps from the
+        test message.
+
+        :param msg: The test message to be parsed.
+        :type msg: str
+        :returns: Parsed data as a dict and the status (PASS/FAIL).
+        :rtype: tuple(dict, str)
+        """
+
+        gbps = {
+            u"NDR": {u"LOWER": -1.0, u"UPPER": -1.0},
+            u"PDR": {u"LOWER": -1.0, u"UPPER": -1.0}
+        }
+        status = u"FAIL"
+        groups = re.search(self.REGEX_NDRPDR_GBPS, msg)
+
+        if groups is not None:
+            try:
+                gbps[u"NDR"][u"LOWER"] = float(groups.group(1))
+                gbps[u"NDR"][u"UPPER"] = float(groups.group(2))
+                gbps[u"PDR"][u"LOWER"] = float(groups.group(3))
+                gbps[u"PDR"][u"UPPER"] = float(groups.group(4))
+                status = u"PASS"
+            except (IndexError, ValueError):
+                pass
+
+        return gbps, status
+
     def _get_plr_throughput(self, msg):
         """Get PLRsearch lower bound and PLRsearch upper bound from the test
         message.
@@ -781,10 +856,7 @@ class ExecutionChecker(ResultVisitor):
             },
         }
 
-        # TODO: Rewrite when long and base are not needed
-        groups = re.search(self.REGEX_NDRPDR_LAT_LONG, msg)
-        if groups is None:
-            groups = re.search(self.REGEX_NDRPDR_LAT, msg)
+        groups = re.search(self.REGEX_NDRPDR_LAT, msg)
         if groups is None:
             groups = re.search(self.REGEX_NDRPDR_LAT_BASE, msg)
         if groups is None:
@@ -1003,11 +1075,16 @@ class ExecutionChecker(ResultVisitor):
             name = test.name.lower()
 
         # Remove TC number from the TC long name (backward compatibility):
-        self._test_id = re.sub(self.REGEX_TC_NUMBER, u"", longname)
+        self._test_id = re.sub(
+            self.REGEX_TC_NUMBER, u"", longname.replace(u"snat", u"nat")
+        )
         # Remove TC number from the TC name (not needed):
-        test_result[u"name"] = re.sub(self.REGEX_TC_NUMBER, "", name)
+        test_result[u"name"] = re.sub(
+            self.REGEX_TC_NUMBER, "", name.replace(u"snat", u"nat")
+        )
 
-        test_result[u"parent"] = test.parent.name.lower()
+        test_result[u"parent"] = test.parent.name.lower().\
+            replace(u"snat", u"nat")
         test_result[u"tags"] = tags
         test_result["doc"] = test.doc.\
             replace(u'"', u"'").\
@@ -1020,9 +1097,18 @@ class ExecutionChecker(ResultVisitor):
 
         if test.status == u"PASS":
             if u"NDRPDR" in tags:
-                test_result[u"msg"] = self._get_data_from_perf_test_msg(
-                    test.message).replace(u'\n', u' |br| ').\
-                    replace(u'\r', u'').replace(u'"', u"'")
+                if u"TCP_PPS" in tags or u"UDP_PPS" in tags:
+                    test_result[u"msg"] = self._get_data_from_pps_test_msg(
+                        test.message).replace(u'\n', u' |br| '). \
+                        replace(u'\r', u'').replace(u'"', u"'")
+                elif u"TCP_CPS" in tags or u"UDP_CPS" in tags:
+                    test_result[u"msg"] = self._get_data_from_cps_test_msg(
+                        test.message).replace(u'\n', u' |br| '). \
+                        replace(u'\r', u'').replace(u'"', u"'")
+                else:
+                    test_result[u"msg"] = self._get_data_from_perf_test_msg(
+                        test.message).replace(u'\n', u' |br| ').\
+                        replace(u'\r', u'').replace(u'"', u"'")
             elif u"MRR" in tags or u"FRMOBL" in tags or u"BMRR" in tags:
                 test_result[u"msg"] = self._get_data_from_mrr_test_msg(
                     test.message).replace(u'\n', u' |br| ').\
@@ -1067,24 +1153,19 @@ class ExecutionChecker(ResultVisitor):
                     return
 
         if test.status == u"PASS":
-            if u"NDRPDR" in tags:
-                test_result[u"type"] = u"NDRPDR"
+            if u"DEVICETEST" in tags:
+                test_result[u"type"] = u"DEVICETEST"
+            elif u"NDRPDR" in tags:
+                if u"TCP_CPS" in tags or u"UDP_CPS" in tags:
+                    test_result[u"type"] = u"CPS"
+                else:
+                    test_result[u"type"] = u"NDRPDR"
                 test_result[u"throughput"], test_result[u"status"] = \
                     self._get_ndrpdr_throughput(test.message)
+                test_result[u"gbps"], test_result[u"status"] = \
+                    self._get_ndrpdr_throughput_gbps(test.message)
                 test_result[u"latency"], test_result[u"status"] = \
                     self._get_ndrpdr_latency(test.message)
-            elif u"SOAK" in tags:
-                test_result[u"type"] = u"SOAK"
-                test_result[u"throughput"], test_result[u"status"] = \
-                    self._get_plr_throughput(test.message)
-            elif u"HOSTSTACK" in tags:
-                test_result[u"type"] = u"HOSTSTACK"
-                test_result[u"result"], test_result[u"status"] = \
-                    self._get_hoststack_data(test.message, tags)
-            elif u"TCP" in tags:
-                test_result[u"type"] = u"TCP"
-                groups = re.search(self.REGEX_TCP, test.message)
-                test_result[u"result"] = int(groups.group(2))
             elif u"MRR" in tags or u"FRMOBL" in tags or u"BMRR" in tags:
                 if u"MRR" in tags:
                     test_result[u"type"] = u"MRR"
@@ -1095,15 +1176,30 @@ class ExecutionChecker(ResultVisitor):
                 groups = re.search(self.REGEX_BMRR, test.message)
                 if groups is not None:
                     items_str = groups.group(1)
-                    items_float = [float(item.strip()) for item
-                                   in items_str.split(",")]
+                    items_float = [
+                        float(item.strip().replace(u"'", u""))
+                        for item in items_str.split(",")
+                    ]
                     # Use whole list in CSIT-1180.
                     stats = jumpavg.AvgStdevStats.for_runs(items_float)
                     test_result[u"result"][u"receive-rate"] = stats.avg
+                    test_result[u"result"][u"receive-stdev"] = stats.stdev
                 else:
                     groups = re.search(self.REGEX_MRR, test.message)
                     test_result[u"result"][u"receive-rate"] = \
                         float(groups.group(3)) / float(groups.group(1))
+            elif u"SOAK" in tags:
+                test_result[u"type"] = u"SOAK"
+                test_result[u"throughput"], test_result[u"status"] = \
+                    self._get_plr_throughput(test.message)
+            elif u"HOSTSTACK" in tags:
+                test_result[u"type"] = u"HOSTSTACK"
+                test_result[u"result"], test_result[u"status"] = \
+                    self._get_hoststack_data(test.message, tags)
+            elif u"TCP" in tags:
+                test_result[u"type"] = u"TCP"
+                groups = re.search(self.REGEX_TCP, test.message)
+                test_result[u"result"] = int(groups.group(2))
             elif u"RECONF" in tags:
                 test_result[u"type"] = u"RECONF"
                 test_result[u"result"] = None
@@ -1116,8 +1212,6 @@ class ExecutionChecker(ResultVisitor):
                     }
                 except (AttributeError, IndexError, ValueError, TypeError):
                     test_result[u"status"] = u"FAIL"
-            elif u"DEVICETEST" in tags:
-                test_result[u"type"] = u"DEVICETEST"
             else:
                 test_result[u"status"] = u"FAIL"
                 self._data[u"tests"][self._test_id] = test_result
@@ -1190,12 +1284,10 @@ class ExecutionChecker(ResultVisitor):
         :returns: Nothing.
         """
         if test_kw.name.count(u"Show Runtime On All Duts") or \
-                test_kw.name.count(u"Show Runtime Counters On All Duts"):
+                test_kw.name.count(u"Show Runtime Counters On All Duts") or \
+                test_kw.name.count(u"Vpp Show Runtime On All Duts"):
             self._msg_type = u"test-show-runtime"
             self._sh_run_counter += 1
-        elif test_kw.name.count(u"Install Dpdk Test On All Duts") and \
-                not self._version:
-            self._msg_type = u"dpdk-version"
         else:
             return
         test_kw.messages.visit(self)
@@ -1232,6 +1324,9 @@ class ExecutionChecker(ResultVisitor):
         if setup_kw.name.count(u"Show Vpp Version On All Duts") \
                 and not self._version:
             self._msg_type = u"vpp-version"
+        elif setup_kw.name.count(u"Install Dpdk Framework On All Duts") and \
+                not self._version:
+            self._msg_type = u"dpdk-version"
         elif setup_kw.name.count(u"Set Global Variable") \
                 and not self._timestamp:
             self._msg_type = u"timestamp"
@@ -1393,16 +1488,14 @@ class InputData:
         """
         return self.data[job][build][u"tests"]
 
-    def _parse_tests(self, job, build, log):
+    def _parse_tests(self, job, build):
         """Process data from robot output.xml file and return JSON structured
         data.
 
         :param job: The name of job which build output data will be processed.
         :param build: The build which output data will be processed.
-        :param log: List of log messages.
         :type job: str
         :type build: dict
-        :type log: list of tuples (severity, msg)
         :returns: JSON data structure.
         :rtype: dict
         """
@@ -1416,9 +1509,8 @@ class InputData:
             try:
                 result = ExecutionResult(data_file)
             except errors.DataError as err:
-                log.append(
-                    (u"ERROR", f"Error occurred while parsing output.xml: "
-                               f"{repr(err)}")
+                logging.error(
+                    f"Error occurred while parsing output.xml: {repr(err)}"
                 )
                 return None
         checker = ExecutionChecker(metadata, self._cfg.mapping,
@@ -1443,40 +1535,30 @@ class InputData:
         :type repeat: int
         """
 
-        logs = list()
-
-        logs.append(
-            (u"INFO", f"  Processing the job/build: {job}: {build[u'build']}")
-        )
+        logging.info(f"  Processing the job/build: {job}: {build[u'build']}")
 
         state = u"failed"
         success = False
         data = None
         do_repeat = repeat
         while do_repeat:
-            success = download_and_unzip_data_file(self._cfg, job, build, pid,
-                                                   logs)
+            success = download_and_unzip_data_file(self._cfg, job, build, pid)
             if success:
                 break
             do_repeat -= 1
         if not success:
-            logs.append(
-                (u"ERROR",
-                 f"It is not possible to download the input data file from the "
-                 f"job {job}, build {build[u'build']}, or it is damaged. "
-                 f"Skipped.")
+            logging.error(
+                f"It is not possible to download the input data file from the "
+                f"job {job}, build {build[u'build']}, or it is damaged. "
+                f"Skipped."
             )
         if success:
-            logs.append(
-                (u"INFO",
-                 f"    Processing data from the build {build[u'build']} ...")
-            )
-            data = self._parse_tests(job, build, logs)
+            logging.info(f"    Processing data from build {build[u'build']}")
+            data = self._parse_tests(job, build)
             if data is None:
-                logs.append(
-                    (u"ERROR",
-                     f"Input data file from the job {job}, build "
-                     f"{build[u'build']} is damaged. Skipped.")
+                logging.error(
+                    f"Input data file from the job {job}, build "
+                    f"{build[u'build']} is damaged. Skipped."
                 )
             else:
                 state = u"processed"
@@ -1484,13 +1566,13 @@ class InputData:
             try:
                 remove(build[u"file-name"])
             except OSError as err:
-                logs.append(
-                    ("ERROR", f"Cannot remove the file {build[u'file-name']}: "
-                              f"{repr(err)}")
+                logging.error(
+                    f"Cannot remove the file {build[u'file-name']}: {repr(err)}"
                 )
 
         # If the time-period is defined in the specification file, remove all
         # files which are outside the time period.
+        is_last = False
         timeperiod = self._cfg.input.get(u"time-period", None)
         if timeperiod and data:
             now = dt.utcnow()
@@ -1504,26 +1586,20 @@ class InputData:
                         # Remove the data and the file:
                         state = u"removed"
                         data = None
-                        logs.append(
-                            (u"INFO",
-                             f"    The build {job}/{build[u'build']} is "
-                             f"outdated, will be removed.")
+                        is_last = True
+                        logging.info(
+                            f"    The build {job}/{build[u'build']} is "
+                            f"outdated, will be removed."
                         )
-        logs.append((u"INFO", u"  Done."))
-
-        for level, line in logs:
-            if level == u"INFO":
-                logging.info(line)
-            elif level == u"ERROR":
-                logging.error(line)
-            elif level == u"DEBUG":
-                logging.debug(line)
-            elif level == u"CRITICAL":
-                logging.critical(line)
-            elif level == u"WARNING":
-                logging.warning(line)
-
-        return {u"data": data, u"state": state, u"job": job, u"build": build}
+        logging.info(u"  Done.")
+
+        return {
+            u"data": data,
+            u"state": state,
+            u"job": job,
+            u"build": build,
+            u"last": is_last
+        }
 
     def download_and_parse_data(self, repeat=1):
         """Download the input data files, parse input data from input files and
@@ -1540,6 +1616,8 @@ class InputData:
             for build in builds:
 
                 result = self._download_and_parse_build(job, build, repeat)
+                if result[u"last"]:
+                    break
                 build_nr = result[u"build"][u"build"]
 
                 if result[u"data"]:
@@ -1593,6 +1671,11 @@ class InputData:
         if not isfile(local_file):
             raise PresentationError(f"The file {local_file} does not exist.")
 
+        try:
+            build_nr = int(local_file.split(u"/")[-1].split(u".")[0])
+        except (IndexError, ValueError):
+            pass
+
         build = {
             u"build": build_nr,
             u"status": u"failed",
@@ -1603,7 +1686,7 @@ class InputData:
         self._cfg.add_build(job, build)
 
         logging.info(f"Processing {job}: {build_nr:2d}: {local_file}")
-        data = self._parse_tests(job, build, list())
+        data = self._parse_tests(job, build)
         if data is None:
             raise PresentationError(
                 f"Error occurred while parsing the file {local_file}"
@@ -1902,9 +1985,10 @@ class InputData:
                                                 data[job][str(build)][
                                                     test_id][param] = u"No Data"
                         except KeyError as err:
-                            logging.error(repr(err))
                             if continue_on_error:
+                                logging.debug(repr(err))
                                 continue
+                            logging.error(repr(err))
                             return None
             return data