aboutsummaryrefslogtreecommitdiffstats
path: root/resources/tools/wrk/wrk_traffic_profile_parser.py
diff options
context:
space:
mode:
Diffstat (limited to 'resources/tools/wrk/wrk_traffic_profile_parser.py')
-rw-r--r--resources/tools/wrk/wrk_traffic_profile_parser.py286
1 files changed, 286 insertions, 0 deletions
diff --git a/resources/tools/wrk/wrk_traffic_profile_parser.py b/resources/tools/wrk/wrk_traffic_profile_parser.py
new file mode 100644
index 0000000000..e1f8365345
--- /dev/null
+++ b/resources/tools/wrk/wrk_traffic_profile_parser.py
@@ -0,0 +1,286 @@
+# Copyright (c) 2018 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:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+
+"""wrk traffic profile parser.
+
+See LLD for the structure of a wrk traffic profile.
+"""
+
+
+from os.path import isfile
+from pprint import pformat
+
+from yaml import load, YAMLError
+from robot.api import logger
+
+from resources.tools.wrk.wrk_errors import WrkError
+
+
+class WrkTrafficProfile(object):
+ """The wrk traffic profile.
+ """
+
+ MANDATORY_PARAMS = ("urls",
+ "first-cpu",
+ "cpus",
+ "duration",
+ "nr-of-threads",
+ "nr-of-connections")
+
+ def __init__(self, profile_name):
+ """Read the traffic profile from the yaml file.
+
+ :param profile_name: Path to the yaml file with the profile.
+ :type profile_name: str
+ :raises: WrkError if it is not possible to parse the profile.
+ """
+
+ self._profile_name = None
+ self._traffic_profile = None
+
+ self.profile_name = profile_name
+
+ try:
+ with open(self.profile_name, 'r') as profile_file:
+ self.traffic_profile = load(profile_file)
+ except IOError as err:
+ raise WrkError(msg="An error occurred while opening the file '{0}'."
+ .format(self.profile_name),
+ details=str(err))
+ except YAMLError as err:
+ raise WrkError(msg="An error occurred while parsing the traffic "
+ "profile '{0}'.".format(self.profile_name),
+ details=str(err))
+
+ self._validate_traffic_profile()
+
+ if self.traffic_profile:
+ logger.debug("\nThe wrk traffic profile '{0}' is valid.\n".
+ format(self.profile_name))
+ logger.debug("wrk traffic profile '{0}':".format(self.profile_name))
+ logger.debug(pformat(self.traffic_profile))
+ else:
+ logger.debug("\nThe wrk traffic profile '{0}' is invalid.\n".
+ format(self.profile_name))
+ raise WrkError("\nThe wrk traffic profile '{0}' is invalid.\n".
+ format(self.profile_name))
+
+ def __repr__(self):
+ return pformat(self.traffic_profile)
+
+ def __str__(self):
+ return pformat(self.traffic_profile)
+
+ def _validate_traffic_profile(self):
+ """Validate the traffic profile.
+
+ The specification, the structure and the rules are described in
+ doc/wrk_lld.rst
+ """
+
+ logger.debug("\nValidating the wrk traffic profile '{0}'...\n".
+ format(self.profile_name))
+
+ # Level 1: Check if the profile is a dictionary:
+ if not isinstance(self.traffic_profile, dict):
+ logger.error("The wrk traffic profile must be a dictionary.")
+ self.traffic_profile = None
+ return
+
+ # Level 2: Check if all mandatory parameters are present:
+ is_valid = True
+ for param in self.MANDATORY_PARAMS:
+ if self.traffic_profile.get(param, None) is None:
+ logger.error("The parameter '{0}' in mandatory.".format(param))
+ is_valid = False
+ if not is_valid:
+ self.traffic_profile = None
+ return
+
+ # Level 3: Mandatory params: Check if urls is a list:
+ is_valid = True
+ if not isinstance(self.traffic_profile["urls"], list):
+ logger.error("The parameter 'urls' must be a list.")
+ is_valid = False
+
+ # Level 3: Mandatory params: Check if cpus is a valid integer:
+ try:
+ cpus = int(self.traffic_profile["cpus"])
+ if cpus < 1:
+ raise ValueError
+ self.traffic_profile["cpus"] = cpus
+ except ValueError:
+ logger.error("The parameter 'cpus' must be an integer greater than "
+ "1.")
+ is_valid = False
+
+ # Level 3: Mandatory params: Check if first-cpu is a valid integer:
+ try:
+ first_cpu = int(self.traffic_profile["first-cpu"])
+ if first_cpu < 0:
+ raise ValueError
+ self.traffic_profile["first-cpu"] = first_cpu
+ except ValueError:
+ logger.error("The parameter 'first-cpu' must be an integer greater "
+ "than 1.")
+ is_valid = False
+
+ # Level 3: Mandatory params: Check if duration is a valid integer:
+ try:
+ duration = int(self.traffic_profile["duration"])
+ if duration < 1:
+ raise ValueError
+ self.traffic_profile["duration"] = duration
+ except ValueError:
+ logger.error("The parameter 'duration' must be an integer "
+ "greater than 1.")
+ is_valid = False
+
+ # Level 3: Mandatory params: Check if nr-of-threads is a valid integer:
+ try:
+ nr_of_threads = int(self.traffic_profile["nr-of-threads"])
+ if nr_of_threads < 1:
+ raise ValueError
+ self.traffic_profile["nr-of-threads"] = nr_of_threads
+ except ValueError:
+ logger.error("The parameter 'nr-of-threads' must be an integer "
+ "greater than 1.")
+ is_valid = False
+
+ # Level 3: Mandatory params: Check if nr-of-connections is a valid
+ # integer:
+ try:
+ nr_of_connections = int(self.traffic_profile["nr-of-connections"])
+ if nr_of_connections < 1:
+ raise ValueError
+ self.traffic_profile["nr-of-connections"] = nr_of_connections
+ except ValueError:
+ logger.error("The parameter 'nr-of-connections' must be an integer "
+ "greater than 1.")
+ is_valid = False
+
+ # Level 4: Optional params: Check if script is present:
+ script = self.traffic_profile.get("script", None)
+ if script is not None:
+ if not isinstance(script, str):
+ logger.error("The path to LuaJIT script in invalid")
+ is_valid = False
+ else:
+ if not isfile(script):
+ logger.error("The file '{0}' in not present.".
+ format(script))
+ is_valid = False
+ else:
+ self.traffic_profile["script"] = None
+ logger.debug("The optional parameter 'LuaJIT script' is not "
+ "defined. No problem.")
+
+ # Level 4: Optional params: Check if header is present:
+ header = self.traffic_profile.get("header", None)
+ if header:
+ if not (isinstance(header, dict) or isinstance(header, str)):
+ logger.error("The parameter 'header' is not valid.")
+ is_valid = False
+ else:
+ if isinstance(header, dict):
+ header_lst = list()
+ for key, val in header.items():
+ header_lst.append("{0}: {1}".format(key, val))
+ if header_lst:
+ self.traffic_profile["header"] = ", ".join(header_lst)
+ else:
+ logger.error("The parameter 'header' is defined but "
+ "empty.")
+ is_valid = False
+ else:
+ self.traffic_profile["header"] = None
+ logger.debug("The optional parameter 'header' is not defined. "
+ "No problem.")
+
+ # Level 4: Optional params: Check if latency is present:
+ latency = self.traffic_profile.get("latency", None)
+ if latency is not None:
+ try:
+ latency = bool(latency)
+ self.traffic_profile["latency"] = latency
+ except ValueError:
+ logger.error("The parameter 'latency' must be boolean.")
+ is_valid = False
+ else:
+ self.traffic_profile["latency"] = False
+ logger.debug("The optional parameter 'latency' is not defined. "
+ "No problem.")
+
+ # Level 4: Optional params: Check if timeout is present:
+ timeout = self.traffic_profile.get("timeout", None)
+ if timeout:
+ try:
+ timeout = int(timeout)
+ if timeout < 1:
+ raise ValueError
+ self.traffic_profile["timeout"] = timeout
+ except ValueError:
+ logger.error("The parameter 'timeout' must be integer greater "
+ "than 1.")
+ is_valid = False
+ else:
+ self.traffic_profile["timeout"] = None
+ logger.debug("The optional parameter 'timeout' is not defined. "
+ "No problem.")
+
+ if not is_valid:
+ self.traffic_profile = None
+ return
+
+ # Level 5: Check dependencies between parameters:
+ # Level 5: Check urls and cpus:
+ if self.traffic_profile["cpus"] % len(self.traffic_profile["urls"]):
+ logger.error("The number of CPUs must be a multiplication of the "
+ "number of URLs.")
+ self.traffic_profile = None
+
+ @property
+ def profile_name(self):
+ """Getter - Profile name.
+
+ :returns: The traffic profile file path
+ :rtype: str
+ """
+ return self._profile_name
+
+ @profile_name.setter
+ def profile_name(self, profile_name):
+ """
+
+ :param profile_name:
+ :type profile_name: str
+ """
+ self._profile_name = profile_name
+
+ @property
+ def traffic_profile(self):
+ """Getter: Traffic profile.
+
+ :returns: The traffic profile.
+ :rtype: dict
+ """
+ return self._traffic_profile
+
+ @traffic_profile.setter
+ def traffic_profile(self, profile):
+ """Setter - Traffic profile.
+
+ :param profile: The new traffic profile.
+ :type profile: dict
+ """
+ self._traffic_profile = profile