From 300ec24108211721d99ac9faf67fd1f91057b95b Mon Sep 17 00:00:00 2001 From: Yaroslav Brustinov Date: Thu, 2 Jun 2016 16:24:49 +0300 Subject: cpu utilization per thread + mbufs per socket + add in tui --- .../stl/trex_stl_lib/trex_stl_client.py | 24 +++++- .../stl/trex_stl_lib/trex_stl_stats.py | 93 +++++++++++++++++++++- .../stl/trex_stl_lib/utils/parsing_opts.py | 14 +++- 3 files changed, 123 insertions(+), 8 deletions(-) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py index 22895a75..70f38bb0 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py @@ -555,14 +555,17 @@ class STLClient(object): self.latency_stats = trex_stl_stats.CLatencyStats(self.ports) + self.util_stats = trex_stl_stats.CUtilStats(self) + self.stats_generator = trex_stl_stats.CTRexInfoGenerator(self.global_stats, self.ports, self.flow_stats, self.latency_stats, + self.util_stats, self.async_client.monitor) - + ############# private functions - used by the class itself ########### @@ -1519,6 +1522,23 @@ class STLClient(object): if not rc: raise STLError(rc) + @__api_check(True) + def get_util_stats(self): + """ + Get utilization stats: + History of TRex CPU utilization per thread (list of lists) + MBUFs memory consumption per CPU socket. + + :parameters: + None + + :raises: + + :exc:`STLError` + + """ + self.logger.pre_cmd('Getting Utilization stats') + return self.util_stats.get_stats() + @__api_check(True) def reset(self, ports = None): @@ -1739,8 +1759,6 @@ class STLClient(object): raise STLError(rc) - - @__api_check(True) def stop (self, ports = None, rx_delay_ms = 10): """ diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py index 94a45577..06009ac3 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py @@ -11,7 +11,6 @@ import datetime import time import re import math -import copy import threading import pprint @@ -22,13 +21,16 @@ PORT_STATUS = 'ps' STREAMS_STATS = 's' LATENCY_STATS = 'ls' LATENCY_HISTOGRAM = 'lh' +CPU_STATS = 'c' +MBUF_STATS = 'm' -ALL_STATS_OPTS = [GLOBAL_STATS, PORT_STATS, PORT_STATUS, STREAMS_STATS, LATENCY_STATS, PORT_GRAPH, LATENCY_HISTOGRAM] +ALL_STATS_OPTS = [GLOBAL_STATS, PORT_STATS, PORT_STATUS, STREAMS_STATS, LATENCY_STATS, PORT_GRAPH, LATENCY_HISTOGRAM, CPU_STATS, MBUF_STATS] COMPACT = [GLOBAL_STATS, PORT_STATS] GRAPH_PORT_COMPACT = [GLOBAL_STATS, PORT_GRAPH] SS_COMPAT = [GLOBAL_STATS, STREAMS_STATS] # stream stats LS_COMPAT = [GLOBAL_STATS, LATENCY_STATS] # latency stats LH_COMPAT = [GLOBAL_STATS, LATENCY_HISTOGRAM] # latency histogram +UT_COMPAT = [GLOBAL_STATS, CPU_STATS, MBUF_STATS] # utilization ExportableStats = namedtuple('ExportableStats', ['raw_data', 'text_table']) @@ -104,6 +106,13 @@ def calculate_diff_raw (samples): return total +# used to sort '64b', '9kb' etc. +def key_cmp_bytes(val): + multiplier = 1 + if 'kb' in val: + multiplier = 1000 + return multiplier * int(val.replace('k', '').replace('b', '')) + # a simple object to keep a watch over a field class WatchedField(object): @@ -138,11 +147,12 @@ class CTRexInfoGenerator(object): STLClient and the ports. """ - def __init__(self, global_stats_ref, ports_dict_ref, rx_stats_ref, latency_stats_ref, async_monitor): + def __init__(self, global_stats_ref, ports_dict_ref, rx_stats_ref, latency_stats_ref, util_stats_ref, async_monitor): self._global_stats = global_stats_ref self._ports_dict = ports_dict_ref self._rx_stats_ref = rx_stats_ref self._latency_stats_ref = latency_stats_ref + self._util_stats_ref = util_stats_ref self._async_monitor = async_monitor def generate_single_statistic(self, port_id_list, statistic_type): @@ -167,6 +177,12 @@ class CTRexInfoGenerator(object): elif statistic_type == LATENCY_HISTOGRAM: return self._generate_latency_histogram() + elif statistic_type == CPU_STATS: + return self._generate_cpu_util_stats() + + elif statistic_type == MBUF_STATS: + return self._generate_mbuf_util_stats() + else: # ignore by returning empty object return {} @@ -404,6 +420,59 @@ class CTRexInfoGenerator(object): stats_table.header(header) return {"latency_histogram": ExportableStats(None, stats_table)} + def _generate_cpu_util_stats(self): + util_stats = self._util_stats_ref.get_stats() + if not util_stats or 'cpu' not in util_stats: + raise Exception("Excepting 'cpu' section in stats %s" % util_stats) + cpu_stats = util_stats['cpu'] + hist_len = len(cpu_stats[0]) + avg_len = min(5, hist_len) + show_len = min(15, hist_len) + stats_table = text_tables.TRexTextTable() + stats_table.header(['Thread', 'Avg', 'Latest'] + list(range(-1, 0 - show_len, -1))) + stats_table.set_cols_align(['l'] + ['r'] * (show_len + 1)) + stats_table.set_cols_width([8, 3, 6] + [3] * (show_len - 1)) + stats_table.set_cols_dtype(['t'] * (show_len + 2)) + for i in range(min(14, len(cpu_stats))): + avg = int(round(sum(cpu_stats[i][:avg_len]) / avg_len)) + stats_table.add_row([i, avg] + cpu_stats[i][:show_len]) + return {'cpu_util(%)': ExportableStats(None, stats_table)} + + def _generate_mbuf_util_stats(self): + util_stats = self._util_stats_ref.get_stats() + if not util_stats or 'mbuf_stats' not in util_stats: + raise Exception("Excepting 'mbuf_stats' section in stats %s" % util_stats) + mbuf_stats = util_stats['mbuf_stats'] + for mbufs_per_socket in mbuf_stats.values(): + first_socket_mbufs = mbufs_per_socket + break + if not self._util_stats_ref.mbuf_types_list: + mbuf_keys = list(first_socket_mbufs.keys()) + mbuf_keys.sort(key = key_cmp_bytes) + self._util_stats_ref.mbuf_types_list = mbuf_keys + types_len = len(self._util_stats_ref.mbuf_types_list) + stats_table = text_tables.TRexTextTable() + stats_table.set_cols_align(['l'] + ['r'] * types_len) + stats_table.set_cols_width([10] + [7] * types_len) + stats_table.set_cols_dtype(['t'] * (types_len + 1)) + stats_table.header([''] + self._util_stats_ref.mbuf_types_list) + total_list = [] + for mbuf_type in self._util_stats_ref.mbuf_types_list: + total_list.append(first_socket_mbufs[mbuf_type][1]) + stats_table.add_row(['Total:'] + total_list) + stats_table.add_row(['Used:'] + [''] * types_len) + for socket_name, mbufs in mbuf_stats.items(): + socket_show_name = socket_name.replace('cpu-', '').replace('-', ' ').capitalize() + ':' + used_list = [] + percentage_list = [] + for mbuf_type in self._util_stats_ref.mbuf_types_list: + used = mbufs[mbuf_type][1] - mbufs[mbuf_type][0] + used_list.append(used) + percentage_list.append('%s%%' % int(100 * used / mbufs[mbuf_type][1])) + stats_table.add_row([socket_show_name] + used_list) + stats_table.add_row(['Percent:'] + percentage_list) + return {'mbuf_util': ExportableStats(None, stats_table)} + @staticmethod def _get_rational_block_char(value, range_start, interval): # in Konsole, utf-8 is sometimes printed with artifacts, return ascii for now @@ -1280,7 +1349,23 @@ class CRxStats(CTRexStats): return stats - +class CUtilStats(CTRexStats): + + def __init__(self, client): + super(CUtilStats, self).__init__() + self.client = client + self.history = deque(maxlen = 1) + self.mbuf_types_list = None + + def get_stats(self, force = False): + time_now = time.time() + if self.last_update_ts + 1 < time_now or not self.history or force: + rc = self.client._transmit('get_utilization') + if not rc: + raise Exception(rc) + self.last_update_ts = time_now + self.history.append(rc.data()) + return self.history[-1] if __name__ == "__main__": pass diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py index 98e3ca6a..a435e54e 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py @@ -39,6 +39,8 @@ PORT_STATS = 51 PORT_STATUS = 52 STREAMS_STATS = 53 STATS_MASK = 54 +CPU_STATS = 55 +MBUF_STATS = 56 STREAMS_MASK = 60 # ALL_STREAMS = 61 @@ -346,6 +348,14 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], {'action': 'store_true', 'help': "Fetch only streams stats"}), + CPU_STATS: ArgumentPack(['-c'], + {'action': 'store_true', + 'help': "Fetch only CPU utilization stats"}), + + MBUF_STATS: ArgumentPack(['-m'], + {'action': 'store_true', + 'help': "Fetch only MBUF utilization stats"}), + STREAMS_MASK: ArgumentPack(['--streams'], {"nargs": '+', 'dest':'streams', @@ -371,7 +381,9 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], STATS_MASK: ArgumentGroup(MUTEX, [GLOBAL_STATS, PORT_STATS, PORT_STATUS, - STREAMS_STATS], + STREAMS_STATS, + CPU_STATS, + MBUF_STATS], {}) } -- cgit From 5216c6a22a2324bf555b590b34594a63d8aeeb7d Mon Sep 17 00:00:00 2001 From: Yaroslav Brustinov Date: Sun, 5 Jun 2016 13:19:54 +0300 Subject: cpu utilization Python get_stats replace "force" flag with "use_1sec_cache", default False. --- .../trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py index 132d34ee..d23dd33f 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py @@ -421,7 +421,7 @@ class CTRexInfoGenerator(object): return {"latency_histogram": ExportableStats(None, stats_table)} def _generate_cpu_util_stats(self): - util_stats = self._util_stats_ref.get_stats() + util_stats = self._util_stats_ref.get_stats(use_1sec_cache = True) if not util_stats or 'cpu' not in util_stats: raise Exception("Excepting 'cpu' section in stats %s" % util_stats) cpu_stats = util_stats['cpu'] @@ -439,7 +439,7 @@ class CTRexInfoGenerator(object): return {'cpu_util(%)': ExportableStats(None, stats_table)} def _generate_mbuf_util_stats(self): - util_stats = self._util_stats_ref.get_stats() + util_stats = self._util_stats_ref.get_stats(use_1sec_cache = True) if not util_stats or 'mbuf_stats' not in util_stats: raise Exception("Excepting 'mbuf_stats' section in stats %s" % util_stats) mbuf_stats = util_stats['mbuf_stats'] @@ -1358,9 +1358,9 @@ class CUtilStats(CTRexStats): self.history = deque(maxlen = 1) self.mbuf_types_list = None - def get_stats(self, force = False): + def get_stats(self, use_1sec_cache = False): time_now = time.time() - if self.last_update_ts + 1 < time_now or not self.history or force: + if self.last_update_ts + 1 < time_now or not self.history or not use_1sec_cache: rc = self.client._transmit('get_utilization') if not rc: raise Exception(rc) -- cgit From 1eed7e59f23d3ab9b957d9822eefe72877e291da Mon Sep 17 00:00:00 2001 From: Yaroslav Brustinov Date: Mon, 6 Jun 2016 14:32:36 +0300 Subject: MBUF utilization: add size of RAM (MB) the MBUFs take --- .../stl/trex_stl_lib/trex_stl_stats.py | 124 ++++++++++++--------- 1 file changed, 71 insertions(+), 53 deletions(-) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py index d23dd33f..990a40da 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py @@ -106,12 +106,12 @@ def calculate_diff_raw (samples): return total -# used to sort '64b', '9kb' etc. -def key_cmp_bytes(val): - multiplier = 1 - if 'kb' in val: - multiplier = 1000 - return multiplier * int(val.replace('k', '').replace('b', '')) +get_number_of_bytes_cache = {} +# get number of bytes: '64b'->64, '9kb'->9000 etc. +def get_number_of_bytes(val): + if val not in get_number_of_bytes_cache: + get_number_of_bytes_cache[val] = int(val[:-1].replace('k', '000')) + return get_number_of_bytes_cache[val] # a simple object to keep a watch over a field class WatchedField(object): @@ -422,55 +422,69 @@ class CTRexInfoGenerator(object): def _generate_cpu_util_stats(self): util_stats = self._util_stats_ref.get_stats(use_1sec_cache = True) - if not util_stats or 'cpu' not in util_stats: - raise Exception("Excepting 'cpu' section in stats %s" % util_stats) - cpu_stats = util_stats['cpu'] - hist_len = len(cpu_stats[0]) - avg_len = min(5, hist_len) - show_len = min(15, hist_len) stats_table = text_tables.TRexTextTable() - stats_table.header(['Thread', 'Avg', 'Latest'] + list(range(-1, 0 - show_len, -1))) - stats_table.set_cols_align(['l'] + ['r'] * (show_len + 1)) - stats_table.set_cols_width([8, 3, 6] + [3] * (show_len - 1)) - stats_table.set_cols_dtype(['t'] * (show_len + 2)) - for i in range(min(14, len(cpu_stats))): - avg = int(round(sum(cpu_stats[i][:avg_len]) / avg_len)) - stats_table.add_row([i, avg] + cpu_stats[i][:show_len]) + if util_stats: + if 'cpu' not in util_stats: + raise Exception("Excepting 'cpu' section in stats %s" % util_stats) + cpu_stats = util_stats['cpu'] + hist_len = len(cpu_stats[0]) + avg_len = min(5, hist_len) + show_len = min(15, hist_len) + stats_table.header(['Thread', 'Avg', 'Latest'] + list(range(-1, 0 - show_len, -1))) + stats_table.set_cols_align(['l'] + ['r'] * (show_len + 1)) + stats_table.set_cols_width([8, 3, 6] + [3] * (show_len - 1)) + stats_table.set_cols_dtype(['t'] * (show_len + 2)) + for i in range(min(14, len(cpu_stats))): + avg = int(round(sum(cpu_stats[i][:avg_len]) / avg_len)) + stats_table.add_row([i, avg] + cpu_stats[i][:show_len]) + else: + stats_table.add_row(['No Data.']) return {'cpu_util(%)': ExportableStats(None, stats_table)} def _generate_mbuf_util_stats(self): util_stats = self._util_stats_ref.get_stats(use_1sec_cache = True) - if not util_stats or 'mbuf_stats' not in util_stats: - raise Exception("Excepting 'mbuf_stats' section in stats %s" % util_stats) - mbuf_stats = util_stats['mbuf_stats'] - for mbufs_per_socket in mbuf_stats.values(): - first_socket_mbufs = mbufs_per_socket - break - if not self._util_stats_ref.mbuf_types_list: - mbuf_keys = list(first_socket_mbufs.keys()) - mbuf_keys.sort(key = key_cmp_bytes) - self._util_stats_ref.mbuf_types_list = mbuf_keys - types_len = len(self._util_stats_ref.mbuf_types_list) stats_table = text_tables.TRexTextTable() - stats_table.set_cols_align(['l'] + ['r'] * types_len) - stats_table.set_cols_width([10] + [7] * types_len) - stats_table.set_cols_dtype(['t'] * (types_len + 1)) - stats_table.header([''] + self._util_stats_ref.mbuf_types_list) - total_list = [] - for mbuf_type in self._util_stats_ref.mbuf_types_list: - total_list.append(first_socket_mbufs[mbuf_type][1]) - stats_table.add_row(['Total:'] + total_list) - stats_table.add_row(['Used:'] + [''] * types_len) - for socket_name, mbufs in mbuf_stats.items(): - socket_show_name = socket_name.replace('cpu-', '').replace('-', ' ').capitalize() + ':' - used_list = [] - percentage_list = [] + if util_stats: + if 'mbuf_stats' not in util_stats: + raise Exception("Excepting 'mbuf_stats' section in stats %s" % util_stats) + mbuf_stats = util_stats['mbuf_stats'] + for mbufs_per_socket in mbuf_stats.values(): + first_socket_mbufs = mbufs_per_socket + break + if not self._util_stats_ref.mbuf_types_list: + mbuf_keys = list(first_socket_mbufs.keys()) + mbuf_keys.sort(key = get_number_of_bytes) + self._util_stats_ref.mbuf_types_list = mbuf_keys + types_len = len(self._util_stats_ref.mbuf_types_list) + stats_table.set_cols_align(['l'] + ['r'] * (types_len + 1)) + stats_table.set_cols_width([10] + [7] * (types_len + 1)) + stats_table.set_cols_dtype(['t'] * (types_len + 2)) + stats_table.header([''] + self._util_stats_ref.mbuf_types_list + ['RAM(MB)']) + total_list = [] + sum_totals = 0 for mbuf_type in self._util_stats_ref.mbuf_types_list: - used = mbufs[mbuf_type][1] - mbufs[mbuf_type][0] - used_list.append(used) - percentage_list.append('%s%%' % int(100 * used / mbufs[mbuf_type][1])) - stats_table.add_row([socket_show_name] + used_list) - stats_table.add_row(['Percent:'] + percentage_list) + sum_totals += first_socket_mbufs[mbuf_type][1] * get_number_of_bytes(mbuf_type) + 64 + total_list.append(first_socket_mbufs[mbuf_type][1]) + sum_totals *= len(list(mbuf_stats.values())) + total_list.append(int(sum_totals/1e6)) + stats_table.add_row(['Total:'] + total_list) + stats_table.add_row(['Used:'] + [''] * (types_len + 1)) + for socket_name in sorted(list(mbuf_stats.keys())): + mbufs = mbuf_stats[socket_name] + socket_show_name = socket_name.replace('cpu-', '').replace('-', ' ').capitalize() + ':' + sum_used = 0 + used_list = [] + percentage_list = [] + for mbuf_type in self._util_stats_ref.mbuf_types_list: + used = mbufs[mbuf_type][1] - mbufs[mbuf_type][0] + sum_used += used * get_number_of_bytes(mbuf_type) + 64 + used_list.append(used) + percentage_list.append('%s%%' % int(100 * used / mbufs[mbuf_type][1])) + used_list.append(int(sum_used/1e6)) + stats_table.add_row([socket_show_name] + used_list) + stats_table.add_row(['Percent:'] + percentage_list + ['']) + else: + stats_table.add_row(['No Data.']) return {'mbuf_util': ExportableStats(None, stats_table)} @staticmethod @@ -1357,15 +1371,19 @@ class CUtilStats(CTRexStats): self.client = client self.history = deque(maxlen = 1) self.mbuf_types_list = None + self.last_update_ts = 0 def get_stats(self, use_1sec_cache = False): time_now = time.time() if self.last_update_ts + 1 < time_now or not self.history or not use_1sec_cache: - rc = self.client._transmit('get_utilization') - if not rc: - raise Exception(rc) - self.last_update_ts = time_now - self.history.append(rc.data()) + if self.client.is_connected(): + rc = self.client._transmit('get_utilization') + if not rc: + raise Exception(rc) + self.last_update_ts = time_now + self.history.append(rc.data()) + else: + self.history.append({}) return self.history[-1] if __name__ == "__main__": -- cgit From 07d5732a49bab856ab773e4a4762a7983cb5a538 Mon Sep 17 00:00:00 2001 From: Yaroslav Brustinov Date: Thu, 9 Jun 2016 11:55:31 +0300 Subject: typo (GitHub pull request #14) --- scripts/automation/trex_control_plane/stl/trex_stl_lib/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/__init__.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/__init__.py index ba9459c1..c6e14df3 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/__init__.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/__init__.py @@ -1,7 +1,7 @@ import sys if sys.version_info < (2, 7): - print("\n**** TRex STL pacakge requires Python version >= 2.7 ***\n") + print("\n**** TRex STL package requires Python version >= 2.7 ***\n") exit(-1) from . import trex_stl_ext -- cgit From 0422016ab056245449e0e5bdf0dceef2c4f06e31 Mon Sep 17 00:00:00 2001 From: imarom Date: Mon, 20 Jun 2016 14:30:46 +0300 Subject: bug: multiplier can be string or unicode --- .../stl/trex_stl_lib/trex_stl_client.py | 41 +++++++++------------- .../stl/trex_stl_lib/trex_stl_exceptions.py | 8 ++++- .../stl/trex_stl_lib/utils/common.py | 1 + .../stl/trex_stl_lib/utils/parsing_opts.py | 4 --- 4 files changed, 25 insertions(+), 29 deletions(-) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py index 22895a75..4d1125c8 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py @@ -1700,6 +1700,11 @@ class STLClient(object): ports = self._validate_port_list(ports) + validate_type('mult', mult, basestring) + validate_type('force', force, bool) + validate_type('duration', duration, (int, float)) + validate_type('total', total, bool) + # verify multiplier mult_obj = parsing_opts.decode_multiplier(mult, allow_update = False, @@ -1707,17 +1712,6 @@ class STLClient(object): if not mult_obj: raise STLArgumentError('mult', mult) - # some type checkings - - if not type(force) is bool: - raise STLArgumentError('force', force) - - if not isinstance(duration, (int, float)): - raise STLArgumentError('duration', duration) - - if not type(total) is bool: - raise STLArgumentError('total', total) - # verify ports are stopped or force stop them active_ports = list(set(self.get_active_ports()).intersection(ports)) @@ -1762,11 +1756,12 @@ class STLClient(object): """ - ports = ports if ports is not None else self.get_active_ports() - ports = self._validate_port_list(ports) + if ports is None: + ports = self.get_active_ports() + if not ports: + return - if not ports: - return + ports = self._validate_port_list(ports) self.logger.pre_cmd("Stopping traffic on port(s) {0}:".format(ports)) rc = self.__stop(ports) @@ -1815,6 +1810,9 @@ class STLClient(object): ports = ports if ports is not None else self.get_active_ports() ports = self._validate_port_list(ports) + validate_type('mult', mult, basestring) + validate_type('force', force, bool) + validate_type('total', total, bool) # verify multiplier mult_obj = parsing_opts.decode_multiplier(mult, @@ -1823,10 +1821,6 @@ class STLClient(object): if not mult_obj: raise STLArgumentError('mult', mult) - # verify total - if not type(total) is bool: - raise STLArgumentError('total', total) - # call low level functions self.logger.pre_cmd("Updating traffic on port(s) {0}:".format(ports)) @@ -2050,6 +2044,10 @@ class STLClient(object): ports = ports if ports is not None else self.get_acquired_ports() ports = self._validate_port_list(ports) + validate_type('mult', mult, basestring) + validate_type('duration', duration, (int, float)) + validate_type('total', total, bool) + # verify multiplier mult_obj = parsing_opts.decode_multiplier(mult, @@ -2058,11 +2056,6 @@ class STLClient(object): if not mult_obj: raise STLArgumentError('mult', mult) - - if not isinstance(duration, (int, float)): - raise STLArgumentError('duration', duration) - - self.logger.pre_cmd("Validating streams on port(s) {0}:".format(ports)) rc = self.__validate(ports) self.logger.post_cmd(rc) diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_exceptions.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_exceptions.py index 3c443ba6..b6fad865 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_exceptions.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_exceptions.py @@ -4,6 +4,11 @@ import linecache from .utils.text_opts import * +try: + basestring +except NameError: + basestring = str + # basic error for API class STLError(Exception): def __init__ (self, msg): @@ -56,7 +61,8 @@ class STLArgumentError(STLError): # raised when argument type is not valid for operation class STLTypeError(STLError): def __init__ (self, arg_name, arg_type, valid_types): - self.msg = "Argument: '%s' invalid type: %s, expecting type(s): %s." % (arg_name, arg_type, valid_types) + self.msg = "Argument: '%s' invalid type: '%s', expecting type(s): %s." % (arg_name, arg_type.__name__, + [t.__name__ for t in valid_types] if isinstance(valid_types, tuple) else valid_types.__name__) # raised when timeout occurs class STLTimeoutError(STLError): diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py index b4903e81..6835ea5f 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py @@ -64,3 +64,4 @@ def list_difference (l1, l2): def is_sub_list (l1, l2): return set(l1) <= set(l2) + diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py index 98e3ca6a..9ef14612 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py @@ -90,10 +90,6 @@ match_multiplier_help = """Multiplier should be passed in the following format: # value should be divided def decode_multiplier(val, allow_update = False, divide_count = 1): - # must be string - if not isinstance(val, str): - return None - # do we allow updates ? +/- if not allow_update: match = re.match("^(\d+(\.\d+)?)(bps|kbps|mbps|gbps|pps|kpps|mpps|%?)$", val) -- cgit From 3bf54917e0d8817dbadaae73a7545a011676cccf Mon Sep 17 00:00:00 2001 From: Ido Barnea Date: Tue, 21 Jun 2016 15:30:16 +0300 Subject: profile fixes --- .../stl/trex_stl_lib/utils/parsing_opts.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py index 9ef14612..bb28d3af 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py @@ -240,26 +240,24 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], 'type': int}), PROMISCUOUS: ArgumentPack(['--prom'], - {'help': "sets port promiscuous on", + {'help': "Sets port promiscuous on", 'dest': "prom", 'default': None, 'action': "store_true"}), - TUNABLES: ArgumentPack(['-t'], - {'help': "sets tunable for a profile", + {'help': "Sets tunables for a profile. Example: '-t fsize=100,pg_id=7'", + 'metavar': 'T1=VAL[,T2=VAL ...]', 'dest': "tunables", 'default': None, 'type': decode_tunables}), - NO_PROMISCUOUS: ArgumentPack(['--no_prom'], - {'help': "sets port promiscuous off", + {'help': "Sets port promiscuous off", 'dest': "prom", 'default': None, 'action': "store_false"}), - PORT_LIST: ArgumentPack(['--port', '-p'], {"nargs": '+', 'dest':'ports', @@ -465,4 +463,4 @@ def gen_parser(stateless_client, op_name, description, *args): if __name__ == "__main__": - pass \ No newline at end of file + pass -- cgit From 9a1356bc05d663555b9b62971aff6219e17a767c Mon Sep 17 00:00:00 2001 From: imarom Date: Tue, 21 Jun 2016 09:54:04 +0300 Subject: FLOW_STATS: extract start_stream from the compiler to start_traffic --- .../automation/trex_control_plane/stl/trex_stl_lib/utils/text_opts.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_opts.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_opts.py index 7e0bf9e4..26e64dae 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_opts.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_opts.py @@ -61,6 +61,9 @@ def format_time (t_sec): if t_sec < 0: return "infinite" + if t_sec == 0: + return "zero" + if t_sec < 1: # low numbers for unit in ['ms', 'usec', 'ns']: -- cgit From 9249859480c57960905f37282e9fa8047cf17484 Mon Sep 17 00:00:00 2001 From: Yaroslav Brustinov Date: Wed, 22 Jun 2016 14:22:54 +0300 Subject: STL Python API stats - add histogram key if it's absent --- scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py | 1 + 1 file changed, 1 insertion(+) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py index 0ec98a0d..88a94865 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py @@ -1036,6 +1036,7 @@ class CLatencyStats(CTRexStats): output[int_pg_id]['latency']['total_min'] = min_val else: output[int_pg_id]['latency']['total_min'] = StatNotAvailable('total_min') + output[int_pg_id]['latency']['histogram'] = {} self.latest_stats = output return True -- cgit From 24c22cf22f429c5214b1f1beba5145e7c4a2c4a8 Mon Sep 17 00:00:00 2001 From: Ido Barnea Date: Thu, 23 Jun 2016 09:19:54 +0300 Subject: get_stats documentation --- .../stl/trex_stl_lib/trex_stl_client.py | 153 ++++++++++++++++++++- 1 file changed, 148 insertions(+), 5 deletions(-) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py index 4d1125c8..6dec6fa7 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py @@ -1254,18 +1254,161 @@ class STLClient(object): # get stats - def get_stats (self, ports = None, async_barrier = True): + def get_stats (self, ports = None, sync_now = True): + """ + Return dictionary containing statistics information gathered from the server. + + :parameters: + + ports - List of ports to retreive stats on. + If None, assume the request is for all acquired ports. + + sync_now - Boolean - If true, create a call to the server to get latest stats, and wait for result to arrive. Otherwise, return last stats saved in client cache. + Downside of putting True is a slight delay (few 10th msecs) in getting the result. For practical uses, value should be True. + :return: + Statistics dictionary of dictionaries with the following format: + + =============================== =============== + key Meaning + =============================== =============== + :ref:`numbers (0,1,..` Statistcs per port number + :ref:`total ` Sum of port statistics + :ref:`flow_stats ` Per flow statistics + :ref:`global ` Global statistics + :ref:`latency ` Per flow statistics regarding flow latency + =============================== =============== + + Below is description of each of the inner dictionaries. + + .. _total: + + **total** and per port statistics contain dictionary with following format. + + =============================== =============== + key Meaning + =============================== =============== + ibytes Number of input bytes + ierrors Number of input errors + ipackets Number of input packets + obytes Number of output bytes + oerrors Number of output errors + opackets Number of output packets + rx_bps Receive bytes per second rate + rx_pps Receive packet per second rate + tx_bps Transmit bytes per second rate + tx_pps Transmit packet per second rate + =============================== =============== + + .. _flow_stats: + + **flow_stats** contains dictionaries per packet group id (pg id). Each one with the following structure. + + ================= =============== + key Meaning + ================= =============== + rx_bps Receivd bytes per second rate + rx_bps_l1 Receivd bytes per second rate, including layer one + rx_bytes Total number of received bytes + rx_pkts Total number of received packets + rx_pps Received packets per second + tx_bps Transmitted bytes per second rate + tx_bps_l1 Transmitted bytes per second rate, including layer one + tx_bytes Total number of sent bytes + tx_pkts Total number of sent packets + tx_pps Transmit packets per second + ================= =============== + + .. _global: + + **global** + + ================= =============== + key Meaning + ================= =============== + bw_per_core Estimated byte rate Trex can support per core. This is calculated by extrapolation of current rate and load on transmitting cores. + cpu_util Estimate of the average utilization percentage of the transimitting cores + queue_full Total number of packets we could not transmit because NIC TX queue was full. This usually indicates that the rate we trying to transmit is too high for this port + rx_cpu_util Estimate of the utilization percentage of the core handling RX traffic + rx_drop_bps Received bytes per second drop rate + rx_bps Received bytes per second rate + rx_pps Received packets per second rate + tx_bps Transmit bytes per second rate + tx_pps Transmit packets per second rate + ================= =============== + + .. _latency: + + **latency** contains dictionary per packet group id (pg id). Each one with the following structure. + + =========================== =============== + key Meaning + =========================== =============== + :ref:`err_cntrs` Counters describing errors that occured with this pg id + :ref:`latency` Information regarding packet latency + =========================== =============== + + Following are the inner dictionaries of latency + + .. _err-cntrs: + + **err-cntrs** + + ================= =============== + key Meaning (see better explanation below the table) + ================= =============== + dropped How many packets were dropped. + dup How many packets were duplicated. + out_of_order How many packets we received out of order. + seq_too_high How many events of packet with sequence number too high we saw. + seq_too_low How many events of packet with sequence number too low we saw. + ================= =============== + + For calculating packet error events, we add sequence number to each packet's payload. We decide what went wrong only according to sequence number + of last packet received and that of the previous packet. 'seq_too_low' and 'seq_too_high' count events we see. 'dup', 'out_of_order' and 'dropped' + are heuristics we apply to try and understand what happened. They will be accurate in common error scenarios. + We describe few scenarios below to help understand this. + + Scenario 1: Received packet with seq num 10, and another one with seq num 10. We increment 'dup' and 'seq_too_low' by 1. + + Scenario 2: Received pacekt with seq num 10 and then packet with seq num 15. We assume 4 packets were dropped, and increment 'dropped' by 4, and 'seq_too_high' by 1. + We expect next packet to arrive with sequence number 16. + + Scenario 2 continue: Received packet with seq num 11. We increment 'seq_too_low' by 1. We increment 'out_of_order' by 1. We *decrement* 'dropped' by 1. + (We assume here that one of the packets we considered as dropped before, actually arrived out of order). + + + .. _lat_inner: + + **latency** + + ================= =============== + key Meaning + ================= =============== + average Average latency over the stream lifetime (usec). Total average is computed each sampling period by following formula: = /2 + /2 + histogram Dictionary describing logarithmic distribution histogram of packet latencies. Keys in the dictionary represent range of latencies (in usec). Values are the total number of packets received in this latency range. For example, an entry {100:13} would mean that we saw 13 packets with latency in the range between 100 and 200 usec. + jitter Jitter of latency samples, computed as described in :rfc:`3550#appendix-A.8` + last_max Maximum latency measured between last two data reads from server. + total_max Maximum latency measured over the stream lifetime (in usec). + total_min Minimum latency measured over the stream lifetime (in usec). + ================= =============== + + + + :raises: + None + + """ # by default use all acquired ports ports = ports if ports is not None else self.get_acquired_ports() ports = self._validate_port_list(ports) # check async barrier - if not type(async_barrier) is bool: - raise STLArgumentError('async_barrier', async_barrier) + if not type(sync_now) is bool: + raise STLArgumentError('sync_now', sync_now) # if the user requested a barrier - use it - if async_barrier: + if sync_now: rc = self.async_client.barrier() if not rc: raise STLError(rc) @@ -1279,7 +1422,7 @@ class STLClient(object): :parameters: ev_type_filter - 'info', 'warning' or a list of those - default is no filter + default: no filter :return: logged events -- cgit From e43c2856b2aec77c70a5c70d9ea0b87cf7d34eea Mon Sep 17 00:00:00 2001 From: Hanoh Haim Date: Thu, 23 Jun 2016 11:10:28 +0300 Subject: minor update for the stats doc --- .../stl/trex_stl_lib/trex_stl_client.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py index 6dec6fa7..c1833754 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py @@ -1284,18 +1284,20 @@ class STLClient(object): **total** and per port statistics contain dictionary with following format. + Most of the bytes counters (unless specified otherwise) are in L2 layer including the FCS. e.g. minimum packet size in 64 bytes + =============================== =============== key Meaning =============================== =============== - ibytes Number of input bytes + ibytes Number of input bytes ierrors Number of input errors - ipackets Number of input packets - obytes Number of output bytes + ipackets Number of input packets + obytes Number of output bytes oerrors Number of output errors opackets Number of output packets - rx_bps Receive bytes per second rate + rx_bps Receive bytes per second rate (L2 layer) rx_pps Receive packet per second rate - tx_bps Transmit bytes per second rate + tx_bps Transmit bytes per second rate (L2 layer) tx_pps Transmit packet per second rate =============================== =============== @@ -1327,8 +1329,8 @@ class STLClient(object): ================= =============== bw_per_core Estimated byte rate Trex can support per core. This is calculated by extrapolation of current rate and load on transmitting cores. cpu_util Estimate of the average utilization percentage of the transimitting cores - queue_full Total number of packets we could not transmit because NIC TX queue was full. This usually indicates that the rate we trying to transmit is too high for this port - rx_cpu_util Estimate of the utilization percentage of the core handling RX traffic + queue_full Total number of packets transmitted while the NIC TX queue was full. The packets will be transmitted, eventually, but will create high CPU%due to polling the queue. This usually indicates that the rate we trying to transmit is too high for this port. + rx_cpu_util Estimate of the utilization percentage of the core handling RX traffic. Too high value of this CPU utilization could cause drop of latency streams. rx_drop_bps Received bytes per second drop rate rx_bps Received bytes per second rate rx_pps Received packets per second rate @@ -1356,7 +1358,7 @@ class STLClient(object): ================= =============== key Meaning (see better explanation below the table) ================= =============== - dropped How many packets were dropped. + dropped How many packets were dropped (estimation) dup How many packets were duplicated. out_of_order How many packets we received out of order. seq_too_high How many events of packet with sequence number too high we saw. @@ -1384,10 +1386,10 @@ class STLClient(object): ================= =============== key Meaning ================= =============== - average Average latency over the stream lifetime (usec). Total average is computed each sampling period by following formula: = /2 + /2 + average Average latency over the stream lifetime (usec).Low pass filter is applied to the last window average.It is computed each sampling period by following formula: = /2 + /2 histogram Dictionary describing logarithmic distribution histogram of packet latencies. Keys in the dictionary represent range of latencies (in usec). Values are the total number of packets received in this latency range. For example, an entry {100:13} would mean that we saw 13 packets with latency in the range between 100 and 200 usec. jitter Jitter of latency samples, computed as described in :rfc:`3550#appendix-A.8` - last_max Maximum latency measured between last two data reads from server. + last_max Maximum latency measured between last two data reads from server (0.5 sec window). total_max Maximum latency measured over the stream lifetime (in usec). total_min Minimum latency measured over the stream lifetime (in usec). ================= =============== -- cgit From f672c6c1ac980fa248298b679603da3645735787 Mon Sep 17 00:00:00 2001 From: imarom Date: Wed, 22 Jun 2016 18:03:43 +0300 Subject: FLOW_STATS: removed line_util fields --- .../stl/trex_stl_lib/trex_stl_stats.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py index 88a94865..3effa1f0 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py @@ -1047,12 +1047,7 @@ class CRxStats(CTRexStats): def __init__(self, ports): super(CRxStats, self).__init__() self.ports = ports - self.ports_speed = {} - def get_ports_speed(self): - for port in self.ports: - self.ports_speed[str(port)] = self.ports[port].get_speed_bps() - self.ports_speed['total'] = sum(self.ports_speed.values()) # calculates a diff between previous snapshot # and current one @@ -1170,8 +1165,8 @@ class CRxStats(CTRexStats): return # TX - self.get_ports_speed() for port in pg_current['tx_pkts'].keys(): + prev_tx_pps = pg_prev['tx_pps'].get(port) now_tx_pkts = pg_current['tx_pkts'].get(port) prev_tx_pkts = pg_prev['tx_pkts'].get(port) @@ -1180,19 +1175,20 @@ class CRxStats(CTRexStats): prev_tx_bps = pg_prev['tx_bps'].get(port) now_tx_bytes = pg_current['tx_bytes'].get(port) prev_tx_bytes = pg_prev['tx_bytes'].get(port) + pg_current['tx_bps'][port], pg_current['tx_bps_lpf'][port] = self.calc_bps(prev_tx_bps, now_tx_bytes, prev_tx_bytes, diff_sec) if pg_current['tx_bps'].get(port) != None and pg_current['tx_pps'].get(port) != None: pg_current['tx_bps_L1'][port] = calc_bps_L1(pg_current['tx_bps'][port], pg_current['tx_pps'][port]) pg_current['tx_bps_L1_lpf'][port] = calc_bps_L1(pg_current['tx_bps_lpf'][port], pg_current['tx_pps_lpf'][port]) - pg_current['tx_line_util'][port] = 100.0 * pg_current['tx_bps_L1'][port] / self.ports_speed[port] else: pg_current['tx_bps_L1'][port] = None pg_current['tx_bps_L1_lpf'][port] = None - pg_current['tx_line_util'][port] = None + # RX for port in pg_current['rx_pkts'].keys(): + prev_rx_pps = pg_prev['rx_pps'].get(port) now_rx_pkts = pg_current['rx_pkts'].get(port) prev_rx_pkts = pg_prev['rx_pkts'].get(port) @@ -1205,11 +1201,9 @@ class CRxStats(CTRexStats): if pg_current['rx_bps'].get(port) != None and pg_current['rx_pps'].get(port) != None: pg_current['rx_bps_L1'][port] = calc_bps_L1(pg_current['rx_bps'][port], pg_current['rx_pps'][port]) pg_current['rx_bps_L1_lpf'][port] = calc_bps_L1(pg_current['rx_bps_lpf'][port], pg_current['rx_pps_lpf'][port]) - pg_current['rx_line_util'][port] = 100.0 * pg_current['rx_bps_L1'][port] / self.ports_speed[port] else: pg_current['rx_bps_L1'][port] = None pg_current['rx_bps_L1_lpf'][port] = None - pg_current['rx_line_util'][port] = None def calc_pps (self, prev_bw, now, prev, diff_sec): @@ -1272,7 +1266,7 @@ class CRxStats(CTRexStats): stats[int(pg_id)][field][int(port)] = val if val != 'N/A' else StatNotAvailable(field) # BW values - for field in ['tx_pps', 'tx_bps', 'tx_bps_L1', 'rx_pps', 'rx_bps', 'rx_bps_L1', 'tx_line_util', 'rx_line_util']: + for field in ['tx_pps', 'tx_bps', 'tx_bps_L1', 'rx_pps', 'rx_bps', 'rx_bps_L1']: val = self.get([pg_id, field, 'total']) stats[int(pg_id)][field] = {'total': val if val != 'N/A' else StatNotAvailable(field)} for port in value[field].keys(): -- cgit From 7772d0893579d0627c10515aeb6d9c9c8204316d Mon Sep 17 00:00:00 2001 From: Ido Barnea Date: Wed, 29 Jun 2016 16:18:13 +0300 Subject: flow stat/latency error counters code + documentation --- .../stl/trex_stl_lib/trex_stl_client.py | 51 ++++++++++++++++------ .../stl/trex_stl_lib/trex_stl_stats.py | 18 ++++++++ 2 files changed, 56 insertions(+), 13 deletions(-) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py index c1833754..acedf24f 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py @@ -1284,7 +1284,7 @@ class STLClient(object): **total** and per port statistics contain dictionary with following format. - Most of the bytes counters (unless specified otherwise) are in L2 layer including the FCS. e.g. minimum packet size in 64 bytes + Most of the bytes counters (unless specified otherwise) are in L2 layer, including the Ethernet FCS. e.g. minimum packet size is 64 bytes =============================== =============== key Meaning @@ -1303,21 +1303,34 @@ class STLClient(object): .. _flow_stats: - **flow_stats** contains dictionaries per packet group id (pg id). Each one with the following structure. + **flow_stats** contains :ref:`global dictionary `, and dictionaries per packet group id (pg id). See structures below. + + **per pg_id flow stat** dictionaries have following structure: ================= =============== key Meaning ================= =============== - rx_bps Receivd bytes per second rate - rx_bps_l1 Receivd bytes per second rate, including layer one + rx_bps Received bytes per second rate + rx_bps_l1 Received bytes per second rate, including layer one rx_bytes Total number of received bytes rx_pkts Total number of received packets rx_pps Received packets per second - tx_bps Transmitted bytes per second rate - tx_bps_l1 Transmitted bytes per second rate, including layer one + tx_bps Transmit bytes per second rate + tx_bps_l1 Transmit bytes per second rate, including layer one tx_bytes Total number of sent bytes tx_pkts Total number of sent packets - tx_pps Transmit packets per second + tx_pps Transmit packets per second rate + ================= =============== + + .. _flow_stats_global: + + **global flow stats** dictionary has the following structure: + + ================= =============== + key Meaning + ================= =============== + rx_err Number of flow statistics packets received that we could not associate to any pg_id. This can happen if latency on the used setup is large. See :ref:`wait_on_traffic ` rx_delay_ms parameter for details. + tx_err Number of flow statistics packets transmitted that we could not associate to any pg_id. This is never expected. If you see this different than 0, please report. ================= =============== .. _global: @@ -1340,7 +1353,9 @@ class STLClient(object): .. _latency: - **latency** contains dictionary per packet group id (pg id). Each one with the following structure. + **latency** contains :ref:`global dictionary `, and dictionaries per packet group id (pg id). Each one with the following structure. + + **per pg_id latency stat** dictionaries have following structure: =========================== =============== key Meaning @@ -1394,7 +1409,16 @@ class STLClient(object): total_min Minimum latency measured over the stream lifetime (in usec). ================= =============== + .. _lat_stats_global: + **global latency stats** dictionary has the following structure: + + ================= =============== + key Meaning + ================= =============== + old_flow Number of latency statistics packets received that we could not associate to any pg_id. This can happen if latency on the used setup is large. See :ref:`wait_on_traffic ` rx_delay_ms parameter for details. + bad_hdr Number of latency packets received with bad latency data. This can happen becuase of garbage packets in the network, or if the DUT causes packet corruption. + ================= =============== :raises: None @@ -2274,6 +2298,8 @@ class STLClient(object): @__api_check(True) def wait_on_traffic (self, ports = None, timeout = 60, rx_delay_ms = 10): """ + .. _wait_on_traffic: + Block until traffic on specified port(s) has ended :parameters: @@ -2284,12 +2310,11 @@ class STLClient(object): timeout in seconds rx_delay_ms : int - time to wait until RX filters are removed - this value should reflect the time it takes - packets which were transmitted to arrive + Time to wait (in milliseconds) after last packet was sent, until RX filters used for + measuring flow statistics and latency are removed. + This value should reflect the time it takes packets which were transmitted to arrive to the destination. - after this time the RX filters will be removed - + After this time, RX filters will be removed, and packets arriving for per flow statistics feature and latency flows will be counted as errors. :raises: + :exc:`STLTimeoutError` - in case timeout has expired diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py index 3effa1f0..f74c8fa5 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py @@ -1010,6 +1010,13 @@ class CLatencyStats(CTRexStats): snapshot = {} output = {} + output['global'] = {} + for field in ['bad_hdr', 'old_flow']: + if 'global' in snapshot and field in snapshot['global']: + output['global'][field] = snapshot['global'][field] + else: + output['global'][field] = 0 + # we care only about the current active keys pg_ids = list(filter(is_intable, snapshot.keys())) @@ -1107,6 +1114,14 @@ class CRxStats(CTRexStats): # copy timestamp field output['ts'] = current['ts'] + # global (not per pg_id) error counters + output['global'] = {} + for field in ['rx_err', 'tx_err']: + output['global'][field] = {} + if 'global' in current and field in current['global']: + for port in current['global'][field]: + output['global'][field][int(port)] = current['global'][field][port] + # we care only about the current active keys pg_ids = list(filter(is_intable, current.keys())) @@ -1254,6 +1269,9 @@ class CRxStats(CTRexStats): for pg_id, value in self.latest_stats.items(): # skip non ints if not is_intable(pg_id): + # 'global' stats are in the same level of the pg_ids. We do want them to go to the user + if pg_id == 'global': + stats[pg_id] = value continue # bare counters stats[int(pg_id)] = {} -- cgit From c5d7c2ee9859b5ba772c6a9be46ced61f2ad25c3 Mon Sep 17 00:00:00 2001 From: imarom Date: Tue, 5 Jul 2016 10:46:04 +0300 Subject: profile console command was broken on Python 3 --- .../automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py index ae7c23f2..575d4025 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py @@ -2974,7 +2974,7 @@ class STLClient(object): if profile_type == 'python': self.logger.log('Type: {:^12}'.format('Python Module')) - self.logger.log('Tunables: {:^12}'.format(['{0} = {1}'.format(k ,v) for k, v in info['tunables'].items()])) + self.logger.log('Tunables: {:^12}'.format(str(['{0} = {1}'.format(k ,v) for k, v in info['tunables'].items()]))) elif profile_type == 'yaml': self.logger.log('Type: {:^12}'.format('YAML')) -- cgit From e8296c89b60b6ebee39111aea07aedee2fd0400f Mon Sep 17 00:00:00 2001 From: imarom Date: Tue, 5 Jul 2016 15:28:11 +0300 Subject: TUI phase 500 ! --- .../stl/trex_stl_lib/trex_stl_client.py | 74 +++++++++++++--------- 1 file changed, 43 insertions(+), 31 deletions(-) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py index 575d4025..153985ae 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py @@ -2428,13 +2428,14 @@ class STLClient(object): rc = f(*args) except STLError as e: client.logger.log("Log:\n" + format_text(e.brief() + "\n", 'bold')) - return + return RC_ERR(e.brief()) # if got true - print time if rc: delta = time.time() - time1 client.logger.log(format_time(delta) + "\n") + return rc return wrap @@ -2587,14 +2588,14 @@ class STLClient(object): opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True) if opts is None: - return + return RC_ERR("invalid arguments for 'start'") active_ports = list_intersect(self.get_active_ports(), opts.ports) if active_ports: if not opts.force: msg = "Port(s) {0} are active - please stop them or add '--force'\n".format(active_ports) self.logger.log(format_text(msg, 'bold')) - return + return RC_ERR(msg) else: self.stop(active_ports) @@ -2612,8 +2613,10 @@ class STLClient(object): else: # must be exact if len(opts.ports) != len(opts.tunables): - self.logger.log('tunables section count must be 1 or exactly as the number of ports: got {0}'.format(len(opts.tunables))) - return + msg = 'tunables section count must be 1 or exactly as the number of ports: got {0}'.format(len(opts.tunables)) + self.logger.log(msg) + return RC_ERR(msg) + tunables = opts.tunables @@ -2633,9 +2636,10 @@ class STLClient(object): self.add_streams(profile.get_streams(), ports = port) except STLError as e: - self.logger.log(format_text("\nError while loading profile '{0}'\n".format(opts.file[0]), 'bold')) + msg = format_text("\nError while loading profile '{0}'\n".format(opts.file[0]), 'bold') + self.logger.log(msg) self.logger.log(e.brief() + "\n") - return + return RC_ERR(msg) if opts.dry: @@ -2647,8 +2651,7 @@ class STLClient(object): opts.duration, opts.total) - # true means print time - return True + return RC_OK() @@ -2662,23 +2665,25 @@ class STLClient(object): opts = parser.parse_args(line.split(), default_ports = self.get_active_ports(), verify_acquired = True) if opts is None: - return + return RC_ERR("invalid arguments for 'stop'") # find the relevant ports ports = list_intersect(opts.ports, self.get_active_ports()) if not ports: if not opts.ports: - self.logger.log('stop - no active ports') + msg = 'stop - no active ports' else: - self.logger.log('stop - no active traffic on ports {0}'.format(opts.ports)) - return + msg = 'stop - no active traffic on ports {0}'.format(opts.ports) + + self.logger.log(msg) + return RC_ERR(msg) # call API self.stop(ports) # true means print time - return True + return RC_OK() @__console @@ -2694,22 +2699,24 @@ class STLClient(object): opts = parser.parse_args(line.split(), default_ports = self.get_active_ports(), verify_acquired = True) if opts is None: - return + return RC_ERR("invalid arguments for 'update'") # find the relevant ports ports = list_intersect(opts.ports, self.get_active_ports()) if not ports: if not opts.ports: - self.logger.log('update - no active ports') + msg = 'update - no active ports' else: - self.logger.log('update - no active traffic on ports {0}'.format(opts.ports)) - return + msg = 'update - no active traffic on ports {0}'.format(opts.ports) + + self.logger.log(msg) + return RC_ERR(msg) self.update(ports, opts.mult, opts.total, opts.force) # true means print time - return True + return RC_OK() @__console @@ -2722,26 +2729,29 @@ class STLClient(object): opts = parser.parse_args(line.split(), default_ports = self.get_transmitting_ports(), verify_acquired = True) if opts is None: - return + return RC_ERR("invalid arguments for 'pause'") # check for already paused case if opts.ports and is_sub_list(opts.ports, self.get_paused_ports()): - self.logger.log('pause - all of port(s) {0} are already paused'.format(opts.ports)) - return + msg = 'pause - all of port(s) {0} are already paused'.format(opts.ports) + self.logger.log(msg) + return RC_ERR(msg) # find the relevant ports ports = list_intersect(opts.ports, self.get_transmitting_ports()) if not ports: if not opts.ports: - self.logger.log('pause - no transmitting ports') + msg = 'pause - no transmitting ports' else: - self.logger.log('pause - none of ports {0} are transmitting'.format(opts.ports)) - return + msg = 'pause - none of ports {0} are transmitting'.format(opts.ports) + + self.logger.log(msg) + return RC_ERR(msg) self.pause(ports) # true means print time - return True + return RC_OK() @__console @@ -2754,22 +2764,24 @@ class STLClient(object): opts = parser.parse_args(line.split(), default_ports = self.get_paused_ports(), verify_acquired = True) if opts is None: - return + return RC_ERR("invalid arguments for 'resume'") # find the relevant ports ports = list_intersect(opts.ports, self.get_paused_ports()) if not ports: if not opts.ports: - self.logger.log('resume - no paused ports') + msg = 'resume - no paused ports' else: - self.logger.log('resume - none of ports {0} are paused'.format(opts.ports)) - return + msg = 'resume - none of ports {0} are paused'.format(opts.ports) + + self.logger.log(msg) + return RC_ERR(msg) self.resume(ports) # true means print time - return True + return RC_OK() @__console -- cgit From 1ccf48c7a63d11d51ebf2fedcb80022124503355 Mon Sep 17 00:00:00 2001 From: imarom Date: Wed, 6 Jul 2016 15:35:13 +0300 Subject: TUI mode (--tui) locked on legend --- .../trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py index ceaf1af8..1b417d65 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py @@ -33,6 +33,7 @@ NO_PROMISCUOUS = 20 PROMISCUOUS_SWITCH = 21 TUNABLES = 22 REMOTE_FILE = 23 +LOCKED = 24 GLOBAL_STATS = 50 PORT_STATS = 51 @@ -321,6 +322,11 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], 'default': False, 'help': "Starts TUI in xterm window"}), + LOCKED: ArgumentPack(['-l', '--locked'], + {'action': 'store_true', + 'dest': 'locked', + 'default': False, + 'help': "Locks TUI on legend mode"}), FULL_OUTPUT: ArgumentPack(['--full'], {'action': 'store_true', -- cgit From f374d4fda8db1862311355c1b189f2d1e94f15de Mon Sep 17 00:00:00 2001 From: Yaroslav Brustinov Date: Wed, 6 Jul 2016 17:25:50 +0300 Subject: increase server<->client API version from 1.2 to 1.3 various fixes --- .../automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py index 153985ae..7c8a5fbf 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py @@ -511,8 +511,7 @@ class STLClient(object): self.connected = False # API classes - self.api_vers = [ {'type': 'core', 'major': 1, 'minor':2 } - ] + self.api_vers = [ {'type': 'core', 'major': 1, 'minor': 3 } ] self.api_h = {'core': None} # logger -- cgit From 6d028fcd21df1d1954f679443ffddc35364552f7 Mon Sep 17 00:00:00 2001 From: imarom Date: Thu, 21 Jul 2016 17:21:04 +0300 Subject: faster TUI: no flickers and more... 1. made stdout full buffered (no in-middle print) 2. added TAB for filenames auto-complete --- .../trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py index 1b417d65..caa5aea8 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py @@ -396,6 +396,10 @@ class CCmdArgParser(argparse.ArgumentParser): self.cmd_name = kwargs.get('prog') + # hook this to the logger + def _print_message(self, message, file=None): + self.stateless_client.logger.log(message) + def has_ports_cfg (self, opts): return hasattr(opts, "all_ports") or hasattr(opts, "ports") -- cgit From a913ed85424bd1ab38c8842dd16dd10b90db12fe Mon Sep 17 00:00:00 2001 From: imarom Date: Mon, 25 Jul 2016 11:20:16 +0300 Subject: TUI tweaks --- .../stl/trex_stl_lib/trex_stl_client.py | 130 +++++++++++---------- .../stl/trex_stl_lib/utils/parsing_opts.py | 31 +++-- 2 files changed, 91 insertions(+), 70 deletions(-) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py index 7c8a5fbf..a4f26f69 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py @@ -2442,7 +2442,7 @@ class STLClient(object): def ping_line (self, line): '''pings the server''' self.ping() - return True + return RC_OK() @__console def connect_line (self, line): @@ -2454,14 +2454,13 @@ class STLClient(object): parsing_opts.FORCE) opts = parser.parse_args(line.split(), default_ports = self.get_all_ports()) - if opts is None: - return + if not opts: + return opts self.connect() self.acquire(ports = opts.ports, force = opts.force) - # true means print time - return True + return RC_OK() @__console @@ -2476,19 +2475,19 @@ class STLClient(object): parsing_opts.FORCE) opts = parser.parse_args(line.split(), default_ports = self.get_all_ports()) - if opts is None: - return + if not opts: + return opts # filter out all the already owned ports ports = list_difference(opts.ports, self.get_acquired_ports()) if not ports: - self.logger.log("acquire - all port(s) {0} are already acquired".format(opts.ports)) - return + msg = "acquire - all of port(s) {0} are already acquired".format(opts.ports) + self.logger.log(format_text(msg, 'bold')) + return RC_ERR(msg) self.acquire(ports = ports, force = opts.force) - # true means print time - return True + return RC_OK() # @@ -2502,23 +2501,24 @@ class STLClient(object): parsing_opts.PORT_LIST_WITH_ALL) opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports()) - if opts is None: - return + if not opts: + return opts ports = list_intersect(opts.ports, self.get_acquired_ports()) if not ports: if not opts.ports: - self.logger.log("release - no acquired ports") - return + msg = "release - no acquired ports" + self.logger.log(format_text(msg, 'bold')) + return RC_ERR(msg) else: - self.logger.log("release - none of port(s) {0} are acquired".format(opts.ports)) - return + msg = "release - none of port(s) {0} are acquired".format(opts.ports) + self.logger.log(format_text(msg, 'bold')) + return RC_ERR(msg) self.release(ports = ports) - # true means print time - return True + return RC_OK() @__console @@ -2530,23 +2530,23 @@ class STLClient(object): self.reacquire_line.__doc__) opts = parser.parse_args(line.split()) - if opts is None: - return + if not opts: + return opts # find all the on-owned ports under your name my_unowned_ports = list_difference([k for k, v in self.ports.items() if v.get_owner() == self.username], self.get_acquired_ports()) if not my_unowned_ports: - self.logger.log("reacquire - no unowned ports under '{0}'".format(self.username)) - return + msg = "reacquire - no unowned ports under '{0}'".format(self.username) + self.logger.log(msg) + return RC_ERR(msg) self.acquire(ports = my_unowned_ports, force = True) - return True + return RC_OK() @__console def disconnect_line (self, line): self.disconnect() - @__console @@ -2559,13 +2559,12 @@ class STLClient(object): parsing_opts.PORT_LIST_WITH_ALL) opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True) - if opts is None: - return + if not opts: + return opts self.reset(ports = opts.ports) - # true means print time - return True + return RC_OK() @@ -2586,8 +2585,8 @@ class STLClient(object): parsing_opts.DRY_RUN) opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True) - if opts is None: - return RC_ERR("invalid arguments for 'start'") + if not opts: + return opts active_ports = list_intersect(self.get_active_ports(), opts.ports) if active_ports: @@ -2663,8 +2662,8 @@ class STLClient(object): parsing_opts.PORT_LIST_WITH_ALL) opts = parser.parse_args(line.split(), default_ports = self.get_active_ports(), verify_acquired = True) - if opts is None: - return RC_ERR("invalid arguments for 'stop'") + if not opts: + return opts # find the relevant ports @@ -2681,7 +2680,6 @@ class STLClient(object): # call API self.stop(ports) - # true means print time return RC_OK() @@ -2697,8 +2695,8 @@ class STLClient(object): parsing_opts.FORCE) opts = parser.parse_args(line.split(), default_ports = self.get_active_ports(), verify_acquired = True) - if opts is None: - return RC_ERR("invalid arguments for 'update'") + if not opts: + return opts # find the relevant ports @@ -2714,7 +2712,6 @@ class STLClient(object): self.update(ports, opts.mult, opts.total, opts.force) - # true means print time return RC_OK() @@ -2727,8 +2724,8 @@ class STLClient(object): parsing_opts.PORT_LIST_WITH_ALL) opts = parser.parse_args(line.split(), default_ports = self.get_transmitting_ports(), verify_acquired = True) - if opts is None: - return RC_ERR("invalid arguments for 'pause'") + if not opts: + return opts # check for already paused case if opts.ports and is_sub_list(opts.ports, self.get_paused_ports()): @@ -2749,7 +2746,6 @@ class STLClient(object): self.pause(ports) - # true means print time return RC_OK() @@ -2762,8 +2758,8 @@ class STLClient(object): parsing_opts.PORT_LIST_WITH_ALL) opts = parser.parse_args(line.split(), default_ports = self.get_paused_ports(), verify_acquired = True) - if opts is None: - return RC_ERR("invalid arguments for 'resume'") + if not opts: + return opts # find the relevant ports ports = list_intersect(opts.ports, self.get_paused_ports()) @@ -2794,8 +2790,8 @@ class STLClient(object): opts = parser.parse_args(line.split()) - if opts is None: - return + if not opts: + return opts self.clear_stats(opts.ports) @@ -2814,8 +2810,8 @@ class STLClient(object): opts = parser.parse_args(line.split()) - if opts is None: - return + if not opts: + return opts # determine stats mask mask = self.__get_mask_keys(**self.__filter_namespace_args(opts, trex_stl_stats.ALL_STATS_OPTS)) @@ -2845,8 +2841,8 @@ class STLClient(object): opts = parser.parse_args(line.split()) - if opts is None: - return + if not opts: + return opts streams = self._get_streams(opts.ports, set(opts.streams)) if not streams: @@ -2872,8 +2868,8 @@ class STLClient(object): parsing_opts.PORT_LIST_WITH_ALL) opts = parser.parse_args(line.split()) - if opts is None: - return + if not opts: + return opts self.validate(opts.ports) @@ -2897,8 +2893,8 @@ class STLClient(object): parsing_opts.FORCE) opts = parser.parse_args(line.split()) - if opts is None: - return + if not opts: + return opts active_ports = list(set(self.get_active_ports()).intersection(opts.ports)) @@ -2906,7 +2902,7 @@ class STLClient(object): if not opts.force: msg = "Port(s) {0} are active - please stop them or add '--force'\n".format(active_ports) self.logger.log(format_text(msg, 'bold')) - return + return RC_ERR(msg) else: self.stop(active_ports) @@ -2930,7 +2926,7 @@ class STLClient(object): - return True + return RC_OK() @@ -2945,8 +2941,8 @@ class STLClient(object): parsing_opts.PROMISCUOUS_SWITCH) opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True) - if opts is None: - return + if not opts: + return opts # if no attributes - fall back to printing the status if opts.prom is None: @@ -2954,7 +2950,7 @@ class STLClient(object): return self.set_port_attr(opts.ports, opts.prom) - + return RC_OK() @__console @@ -2967,8 +2963,8 @@ class STLClient(object): parsing_opts.FILE_PATH) opts = parser.parse_args(line.split()) - if opts is None: - return + if not opts: + return opts info = STLProfile.get_info(opts.file[0]) @@ -3024,8 +3020,8 @@ class STLClient(object): *x) opts = parser.parse_args(line.split()) - if opts is None: - return + if not opts: + return opts ev_type_filter = [] @@ -3046,3 +3042,15 @@ class STLClient(object): if opts.clear: self.clear_events() + def generate_prompt (self, prefix = 'trex'): + if not self.is_connected(): + return "{0}(offline)>".format(prefix) + + elif not self.get_acquired_ports(): + return "{0}(read-only)>".format(prefix) + + elif self.is_all_ports_acquired(): + return "{0}>".format(prefix) + + else: + return "{0} {1}>".format(prefix, self.get_acquired_ports()) diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py index caa5aea8..8be154af 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py @@ -1,6 +1,9 @@ import argparse from collections import namedtuple from .common import list_intersect, list_difference +from .text_opts import format_text +from ..trex_stl_types import * + import sys import re import os @@ -400,6 +403,11 @@ class CCmdArgParser(argparse.ArgumentParser): def _print_message(self, message, file=None): self.stateless_client.logger.log(message) + def error(self, message): + self.print_usage() + self._print_message(('%s: error: %s\n') % (self.prog, message)) + raise ValueError(message) + def has_ports_cfg (self, opts): return hasattr(opts, "all_ports") or hasattr(opts, "ports") @@ -407,7 +415,7 @@ class CCmdArgParser(argparse.ArgumentParser): try: opts = super(CCmdArgParser, self).parse_args(args, namespace) if opts is None: - return None + return RC_ERR("'{0}' - invalid arguments".format(self.cmd_name)) if not self.has_ports_cfg(opts): return opts @@ -422,8 +430,9 @@ class CCmdArgParser(argparse.ArgumentParser): # so maybe we have ports configured invalid_ports = list_difference(opts.ports, self.stateless_client.get_all_ports()) if invalid_ports: - self.stateless_client.logger.log("{0}: port(s) {1} are not valid port IDs".format(self.cmd_name, invalid_ports)) - return None + msg = "{0}: port(s) {1} are not valid port IDs".format(self.cmd_name, invalid_ports) + self.stateless_client.logger.log(format_text(msg, 'bold')) + return RC_ERR(msg) # verify acquired ports if verify_acquired: @@ -431,21 +440,25 @@ class CCmdArgParser(argparse.ArgumentParser): diff = list_difference(opts.ports, acquired_ports) if diff: - self.stateless_client.logger.log("{0} - port(s) {1} are not acquired".format(self.cmd_name, diff)) - return None + msg = "{0} - port(s) {1} are not acquired".format(self.cmd_name, diff) + self.stateless_client.logger.log(format_text(msg, 'bold')) + return RC_ERR(msg) # no acquire ports at all if not acquired_ports: - self.stateless_client.logger.log("{0} - no acquired ports".format(self.cmd_name)) - return None - + msg = "{0} - no acquired ports".format(self.cmd_name) + self.stateless_client.logger.log(format_text(msg, 'bold')) + return RC_ERR(msg) return opts + except ValueError as e: + return RC_ERR("'{0}' - {1}".format(self.cmd_name, str(e))) + except SystemExit: # recover from system exit scenarios, such as "help", or bad arguments. - return None + return RC_ERR("'{0}' - {1}".format(self.cmd_name, "no action")) def get_flags (opt): -- cgit From e3b43560ff867c35ee726da9a98aed8acdc53b70 Mon Sep 17 00:00:00 2001 From: imarom Date: Mon, 25 Jul 2016 15:21:30 +0300 Subject: TRex console - add support for L1 BPS https://trex-tgn.cisco.com/youtrack/issue/trex-230 --- .../stl/trex_stl_lib/utils/parsing_opts.py | 92 +++++++++++----------- 1 file changed, 44 insertions(+), 48 deletions(-) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py index 8be154af..af7e90c1 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py @@ -96,78 +96,74 @@ match_multiplier_help = """Multiplier should be passed in the following format: # value should be divided def decode_multiplier(val, allow_update = False, divide_count = 1): + factor_table = {None: 1, 'k': 1e3, 'm': 1e6, 'g': 1e9} + pattern = "^(\d+(\.\d+)?)(((k|m|g)?(bpsl1|pps|bps))|%)?" + # do we allow updates ? +/- if not allow_update: - match = re.match("^(\d+(\.\d+)?)(bps|kbps|mbps|gbps|pps|kpps|mpps|%?)$", val) + pattern += "$" + match = re.match(pattern, val) op = None else: - match = re.match("^(\d+(\.\d+)?)(bps|kbps|mbps|gbps|pps|kpps|mpps|%?)([\+\-])?$", val) + pattern += "([\+\-])?$" + match = re.match(pattern, val) if match: - op = match.group(4) + op = match.group(7) else: op = None result = {} - if match: - - value = float(match.group(1)) - unit = match.group(3) - + if not match: + return None - - # raw type (factor) - if not unit: - result['type'] = 'raw' - result['value'] = value + # value in group 1 + value = float(match.group(1)) - elif unit == 'bps': - result['type'] = 'bps' - result['value'] = value + # decode unit as whole + unit = match.group(3) - elif unit == 'kbps': - result['type'] = 'bps' - result['value'] = value * 1000 + # k,m,g + factor = match.group(5) - elif unit == 'mbps': - result['type'] = 'bps' - result['value'] = value * 1000 * 1000 + # type of multiplier + m_type = match.group(6) - elif unit == 'gbps': - result['type'] = 'bps' - result['value'] = value * 1000 * 1000 * 1000 + # raw type (factor) + if not unit: + result['type'] = 'raw' + result['value'] = value - elif unit == 'pps': - result['type'] = 'pps' - result['value'] = value + # percentage + elif unit == '%': + result['type'] = 'percentage' + result['value'] = value - elif unit == "kpps": - result['type'] = 'pps' - result['value'] = value * 1000 + elif m_type == 'bps': + result['type'] = 'bps' + result['value'] = value * factor_table[factor] - elif unit == "mpps": - result['type'] = 'pps' - result['value'] = value * 1000 * 1000 + elif m_type == 'pps': + result['type'] = 'pps' + result['value'] = value * factor_table[factor] - elif unit == "%": - result['type'] = 'percentage' - result['value'] = value + elif m_type == 'bpsl1': + result['type'] = 'bpsl1' + result['value'] = value * factor_table[factor] - if op == "+": - result['op'] = "add" - elif op == "-": - result['op'] = "sub" - else: - result['op'] = "abs" + if op == "+": + result['op'] = "add" + elif op == "-": + result['op'] = "sub" + else: + result['op'] = "abs" - if result['op'] != 'percentage': - result['value'] = result['value'] / divide_count + if result['op'] != 'percentage': + result['value'] = result['value'] / divide_count - return result + return result - else: - return None def match_multiplier(val): -- cgit From 90c64917b59e83556454d1338634473cdcd952a9 Mon Sep 17 00:00:00 2001 From: imarom Date: Tue, 26 Jul 2016 11:47:15 +0300 Subject: some more TUI fixes --- .../stl/trex_stl_lib/trex_stl_port.py | 14 +++++++++++--- .../stl/trex_stl_lib/trex_stl_stats.py | 22 +++++++++++++--------- .../stl/trex_stl_lib/utils/text_opts.py | 5 ++++- 3 files changed, 28 insertions(+), 13 deletions(-) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py index be46e95f..d239fc57 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py @@ -68,7 +68,7 @@ class Port(object): self.has_rx_streams = False self.owner = '' - + self.last_factor_type = None # decorator to verify port is up def up(func): @@ -417,6 +417,9 @@ class Port(object): self.state = last_state return self.err(rc.err()) + # save this for TUI + self.last_factor_type = mul['type'] + return self.ok() @@ -424,7 +427,7 @@ class Port(object): # with force ignores the cached state and sends the command @owned def stop (self, force = False): - + # if not is not active and not force - go back if not self.is_active() and not force: return self.ok() @@ -437,10 +440,11 @@ class Port(object): return self.err(rc.err()) self.state = self.STATE_STREAMS + self.last_factor_type = None # timestamp for last tx self.tx_stopped_ts = datetime.now() - + return self.ok() @@ -535,6 +539,9 @@ class Port(object): if rc.bad(): return self.err(rc.err()) + # save this for TUI + self.last_factor_type = mul['type'] + return self.ok() @owned @@ -712,6 +719,7 @@ class Port(object): # until thread is locked - order is important self.tx_stopped_ts = datetime.now() self.state = self.STATE_STREAMS + self.last_factor_type = None # rest of the events are used for TUI / read only sessions def async_event_port_stopped (self): diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py index af4d6f69..1bf0a9a4 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py @@ -776,12 +776,12 @@ class CTRexStats(object): return value - def get(self, field, format=False, suffix=""): + def get(self, field, format=False, suffix="", opts = None): value = self._get(self.latest_stats, field) if value == None: return 'N/A' - return value if not format else format_num(value, suffix) + return value if not format else format_num(value, suffix = suffix, opts = opts) def get_rel(self, field, format=False, suffix=""): @@ -1020,14 +1020,20 @@ class CPortStats(CTRexStats): else: state = format_text(state, 'bold') + # default rate format modifiers + rate_format = {'bpsl1': None, 'bps': None, 'pps': None, 'percentage': 'bold'} + # mark owned ports by color if self._port_obj: owner = self._port_obj.get_owner() + rate_format[self._port_obj.last_factor_type] = ('blue', 'bold') if self._port_obj.is_acquired(): owner = format_text(owner, 'green') + else: owner = '' + return {"owner": owner, "state": "{0}".format(state), "speed": self._port_obj.get_formatted_speed() if self._port_obj else '', @@ -1038,21 +1044,19 @@ class CPortStats(CTRexStats): "-----": " ", "Tx bps L1": "{0} {1}".format(self.get_trend_gui("m_total_tx_bps_L1", show_value = False), - self.get("m_total_tx_bps_L1", format = True, suffix = "bps")), + self.get("m_total_tx_bps_L1", format = True, suffix = "bps", opts = rate_format['bpsl1'])), "Tx bps L2": "{0} {1}".format(self.get_trend_gui("m_total_tx_bps", show_value = False), - self.get("m_total_tx_bps", format = True, suffix = "bps")), + self.get("m_total_tx_bps", format = True, suffix = "bps", opts = rate_format['bps'])), - "Line Util.": "{0} {1}".format(self.get_trend_gui("m_percentage", show_value = False), - format_text( - self.get("m_percentage", format = True, suffix = "%") if self._port_obj else "", - 'bold')) if self._port_obj else "", + "Line Util.": "{0} {1}".format(self.get_trend_gui("m_percentage", show_value = False) if self._port_obj else "", + self.get("m_percentage", format = True, suffix = "%", opts = rate_format['percentage']) if self._port_obj else ""), "Rx bps": "{0} {1}".format(self.get_trend_gui("m_total_rx_bps", show_value = False), self.get("m_total_rx_bps", format = True, suffix = "bps")), "Tx pps": "{0} {1}".format(self.get_trend_gui("m_total_tx_pps", show_value = False), - self.get("m_total_tx_pps", format = True, suffix = "pps")), + self.get("m_total_tx_pps", format = True, suffix = "pps", opts = rate_format['pps'])), "Rx pps": "{0} {1}".format(self.get_trend_gui("m_total_rx_pps", show_value = False), self.get("m_total_rx_pps", format = True, suffix = "pps")), diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_opts.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_opts.py index 26e64dae..bfb96950 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_opts.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_opts.py @@ -27,7 +27,10 @@ class TextCodesStripper: def strip (s): return re.sub(TextCodesStripper.pattern, '', s) -def format_num (size, suffix = "", compact = True, opts = ()): +def format_num (size, suffix = "", compact = True, opts = None): + if opts is None: + opts = () + txt = "NaN" if type(size) == str: -- cgit From 344e3045d8346b4b204692e591e1556fc2333f97 Mon Sep 17 00:00:00 2001 From: imarom Date: Wed, 27 Jul 2016 11:08:09 +0300 Subject: support for graceful shutdown --- .../stl/trex_stl_lib/trex_stl_client.py | 47 ++++++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) (limited to 'scripts/automation/trex_control_plane/stl/trex_stl_lib') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py index a4f26f69..4e3d3092 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py @@ -1658,8 +1658,8 @@ class STLClient(object): """ - self.logger.pre_cmd( "Pinging the server on '{0}' port '{1}': ".format(self.connection_info['server'], - self.connection_info['sync_port'])) + self.logger.pre_cmd("Pinging the server on '{0}' port '{1}': ".format(self.connection_info['server'], + self.connection_info['sync_port'])) rc = self._transmit("ping", api_class = None) self.logger.post_cmd(rc) @@ -1667,6 +1667,30 @@ class STLClient(object): if not rc: raise STLError(rc) + @__api_check(True) + def server_shutdown (self, force = False): + """ + Sends the server a request for total shutdown + + :parameters: + force - shutdown server even if some ports are owned by another + user + + :raises: + + :exc:`STLError` + + """ + + self.logger.pre_cmd("Sending shutdown request for the server") + + rc = self._transmit("shutdown", params = {'force': force, 'user': self.username}) + + self.logger.post_cmd(rc) + + if not rc: + raise STLError(rc) + + @__api_check(True) def get_active_pgids(self): """ @@ -2107,7 +2131,7 @@ class STLClient(object): ports = ports if ports is not None else self.get_acquired_ports() ports = self._validate_port_list(ports) - validate_type('pcap_filename', pcap_filename, str) + validate_type('pcap_filename', pcap_filename, basestring) validate_type('ipg_usec', ipg_usec, (float, int, type(None))) validate_type('speedup', speedup, (float, int)) validate_type('count', count, int) @@ -2174,7 +2198,7 @@ class STLClient(object): ports = ports if ports is not None else self.get_acquired_ports() ports = self._validate_port_list(ports) - validate_type('pcap_filename', pcap_filename, str) + validate_type('pcap_filename', pcap_filename, basestring) validate_type('ipg_usec', ipg_usec, (float, int, type(None))) validate_type('speedup', speedup, (float, int)) validate_type('count', count, int) @@ -2444,6 +2468,21 @@ class STLClient(object): self.ping() return RC_OK() + @__console + def shutdown_line (self, line): + '''shutdown the server''' + parser = parsing_opts.gen_parser(self, + "shutdown", + self.shutdown_line.__doc__, + parsing_opts.FORCE) + + opts = parser.parse_args(line.split()) + if not opts: + return opts + + self.server_shutdown(force = opts.force) + return RC_OK() + @__console def connect_line (self, line): '''Connects to the TRex server and acquire ports''' -- cgit