#!/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): """ Instantiate a T-Rex client object, and connecting it to listening daemon-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-daemon server can be reached default value: **8090** trex_zmq_port : int the port number on which trex's zmq module will interact with daemon server default value: **4500** verbose : bool sets a verbose output on supported 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 initiator is the same client as the T-Rex run initiator. :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 initiator is the same client as the T-Rex run initiator. :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