diff options
author | 2015-06-24 14:03:29 +0300 | |
---|---|---|
committer | 2015-06-24 14:03:29 +0300 | |
commit | 8b52a31ed2c299b759f330c4f976b9c70f5765f4 (patch) | |
tree | 9d6da5438b5b56b1d2d57e6c13494b4e65d000e7 /scripts/automation/trex_control_plane/client |
first version
Diffstat (limited to 'scripts/automation/trex_control_plane/client')
4 files changed, 1166 insertions, 0 deletions
diff --git a/scripts/automation/trex_control_plane/client/__init__.py b/scripts/automation/trex_control_plane/client/__init__.py new file mode 100755 index 00000000..e1d24710 --- /dev/null +++ b/scripts/automation/trex_control_plane/client/__init__.py @@ -0,0 +1 @@ +__all__ = ["trex_client"]
diff --git a/scripts/automation/trex_control_plane/client/outer_packages.py b/scripts/automation/trex_control_plane/client/outer_packages.py new file mode 100755 index 00000000..a7c34e48 --- /dev/null +++ b/scripts/automation/trex_control_plane/client/outer_packages.py @@ -0,0 +1,29 @@ +#!/router/bin/python
+
+import sys,site
+import platform,os
+
+CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
+ROOT_PATH = os.path.abspath(os.path.join(CURRENT_PATH, os.pardir)) # path to trex_control_plane directory
+PATH_TO_PYTHON_LIB = os.path.abspath(os.path.join(ROOT_PATH, 'python_lib'))
+
+
+CLIENT_MODULES = ['enum34-1.0.4',
+ # 'jsonrpclib-0.1.3',
+ 'jsonrpclib-pelix-0.2.5',
+ 'termstyle',
+ 'rpc_exceptions-0.1'
+ ]
+
+def import_client_modules ():
+ sys.path.append(ROOT_PATH)
+ import_module_list(CLIENT_MODULES)
+
+def import_module_list (modules_list):
+ assert(isinstance(modules_list, list))
+ for p in modules_list:
+ full_path = os.path.join(PATH_TO_PYTHON_LIB, p)
+ fix_path = os.path.normcase(full_path) #CURRENT_PATH+p)
+ site.addsitedir(full_path)
+
+import_client_modules()
diff --git a/scripts/automation/trex_control_plane/client/trex_adv_client.py b/scripts/automation/trex_control_plane/client/trex_adv_client.py new file mode 100755 index 00000000..b3fe3dad --- /dev/null +++ b/scripts/automation/trex_control_plane/client/trex_adv_client.py @@ -0,0 +1,70 @@ +#!/router/bin/python + +import trex_client +from jsonrpclib import ProtocolError, AppError + +class CTRexAdvClient(trex_client.CTRexClient): + def __init__ (self, trex_host, max_history_size = 100, trex_daemon_port = 8090, trex_zmq_port = 4500, verbose = False): + super(CTRexAdvClient, self).__init__(trex_host, max_history_size, trex_daemon_port, trex_zmq_port, verbose) + pass + + # T-REX KIWI advanced methods + def start_quick_trex(self, pcap_file, d, delay, dual, ipv6, times, interfaces): + try: + return self.server.start_quick_trex(pcap_file = pcap_file, duration = d, dual = dual, delay = delay, ipv6 = ipv6, times = times, interfaces = interfaces) + except AppError as err: + self.__handle_AppError_exception(err.args[0]) + except ProtocolError: + raise + finally: + self.prompt_verbose_data() + + def stop_quick_trex(self): + try: + return self.server.stop_quick_trex() + except AppError as err: + self.__handle_AppError_exception(err.args[0]) + except ProtocolError: + raise + finally: + self.prompt_verbose_data() + +# def is_running(self): +# pass + + def get_running_stats(self): + try: + return self.server.get_running_stats() + except AppError as err: + self.__handle_AppError_exception(err.args[0]) + except ProtocolError: + raise + finally: + self.prompt_verbose_data() + + def clear_counters(self): + try: + return self.server.clear_counters() + except AppError as err: + self.__handle_AppError_exception(err.args[0]) + except ProtocolError: + raise + finally: + self.prompt_verbose_data() + + +if __name__ == "__main__": + trex = CTRexAdvClient('trex-dan', trex_daemon_port = 8383, verbose = True) + print trex.start_quick_trex(delay = 10, + dual = True, + d = 20, + interfaces = ["gig0/0/1", "gig0/0/2"], + ipv6 = False, + pcap_file="avl/http_browsing.pcap", + times=3) + print trex.stop_quick_trex() + print trex.get_running_stats() + print trex.clear_counters() + pass + + diff --git a/scripts/automation/trex_control_plane/client/trex_client.py b/scripts/automation/trex_control_plane/client/trex_client.py new file mode 100755 index 00000000..1f297538 --- /dev/null +++ b/scripts/automation/trex_control_plane/client/trex_client.py @@ -0,0 +1,1066 @@ +#!/router/bin/python + +import sys +import os + +try: + # support import for Python 2 + import outer_packages +except ImportError: + # support import for Python 3 + import client.outer_packages +import jsonrpclib +from jsonrpclib import ProtocolError, AppError +from common.trex_status_e import TRexStatus +from common.trex_exceptions import * +from common.trex_exceptions import exception_handler +from client_utils.general_utils import * +from enum import Enum +import socket +import errno +import time +import re +import copy +import binascii +from collections import deque +from json import JSONDecoder +from distutils.util import strtobool + + + +class CTRexClient(object): + """ + This class defines the client side of the RESTfull interaction with T-Rex + """ + + def __init__(self, trex_host, max_history_size = 100, trex_daemon_port = 8090, trex_zmq_port = 4500, verbose = False): + """ + Instatiate a T-Rex client object, and connecting it to listening deamon-server + + :parameters: + trex_host : str + a string of the t-rex ip address or hostname. + max_history_size : int + a number to set the maximum history size of a single T-Rex run. Each sampling adds a new item to history. + + default value : **100** + trex_daemon_port : int + the port number on which the trex-deamon server can be reached + + default value: **8090** + trex_zmq_port : int + the port number on which trex's zmq module will interact with deamon server + + default value: **4500** + verbose : bool + sets a verbose output on suported class method. + + default value : **False** + + :raises: + socket errors, in case server could not be reached. + + """ + self.trex_host = trex_host + self.trex_daemon_port = trex_daemon_port + self.trex_zmq_port = trex_zmq_port + self.seq = None + self.verbose = verbose + self.result_obj = CTRexResult(max_history_size) + self.decoder = JSONDecoder() + self.trex_server_path = "http://{hostname}:{port}/".format( hostname = trex_host, port = trex_daemon_port ) + self.__verbose_print("Connecting to T-Rex @ {trex_path} ...".format( trex_path = self.trex_server_path ) ) + self.history = jsonrpclib.history.History() + self.server = jsonrpclib.Server(self.trex_server_path, history = self.history) + self.check_server_connectivity() + self.__verbose_print("Connection established successfully!") + self._last_sample = time.time() + self.__default_user = get_current_user() + + + def add (self, x, y): + try: + return self.server.add(x,y) + except AppError as err: + self._handle_AppError_exception(err.args[0]) + except ProtocolError: + raise + finally: + self.prompt_verbose_data() + + def start_trex (self, f, d, block_to_success = True, timeout = 30, user = None, **trex_cmd_options): + """ + Request to start a T-Rex run on server. + + :parameters: + f : str + a path (on server) for the injected traffic data (.yaml file) + d : int + the desired duration of the test. must be at least 30 seconds long. + block_to_success : bool + determine if this method blocks until T-Rex changes state from 'Starting' to either 'Idle' or 'Running' + + default value : **True** + timeout : int + maximum time (in seconds) to wait in blocking state until T-Rex changes state from 'Starting' to either 'Idle' or 'Running' + + default value: **30** + user : str + the identity of the the run issuer. + trex_cmd_options : key, val + sets desired T-Rex options using key=val syntax, separated by comma. + for keys with no value, state key=True + + :return: + **True** on success + + :raises: + + :exc:`ValueError`, in case 'd' parameter inserted with wrong value. + + :exc:`trex_exceptions.TRexError`, in case one of the trex_cmd_options raised an exception at server. + + :exc:`trex_exceptions.TRexInUseError`, in case T-Rex is already taken. + + :exc:`trex_exceptions.TRexRequestDenied`, in case T-Rex is reserved for another user than the one trying start T-Rex. + + ProtocolError, in case of error in JSON-RPC protocol. + + """ + user = user or self.__default_user + try: + d = int(d) + if d < 30: # specify a test should take at least 30 seconds long. + raise ValueError + except ValueError: + raise ValueError('d parameter must be integer, specifying how long T-Rex run, and must be larger than 30 secs.') + + trex_cmd_options.update( {'f' : f, 'd' : d} ) + + self.result_obj.clear_results() + try: + issue_time = time.time() + retval = self.server.start_trex(trex_cmd_options, user, block_to_success, timeout) + except AppError as err: + self._handle_AppError_exception(err.args[0]) + except ProtocolError: + raise + finally: + self.prompt_verbose_data() + + if retval!=0: + self.seq = retval # update seq num only on successful submission + return True + else: # T-Rex is has been started by another user + raise TRexInUseError('T-Rex is already being used by another user or process. Try again once T-Rex is back in IDLE state.') + + def stop_trex (self): + """ + Request to stop a T-Rex run on server. + + The request is only valid if the stop intitiator is the same client as the T-Rex run intitiator. + + :parameters: + None + + :return: + + **True** on successful termination + + **False** if request issued but T-Rex wasn't running. + + :raises: + + :exc:`trex_exceptions.TRexRequestDenied`, in case T-Rex ir running but started by another user. + + :exc:`trex_exceptions.TRexIncompleteRunError`, in case one of failed T-Rex run (unexpected termination). + + ProtocolError, in case of error in JSON-RPC protocol. + + """ + try: + return self.server.stop_trex(self.seq) + except AppError as err: + self._handle_AppError_exception(err.args[0]) + except ProtocolError: + raise + finally: + self.prompt_verbose_data() + + def force_kill (self, confirm = True): + """ + Force killing of running T-Rex process (if exists) on the server. + + .. tip:: This method is a safety method and **overrides any running or reserved resources**, and as such isn't designed to be used on a regular basis. + Always consider using :func:`trex_client.CTRexClient.stop_trex` instead. + + In the end of this method, T-Rex will return to IDLE state with no reservation. + + :parameters: + confirm : bool + Prompt a user confirmation before continue terminating T-Rex session + + :return: + + **True** on successful termination + + **False** otherwise. + + :raises: + + ProtocolError, in case of error in JSON-RPC protocol. + + """ + if confirm: + prompt = "WARNING: This will terminate active T-Rex session indiscriminately.\nAre you sure? " + sys.stdout.write('%s [y/n]\n' % prompt) + while True: + try: + if strtobool(user_input().lower()): + break + else: + return + except ValueError: + sys.stdout.write('Please respond with \'y\' or \'n\'.\n') + try: + return self.server.force_trex_kill() + except AppError as err: + # Silence any kind of application errors- by design + return False + except ProtocolError: + raise + finally: + self.prompt_verbose_data() + + def wait_until_kickoff_finish(self, timeout = 40): + """ + Block the client application until T-Rex changes state from 'Starting' to either 'Idle' or 'Running' + + The request is only valid if the stop intitiator is the same client as the T-Rex run intitiator. + + :parameters: + timeout : int + maximum time (in seconds) to wait in blocking state until T-Rex changes state from 'Starting' to either 'Idle' or 'Running' + + :return: + + **True** on successful termination + + **False** if request issued but T-Rex wasn't running. + + :raises: + + :exc:`trex_exceptions.TRexIncompleteRunError`, in case one of failed T-Rex run (unexpected termination). + + ProtocolError, in case of error in JSON-RPC protocol. + + .. note:: Exceptions are throws only when start_trex did not block in the first place, i.e. `block_to_success` parameter was set to `False` + + """ + + try: + return self.server.wait_until_kickoff_finish(timeout) + except AppError as err: + self._handle_AppError_exception(err.args[0]) + except ProtocolError: + raise + finally: + self.prompt_verbose_data() + + def is_running (self, dump_out = False): + """ + Poll for T-Rex running status. + + If T-Rex is running, a history item will be added into result_obj and processed. + + .. tip:: This method is especially useful for iterating until T-Rex run is finished. + + :parameters: + dump_out : dict + if passed, the pointer object is cleared and the latest dump stored in it. + + :return: + + **True** if T-Rex is running. + + **False** if T-Rex is not running. + + :raises: + + :exc:`trex_exceptions.TRexIncompleteRunError`, in case one of failed T-Rex run (unexpected termination). + + :exc:`TypeError`, in case JSON stream decoding error. + + ProtocolError, in case of error in JSON-RPC protocol. + + """ + try: + res = self.get_running_info() + if res == {}: + return False + if (dump_out != False) and (isinstance(dump_out, dict)): # save received dump to given 'dump_out' pointer + dump_out.clear() + dump_out.update(res) + return True + except TRexWarning as err: + if err.code == -12: # TRex is either still at 'Starting' state or in Idle state, however NO error occured + return False + except TRexException: + raise + except ProtocolError as err: + raise + finally: + self.prompt_verbose_data() + + def get_trex_files_path (self): + """ + Fetches the local path in which files are stored when pushed to t-rex server from client. + + :parameters: + None + + :return: + string representation of the desired path + + .. note:: The returned path represents a path on the T-Rex server **local machine** + + :raises: + ProtocolError, in case of error in JSON-RPC protocol. + + """ + try: + return (self.server.get_files_path() + '/') + except AppError as err: + self._handle_AppError_exception(err.args[0]) + except ProtocolError: + raise + finally: + self.prompt_verbose_data() + + def get_running_status (self): + """ + Fetches the current T-Rex status. + + If available, a verbose data will accompany the state itself. + + :parameters: + None + + :return: + dictionary with 'state' and 'verbose' keys. + + :raises: + ProtocolError, in case of error in JSON-RPC protocol. + + """ + try: + res = self.server.get_running_status() + res['state'] = TRexStatus(res['state']) + return res + except AppError as err: + self._handle_AppError_exception(err.args[0]) + except ProtocolError: + raise + finally: + self.prompt_verbose_data() + + def get_running_info (self): + """ + Performs single poll of T-Rex running data and process it into the result object (named `result_obj`). + + .. tip:: This method will throw an exception if T-Rex isn't running. Always consider using :func:`trex_client.CTRexClient.is_running` which handles a single poll operation in safer manner. + + :parameters: + None + + :return: + dictionary containing the most updated data dump from T-Rex. + + :raises: + + :exc:`trex_exceptions.TRexIncompleteRunError`, in case one of failed T-Rex run (unexpected termination). + + :exc:`TypeError`, in case JSON stream decoding error. + + ProtocolError, in case of error in JSON-RPC protocol. + + """ + if not self.is_query_relevance(): + # if requested in timeframe smaller than the original sample rate, return the last known data without interacting with server + return self.result_obj.get_latest_dump() + else: + try: + latest_dump = self.decoder.decode( self.server.get_running_info() ) # latest dump is not a dict, but json string. decode it. + self.result_obj.update_result_data(latest_dump) + return latest_dump + except TypeError as inst: + raise TypeError('JSON-RPC data decoding failed. Check out incoming JSON stream.') + except AppError as err: + self._handle_AppError_exception(err.args[0]) + except ProtocolError: + raise + finally: + self.prompt_verbose_data() + + def sample_until_condition (self, condition_func, time_between_samples = 5): + """ + Automatically sets ongoing sampling of T-Rex data, with sampling rate described by time_between_samples. + + On each fetched dump, the condition_func is applied on the result objects, and if returns True, the sampling will stop. + + :parameters: + condition_func : function + function that operates on result_obj and checks if a condition has been met + + .. note:: `condition_finc` is applied on `CTRexResult` object. Make sure to design a relevant method. + time_between_samples : int + determines the time between each sample of the server + + default value : **5** + + :return: + the first result object (see :class:`CTRexResult` for further details) of the T-Rex run on which the condition has been met. + + :raises: + + :exc:`UserWarning`, in case the condition_func method condition hasn't been met + + :exc:`trex_exceptions.TRexIncompleteRunError`, in case one of failed T-Rex run (unexpected termination). + + :exc:`TypeError`, in case JSON stream decoding error. + + ProtocolError, in case of error in JSON-RPC protocol. + + :exc:`Exception`, in case the condition_func suffered from any kind of exception + + """ + # make sure T-Rex is running. raise exceptions here if any + self.wait_until_kickoff_finish() + try: + while self.is_running(): + results = self.get_result_obj() + if condition_func(results): + # if condition satisfied, stop T-Rex and return result object + self.stop_trex() + return results + time.sleep(time_between_samples) + except TRexWarning: + # means we're back to Idle state, and didn't meet our condition + raise UserWarning("T-Rex results condition wasn't met during T-Rex run.") + except Exception: + # this could come from provided method 'condition_func' + raise + + def sample_to_run_finish (self, time_between_samples = 5): + """ + Automatically sets automatically sampling of T-Rex data with sampling rate described by time_between_samples until T-Rex run finished. + + :parameters: + time_between_samples : int + determines the time between each sample of the server + + default value : **5** + + :return: + the latest result object (see :class:`CTRexResult` for further details) with sampled data. + + :raises: + + :exc:`UserWarning`, in case the condition_func method condition hasn't been met + + :exc:`trex_exceptions.TRexIncompleteRunError`, in case one of failed T-Rex run (unexpected termination). + + :exc:`TypeError`, in case JSON stream decoding error. + + ProtocolError, in case of error in JSON-RPC protocol. + + """ + self.wait_until_kickoff_finish() + + try: + while self.is_running(): + time.sleep(time_between_samples) + except TRexWarning: + pass + results = self.get_result_obj() + return results + + + def get_result_obj (self, copy_obj = True): + """ + Returns the result object of the trex_client's instance. + + By default, returns a **copy** of the objects (so that changes to the original object are masked). + + :parameters: + copy_obj : bool + False means that a reference to the original (possibly changing) object are passed + + defaul value : **True** + + :return: + the latest result object (see :class:`CTRexResult` for further details) with sampled data. + + """ + if copy_obj: + return copy.deepcopy(self.result_obj) + else: + return self.result_obj + + def is_reserved (self): + """ + Checks if T-Rex is currently reserved to any user or not. + + :parameters: + None + + :return: + + **True** if T-Rex is reserved. + + **False** otherwise. + + :raises: + ProtocolError, in case of error in JSON-RPC protocol. + + """ + try: + return self.server.is_reserved() + except AppError as err: + self._handle_AppError_exception(err.args[0]) + except ProtocolError: + raise + finally: + self.prompt_verbose_data() + + def reserve_trex (self, user = None): + """ + Reserves the usage of T-Rex to a certain user. + + When T-Rex is reserved, it can't be reserved. + + :parameters: + user : str + a username of the desired owner of T-Rex + + default: current logged user + + :return: + **True** if reservation made successfully + + :raises: + + :exc:`trex_exceptions.TRexRequestDenied`, in case T-Rex is reserved for another user than the one trying to make the reservation. + + :exc:`trex_exceptions.TRexInUseError`, in case T-Rex is currently running. + + ProtocolError, in case of error in JSON-RPC protocol. + + """ + username = user or self.__default_user + try: + return self.server.reserve_trex(user = username) + except AppError as err: + self._handle_AppError_exception(err.args[0]) + except ProtocolError: + raise + finally: + self.prompt_verbose_data() + + def cancel_reservation (self, user = None): + """ + Cancels a current reservation of T-Rex to a certain user. + + When T-Rex is reserved, no other user can start new T-Rex runs. + + + :parameters: + user : str + a username of the desired owner of T-Rex + + default: current logged user + + :return: + + **True** if reservation canceled successfully, + + **False** if there was no reservation at all. + + :raises: + + :exc:`trex_exceptions.TRexRequestDenied`, in case T-Rex is reserved for another user than the one trying to cancel the reservation. + + ProtocolError, in case of error in JSON-RPC protocol. + + """ + + username = user or self.__default_user + try: + return self.server.cancel_reservation(user = username) + except AppError as err: + self._handle_AppError_exception(err.args[0]) + except ProtocolError: + raise + finally: + self.prompt_verbose_data() + + def push_files (self, filepaths): + """ + Pushes a file (or a list of files) to store locally on server. + + :parameters: + filepaths : str or list + a path to a file to be pushed to server. + if a list of paths is passed, all of those will be pushed to server + + :return: + + **True** if file(s) copied successfully. + + **False** otherwise. + + :raises: + + :exc:`IOError`, in case specified file wasn't found or could not be accessed. + + ProtocolError, in case of error in JSON-RPC protocol. + + """ + paths_list = None + if isinstance(filepaths, str): + paths_list = [filepaths] + elif isinstance(filepaths, list): + paths_list = filepaths + else: + raise TypeError("filepaths argument must be of type str or list") + + for filepath in paths_list: + try: + if not os.path.exists(filepath): + raise IOError(errno.ENOENT, "The requested `{fname}` file wasn't found. Operation aborted.".format( + fname = filepath) ) + else: + filename = os.path.basename(filepath) + with open(filepath, 'rb') as f: + file_content = f.read() + self.server.push_file(filename, binascii.b2a_base64(file_content)) + finally: + self.prompt_verbose_data() + return True + + def is_query_relevance(self): + """ + Checks if time between any two consecutive server queries (asking for live running data) passed. + + .. note:: The allowed minimum time between each two consecutive samples is 0.5 seconds. + + :parameters: + None + + :return: + + **True** if more than 0.5 seconds has been past from last server query. + + **False** otherwise. + + """ + cur_time = time.time() + if cur_time-self._last_sample < 0.5: + return False + else: + self._last_sample = cur_time + return True + + def call_server_mathod_safely (self, method_to_call): + try: + return method_to_call() + except socket.error as e: + if e.errno == errno.ECONNREFUSED: + raise SocketError(errno.ECONNREFUSED, "Connection from T-Rex server was refused. Please make sure the server is up.") + + def check_server_connectivity (self): + """ + Checks for server valid connectivity. + """ + try: + socket.gethostbyname(self.trex_host) + return self.server.connectivity_check() + except socket.gaierror as e: + raise socket.gaierror(e.errno, "Could not resolve server hostname. Please make sure hostname entered correctly.") + except socket.error as e: + if e.errno == errno.ECONNREFUSED: + raise socket.error(errno.ECONNREFUSED, "Connection from T-Rex server was refused. Please make sure the server is up.") + finally: + self.prompt_verbose_data() + + def prompt_verbose_data(self): + """ + This method prompts any verbose data available, only if `verbose` option has been turned on. + """ + if self.verbose: + print ('\n') + print ("(*) JSON-RPC request: "+ self.history.request) + print ("(*) JSON-RPC response: "+ self.history.response) + + def __verbose_print(self, print_str): + """ + This private method prints the `print_str` string only in case self.verbose flag is turned on. + + :parameters: + print_str : str + a string to be printed + + :returns: + None + """ + if self.verbose: + print (print_str) + + + + def _handle_AppError_exception(self, err): + """ + This private method triggres the T-Rex dedicated exception generation in case a general ProtocolError has been raised. + """ + # handle known exceptions based on known error codes. + # if error code is not known, raise ProtocolError + raise exception_handler.gen_exception(err) + + +class CTRexResult(object): + """ + A class containing all results received from T-Rex. + + Ontop to containing the results, this class offers easier data access and extended results processing options + """ + def __init__(self, max_history_size): + """ + Instatiate a T-Rex result object + + :parameters: + max_history_size : int + a number to set the maximum history size of a single T-Rex run. Each sampling adds a new item to history. + + """ + self._history = deque(maxlen = max_history_size) + self.clear_results() + + def __repr__(self): + return ("Is valid history? {arg}\n".format( arg = self.is_valid_hist() ) + + "Done warmup? {arg}\n".format( arg = self.is_done_warmup() ) + + "Expected tx rate: {arg}\n".format( arg = self.get_expected_tx_rate() ) + + "Current tx rate: {arg}\n".format( arg = self.get_current_tx_rate() ) + + "Maximum latency: {arg}\n".format( arg = self.get_max_latency() ) + + "Average latency: {arg}\n".format( arg = self.get_avg_latency() ) + + "Average window latency: {arg}\n".format( arg = self.get_avg_window_latency() ) + + "Total drops: {arg}\n".format( arg = self.get_total_drops() ) + + "Drop rate: {arg}\n".format( arg = self.get_drop_rate() ) + + "History size so far: {arg}\n".format( arg = len(self._history) ) ) + + def get_expected_tx_rate (self): + """ + Fetches the expected TX rate in various units representation + + :parameters: + None + + :return: + dictionary containing the expected TX rate, where the key is the measurement units, and the value is the measurement value. + + """ + return self._expected_tx_rate + + def get_current_tx_rate (self): + """ + Fetches the current TX rate in various units representation + + :parameters: + None + + :return: + dictionary containing the current TX rate, where the key is the measurement units, and the value is the measurement value. + + """ + return self._current_tx_rate + + def get_max_latency (self): + """ + Fetches the maximum latency measured on each of the interfaces + + :parameters: + None + + :return: + dictionary containing the maximum latency, where the key is the measurement interface (`c` indicates client), and the value is the measurement value. + + """ + return self._max_latency + + def get_avg_latency (self): + """ + Fetches the average latency measured on each of the interfaces from the start of T-Rex run + + :parameters: + None + + :return: + dictionary containing the average latency, where the key is the measurement interface (`c` indicates client), and the value is the measurement value. + + The `all` key represents the average of all interfaces' average + + """ + return self._avg_latency + + def get_avg_window_latency (self): + """ + Fetches the average latency measured on each of the interfaces from all the sampled currently stored in window. + + :parameters: + None + + :return: + dictionary containing the average latency, where the key is the measurement interface (`c` indicates client), and the value is the measurement value. + + The `all` key represents the average of all interfaces' average + + """ + return self._avg_window_latency + + def get_total_drops (self): + """ + Fetches the total number of drops identified from the moment T-Rex run began. + + :parameters: + None + + :return: + total drops count (as int) + + """ + return self._total_drops + + def get_drop_rate (self): + """ + Fetches the most recent drop rate in pkts/sec units. + + :parameters: + None + + :return: + current drop rate (as float) + + """ + return self._drop_rate + + def is_valid_hist (self): + """ + Checks if result obejct contains valid data. + + :parameters: + None + + :return: + + **True** if history is valid. + + **False** otherwise. + + """ + return self.valid + + def set_valid_hist (self, valid_stat = True): + """ + Sets result obejct validity status. + + :parameters: + valid_stat : bool + defines the validity status + + dafault value : **True** + + :return: + None + + """ + self.valid = valid_stat + + def is_done_warmup (self): + """ + Checks if T-Rex latest results TX-rate indicates that T-Rex has reached its expected TX-rate. + + :parameters: + None + + :return: + + **True** if expected TX-rate has been reached. + + **False** otherwise. + + """ + return self._done_warmup + + def get_last_value (self, tree_path_to_key, regex = None): + """ + A dynamic getter from the latest sampled data item stored in the result object. + + :parameters: + tree_path_to_key : str + defines a path to desired data. + + .. tip:: | Use '.' to enter one level deeper in dictionary hierarchy. + | Use '[i]' to access the i'th indexed obejct of an array. + + tree_path_to_key : regex + apply a regex to filter results out from a multiple results set. + + Filter applies only on keys of dictionary type. + + dafault value : **None** + + :return: + + a list of values relevant to the specified path + + None if no results were fetched or the history isn't valid. + + """ + if not self.is_valid_hist(): + return None + else: + return CTRexResult.__get_value_by_path(self._history[len(self._history)-1], tree_path_to_key, regex) + + def get_value_list (self, tree_path_to_key, regex = None, filter_none = True): + """ + A dynamic getter from all sampled data items stored in the result object. + + :parameters: + tree_path_to_key : str + defines a path to desired data. + + .. tip:: | Use '.' to enter one level deeper in dictionary hierarchy. + | Use '[i]' to access the i'th indexed object of an array. + + tree_path_to_key : regex + apply a regex to filter results out from a multiple results set. + + Filter applies only on keys of dictionary type. + + dafault value : **None** + + filter_none : bool + specify if None results should be filtered out or not. + + dafault value : **True** + + :return: + + a list of values relevant to the specified path. Each item on the list refers to a single server sample. + + None if no results were fetched or the history isn't valid. + """ + + if not self.is_valid_hist(): + return None + else: + raw_list = list( map(lambda x: CTRexResult.__get_value_by_path(x, tree_path_to_key, regex), self._history) ) + if filter_none: + return list (filter(lambda x: x!=None, raw_list) ) + else: + return raw_list + + def get_latest_dump(self): + """ + A getter to the latest sampled data item stored in the result object. + + :parameters: + None + + :return: + + a dictionary of the latest data item + + an empty dictionary if history is empty. + + """ + history_size = len(self._history) + if history_size != 0: + return self._history[len(self._history) - 1] + else: + return {} + + def update_result_data (self, latest_dump): + """ + Integrates a `latest_dump` dictionary into the CTRexResult object. + + :parameters: + latest_dump : dict + a dictionary with the items desired to be integrated into the object history and stats + + :return: + None + + """ + # add latest dump to history + if latest_dump != {}: + self._history.append(latest_dump) + if not self.valid: + self.valid = True + + # parse important fields and calculate averages and others + if self._expected_tx_rate is None: + # get the expected data only once since it doesn't change + self._expected_tx_rate = CTRexResult.__get_value_by_path(latest_dump, "trex-global.data", "m_tx_expected_\w+") + + self._current_tx_rate = CTRexResult.__get_value_by_path(latest_dump, "trex-global.data", "m_tx_(?!expected_)\w+") + if not self._done_warmup and self._expected_tx_rate is not None: + # check for up to 2% change between expected and actual + if (self._current_tx_rate['m_tx_bps']/self._expected_tx_rate['m_tx_expected_bps'] > 0.98): + self._done_warmup = True + + # handle latency data + latency_pre = "trex-latency" + self._max_latency = self.get_last_value("{latency}.data".format(latency = latency_pre), ".*max-")#None # TBC + # support old typo + if self._max_latency is None: + latency_pre = "trex-latecny" + self._max_latency = self.get_last_value("{latency}.data".format(latency = latency_pre), ".*max-") + + self._avg_latency = self.get_last_value("{latency}.data".format(latency = latency_pre), "avg-")#None # TBC + self._avg_latency = CTRexResult.__avg_all_and_rename_keys(self._avg_latency) + + avg_win_latency_list = self.get_value_list("{latency}.data".format(latency = latency_pre), "avg-") + self._avg_window_latency = CTRexResult.__calc_latency_win_stats(avg_win_latency_list) + + tx_pkts = CTRexResult.__get_value_by_path(latest_dump, "trex-global.data.m_total_tx_pkts") + rx_pkts = CTRexResult.__get_value_by_path(latest_dump, "trex-global.data.m_total_rx_pkts") + if tx_pkts is not None and rx_pkts is not None: + self._total_drops = tx_pkts - rx_pkts + self._drop_rate = CTRexResult.__get_value_by_path(latest_dump, "trex-global.data.m_rx_drop_bps") + + def clear_results (self): + """ + Clears all results and sets the history's validity to `False` + + :parameters: + None + + :return: + None + + """ + self.valid = False + self._done_warmup = False + self._expected_tx_rate = None + self._current_tx_rate = None + self._max_latency = None + self._avg_latency = None + self._avg_window_latency = None + self._total_drops = None + self._drop_rate = None + self._history.clear() + + @staticmethod + def __get_value_by_path (dct, tree_path, regex = None): + try: + for i, p in re.findall(r'(\d+)|([\w|-]+)', tree_path): + dct = dct[p or int(i)] + if regex is not None and isinstance(dct, dict): + res = {} + for key,val in dct.items(): + match = re.match(regex, key) + if match: + res[key]=val + return res + else: + return dct + except (KeyError, TypeError): + return None + + @staticmethod + def __calc_latency_win_stats (latency_win_list): + res = {'all' : None } + port_dict = {'all' : []} + list( map(lambda x: CTRexResult.__update_port_dict(x, port_dict), latency_win_list) ) + + # finally, calculate everages for each list + res['all'] = float("%.3f" % (sum(port_dict['all'])/float(len(port_dict['all']))) ) + port_dict.pop('all') + for port, avg_list in port_dict.items(): + res[port] = float("%.3f" % (sum(avg_list)/float(len(avg_list))) ) + + return res + + @staticmethod + def __update_port_dict (src_avg_dict, dest_port_dict): + all_list = src_avg_dict.values() + dest_port_dict['all'].extend(all_list) + for key, val in src_avg_dict.items(): + reg_res = re.match("avg-(\d+)", key) + if reg_res: + tmp_key = "port"+reg_res.group(1) + if tmp_key in dest_port_dict: + dest_port_dict[tmp_key].append(val) + else: + dest_port_dict[tmp_key] = [val] + + @staticmethod + def __avg_all_and_rename_keys (src_dict): + res = {} + all_list = src_dict.values() + res['all'] = float("%.3f" % (sum(all_list)/float(len(all_list))) ) + for key, val in src_dict.items(): + reg_res = re.match("avg-(\d+)", key) + if reg_res: + tmp_key = "port"+reg_res.group(1) + res[tmp_key] = val # don't touch original fields values + return res + + + +if __name__ == "__main__": + pass + + + |