Report: Add DNV data
[csit.git] / resources / tools / presentation / input_data_parser.py
index ad3d327..fc56af7 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2018 Cisco and/or its affiliates.
+# Copyright (c) 2019 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:
 # 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:
 - filter the data using tags,
 """
 
 - filter the data using tags,
 """
 
-import multiprocessing
-import os
 import re
 import re
+import resource
 import pandas as pd
 import logging
 import pandas as pd
 import logging
+import prettytable
 
 from robot.api import ExecutionResult, ResultVisitor
 from robot import errors
 from collections import OrderedDict
 from string import replace
 from os import remove
 
 from robot.api import ExecutionResult, ResultVisitor
 from robot import errors
 from collections import OrderedDict
 from string import replace
 from os import remove
+from os.path import join
+from datetime import datetime as dt
+from datetime import timedelta
+from json import loads
 from jumpavg.AvgStdevMetadataFactory import AvgStdevMetadataFactory
 
 from input_data_files import download_and_unzip_data_file
 from jumpavg.AvgStdevMetadataFactory import AvgStdevMetadataFactory
 
 from input_data_files import download_and_unzip_data_file
-from utils import Worker
+
+
+# Separator used in file names
+SEPARATOR = "__"
 
 
 class ExecutionChecker(ResultVisitor):
 
 
 class ExecutionChecker(ResultVisitor):
@@ -65,13 +72,91 @@ class ExecutionChecker(ResultVisitor):
             }
         }
         "tests": {
             }
         }
         "tests": {
+            # NDRPDR tests:
             "ID": {
                 "name": "Test name",
                 "parent": "Name of the parent of the test",
             "ID": {
                 "name": "Test name",
                 "parent": "Name of the parent of the test",
-                "doc": "Test documentation"
-                "msg": "Test message"
+                "doc": "Test documentation",
+                "msg": "Test message",
+                "conf-history": "DUT1 and DUT2 VAT History",
+                "show-run": "Show Run",
+                "tags": ["tag 1", "tag 2", "tag n"],
+                "type": "NDRPDR",
+                "status": "PASS" | "FAIL",
+                "throughput": {
+                    "NDR": {
+                        "LOWER": float,
+                        "UPPER": float
+                    },
+                    "PDR": {
+                        "LOWER": float,
+                        "UPPER": float
+                    }
+                },
+                "latency": {
+                    "NDR": {
+                        "direction1": {
+                            "min": float,
+                            "avg": float,
+                            "max": float
+                        },
+                        "direction2": {
+                            "min": float,
+                            "avg": float,
+                            "max": float
+                        }
+                    },
+                    "PDR": {
+                        "direction1": {
+                            "min": float,
+                            "avg": float,
+                            "max": float
+                        },
+                        "direction2": {
+                            "min": float,
+                            "avg": float,
+                            "max": float
+                        }
+                    }
+                }
+            }
+
+            # TCP tests:
+            "ID": {
+                "name": "Test name",
+                "parent": "Name of the parent of the test",
+                "doc": "Test documentation",
+                "msg": "Test message",
                 "tags": ["tag 1", "tag 2", "tag n"],
                 "tags": ["tag 1", "tag 2", "tag n"],
-                "type": "PDR" | "NDR" | "TCP" | "MRR" | "BMRR",
+                "type": "TCP",
+                "status": "PASS" | "FAIL",
+                "result": int
+            }
+
+            # MRR, BMRR tests:
+            "ID": {
+                "name": "Test name",
+                "parent": "Name of the parent of the test",
+                "doc": "Test documentation",
+                "msg": "Test message",
+                "tags": ["tag 1", "tag 2", "tag n"],
+                "type": "MRR" | "BMRR",
+                "status": "PASS" | "FAIL",
+                "result": {
+                    "receive-rate": AvgStdevMetadata,
+                }
+            }
+
+            # TODO: Remove when definitely no NDRPDRDISC tests are used:
+            # NDRPDRDISC tests:
+            "ID": {
+                "name": "Test name",
+                "parent": "Name of the parent of the test",
+                "doc": "Test documentation",
+                "msg": "Test message",
+                "tags": ["tag 1", "tag 2", "tag n"],
+                "type": "PDR" | "NDR",
+                "status": "PASS" | "FAIL",
                 "throughput": {  # Only type: "PDR" | "NDR"
                     "value": int,
                     "unit": "pps" | "bps" | "percentage"
                 "throughput": {  # Only type: "PDR" | "NDR"
                     "value": int,
                     "unit": "pps" | "bps" | "percentage"
@@ -112,15 +197,8 @@ class ExecutionChecker(ResultVisitor):
                         }
                     }
                 },
                         }
                     }
                 },
-                "result": {  # Only type: "TCP"
-                    "value": int,
-                    "unit": "cps" | "rps"
-                },
-                "result": {  # Only type: "MRR" | "BMRR"
-                    "receive-rate": AvgStdevMetadata,
-                },
                 "lossTolerance": "lossTolerance",  # Only type: "PDR"
                 "lossTolerance": "lossTolerance",  # Only type: "PDR"
-                "vat-history": "DUT1 and DUT2 VAT History"
+                "conf-history": "DUT1 and DUT2 VAT History"
                 "show-run": "Show Run"
             },
             "ID" {
                 "show-run": "Show Run"
             },
             "ID" {
@@ -157,7 +235,7 @@ class ExecutionChecker(ResultVisitor):
                 "doc": "Test documentation"
                 "msg": "Test message"
                 "tags": ["tag 1", "tag 2", "tag n"],
                 "doc": "Test documentation"
                 "msg": "Test message"
                 "tags": ["tag 1", "tag 2", "tag n"],
-                "vat-history": "DUT1 and DUT2 VAT History"
+                "conf-history": "DUT1 and DUT2 VAT History"
                 "show-run": "Show Run"
                 "status": "PASS" | "FAIL"
             },
                 "show-run": "Show Run"
                 "status": "PASS" | "FAIL"
             },
@@ -170,8 +248,18 @@ class ExecutionChecker(ResultVisitor):
     .. note:: ID is the lowercase full path to the test.
     """
 
     .. note:: ID is the lowercase full path to the test.
     """
 
+    # TODO: Remove when definitely no NDRPDRDISC tests are used:
     REGEX_RATE = re.compile(r'^[\D\d]*FINAL_RATE:\s(\d+\.\d+)\s(\w+)')
 
     REGEX_RATE = re.compile(r'^[\D\d]*FINAL_RATE:\s(\d+\.\d+)\s(\w+)')
 
+    REGEX_PLR_RATE = re.compile(r'PLRsearch lower bound::\s(\d+.\d+).*\n'
+                                r'PLRsearch upper bound::\s(\d+.\d+)')
+
+    REGEX_NDRPDR_RATE = 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+)')
+
+    # TODO: Remove when definitely no NDRPDRDISC tests are used:
     REGEX_LAT_NDR = re.compile(r'^[\D\d]*'
                                r'LAT_\d+%NDR:\s\[\'(-?\d+/-?\d+/-?\d+)\','
                                r'\s\'(-?\d+/-?\d+/-?\d+)\'\]\s\n'
     REGEX_LAT_NDR = re.compile(r'^[\D\d]*'
                                r'LAT_\d+%NDR:\s\[\'(-?\d+/-?\d+/-?\d+)\','
                                r'\s\'(-?\d+/-?\d+/-?\d+)\'\]\s\n'
@@ -184,13 +272,16 @@ class ExecutionChecker(ResultVisitor):
                                r'LAT_\d+%PDR:\s\[\'(-?\d+/-?\d+/-?\d+)\','
                                r'\s\'(-?\d+/-?\d+/-?\d+)\'\][\D\d]*')
 
                                r'LAT_\d+%PDR:\s\[\'(-?\d+/-?\d+/-?\d+)\','
                                r'\s\'(-?\d+/-?\d+/-?\d+)\'\][\D\d]*')
 
+    REGEX_NDRPDR_LAT = re.compile(r'LATENCY.*\[\'(.*)\', \'(.*)\'\]\s\n.*\n.*\n'
+                                  r'LATENCY.*\[\'(.*)\', \'(.*)\'\]')
+
     REGEX_TOLERANCE = re.compile(r'^[\D\d]*LOSS_ACCEPTANCE:\s(\d*\.\d*)\s'
                                  r'[\D\d]*')
 
     REGEX_TOLERANCE = re.compile(r'^[\D\d]*LOSS_ACCEPTANCE:\s(\d*\.\d*)\s'
                                  r'[\D\d]*')
 
-    REGEX_VERSION_VPP = re.compile(r"(return STDOUT Version:\s*)(.*)")
+    REGEX_VERSION_VPP = re.compile(r"(return STDOUT Version:\s*|"
+                                   r"VPP Version:\s*|VPP version:\s*)(.*)")
 
 
-    REGEX_VERSION_DPDK = re.compile(r"(return STDOUT testpmd)([\d\D\n]*)"
-                                    r"(RTE Version: 'DPDK )(.*)(')")
+    REGEX_VERSION_DPDK = re.compile(r"(DPDK version:\s*|DPDK Version:\s*)(.*)")
 
     REGEX_TCP = re.compile(r'Total\s(rps|cps|throughput):\s([0-9]*).*$')
 
 
     REGEX_TCP = re.compile(r'Total\s(rps|cps|throughput):\s([0-9]*).*$')
 
@@ -206,12 +297,19 @@ class ExecutionChecker(ResultVisitor):
 
     REGEX_TC_NAME_NEW = re.compile(r'-\d+[cC]-')
 
 
     REGEX_TC_NAME_NEW = re.compile(r'-\d+[cC]-')
 
-    def __init__(self, metadata):
+    REGEX_TC_NUMBER = re.compile(r'tc[0-9]{2}-')
+
+    def __init__(self, metadata, mapping, ignore):
         """Initialisation.
 
         :param metadata: Key-value pairs to be included in "metadata" part of
         """Initialisation.
 
         :param metadata: Key-value pairs to be included in "metadata" part of
-        JSON structure.
+            JSON structure.
+        :param mapping: Mapping of the old names of test cases to the new
+            (actual) one.
+        :param ignore: List of TCs to be ignored.
         :type metadata: dict
         :type metadata: dict
+        :type mapping: dict
+        :type ignore: list
         """
 
         # Type of message to parse out from the test messages
         """
 
         # Type of message to parse out from the test messages
@@ -223,12 +321,21 @@ class ExecutionChecker(ResultVisitor):
         # Timestamp
         self._timestamp = None
 
         # Timestamp
         self._timestamp = None
 
+        # Testbed. The testbed is identified by TG node IP address.
+        self._testbed = None
+
+        # Mapping of TCs long names
+        self._mapping = mapping
+
+        # Ignore list
+        self._ignore = ignore
+
         # Number of VAT History messages found:
         # 0 - no message
         # 1 - VAT History of DUT1
         # 2 - VAT History of DUT2
         self._lookup_kw_nr = 0
         # Number of VAT History messages found:
         # 0 - no message
         # 1 - VAT History of DUT1
         # 2 - VAT History of DUT2
         self._lookup_kw_nr = 0
-        self._vat_history_lookup_nr = 0
+        self._conf_history_lookup_nr = 0
 
         # Number of Show Running messages found
         # 0 - no message
 
         # Number of Show Running messages found
         # 0 - no message
@@ -257,7 +364,9 @@ class ExecutionChecker(ResultVisitor):
             "vpp-version": self._get_vpp_version,
             "dpdk-version": self._get_dpdk_version,
             "teardown-vat-history": self._get_vat_history,
             "vpp-version": self._get_vpp_version,
             "dpdk-version": self._get_dpdk_version,
             "teardown-vat-history": self._get_vat_history,
-            "test-show-runtime": self._get_show_run
+            "teardown-papi-history": self._get_papi_history,
+            "test-show-runtime": self._get_show_run,
+            "testbed": self._get_testbed
         }
 
     @property
         }
 
     @property
@@ -269,6 +378,26 @@ class ExecutionChecker(ResultVisitor):
         """
         return self._data
 
         """
         return self._data
 
+    def _get_testbed(self, msg):
+        """Called when extraction of testbed IP is required.
+        The testbed is identified by TG node IP address.
+
+        :param msg: Message to process.
+        :type msg: Message
+        :returns: Nothing.
+        """
+
+        if msg.message.count("Setup of TG node"):
+            reg_tg_ip = re.compile(
+                r'Setup of TG node (\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}) done')
+            try:
+                self._testbed = str(re.search(reg_tg_ip, msg.message).group(1))
+            except (KeyError, ValueError, IndexError, AttributeError):
+                pass
+            finally:
+                self._data["metadata"]["testbed"] = self._testbed
+                self._msg_type = None
+
     def _get_vpp_version(self, msg):
         """Called when extraction of VPP version is required.
 
     def _get_vpp_version(self, msg):
         """Called when extraction of VPP version is required.
 
@@ -277,7 +406,9 @@ class ExecutionChecker(ResultVisitor):
         :returns: Nothing.
         """
 
         :returns: Nothing.
         """
 
-        if msg.message.count("return STDOUT Version:"):
+        if msg.message.count("return STDOUT Version:") or \
+            msg.message.count("VPP Version:") or \
+            msg.message.count("VPP version:"):
             self._version = str(re.search(self.REGEX_VERSION_VPP, msg.message).
                                 group(2))
             self._data["metadata"]["version"] = self._version
             self._version = str(re.search(self.REGEX_VERSION_VPP, msg.message).
                                 group(2))
             self._data["metadata"]["version"] = self._version
@@ -291,10 +422,10 @@ class ExecutionChecker(ResultVisitor):
         :returns: Nothing.
         """
 
         :returns: Nothing.
         """
 
-        if msg.message.count("return STDOUT testpmd"):
+        if msg.message.count("DPDK Version:"):
             try:
                 self._version = str(re.search(
             try:
                 self._version = str(re.search(
-                    self.REGEX_VERSION_DPDK, msg.message). group(4))
+                    self.REGEX_VERSION_DPDK, msg.message). group(2))
                 self._data["metadata"]["version"] = self._version
             except IndexError:
                 pass
                 self._data["metadata"]["version"] = self._version
             except IndexError:
                 pass
@@ -321,9 +452,9 @@ class ExecutionChecker(ResultVisitor):
         :returns: Nothing.
         """
         if msg.message.count("VAT command history:"):
         :returns: Nothing.
         """
         if msg.message.count("VAT command history:"):
-            self._vat_history_lookup_nr += 1
-            if self._vat_history_lookup_nr == 1:
-                self._data["tests"][self._test_ID]["vat-history"] = str()
+            self._conf_history_lookup_nr += 1
+            if self._conf_history_lookup_nr == 1:
+                self._data["tests"][self._test_ID]["conf-history"] = str()
             else:
                 self._msg_type = None
             text = re.sub("[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3} "
             else:
                 self._msg_type = None
             text = re.sub("[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3} "
@@ -331,9 +462,31 @@ class ExecutionChecker(ResultVisitor):
                 replace("\n\n", "\n").replace('\n', ' |br| ').\
                 replace('\r', '').replace('"', "'")
 
                 replace("\n\n", "\n").replace('\n', ' |br| ').\
                 replace('\r', '').replace('"', "'")
 
-            self._data["tests"][self._test_ID]["vat-history"] += " |br| "
-            self._data["tests"][self._test_ID]["vat-history"] += \
-                "**DUT" + str(self._vat_history_lookup_nr) + ":** " + text
+            self._data["tests"][self._test_ID]["conf-history"] += " |br| "
+            self._data["tests"][self._test_ID]["conf-history"] += \
+                "**DUT" + str(self._conf_history_lookup_nr) + ":** " + text
+
+    def _get_papi_history(self, msg):
+        """Called when extraction of PAPI command history is required.
+
+        :param msg: Message to process.
+        :type msg: Message
+        :returns: Nothing.
+        """
+        if msg.message.count("PAPI command history:"):
+            self._conf_history_lookup_nr += 1
+            if self._conf_history_lookup_nr == 1:
+                self._data["tests"][self._test_ID]["conf-history"] = str()
+            else:
+                self._msg_type = None
+            text = re.sub("[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3} "
+                          "PAPI command history:", "", msg.message, count=1). \
+                replace("\n\n", "\n").replace('\n', ' |br| ').\
+                replace('\r', '').replace('"', "'")
+
+            self._data["tests"][self._test_ID]["conf-history"] += " |br| "
+            self._data["tests"][self._test_ID]["conf-history"] += \
+                "**DUT" + str(self._conf_history_lookup_nr) + ":** " + text
 
     def _get_show_run(self, msg):
         """Called when extraction of VPP operational data (output of CLI command
 
     def _get_show_run(self, msg):
         """Called when extraction of VPP operational data (output of CLI command
@@ -343,24 +496,63 @@ class ExecutionChecker(ResultVisitor):
         :type msg: Message
         :returns: Nothing.
         """
         :type msg: Message
         :returns: Nothing.
         """
-        if msg.message.count("return STDOUT Thread "):
+        if msg.message.count("Runtime:"):
             self._show_run_lookup_nr += 1
             if self._lookup_kw_nr == 1 and self._show_run_lookup_nr == 1:
                 self._data["tests"][self._test_ID]["show-run"] = str()
             if self._lookup_kw_nr > 1:
                 self._msg_type = None
             self._show_run_lookup_nr += 1
             if self._lookup_kw_nr == 1 and self._show_run_lookup_nr == 1:
                 self._data["tests"][self._test_ID]["show-run"] = str()
             if self._lookup_kw_nr > 1:
                 self._msg_type = None
-            if self._show_run_lookup_nr == 1:
-                text = msg.message.replace("vat# ", "").\
-                    replace("return STDOUT ", "").replace("\n\n", "\n").\
-                    replace('\n', ' |br| ').\
-                    replace('\r', '').replace('"', "'")
+            if self._show_run_lookup_nr > 0:
+                message = str(msg.message).replace(' ', '').replace('\n', '').\
+                    replace("'", '"').replace('b"', '"').replace('u"', '"')[8:]
+                runtime = loads(message)
+                try:
+                    threads_nr = len(runtime[0]["clocks"])
+                except (IndexError, KeyError):
+                    return
+                tbl_hdr = ["Name", "Calls", "Vectors", "Suspends", "Clocks"]
+                table = [[tbl_hdr, ] for _ in range(threads_nr)]
+                for item in runtime:
+                    for idx in range(threads_nr):
+                        table[idx].append([
+                            item["name"],
+                            item["calls"][idx],
+                            item["vectors"][idx],
+                            item["suspends"][idx],
+                            item["clocks"][idx]
+                        ])
+                text = ""
+                for idx in range(threads_nr):
+                    text += "Thread {idx} ".format(idx=idx)
+                    text += "vpp_main\n" if idx == 0 else \
+                        "vpp_wk_{idx}\n".format(idx=idx-1)
+                    txt_table = None
+                    for row in table[idx]:
+                        if txt_table is None:
+                            txt_table = prettytable.PrettyTable(row)
+                        else:
+                            if any(row[1:]):
+                                txt_table.add_row(row)
+                    txt_table.set_style(prettytable.MSWORD_FRIENDLY)
+                    txt_table.align["Name"] = "l"
+                    txt_table.align["Calls"] = "r"
+                    txt_table.align["Vectors"] = "r"
+                    txt_table.align["Suspends"] = "r"
+                    txt_table.align["Clocks"] = "r"
+
+                    text += txt_table.get_string(sortby="Name") + '\n'
+
+                text = text.replace('\n', ' |br| ').replace('\r', '').\
+                    replace('"', "'")
                 try:
                     self._data["tests"][self._test_ID]["show-run"] += " |br| "
                     self._data["tests"][self._test_ID]["show-run"] += \
                 try:
                     self._data["tests"][self._test_ID]["show-run"] += " |br| "
                     self._data["tests"][self._test_ID]["show-run"] += \
-                        "**DUT" + str(self._lookup_kw_nr) + ":** |br| " + text
+                        "**DUT" + str(self._show_run_lookup_nr) + ":** |br| " \
+                        + text
                 except KeyError:
                     pass
 
                 except KeyError:
                     pass
 
+    # TODO: Remove when definitely no NDRPDRDISC tests are used:
     def _get_latency(self, msg, test_type):
         """Get the latency data from the test message.
 
     def _get_latency(self, msg, test_type):
         """Get the latency data from the test message.
 
@@ -407,6 +599,101 @@ class ExecutionChecker(ResultVisitor):
 
         return latency
 
 
         return latency
 
+    def _get_ndrpdr_throughput(self, msg):
+        """Get NDR_LOWER, NDR_UPPER, PDR_LOWER and PDR_UPPER 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)
+        """
+
+        throughput = {
+            "NDR": {"LOWER": -1.0, "UPPER": -1.0},
+            "PDR": {"LOWER": -1.0, "UPPER": -1.0}
+        }
+        status = "FAIL"
+        groups = re.search(self.REGEX_NDRPDR_RATE, msg)
+
+        if groups is not None:
+            try:
+                throughput["NDR"]["LOWER"] = float(groups.group(1))
+                throughput["NDR"]["UPPER"] = float(groups.group(2))
+                throughput["PDR"]["LOWER"] = float(groups.group(3))
+                throughput["PDR"]["UPPER"] = float(groups.group(4))
+                status = "PASS"
+            except (IndexError, ValueError):
+                pass
+
+        return throughput, status
+
+    def _get_plr_throughput(self, msg):
+        """Get PLRsearch lower bound and PLRsearch upper bound 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)
+        """
+
+        throughput = {
+            "LOWER": -1.0,
+            "UPPER": -1.0
+        }
+        status = "FAIL"
+        groups = re.search(self.REGEX_PLR_RATE, msg)
+
+        if groups is not None:
+            try:
+                throughput["LOWER"] = float(groups.group(1))
+                throughput["UPPER"] = float(groups.group(2))
+                status = "PASS"
+            except (IndexError, ValueError):
+                pass
+
+        return throughput, status
+
+    def _get_ndrpdr_latency(self, msg):
+        """Get LATENCY 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)
+        """
+
+        latency = {
+            "NDR": {
+                "direction1": {"min": -1.0, "avg": -1.0, "max": -1.0},
+                "direction2": {"min": -1.0, "avg": -1.0, "max": -1.0}
+            },
+            "PDR": {
+                "direction1": {"min": -1.0, "avg": -1.0, "max": -1.0},
+                "direction2": {"min": -1.0, "avg": -1.0, "max": -1.0}
+            }
+        }
+        status = "FAIL"
+        groups = re.search(self.REGEX_NDRPDR_LAT, msg)
+
+        if groups is not None:
+            keys = ("min", "avg", "max")
+            try:
+                latency["NDR"]["direction1"] = dict(
+                    zip(keys, [float(l) for l in groups.group(1).split('/')]))
+                latency["NDR"]["direction2"] = dict(
+                    zip(keys, [float(l) for l in groups.group(2).split('/')]))
+                latency["PDR"]["direction1"] = dict(
+                    zip(keys, [float(l) for l in groups.group(3).split('/')]))
+                latency["PDR"]["direction2"] = dict(
+                    zip(keys, [float(l) for l in groups.group(4).split('/')]))
+                status = "PASS"
+            except (IndexError, ValueError):
+                pass
+
+        return latency, 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.
 
@@ -474,9 +761,30 @@ class ExecutionChecker(ResultVisitor):
         :returns: Nothing.
         """
 
         :returns: Nothing.
         """
 
+        longname_orig = test.longname.lower()
+
+        # Check the ignore list
+        if longname_orig in self._ignore:
+            return
+
         tags = [str(tag) for tag in test.tags]
         test_result = dict()
         tags = [str(tag) for tag in test.tags]
         test_result = dict()
-        test_result["name"] = test.name.lower()
+
+        # Change the TC long name and name if defined in the mapping table
+        longname = self._mapping.get(longname_orig, None)
+        if longname is not None:
+            name = longname.split('.')[-1]
+            logging.debug("{0}\n{1}\n{2}\n{3}".format(
+                self._data["metadata"], longname_orig, longname, name))
+        else:
+            longname = longname_orig
+            name = test.name.lower()
+
+        # Remove TC number from the TC long name (backward compatibility):
+        self._test_ID = re.sub(self.REGEX_TC_NUMBER, "", longname)
+        # Remove TC number from the TC name (not needed):
+        test_result["name"] = re.sub(self.REGEX_TC_NUMBER, "", name)
+
         test_result["parent"] = test.parent.name.lower()
         test_result["tags"] = tags
         doc_str = test.doc.replace('"', "'").replace('\n', ' '). \
         test_result["parent"] = test.parent.name.lower()
         test_result["tags"] = tags
         doc_str = test.doc.replace('"', "'").replace('\n', ' '). \
@@ -484,27 +792,10 @@ class ExecutionChecker(ResultVisitor):
         test_result["doc"] = replace(doc_str, ' |br| [', '[', maxreplace=1)
         test_result["msg"] = test.message.replace('\n', ' |br| '). \
             replace('\r', '').replace('"', "'")
         test_result["doc"] = replace(doc_str, ' |br| [', '[', maxreplace=1)
         test_result["msg"] = test.message.replace('\n', ' |br| '). \
             replace('\r', '').replace('"', "'")
+        test_result["type"] = "FUNC"
         test_result["status"] = test.status
         test_result["status"] = test.status
-        self._test_ID = test.longname.lower()
-        if test.status == "PASS" and ("NDRPDRDISC" in tags or
-                                      "TCP" in tags or
-                                      "MRR" in tags or
-                                      "BMRR" in tags):
-            if "NDRDISC" in tags:
-                test_type = "NDR"
-            elif "PDRDISC" in tags:
-                test_type = "PDR"
-            elif "TCP" in tags:
-                test_type = "TCP"
-            elif "MRR" in tags:
-                test_type = "MRR"
-            elif "FRMOBL" in tags or "BMRR" in tags:
-                test_type = "BMRR"
-            else:
-                return
-
-            test_result["type"] = test_type
 
 
+        if "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
             # test case names if necessary.
             # 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
             # test case names if necessary.
@@ -528,11 +819,41 @@ class ExecutionChecker(ResultVisitor):
                                                  count=1)
                 else:
                     test_result["status"] = "FAIL"
                                                  count=1)
                 else:
                     test_result["status"] = "FAIL"
-                    logging.error("The test '{0}' has no or more than one "
+                    self._data["tests"][self._test_ID] = test_result
+                    logging.debug("The test '{0}' has no or more than one "
                                   "multi-threading tags.".format(self._test_ID))
                                   "multi-threading tags.".format(self._test_ID))
+                    logging.debug("Tags: {0}".format(test_result["tags"]))
                     return
 
                     return
 
-            if test_type in ("NDR", "PDR"):
+        if test.status == "PASS" and ("NDRPDRDISC" in tags or
+                                      "NDRPDR" in tags or
+                                      "SOAK" in tags or
+                                      "TCP" in tags or
+                                      "MRR" in tags or
+                                      "BMRR" in tags):
+            # TODO: Remove when definitely no NDRPDRDISC tests are used:
+            if "NDRDISC" in tags:
+                test_result["type"] = "NDR"
+            # TODO: Remove when definitely no NDRPDRDISC tests are used:
+            elif "PDRDISC" in tags:
+                test_result["type"] = "PDR"
+            elif "NDRPDR" in tags:
+                test_result["type"] = "NDRPDR"
+            elif "SOAK" in tags:
+                test_result["type"] = "SOAK"
+            elif "TCP" in tags:
+                test_result["type"] = "TCP"
+            elif "MRR" in tags:
+                test_result["type"] = "MRR"
+            elif "FRMOBL" in tags or "BMRR" in tags:
+                test_result["type"] = "BMRR"
+            else:
+                test_result["status"] = "FAIL"
+                self._data["tests"][self._test_ID] = test_result
+                return
+
+            # TODO: Remove when definitely no NDRPDRDISC tests are used:
+            if test_result["type"] in ("NDR", "PDR"):
                 try:
                     rate_value = str(re.search(
                         self.REGEX_RATE, test.message).group(1))
                 try:
                     rate_value = str(re.search(
                         self.REGEX_RATE, test.message).group(1))
@@ -549,26 +870,38 @@ class ExecutionChecker(ResultVisitor):
                     int(rate_value.split('.')[0])
                 test_result["throughput"]["unit"] = rate_unit
                 test_result["latency"] = \
                     int(rate_value.split('.')[0])
                 test_result["throughput"]["unit"] = rate_unit
                 test_result["latency"] = \
-                    self._get_latency(test.message, test_type)
-                if test_type == "PDR":
+                    self._get_latency(test.message, test_result["type"])
+                if test_result["type"] == "PDR":
                     test_result["lossTolerance"] = str(re.search(
                         self.REGEX_TOLERANCE, test.message).group(1))
 
                     test_result["lossTolerance"] = str(re.search(
                         self.REGEX_TOLERANCE, test.message).group(1))
 
-            elif test_type in ("TCP", ):
+            elif test_result["type"] in ("NDRPDR", ):
+                test_result["throughput"], test_result["status"] = \
+                    self._get_ndrpdr_throughput(test.message)
+                test_result["latency"], test_result["status"] = \
+                    self._get_ndrpdr_latency(test.message)
+
+            elif test_result["type"] in ("SOAK", ):
+                test_result["throughput"], test_result["status"] = \
+                    self._get_plr_throughput(test.message)
+
+            elif test_result["type"] in ("TCP", ):
                 groups = re.search(self.REGEX_TCP, test.message)
                 groups = re.search(self.REGEX_TCP, test.message)
-                test_result["result"] = dict()
-                test_result["result"]["value"] = int(groups.group(2))
-                test_result["result"]["unit"] = groups.group(1)
+                test_result["result"] = int(groups.group(2))
 
 
-            elif test_type in ("MRR", "BMRR"):
+            elif test_result["type"] in ("MRR", "BMRR"):
                 test_result["result"] = dict()
                 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(",")]
                 test_result["result"] = dict()
                 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(",")]
-                    test_result["result"]["receive-rate"] = \
-                        AvgStdevMetadataFactory.from_data(items_float)
+                    metadata = AvgStdevMetadataFactory.from_data(items_float)
+                    # Next two lines have been introduced in CSIT-1179,
+                    # to be removed in CSIT-1180.
+                    metadata.size = 1
+                    metadata.stdev = 0.0
+                    test_result["result"]["receive-rate"] = metadata
                 else:
                     groups = re.search(self.REGEX_MRR, test.message)
                     test_result["result"]["receive-rate"] = \
                 else:
                     groups = re.search(self.REGEX_MRR, test.message)
                     test_result["result"]["receive-rate"] = \
@@ -649,7 +982,7 @@ class ExecutionChecker(ResultVisitor):
             self._lookup_kw_nr += 1
             self._show_run_lookup_nr = 0
             self._msg_type = "test-show-runtime"
             self._lookup_kw_nr += 1
             self._show_run_lookup_nr = 0
             self._msg_type = "test-show-runtime"
-        elif test_kw.name.count("Start The L2fwd Test") and not self._version:
+        elif test_kw.name.count("Install Dpdk Test") and not self._version:
             self._msg_type = "dpdk-version"
         else:
             return
             self._msg_type = "dpdk-version"
         else:
             return
@@ -688,10 +1021,11 @@ class ExecutionChecker(ResultVisitor):
         if setup_kw.name.count("Show Vpp Version On All Duts") \
                 and not self._version:
             self._msg_type = "vpp-version"
         if setup_kw.name.count("Show Vpp Version On All Duts") \
                 and not self._version:
             self._msg_type = "vpp-version"
-
-        elif setup_kw.name.count("Setup performance global Variables") \
+        elif setup_kw.name.count("Set Global Variable") \
                 and not self._timestamp:
             self._msg_type = "timestamp"
                 and not self._timestamp:
             self._msg_type = "timestamp"
+        elif setup_kw.name.count("Setup Framework") and not self._testbed:
+            self._msg_type = "testbed"
         else:
             return
         setup_kw.messages.visit(self)
         else:
             return
         setup_kw.messages.visit(self)
@@ -728,9 +1062,13 @@ class ExecutionChecker(ResultVisitor):
         """
 
         if teardown_kw.name.count("Show Vat History On All Duts"):
         """
 
         if teardown_kw.name.count("Show Vat History On All Duts"):
-            self._vat_history_lookup_nr = 0
+            self._conf_history_lookup_nr = 0
             self._msg_type = "teardown-vat-history"
             teardown_kw.messages.visit(self)
             self._msg_type = "teardown-vat-history"
             teardown_kw.messages.visit(self)
+        elif teardown_kw.name.count("Show Papi History On All Duts"):
+            self._conf_history_lookup_nr = 0
+            self._msg_type = "teardown-papi-history"
+            teardown_kw.messages.visit(self)
 
     def end_teardown_kw(self, teardown_kw):
         """Called when keyword ends. Default implementation does nothing.
 
     def end_teardown_kw(self, teardown_kw):
         """Called when keyword ends. Default implementation does nothing.
@@ -851,8 +1189,7 @@ class InputData(object):
 
         return self.data[job][build]["tests"]
 
 
         return self.data[job][build]["tests"]
 
-    @staticmethod
-    def _parse_tests(job, build, log):
+    def _parse_tests(self, job, build, log):
         """Process data from robot output.xml file and return JSON structured
         data.
 
         """Process data from robot output.xml file and return JSON structured
         data.
 
@@ -878,18 +1215,16 @@ class InputData(object):
                 log.append(("ERROR", "Error occurred while parsing output.xml: "
                                      "{0}".format(err)))
                 return None
                 log.append(("ERROR", "Error occurred while parsing output.xml: "
                                      "{0}".format(err)))
                 return None
-        checker = ExecutionChecker(metadata)
+        checker = ExecutionChecker(metadata, self._cfg.mapping,
+                                   self._cfg.ignore)
         result.visit(checker)
 
         return checker.data
 
         result.visit(checker)
 
         return checker.data
 
-    def _download_and_parse_build(self, pid, data_queue, job, build, repeat):
+    def _download_and_parse_build(self, job, build, repeat, pid=10000):
         """Download and parse the input data file.
 
         :param pid: PID of the process executing this method.
         """Download and parse the input data file.
 
         :param pid: PID of the process executing this method.
-        :param data_queue: Shared memory between processes. Queue which keeps
-            the result data. This data is then read by the main process and used
-            in further processing.
         :param job: Name of the Jenkins job which generated the processed input
             file.
         :param build: Information about the Jenkins build which generated the
         :param job: Name of the Jenkins job which generated the processed input
             file.
         :param build: Information about the Jenkins build which generated the
@@ -897,7 +1232,6 @@ class InputData(object):
         :param repeat: Repeat the download specified number of times if not
             successful.
         :type pid: int
         :param repeat: Repeat the download specified number of times if not
             successful.
         :type pid: int
-        :type data_queue: multiprocessing.Manager().Queue()
         :type job: str
         :type build: dict
         :type repeat: int
         :type job: str
         :type build: dict
         :type repeat: int
@@ -905,9 +1239,6 @@ class InputData(object):
 
         logs = list()
 
 
         logs = list()
 
-        logging.info("  Processing the job/build: {0}: {1}".
-                     format(job, build["build"]))
-
         logs.append(("INFO", "  Processing the job/build: {0}: {1}".
                      format(job, build["build"])))
 
         logs.append(("INFO", "  Processing the job/build: {0}: {1}".
                      format(job, build["build"])))
 
@@ -927,9 +1258,9 @@ class InputData(object):
                                   "'{build}', or it is damaged. Skipped.".
                          format(job=job, build=build["build"])))
         if success:
                                   "'{build}', or it is damaged. Skipped.".
                          format(job=job, build=build["build"])))
         if success:
-            logs.append(("INFO", "  Processing data from the build '{0}' ...".
+            logs.append(("INFO", "    Processing data from the build '{0}' ...".
                          format(build["build"])))
                          format(build["build"])))
-            data = InputData._parse_tests(job, build, logs)
+            data = self._parse_tests(job, build, logs)
             if data is None:
                 logs.append(("ERROR", "Input data file from the job '{job}', "
                                       "build '{build}' is damaged. Skipped.".
             if data is None:
                 logs.append(("ERROR", "Input data file from the job '{job}', "
                                       "build '{build}' is damaged. Skipped.".
@@ -941,17 +1272,59 @@ class InputData(object):
                 remove(build["file-name"])
             except OSError as err:
                 logs.append(("ERROR", "Cannot remove the file '{0}': {1}".
                 remove(build["file-name"])
             except OSError as err:
                 logs.append(("ERROR", "Cannot remove the file '{0}': {1}".
-                             format(build["file-name"], err)))
+                             format(build["file-name"], repr(err))))
+
+        # If the time-period is defined in the specification file, remove all
+        # files which are outside the time period.
+        timeperiod = self._cfg.input.get("time-period", None)
+        if timeperiod and data:
+            now = dt.utcnow()
+            timeperiod = timedelta(int(timeperiod))
+            metadata = data.get("metadata", None)
+            if metadata:
+                generated = metadata.get("generated", None)
+                if generated:
+                    generated = dt.strptime(generated, "%Y%m%d %H:%M")
+                    if (now - generated) > timeperiod:
+                        # Remove the data and the file:
+                        state = "removed"
+                        data = None
+                        logs.append(
+                            ("INFO",
+                             "    The build {job}/{build} is outdated, will be "
+                             "removed".format(job=job, build=build["build"])))
+                        file_name = self._cfg.input["file-name"]
+                        full_name = join(
+                            self._cfg.environment["paths"]["DIR[WORKING,DATA]"],
+                            "{job}{sep}{build}{sep}{name}".format(
+                                job=job,
+                                sep=SEPARATOR,
+                                build=build["build"],
+                                name=file_name))
+                        try:
+                            remove(full_name)
+                            logs.append(("INFO",
+                                         "    The file {name} has been removed".
+                                         format(name=full_name)))
+                        except OSError as err:
+                            logs.append(("ERROR",
+                                         "Cannot remove the file '{0}': {1}".
+                                         format(full_name, repr(err))))
         logs.append(("INFO", "  Done."))
 
         logs.append(("INFO", "  Done."))
 
-        result = {
-            "data": data,
-            "state": state,
-            "job": job,
-            "build": build,
-            "logs": logs
-        }
-        data_queue.put(result)
+        for level, line in logs:
+            if level == "INFO":
+                logging.info(line)
+            elif level == "ERROR":
+                logging.error(line)
+            elif level == "DEBUG":
+                logging.debug(line)
+            elif level == "CRITICAL":
+                logging.critical(line)
+            elif level == "WARNING":
+                logging.warning(line)
+
+        return {"data": data, "state": state, "job": job, "build": build}
 
     def download_and_parse_data(self, repeat=1):
         """Download the input data files, parse input data from input files and
 
     def download_and_parse_data(self, repeat=1):
         """Download the input data files, parse input data from input files and
@@ -964,73 +1337,34 @@ class InputData(object):
 
         logging.info("Downloading and parsing input files ...")
 
 
         logging.info("Downloading and parsing input files ...")
 
-        work_queue = multiprocessing.JoinableQueue()
-        manager = multiprocessing.Manager()
-        data_queue = manager.Queue()
-        cpus = multiprocessing.cpu_count()
-
-        workers = list()
-        for cpu in range(cpus):
-            worker = Worker(work_queue,
-                            data_queue,
-                            self._download_and_parse_build)
-            worker.daemon = True
-            worker.start()
-            workers.append(worker)
-            os.system("taskset -p -c {0} {1} > /dev/null 2>&1".
-                      format(cpu, worker.pid))
-
         for job, builds in self._cfg.builds.items():
             for build in builds:
         for job, builds in self._cfg.builds.items():
             for build in builds:
-                work_queue.put((job, build, repeat))
 
 
-        work_queue.join()
+                result = self._download_and_parse_build(job, build, repeat)
+                build_nr = result["build"]["build"]
 
 
-        logging.info("Done.")
+                if result["data"]:
+                    data = result["data"]
+                    build_data = pd.Series({
+                        "metadata": pd.Series(
+                            data["metadata"].values(),
+                            index=data["metadata"].keys()),
+                        "suites": pd.Series(data["suites"].values(),
+                                            index=data["suites"].keys()),
+                        "tests": pd.Series(data["tests"].values(),
+                                           index=data["tests"].keys())})
 
 
-        while not data_queue.empty():
-            result = data_queue.get()
-
-            job = result["job"]
-            build_nr = result["build"]["build"]
-
-            if result["data"]:
-                data = result["data"]
-                build_data = pd.Series({
-                    "metadata": pd.Series(data["metadata"].values(),
-                                          index=data["metadata"].keys()),
-                    "suites": pd.Series(data["suites"].values(),
-                                        index=data["suites"].keys()),
-                    "tests": pd.Series(data["tests"].values(),
-                                       index=data["tests"].keys())})
-
-                if self._input_data.get(job, None) is None:
-                    self._input_data[job] = pd.Series()
-                self._input_data[job][str(build_nr)] = build_data
-
-                self._cfg.set_input_file_name(job, build_nr,
-                                              result["build"]["file-name"])
-
-            self._cfg.set_input_state(job, build_nr, result["state"])
-
-            for item in result["logs"]:
-                if item[0] == "INFO":
-                    logging.info(item[1])
-                elif item[0] == "ERROR":
-                    logging.error(item[1])
-                elif item[0] == "DEBUG":
-                    logging.debug(item[1])
-                elif item[0] == "CRITICAL":
-                    logging.critical(item[1])
-                elif item[0] == "WARNING":
-                    logging.warning(item[1])
-
-        del data_queue
-
-        # Terminate all workers
-        for worker in workers:
-            worker.terminate()
-            worker.join()
+                    if self._input_data.get(job, None) is None:
+                        self._input_data[job] = pd.Series()
+                    self._input_data[job][str(build_nr)] = build_data
+
+                    self._cfg.set_input_file_name(
+                        job, build_nr, result["build"]["file-name"])
+
+                self._cfg.set_input_state(job, build_nr, result["state"])
+
+                logging.info("Memory allocation: {0:,d}MB".format(
+                    resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1000))
 
         logging.info("Done.")
 
 
         logging.info("Done.")
 
@@ -1120,6 +1454,8 @@ class InputData(object):
 
         if params is None:
             params = element.get("parameters", None)
 
         if params is None:
             params = element.get("parameters", None)
+            if params:
+                params.append("type")
 
         data = pd.Series()
         try:
 
         data = pd.Series()
         try:
@@ -1162,6 +1498,96 @@ class InputData(object):
                           "tags are enclosed by apostrophes.".format(cond))
             return None
 
                           "tags are enclosed by apostrophes.".format(cond))
             return None
 
+    def filter_tests_by_name(self, element, params=None, data_set="tests",
+                             continue_on_error=False):
+        """Filter required data from the given jobs and builds.
+
+        The output data structure is:
+
+        - job 1
+          - build 1
+            - test (or suite) 1 ID:
+              - param 1
+              - param 2
+              ...
+              - param n
+            ...
+            - test (or suite) n ID:
+            ...
+          ...
+          - build n
+        ...
+        - job n
+
+        :param element: Element which will use the filtered data.
+        :param params: Parameters which will be included in the output. If None,
+        all parameters are included.
+        :param data_set: The set of data to be filtered: tests, suites,
+        metadata.
+        :param continue_on_error: Continue if there is error while reading the
+        data. The Item will be empty then
+        :type element: pandas.Series
+        :type params: list
+        :type data_set: str
+        :type continue_on_error: bool
+        :returns: Filtered data.
+        :rtype pandas.Series
+        """
+
+        include = element.get("include", None)
+        if not include:
+            logging.warning("No tests to include, skipping the element.")
+            return None
+
+        if params is None:
+            params = element.get("parameters", None)
+            if params:
+                params.append("type")
+
+        data = pd.Series()
+        try:
+            for job, builds in element["data"].items():
+                data[job] = pd.Series()
+                for build in builds:
+                    data[job][str(build)] = pd.Series()
+                    for test in include:
+                        try:
+                            reg_ex = re.compile(str(test).lower())
+                            for test_ID in self.data[job][str(build)]\
+                                    [data_set].keys():
+                                if re.match(reg_ex, str(test_ID).lower()):
+                                    test_data = self.data[job][str(build)]\
+                                        [data_set][test_ID]
+                                    data[job][str(build)][test_ID] = pd.Series()
+                                    if params is None:
+                                        for param, val in test_data.items():
+                                            data[job][str(build)][test_ID]\
+                                                [param] = val
+                                    else:
+                                        for param in params:
+                                            try:
+                                                data[job][str(build)][test_ID]\
+                                                    [param] = test_data[param]
+                                            except KeyError:
+                                                data[job][str(build)][test_ID]\
+                                                    [param] = "No Data"
+                        except KeyError as err:
+                            logging.error("{err!r}".format(err=err))
+                            if continue_on_error:
+                                continue
+                            else:
+                                return None
+            return data
+
+        except (KeyError, IndexError, ValueError) as err:
+            logging.error("Missing mandatory parameter in the element "
+                          "specification: {err!r}".format(err=err))
+            return None
+        except AttributeError as err:
+            logging.error("{err!r}".format(err=err))
+            return None
+
+
     @staticmethod
     def merge_data(data):
         """Merge data from more jobs and builds to a simple data structure.
     @staticmethod
     def merge_data(data):
         """Merge data from more jobs and builds to a simple data structure.