Report: Fix title levels in auto generated files
[csit.git] / resources / tools / presentation / input_data_parser.py
index ddfd96c..c43d460 100644 (file)
@@ -29,7 +29,10 @@ from os import remove
 from datetime import datetime as dt
 from datetime import timedelta
 from json import loads
 from datetime import datetime as dt
 from datetime import timedelta
 from json import loads
+from json.decoder import JSONDecodeError
 
 
+import hdrh.histogram
+import hdrh.codec
 import prettytable
 import pandas as pd
 
 import prettytable
 import pandas as pd
 
@@ -212,13 +215,14 @@ class ExecutionChecker(ResultVisitor):
         r'PDR_UPPER:\s(\d+.\d+)'
     )
     REGEX_PERF_MSG_INFO = re.compile(
         r'PDR_UPPER:\s(\d+.\d+)'
     )
     REGEX_PERF_MSG_INFO = re.compile(
-        r'NDR_LOWER:\s(\d+.\d+)\s([a-zA-Z]*).*\s(\d+.\d+)\s([a-zA-Z]*).*\n'
-        r'LATENCY.*\[\'(.*)\', \'(.*)\'\].*\n'
-        r'NDR_UPPER:\s(\d+.\d+)\s([a-zA-Z]*).*\s(\d+.\d+)\s([a-zA-Z]*).*\n'
-        r'PDR_LOWER:\s(\d+.\d+)\s([a-zA-Z]*).*\s(\d+.\d+)\s([a-zA-Z]*).*\n'
-        r'LATENCY.*\[\'(.*)\', \'(.*)\'\].*\n'
-        r'PDR_UPPER:\s(\d+.\d+)\s([a-zA-Z]*).*\s(\d+.\d+)\s([a-zA-Z]*)'
+        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'
+        r'Latency at 90% PDR:.*\[\'(.*)\', \'(.*)\'\].*\n'
+        r'Latency at 50% PDR:.*\[\'(.*)\', \'(.*)\'\].*\n'
+        r'Latency at 10% PDR:.*\[\'(.*)\', \'(.*)\'\].*\n'
     )
     )
+    REGEX_MRR_MSG_INFO = re.compile(r'.*\[(.*)\]')
+
     # TODO: Remove when not needed
     REGEX_NDRPDR_LAT_BASE = re.compile(
         r'LATENCY.*\[\'(.*)\', \'(.*)\'\]\s\n.*\n.*\n'
     # TODO: Remove when not needed
     REGEX_NDRPDR_LAT_BASE = re.compile(
         r'LATENCY.*\[\'(.*)\', \'(.*)\'\]\s\n.*\n.*\n'
@@ -315,6 +319,8 @@ class ExecutionChecker(ResultVisitor):
         # 2 - PAPI History of DUT2
         self._conf_history_lookup_nr = 0
 
         # 2 - PAPI History of DUT2
         self._conf_history_lookup_nr = 0
 
+        self._sh_run_counter = 0
+
         # Test ID of currently processed test- the lowercase full path to the
         # test
         self._test_id = None
         # Test ID of currently processed test- the lowercase full path to the
         # test
         self._test_id = None
@@ -352,15 +358,34 @@ class ExecutionChecker(ResultVisitor):
         """
         return self._data
 
         """
         return self._data
 
+    def _get_data_from_mrr_test_msg(self, msg):
+        """Get info from message of MRR performance tests.
+
+        :param msg: Message to be processed.
+        :type msg: str
+        :returns: Processed message or original message if a problem occurs.
+        :rtype: str
+        """
+
+        groups = re.search(self.REGEX_MRR_MSG_INFO, msg)
+        if not groups or groups.lastindex != 1:
+            return u"Test Failed."
+
+        try:
+            data = groups.group(1).split(u", ")
+        except (AttributeError, IndexError, ValueError, KeyError):
+            return u"Test Failed."
+
+        out_str = u"["
+        try:
+            for item in data:
+                out_str += f"{(float(item) / 1e6):.2f}, "
+            return out_str[:-2] + u"]"
+        except (AttributeError, IndexError, ValueError, KeyError):
+            return u"Test Failed."
+
     def _get_data_from_perf_test_msg(self, msg):
     def _get_data_from_perf_test_msg(self, msg):
-        """Get
-            - NDR_LOWER
-            - LATENCY
-            - NDR_UPPER
-            - PDR_LOWER
-            - LATENCY
-            - PDR_UPPER
-        from message of NDRPDR performance tests.
+        """Get info from message of NDRPDR performance tests.
 
         :param msg: Message to be processed.
         :type msg: str
 
         :param msg: Message to be processed.
         :type msg: str
@@ -369,71 +394,103 @@ class ExecutionChecker(ResultVisitor):
         """
 
         groups = re.search(self.REGEX_PERF_MSG_INFO, msg)
         """
 
         groups = re.search(self.REGEX_PERF_MSG_INFO, msg)
-        if not groups or groups.lastindex != 20:
-            return msg
+        if not groups or groups.lastindex != 10:
+            return u"Test Failed."
 
         try:
             data = {
                 u"ndr_low": float(groups.group(1)),
 
         try:
             data = {
                 u"ndr_low": float(groups.group(1)),
-                u"ndr_low_unit": groups.group(2),
-                u"ndr_low_b": float(groups.group(3)),
-                u"ndr_low_b_unit": groups.group(4),
-                u"ndr_lat_1": groups.group(5),
-                u"ndr_lat_2": groups.group(6),
-                u"ndr_up": float(groups.group(7)),
-                u"ndr_up_unit": groups.group(8),
-                u"ndr_up_b": float(groups.group(9)),
-                u"ndr_up_b_unit": groups.group(10),
-                u"pdr_low": float(groups.group(11)),
-                u"pdr_low_unit": groups.group(12),
-                u"pdr_low_b": float(groups.group(13)),
-                u"pdr_low_b_unit": groups.group(14),
-                u"pdr_lat_1": groups.group(15),
-                u"pdr_lat_2": groups.group(16),
-                u"pdr_up": float(groups.group(17)),
-                u"pdr_up_unit": groups.group(18),
-                u"pdr_up_b": float(groups.group(19)),
-                u"pdr_up_b_unit": groups.group(20)
+                u"ndr_low_b": float(groups.group(2)),
+                u"pdr_low": float(groups.group(3)),
+                u"pdr_low_b": float(groups.group(4)),
+                u"pdr_lat_90_1": groups.group(5),
+                u"pdr_lat_90_2": groups.group(6),
+                u"pdr_lat_50_1": groups.group(7),
+                u"pdr_lat_50_2": groups.group(8),
+                u"pdr_lat_10_1": groups.group(9),
+                u"pdr_lat_10_2": groups.group(10),
             }
         except (AttributeError, IndexError, ValueError, KeyError):
             }
         except (AttributeError, IndexError, ValueError, KeyError):
-            return msg
+            return u"Test Failed."
 
 
-        def _process_lat(in_str):
+        def _process_lat(in_str_1, in_str_2):
             """Extract min, avg, max values from latency string.
 
             """Extract min, avg, max values from latency string.
 
-            :param in_str: Latency string produced by robot framework.
-            :type in_str: str
-            :returns: Processed latency string or original string if a problem
-                occurs.
-            :rtype: str
+            :param in_str_1: Latency string for one direction produced by robot
+                framework.
+            :param in_str_2: Latency string for second direction produced by
+                robot framework.
+            :type in_str_1: str
+            :type in_str_2: str
+            :returns: Processed latency string or None if a problem occurs.
+            :rtype: tuple
             """
             """
-            in_list = in_str.split('/', 3)
-            if len(in_list) < 3:
-                return in_str
+            in_list_1 = in_str_1.split('/', 3)
+            in_list_2 = in_str_2.split('/', 3)
+
+            if len(in_list_1) != 4 and len(in_list_2) != 4:
+                return None
+
+            in_list_1[3] += u"=" * (len(in_list_1[3]) % 4)
+            try:
+                hdr_lat_1 = hdrh.histogram.HdrHistogram.decode(in_list_1[3])
+            except hdrh.codec.HdrLengthException:
+                return None
+
+            in_list_2[3] += u"=" * (len(in_list_2[3]) % 4)
+            try:
+                hdr_lat_2 = hdrh.histogram.HdrHistogram.decode(in_list_2[3])
+            except hdrh.codec.HdrLengthException:
+                return None
+
+            if hdr_lat_1 and hdr_lat_2:
+                hdr_lat = (
+                    hdr_lat_1.get_value_at_percentile(50.0),
+                    hdr_lat_1.get_value_at_percentile(90.0),
+                    hdr_lat_1.get_value_at_percentile(99.0),
+                    hdr_lat_2.get_value_at_percentile(50.0),
+                    hdr_lat_2.get_value_at_percentile(90.0),
+                    hdr_lat_2.get_value_at_percentile(99.0)
+                )
 
 
-            return f"min={in_list[0]}, avg={in_list[1]}, max={in_list[2]}"
+                if all(hdr_lat):
+                    return hdr_lat
+
+            return None
 
         try:
 
         try:
-            return (
-                f"NDR Lower: {(data[u'ndr_low'] / 1e6):.2f}"
-                f"M{data[u'ndr_low_unit']}, "
-                f"{data[u'ndr_low_b']:.2f}{data[u'ndr_low_b_unit']}\n"
-                # f"NDR Upper: {(data[u'ndr_up'] / 1e6):.2f}"
-                # f"M{data[u'ndr_up_unit']}, "
-                # f"{data[u'ndr_up_b']:.2f}{data[u'ndr_up_b_unit']}\n"
-                f"NDR Latency W-E: {_process_lat(data[u'ndr_lat_1'])}\n"
-                f"NDR Latency E-W: {_process_lat(data[u'ndr_lat_2'])}\n"
-                f"PDR Lower: {(data[u'pdr_low'] / 1e6):.2f}"
-                f"M{data[u'pdr_low_unit']}, "
-                f"{data[u'pdr_low_b']:.2f}{data[u'pdr_low_b_unit']}\n"
-                # f"PDR Upper: {(data[u'pdr_up'] / 1e6):.2f}"
-                # f"M{data[u'pdr_up_unit']}, "
-                # f"{data[u'pdr_up_b']:.2f}{data[u'pdr_up_b_unit']}\n"
-                f"PDR Latency W-E: {_process_lat(data[u'pdr_lat_1'])}\n"
-                f"PDR Latency E-W: {_process_lat(data[u'pdr_lat_2'])}"
+            out_msg = (
+                f"1. {(data[u'ndr_low'] / 1e6):5.2f}      "
+                f"{data[u'ndr_low_b']:5.2f}"
+                f"\n2. {(data[u'pdr_low'] / 1e6):5.2f}      "
+                f"{data[u'pdr_low_b']:5.2f}"
             )
             )
+            latency = (
+                _process_lat(data[u'pdr_lat_10_1'], data[u'pdr_lat_10_2']),
+                _process_lat(data[u'pdr_lat_50_1'], data[u'pdr_lat_50_2']),
+                _process_lat(data[u'pdr_lat_90_1'], data[u'pdr_lat_90_2'])
+            )
+            if all(latency):
+                max_len = len(str(max((max(item) for item in latency))))
+                max_len = 4 if max_len < 4 else max_len
+
+                for idx, lat in enumerate(latency):
+                    if not idx:
+                        out_msg += u"\n"
+                    out_msg += (
+                        f"\n{idx + 3}. "
+                        f"{lat[0]:{max_len}d} "
+                        f"{lat[1]:{max_len}d} "
+                        f"{lat[2]:{max_len}d}      "
+                        f"{lat[3]:{max_len}d} "
+                        f"{lat[4]:{max_len}d} "
+                        f"{lat[5]:{max_len}d} "
+                    )
+
+            return out_msg
+
         except (AttributeError, IndexError, ValueError, KeyError):
         except (AttributeError, IndexError, ValueError, KeyError):
-            return msg
+            return u"Test Failed."
 
     def _get_testbed(self, msg):
         """Called when extraction of testbed IP is required.
 
     def _get_testbed(self, msg):
         """Called when extraction of testbed IP is required.
@@ -559,6 +616,10 @@ class ExecutionChecker(ResultVisitor):
         if not msg.message.count(u"stats runtime"):
             return
 
         if not msg.message.count(u"stats runtime"):
             return
 
+        # Temporary solution
+        if self._sh_run_counter > 1:
+            return
+
         if u"show-run" not in self._data[u"tests"][self._test_id].keys():
             self._data[u"tests"][self._test_id][u"show-run"] = dict()
 
         if u"show-run" not in self._data[u"tests"][self._test_id].keys():
             self._data[u"tests"][self._test_id][u"show-run"] = dict()
 
@@ -811,6 +872,40 @@ class ExecutionChecker(ResultVisitor):
 
         return latency, u"FAIL"
 
 
         return latency, u"FAIL"
 
+    @staticmethod
+    def _get_hoststack_data(msg, tags):
+        """Get data from the hoststack test message.
+
+        :param msg: The test message to be parsed.
+        :param tags: Test tags.
+        :type msg: str
+        :type tags: list
+        :returns: Parsed data as a JSON dict and the status (PASS/FAIL).
+        :rtype: tuple(dict, str)
+        """
+        result = dict()
+        status = u"FAIL"
+
+        msg = msg.replace(u"'", u'"').replace(u" ", u"")
+        if u"LDPRELOAD" in tags:
+            try:
+                result = loads(msg)
+                status = u"PASS"
+            except JSONDecodeError:
+                pass
+        elif u"VPPECHO" in tags:
+            try:
+                msg_lst = msg.replace(u"}{", u"} {").split(u" ")
+                result = dict(
+                    client=loads(msg_lst[0]),
+                    server=loads(msg_lst[1])
+                )
+                status = u"PASS"
+            except (JSONDecodeError, IndexError):
+                pass
+
+        return result, status
+
     def visit_suite(self, suite):
         """Implements traversing through the suite and its direct children.
 
     def visit_suite(self, suite):
         """Implements traversing through the suite and its direct children.
 
@@ -882,6 +977,8 @@ class ExecutionChecker(ResultVisitor):
         :returns: Nothing.
         """
 
         :returns: Nothing.
         """
 
+        self._sh_run_counter = 0
+
         longname_orig = test.longname.lower()
 
         # Check the ignore list
         longname_orig = test.longname.lower()
 
         # Check the ignore list
@@ -916,13 +1013,24 @@ class ExecutionChecker(ResultVisitor):
             replace(u'\r', u'').\
             replace(u'[', u' |br| [').\
             replace(u' |br| [', u'[', 1)
             replace(u'\r', u'').\
             replace(u'[', u' |br| [').\
             replace(u' |br| [', u'[', 1)
-        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"'")
         test_result[u"type"] = u"FUNC"
         test_result[u"status"] = test.status
 
         test_result[u"type"] = u"FUNC"
         test_result[u"status"] = test.status
 
+        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"'")
+            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| ').\
+                    replace(u'\r', u'').replace(u'"', u"'")
+            else:
+                test_result[u"msg"] = test.message.replace(u'\n', u' |br| ').\
+                    replace(u'\r', u'').replace(u'"', u"'")
+        else:
+            test_result[u"msg"] = u"Test Failed."
+
         if u"PERFTEST" in tags:
             # Replace info about cores (e.g. -1c-) with the info about threads
             # and cores (e.g. -1t1c-) in the long test case names and in the
         if u"PERFTEST" in tags:
             # Replace info about cores (e.g. -1c-) with the info about threads
             # and cores (e.g. -1t1c-) in the long test case names and in the
@@ -938,14 +1046,14 @@ class ExecutionChecker(ResultVisitor):
                         tag_tc = tag
 
                 if tag_count == 1:
                         tag_tc = tag
 
                 if tag_count == 1:
-                    self._test_id = re.sub(self.REGEX_TC_NAME_NEW,
-                                           f"-{tag_tc.lower()}-",
-                                           self._test_id,
-                                           count=1)
-                    test_result[u"name"] = re.sub(self.REGEX_TC_NAME_NEW,
-                                                  f"-{tag_tc.lower()}-",
-                                                  test_result["name"],
-                                                  count=1)
+                    self._test_id = re.sub(
+                        self.REGEX_TC_NAME_NEW, f"-{tag_tc.lower()}-",
+                        self._test_id, count=1
+                    )
+                    test_result[u"name"] = re.sub(
+                        self.REGEX_TC_NAME_NEW, f"-{tag_tc.lower()}-",
+                        test_result["name"], count=1
+                    )
                 else:
                     test_result[u"status"] = u"FAIL"
                     self._data[u"tests"][self._test_id] = test_result
                 else:
                     test_result[u"status"] = u"FAIL"
                     self._data[u"tests"][self._test_id] = test_result
@@ -967,6 +1075,10 @@ class ExecutionChecker(ResultVisitor):
                 test_result[u"type"] = u"SOAK"
                 test_result[u"throughput"], test_result[u"status"] = \
                     self._get_plr_throughput(test.message)
                 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)
             elif u"TCP" in tags:
                 test_result[u"type"] = u"TCP"
                 groups = re.search(self.REGEX_TCP, test.message)
@@ -1002,6 +1114,8 @@ class ExecutionChecker(ResultVisitor):
                     }
                 except (AttributeError, IndexError, ValueError, TypeError):
                     test_result[u"status"] = u"FAIL"
                     }
                 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
             else:
                 test_result[u"status"] = u"FAIL"
                 self._data[u"tests"][self._test_id] = test_result
@@ -1076,7 +1190,9 @@ class ExecutionChecker(ResultVisitor):
         if test_kw.name.count(u"Show Runtime On All Duts") or \
                 test_kw.name.count(u"Show Runtime Counters On All Duts"):
             self._msg_type = u"test-show-runtime"
         if test_kw.name.count(u"Show Runtime On All Duts") or \
                 test_kw.name.count(u"Show Runtime Counters On All Duts"):
             self._msg_type = u"test-show-runtime"
-        elif test_kw.name.count(u"Install Dpdk Test") and not self._version:
+            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
             self._msg_type = u"dpdk-version"
         else:
             return