diff options
author | Yaroslav Brustinov <ybrustin@cisco.com> | 2016-04-11 01:47:32 +0300 |
---|---|---|
committer | Yaroslav Brustinov <ybrustin@cisco.com> | 2016-04-11 01:47:32 +0300 |
commit | af49c70aaaecb897c7d3b4bddb18f504d32b0546 (patch) | |
tree | 4b30e6a2533379fd3febd512aab4e6bc359c7ba7 /scripts/automation | |
parent | 9b790cd6df9545ad69515560f6af62a6638a4093 (diff) |
hltapi stream stats, port tx/rx graphs at console
Diffstat (limited to 'scripts/automation')
9 files changed, 321 insertions, 221 deletions
diff --git a/scripts/automation/regression/stateful_tests/trex_general_test.py b/scripts/automation/regression/stateful_tests/trex_general_test.py index 5a13e5ff..7eae3224 100755 --- a/scripts/automation/regression/stateful_tests/trex_general_test.py +++ b/scripts/automation/regression/stateful_tests/trex_general_test.py @@ -135,10 +135,16 @@ class CTRexGeneral_Test(unittest.TestCase): if res[name] != float(val): self.fail('TRex results[%s]==%f and not as expected %f ' % (name, res[name], val)) - def check_CPU_benchmark (self, trex_res, err = 10, minimal_cpu = 30, maximal_cpu = 85): + def check_CPU_benchmark (self, trex_res, err = 10, minimal_cpu = None, maximal_cpu = 85): #cpu_util = float(trex_res.get_last_value("trex-global.data.m_cpu_util")) cpu_util = sum([float(x) for x in trex_res.get_value_list("trex-global.data.m_cpu_util")[-4:-1]]) / 3 # mean of 3 values before last - + + if minimal_cpu is None: + if '1G' in self.modes: + minimal_cpu = 1 + else: + minimal_cpu = 30 + if not self.is_virt_nics: if cpu_util > maximal_cpu: self.fail("CPU is too high (%s%%), probably queue full." % cpu_util ) diff --git a/scripts/automation/regression/stateful_tests/trex_nat_test.py b/scripts/automation/regression/stateful_tests/trex_nat_test.py index e7fe5ca5..e429bc17 100755 --- a/scripts/automation/regression/stateful_tests/trex_nat_test.py +++ b/scripts/automation/regression/stateful_tests/trex_nat_test.py @@ -161,8 +161,8 @@ class CTRexNat_Test(CTRexGeneral_Test):#(unittest.TestCase): self.assert_gt(nat_stats['num_of_hits'], 50000, 'total nat hits is not high enough') def tearDown(self): - CTRexGeneral_Test.tearDown(self) self.router.clear_nat_translations() + CTRexGeneral_Test.tearDown(self) if __name__ == "__main__": diff --git a/scripts/automation/trex_control_plane/stf/trex_stf_lib/trex_client.py b/scripts/automation/trex_control_plane/stf/trex_stf_lib/trex_client.py index 074d9060..9ca13e17 100755 --- a/scripts/automation/trex_control_plane/stf/trex_stf_lib/trex_client.py +++ b/scripts/automation/trex_control_plane/stf/trex_stf_lib/trex_client.py @@ -1224,13 +1224,12 @@ class CTRexResult(object): continue hist_last_keys = deque([res['histogram'][-1]['key']], maxlen = 2) sum_high = 0.0 - - for i, elem in enumerate(reversed(res['histogram'])): + for elem in reversed(res['histogram']): sum_high += elem['val'] hist_last_keys.append(elem['key']) if sum_high / res['cnt'] >= filtered_latency_amount: break - result[max_port] = sum(hist_last_keys) / len(hist_last_keys) + result[max_port] = (hist_last_keys[0] + hist_last_keys[-1]) / 2 else: return {} return result diff --git a/scripts/automation/trex_control_plane/stl/console/trex_console.py b/scripts/automation/trex_control_plane/stl/console/trex_console.py index da4c4486..aade572f 100755 --- a/scripts/automation/trex_control_plane/stl/console/trex_console.py +++ b/scripts/automation/trex_control_plane/stl/console/trex_console.py @@ -358,6 +358,7 @@ class TRexConsole(TRexGeneralCmd): ports = self.stateless_client.get_acquired_ports() if not ports: print("No ports acquired\n") + return with self.stateless_client.logger.supress(): table = stl_map_ports(self.stateless_client, ports = ports) diff --git a/scripts/automation/trex_control_plane/stl/console/trex_tui.py b/scripts/automation/trex_control_plane/stl/console/trex_tui.py index 88c53d10..a842e172 100644 --- a/scripts/automation/trex_control_plane/stl/console/trex_tui.py +++ b/scripts/automation/trex_control_plane/stl/console/trex_tui.py @@ -2,7 +2,7 @@ import termios import sys import os import time -from collections import OrderedDict +from collections import OrderedDict, deque import datetime if sys.version_info > (3,0): @@ -40,6 +40,7 @@ class TrexTUIPanel(object): self.mng = mng self.name = name self.stateless_client = mng.stateless_client + self.is_graph = False def show (self): raise NotImplementedError("must implement this") @@ -75,7 +76,7 @@ class TrexTUIDashBoard(TrexTUIPanel): def get_key_actions (self): - allowed = {} + allowed = OrderedDict() allowed['c'] = self.key_actions['c'] @@ -152,19 +153,26 @@ class TrexTUIPort(TrexTUIPanel): self.key_actions['r'] = {'action': self.action_resume, 'legend': 'resume', 'show': True} self.key_actions['+'] = {'action': self.action_raise, 'legend': 'up 5%', 'show': True} self.key_actions['-'] = {'action': self.action_lower, 'legend': 'low 5%', 'show': True} + self.key_actions['t'] = {'action': self.action_toggle_graph, 'legend': 'toggle graph', 'show': True} def show (self): - stats = self.stateless_client._get_formatted_stats([self.port_id]) - # print stats to screen - for stat_type, stat_data in stats.items(): - text_tables.print_table_with_header(stat_data.text_table, stat_type) + if self.mng.tui.is_graph is False: + stats = self.stateless_client._get_formatted_stats([self.port_id]) + # print stats to screen + for stat_type, stat_data in stats.items(): + text_tables.print_table_with_header(stat_data.text_table, stat_type) + else: + stats = self.stateless_client._get_formatted_stats([self.port_id], stats_mask = trex_stl_stats.GRAPH_PORT_COMPACT) + for stat_type, stat_data in stats.items(): + text_tables.print_table_with_header(stat_data.text_table, stat_type) def get_key_actions (self): - allowed = {} + allowed = OrderedDict() allowed['c'] = self.key_actions['c'] + allowed['t'] = self.key_actions['t'] if self.stateless_client.is_all_ports_acquired(): return allowed @@ -180,7 +188,14 @@ class TrexTUIPort(TrexTUIPanel): return allowed - # actions + def action_toggle_graph(self): + try: + self.mng.tui.is_graph = not self.mng.tui.is_graph + except Exception: + pass + + return "" + def action_pause (self): try: self.stateless_client.pause(ports = [self.port_id]) @@ -399,6 +414,7 @@ class TrexTUI(): STATE_ACTIVE = 0 STATE_LOST_CONT = 1 STATE_RECONNECT = 2 + is_graph = False def __init__ (self, stateless_client): self.stateless_client = stateless_client 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 bddc4ad0..0f297619 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 @@ -474,7 +474,7 @@ class STLClient(object): self.server_version, self.ports) - self.flow_stats = trex_stl_stats.CRxStats() + self.flow_stats = trex_stl_stats.CRxStats(self.ports) self.stats_generator = trex_stl_stats.CTRexInfoGenerator(self.global_stats, self.ports, diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_hltapi.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_hltapi.py index b506137b..1bec3b6b 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_hltapi.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_hltapi.py @@ -172,12 +172,22 @@ import sys import os import socket import copy -from collections import defaultdict +from collections import defaultdict, OrderedDict from .api import * from .trex_stl_types import * from .utils.common import get_number +class LRU_cache(OrderedDict): + def __init__(self, maxlen = 20, *args, **kwargs): + OrderedDict.__init__(self, *args, **kwargs) + self.maxlen = maxlen + + def __setitem__(self, *args, **kwargs): + OrderedDict.__setitem__(self, *args, **kwargs) + if len(self) > self.maxlen: + self.popitem(last = False) + class HLT_ERR(dict): def __init__(self, log = 'Unknown error', **kwargs): @@ -201,7 +211,7 @@ def merge_kwargs(default_kwargs, user_kwargs): for key, value in user_kwargs.items(): if key in kwargs: kwargs[key] = value - elif key in ('save_to_yaml', 'save_to_pcap'): # internal debug arguments + elif key in ('save_to_yaml', 'save_to_pcap', 'pg_id'): # internal arguments kwargs[key] = value else: print("Warning: provided parameter '%s' is not supported" % key) @@ -338,8 +348,11 @@ class CTRexHltApi(object): def __init__(self, verbose = 0): self.trex_client = None self.verbose = verbose - self._streams_history = {} # streams per stream_id per port in format of HLT arguments for modify later - + self._last_pg_id = 0 # pg_id acts as stream_handle + self._streams_history = {} # streams in format of HLT arguments for modify later + self._native_handle_by_pg_id = {} # pg_id -> native handle + port + self._pg_id_by_id = {} # stream_id -> pg_id + self._pg_id_by_name = {} # name -> pg_id ########################### # Session functions # @@ -375,9 +388,6 @@ class CTRexHltApi(object): self.trex_client = None return HLT_ERR('Could not acquire ports %s: %s' % (port_list, e if isinstance(e, STLError) else traceback.format_exc())) - # since only supporting single TRex at the moment, 1:1 map - port_handle = self.trex_client.get_acquired_ports() - # arrived here, all desired ports were successfully acquired if kwargs['reset']: # remove all port traffic configuration from TRex @@ -389,7 +399,7 @@ class CTRexHltApi(object): return HLT_ERR('Error in reset traffic: %s' % e if isinstance(e, STLError) else traceback.format_exc()) self._streams_history = CStreamsPerPort(hlt_history = True) - return HLT_OK(port_handle = port_handle) + return HLT_OK(port_handle = dict([(port_id, port_id) for port_id in port_list])) def cleanup_session(self, **user_kwargs): kwargs = merge_kwargs(cleanup_session_kwargs, user_kwargs) @@ -446,23 +456,9 @@ class CTRexHltApi(object): kwargs = merge_kwargs(traffic_config_kwargs, user_kwargs) stream_id = kwargs['stream_id'] mode = kwargs['mode'] - if type(stream_id) is list: - if len(stream_id) > 1: - streams_per_port = CStreamsPerPort() - for each_stream_id in stream_id: - user_kwargs[stream_id] = each_stream_id - res = self.traffic_config(**user_kwargs) - if type(res) is HLT_ERR: - return res - streams_per_port.add_streams_from_res(res) - if mode == 'create': - return HLT_OK(stream_id = streams_per_port) - else: - return HLT_OK() - else: - stream_id = stream_id[0] - + pg_id = None port_handle = port_list = self._parse_port_list(kwargs['port_handle']) + ALLOWED_MODES = ['create', 'modify', 'remove', 'enable', 'disable', 'reset'] if mode not in ALLOWED_MODES: return HLT_ERR('Mode must be one of the following values: %s' % ALLOWED_MODES) @@ -480,7 +476,7 @@ class CTRexHltApi(object): if mode == 'remove': if stream_id is None: return HLT_ERR('Please specify stream_id to remove.') - if type(stream_id) is str and stream_id == 'all': + if stream_id == 'all': try: self.trex_client.remove_all_streams(port_handle) for port in port_handle: @@ -504,14 +500,14 @@ class CTRexHltApi(object): # self._streams_history[stream_id].update(kwargs) # <- the modification if mode == 'modify': # we remove stream and create new one with same stream_id - stream_id = kwargs.get('stream_id') - if stream_id is None: + pg_id = kwargs.get('stream_id') + if pg_id is None: return HLT_ERR('Please specify stream_id to modify.') if len(port_handle) > 1: for port in port_handle: - user_kwargs[port_handle] = port - res = self.traffic_config(**user_kwargs) # recurse per port, each port can have different stream with such id + user_kwargs['port_handle'] = port + res = self.traffic_config(**user_kwargs) else: if type(port_handle) is list: port = port_handle[0] @@ -519,9 +515,9 @@ class CTRexHltApi(object): port = port_handle if port not in self._streams_history: return HLT_ERR('Port %s was not used/acquired' % port) - if stream_id not in self._streams_history[port]: + if pg_id not in self._streams_history[port]: return HLT_ERR('This stream_id (%s) was not used before at port %s, please create new.' % (stream_id, port)) - kwargs.update(self._streams_history[port][stream_id]) + kwargs.update(self._streams_history[port][pg_id]) kwargs.update(user_kwargs) try: self.trex_client.remove_streams(stream_id, port_handle) @@ -530,24 +526,24 @@ class CTRexHltApi(object): if mode == 'create' or mode == 'modify': # create a new stream with desired attributes, starting by creating packet - streams_per_port = CStreamsPerPort() if is_true(kwargs['bidirectional']): # two streams with opposite directions del user_kwargs['bidirectional'] + stream_per_port = {} save_to_yaml = user_kwargs.get('save_to_yaml') bidirect_err = 'When using bidirectional flag, ' if len(port_handle) != 1: return HLT_ERR(bidirect_err + 'port_handle1 should be single port handle.') + port_handle = port_handle[0] port_handle2 = kwargs['port_handle2'] if (type(port_handle2) is list and len(port_handle2) > 1) or port_handle2 is None: return HLT_ERR(bidirect_err + 'port_handle2 should be single port handle.') try: if save_to_yaml and type(save_to_yaml) is str: user_kwargs['save_to_yaml'] = save_to_yaml.replace('.yaml', '_bi1.yaml') - user_kwargs['port_handle'] = port_handle[0] res1 = self.traffic_config(**user_kwargs) if res1['status'] == 0: raise STLError('Could not create bidirectional stream 1: %s' % res1['log']) - streams_per_port.add_streams_from_res(res1) + stream_per_port[port_handle] = res1['stream_id'] kwargs['direction'] = 1 - kwargs['direction'] # not correct_direction(user_kwargs, kwargs) if save_to_yaml and type(save_to_yaml) is str: @@ -556,11 +552,11 @@ class CTRexHltApi(object): res2 = self.traffic_config(**user_kwargs) if res2['status'] == 0: raise STLError('Could not create bidirectional stream 2: %s' % res2['log']) - streams_per_port.add_streams_from_res(res2) + stream_per_port[port_handle2] = res2['stream_id'] except Exception as e: return HLT_ERR('Could not generate bidirectional traffic: %s' % e if isinstance(e, STLError) else traceback.format_exc()) if mode == 'create': - return HLT_OK(stream_id = streams_per_port) + return HLT_OK(stream_id = stream_per_port) else: return HLT_OK() @@ -568,6 +564,9 @@ class CTRexHltApi(object): if kwargs['load_profile']: stream_obj = STLProfile.load_py(kwargs['load_profile'], direction = kwargs['direction']) else: + if not pg_id: + pg_id = self._get_available_pg_id() + user_kwargs['pg_id'] = pg_id stream_obj = STLHltStream(**user_kwargs) except Exception as e: return HLT_ERR('Could not create stream: %s' % e if isinstance(e, STLError) else traceback.format_exc()) @@ -583,10 +582,7 @@ class CTRexHltApi(object): except Exception as e: return HLT_ERR('Could not add stream to ports: %s' % e if isinstance(e, STLError) else traceback.format_exc()) if mode == 'create': - if len(stream_id_arr) == 1: - return HLT_OK(stream_id = dict((port, stream_id_arr[0]) for port in port_handle)) - else: - return HLT_OK(stream_id = dict((port, stream_id_arr) for port in port_handle)) + return HLT_OK(stream_id = pg_id) else: return HLT_OK() @@ -652,42 +648,64 @@ class CTRexHltApi(object): kwargs = merge_kwargs(traffic_stats_kwargs, user_kwargs) mode = kwargs['mode'] port_handle = kwargs['port_handle'] + if type(port_handle) is not list: + port_handle = [port_handle] ALLOWED_MODES = ['aggregate', 'streams', 'all'] if mode not in ALLOWED_MODES: return HLT_ERR("'mode' must be one of the following values: %s" % ALLOWED_MODES) - if mode == 'streams': - return HLT_ERR("mode 'streams' not implemented'") - if mode in ('all', 'aggregate'): - hlt_stats_dict = {} - try: - stats = self.trex_client.get_stats(port_handle) - except Exception as e: - return HLT_ERR('Could not retrieve stats: %s' % e if isinstance(e, STLError) else traceback.format_exc()) - for port_id, stat_dict in stats.items(): - if is_integer(port_id): - hlt_stats_dict[port_id] = { - 'aggregate': { - 'tx': { - 'pkt_bit_rate': stat_dict.get('tx_bps'), - 'pkt_byte_count': stat_dict.get('obytes'), - 'pkt_count': stat_dict.get('opackets'), - 'pkt_rate': stat_dict.get('tx_pps'), - 'total_pkt_bytes': stat_dict.get('obytes'), - 'total_pkt_rate': stat_dict.get('tx_pps'), - 'total_pkts': stat_dict.get('opackets'), - }, - 'rx': { - 'pkt_bit_rate': stat_dict.get('rx_bps'), - 'pkt_byte_count': stat_dict.get('ibytes'), - 'pkt_count': stat_dict.get('ipackets'), - 'pkt_rate': stat_dict.get('rx_pps'), - 'total_pkt_bytes': stat_dict.get('ibytes'), - 'total_pkt_rate': stat_dict.get('rx_pps'), - 'total_pkts': stat_dict.get('ipackets'), + hlt_stats_dict = dict([(port, {}) for port in port_handle]) + try: + stats = self.trex_client.get_stats(port_handle) + if mode in ('all', 'aggregate'): + for port_id in port_handle: + port_stats = stats[port_id] + if is_integer(port_id): + hlt_stats_dict[port_id]['aggregate'] = { + 'tx': { + 'pkt_bit_rate': port_stats.get('tx_bps', 0), + 'pkt_byte_count': port_stats.get('obytes', 0), + 'pkt_count': port_stats.get('opackets', 0), + 'pkt_rate': port_stats.get('tx_pps', 0), + 'total_pkt_bytes': port_stats.get('obytes', 0), + 'total_pkt_rate': port_stats.get('tx_pps', 0), + 'total_pkts': port_stats.get('opackets', 0), + }, + 'rx': { + 'pkt_bit_rate': port_stats.get('rx_bps', 0), + 'pkt_byte_count': port_stats.get('ibytes', 0), + 'pkt_count': port_stats.get('ipackets', 0), + 'pkt_rate': port_stats.get('rx_pps', 0), + 'total_pkt_bytes': port_stats.get('ibytes', 0), + 'total_pkt_rate': port_stats.get('rx_pps', 0), + 'total_pkts': port_stats.get('ipackets', 0), + } + } + if mode in ('all', 'streams'): + for pg_id, pg_stats in stats['flow_stats'].items(): + for port_id in port_handle: + if 'stream' not in hlt_stats_dict[port_id]: + hlt_stats_dict[port_id]['stream'] = {} + hlt_stats_dict[port_id]['stream'][pg_id] = { + 'tx': { + 'total_pkts': pg_stats['tx_pkts'].get(port_id, 0), + 'total_pkt_bytes': pg_stats['tx_bytes'].get(port_id, 0), + 'total_pkts_bytes': pg_stats['tx_bytes'].get(port_id, 0), + 'total_pkt_bit_rate': pg_stats['tx_bps'].get(port_id, 0), + 'total_pkt_rate': pg_stats['tx_pps'].get(port_id, 0), + 'line_rate_percentage': pg_stats['tx_line_util'].get(port_id, 0), + }, + 'rx': { + 'total_pkts': pg_stats['rx_pkts'].get(port_id, 0), + 'total_pkt_bytes': pg_stats['rx_bytes'].get(port_id, 0), + 'total_pkts_bytes': pg_stats['rx_bytes'].get(port_id, 0), + 'total_pkt_bit_rate': pg_stats['rx_bps'].get(port_id, 0), + 'total_pkt_rate': pg_stats['rx_pps'].get(port_id, 0), + 'line_rate_percentage': pg_stats['rx_line_util'].get(port_id, 0), + }, } - } - } - return HLT_OK(hlt_stats_dict) + except Exception as e: + return HLT_ERR('Could not retrieve stats: %s' % e if isinstance(e, STLError) else traceback.format_exc()) + return HLT_OK(hlt_stats_dict) # timeout = maximal time to wait def wait_on_traffic(self, port_handle = None, timeout = 60): @@ -700,6 +718,18 @@ class CTRexHltApi(object): # Private functions # ########################### + def _get_available_pg_id(self): + pg_id = self._last_pg_id + used_pg_ids = self.trex_client.get_stats()['flow_stats'].keys() + for i in range(65535): + pg_id += 1 + if pg_id not in used_pg_ids: + self._last_pg_id = pg_id + return pg_id + if pg_id == 65535: + pg_id = 0 + raise STLError('Could not find free pg_id in range [1, 65535].') + # remove streams from given port(s). # stream_id can be: # * int - exact stream_id value @@ -812,30 +842,33 @@ def STLHltStream(**user_kwargs): # packet generation packet = generate_packet(**user_kwargs) + + # stream generation try: rate_types_dict = {'rate_pps': 'pps', 'rate_bps': 'bps_L2', 'rate_percent': 'percentage'} rate_stateless = {rate_types_dict[rate_key]: float(kwargs[rate_key])} transmit_mode = kwargs['transmit_mode'] pkts_per_burst = kwargs['pkts_per_burst'] if transmit_mode == 'continuous': - transmit_mode_class = STLTXCont(**rate_stateless) + transmit_mode_obj = STLTXCont(**rate_stateless) elif transmit_mode == 'single_burst': - transmit_mode_class = STLTXSingleBurst(total_pkts = pkts_per_burst, **rate_stateless) + transmit_mode_obj = STLTXSingleBurst(total_pkts = pkts_per_burst, **rate_stateless) elif transmit_mode == 'multi_burst': - transmit_mode_class = STLTXMultiBurst(total_pkts = pkts_per_burst, count = int(kwargs['burst_loop_count']), + transmit_mode_obj = STLTXMultiBurst(total_pkts = pkts_per_burst, count = int(kwargs['burst_loop_count']), ibg = kwargs['inter_burst_gap'], **rate_stateless) else: raise STLError('transmit_mode %s not supported/implemented') except Exception as e: - raise STLError('Could not create transmit_mode class %s: %s' % (transmit_mode, e if isinstance(e, STLError) else traceback.format_exc())) + raise STLError('Could not create transmit_mode object %s: %s' % (transmit_mode, e if isinstance(e, STLError) else traceback.format_exc())) - # stream generation try: + pg_id = kwargs.get('pg_id') stream = STLStream(packet = packet, random_seed = 1 if is_true(kwargs['consistent_random']) else 0, #enabled = True, #self_start = True, - mode = transmit_mode_class, + flow_stats = STLFlowStats(pg_id) if pg_id else None, + mode = transmit_mode_obj, stream_id = kwargs['stream_id'], name = kwargs['name'], ) @@ -848,8 +881,12 @@ def STLHltStream(**user_kwargs): stream.dump_to_yaml(debug_filename) return stream +packet_cache = LRU_cache(maxlen = 20) + def generate_packet(**user_kwargs): correct_macs(user_kwargs) + if repr(user_kwargs) in packet_cache: + return packet_cache[repr(user_kwargs)] kwargs = merge_kwargs(traffic_config_kwargs, user_kwargs) correct_sizes(kwargs) # we are producing the packet - 4 bytes fcs correct_direction(kwargs, kwargs) @@ -868,8 +905,12 @@ def generate_packet(**user_kwargs): kwargs['mac_dst'] = None kwargs['mac_src_mode'] = 'fixed' kwargs['mac_dst_mode'] = 'fixed' - - l2_layer = Ether(src = kwargs['mac_src'], dst = kwargs['mac_dst']) + ethernet_kwargs = {} + if kwargs['mac_src']: + ethernet_kwargs['src'] = kwargs['mac_src'] + if kwargs['mac_dst']: + ethernet_kwargs['dst'] = kwargs['mac_dst'] + l2_layer = Ether(**ethernet_kwargs) # Eth VM, change only 32 lsb if kwargs['mac_src_mode'] != 'fixed': @@ -1475,6 +1516,7 @@ def generate_packet(**user_kwargs): debug_filename = kwargs.get('save_to_pcap') if type(debug_filename) is str: pkt.dump_pkt_to_pcap(debug_filename) + packet_cache[repr(user_kwargs)] = pkt return pkt def get_TOS(user_kwargs, kwargs): 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 a4bb64db..dd597648 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 @@ -2,6 +2,7 @@ from .utils import text_tables from .utils.text_opts import format_text, format_threshold, format_num +from .trex_stl_types import StatNotAvailable from collections import namedtuple, OrderedDict, deque import sys @@ -16,11 +17,13 @@ import pprint GLOBAL_STATS = 'g' PORT_STATS = 'p' +PORT_GRAPH = 'pg' PORT_STATUS = 'ps' STREAMS_STATS = 's' -ALL_STATS_OPTS = [GLOBAL_STATS, PORT_STATS, PORT_STATUS, STREAMS_STATS] +ALL_STATS_OPTS = [GLOBAL_STATS, PORT_STATS, PORT_STATUS, STREAMS_STATS, PORT_GRAPH] COMPACT = [GLOBAL_STATS, PORT_STATS] +GRAPH_PORT_COMPACT = [GLOBAL_STATS, PORT_GRAPH] SS_COMPAT = [GLOBAL_STATS, STREAMS_STATS] ExportableStats = namedtuple('ExportableStats', ['raw_data', 'text_table']) @@ -107,11 +110,15 @@ class CTRexInfoGenerator(object): elif statistic_type == PORT_STATS: return self._generate_port_stats(port_id_list) + elif statistic_type == PORT_GRAPH: + return self._generate_port_graph(port_id_list) + elif statistic_type == PORT_STATUS: return self._generate_port_status(port_id_list) elif statistic_type == STREAMS_STATS: return self._generate_streams_stats() + else: # ignore by returning empty object return {} @@ -163,70 +170,49 @@ class CTRexInfoGenerator(object): return {"streams_statistics": ExportableStats(sstats_data, stats_table)} - - - per_stream_stats = OrderedDict([("owner", []), - ("state", []), - ("--", []), - ("Tx bps L2", []), - ("Tx bps L1", []), - ("Tx pps", []), - ("Line Util.", []), - - ("---", []), - ("Rx bps", []), - ("Rx pps", []), - - ("----", []), - ("opackets", []), - ("ipackets", []), - ("obytes", []), - ("ibytes", []), - ("tx-bytes", []), - ("rx-bytes", []), - ("tx-pkts", []), - ("rx-pkts", []), - - ("-----", []), - ("oerrors", []), - ("ierrors", []), - - ] - ) - - total_stats = CPortStats(None) - - for port_obj in relevant_ports: - # fetch port data - port_stats = port_obj.generate_port_stats() - - total_stats += port_obj.port_stats - - # populate to data structures - return_stats_data[port_obj.port_id] = port_stats - self.__update_per_field_dict(port_stats, per_field_stats) + @staticmethod + def _get_rational_block_char(value, range_start, interval): + # in Konsole, utf-8 is sometimes printed with artifacts, return ascii for now + return 'X' if value >= range_start + float(interval) / 2 else ' ' + value -= range_start + ratio = float(value) / interval + if ratio <= 0.0625: + return u' ' # empty block + if ratio <= 0.1875: + return u'\u2581' # 1/8 + if ratio <= 0.3125: + return u'\u2582' # 2/4 + if ratio <= 0.4375: + return u'\u2583' # 3/8 + if ratio <= 0.5625: + return u'\u2584' # 4/8 + if ratio <= 0.6875: + return u'\u2585' # 5/8 + if ratio <= 0.8125: + return u'\u2586' # 6/8 + if ratio <= 0.9375: + return u'\u2587' # 7/8 + return u'\u2588' # full block + + def _generate_port_graph(self, port_id_list): + relevant_port = self.__get_relevant_ports(port_id_list)[0] + hist_len = len(relevant_port.port_stats.history) + hist_maxlen = relevant_port.port_stats.history.maxlen + util_tx_hist = [0] * (hist_maxlen - hist_len) + [round(relevant_port.port_stats.history[i]['m_percentage']) for i in range(hist_len)] + util_rx_hist = [0] * (hist_maxlen - hist_len) + [round(relevant_port.port_stats.history[i]['m_rx_percentage']) for i in range(hist_len)] - total_cols = len(relevant_ports) - header = ["port"] + [port.port_id for port in relevant_ports] - - if (total_cols > 1): - self.__update_per_field_dict(total_stats.generate_stats(), per_field_stats) - header += ['total'] - total_cols += 1 stats_table = text_tables.TRexTextTable() - stats_table.set_cols_align(["l"] + ["r"] * total_cols) - stats_table.set_cols_width([10] + [17] * total_cols) - stats_table.set_cols_dtype(['t'] + ['t'] * total_cols) - - stats_table.add_rows([[k] + v - for k, v in per_field_stats.items()], - header=False) + stats_table.header([' Util(%)', 'TX', 'RX']) + stats_table.set_cols_align(['c', 'c', 'c']) + stats_table.set_cols_width([8, hist_maxlen, hist_maxlen]) + stats_table.set_cols_dtype(['t', 't', 't']) - stats_table.header(header) - - return {"streams_statistics": ExportableStats(return_stats_data, stats_table)} + for y in range(95, -1, -5): + stats_table.add_row([y, ''.join([self._get_rational_block_char(util_tx, y, 5) for util_tx in util_tx_hist]), + ''.join([self._get_rational_block_char(util_rx, y, 5) for util_rx in util_rx_hist])]) + return {"port_statistics": ExportableStats({}, stats_table)} def _generate_port_stats(self, port_id_list): relevant_ports = self.__get_relevant_ports(port_id_list) @@ -374,7 +360,8 @@ class CTRexInfoGenerator(object): # display only the first FOUR options, by design if len(ports) > 4: - self.logger.log(format_text("[WARNING]: ", 'magenta', 'bold'), format_text("displaying up to 4 ports", 'magenta')) + #self.logger is not defined + #self.logger.log(format_text("[WARNING]: ", 'magenta', 'bold'), format_text("displaying up to 4 ports", 'magenta')) ports = ports[:4] return ports @@ -400,7 +387,7 @@ class CTRexStats(object): self.reference_stats = {} self.latest_stats = {} self.last_update_ts = time.time() - self.history = deque(maxlen = 10) + self.history = deque(maxlen = 30) self.lock = threading.Lock() self.has_baseline = False @@ -444,6 +431,7 @@ class CTRexStats(object): def clear_stats(self): self.reference_stats = copy.deepcopy(self.latest_stats) + self.history.clear() def invalidate (self): @@ -469,19 +457,18 @@ class CTRexStats(object): def get(self, field, format=False, suffix=""): value = self._get(self.latest_stats, field) if value == None: - return "N/A" + return 'N/A' return value if not format else format_num(value, suffix) def get_rel(self, field, format=False, suffix=""): - ref_value = self._get(self.reference_stats, field) latest_value = self._get(self.latest_stats, field) # latest value is an aggregation - must contain the value if latest_value == None: - return "N/A" + return 'N/A' if ref_value == None: ref_value = 0 @@ -493,7 +480,7 @@ class CTRexStats(object): # get trend for a field def get_trend (self, field, use_raw = False, percision = 10.0): - if not field in self.latest_stats: + if field not in self.latest_stats: return 0 # not enough history - no trend @@ -506,7 +493,7 @@ class CTRexStats(object): # must lock, deque is not thread-safe for iteration with self.lock: - field_samples = [sample[field] for sample in self.history] + field_samples = [sample[field] for sample in list(self.history)[-5:]] if use_raw: return calculate_diff_raw(field_samples) @@ -694,10 +681,14 @@ class CPortStats(CTRexStats): # L1 bps bps = snapshot.get("m_total_tx_bps") pps = snapshot.get("m_total_tx_pps") + rx_bps = snapshot.get("m_total_rx_bps") + rx_pps = snapshot.get("m_total_rx_pps") bps_L1 = calc_bps_L1(bps, pps) + rx_bps_L1 = calc_bps_L1(rx_bps, rx_pps) snapshot['m_total_tx_bps_L1'] = bps_L1 snapshot['m_percentage'] = (bps_L1 / self._port_obj.get_speed_bps()) * 100 + snapshot['m_rx_percentage'] = (rx_bps_L1 / self._port_obj.get_speed_bps()) * 100 # simple... self.latest_stats = snapshot @@ -769,9 +760,15 @@ class CPortStats(CTRexStats): # RX stats objects - COMPLEX :-( class CRxStats(CTRexStats): - def __init__(self): + 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 @@ -797,7 +794,7 @@ class CRxStats(CTRexStats): for field in ['tx_pkts', 'tx_bytes', 'rx_pkts', 'rx_bytes']: # is in the first time ? (nothing in prev) - if not field in output: + if field not in output: output[field] = {} # does the current snapshot has this field ? @@ -869,46 +866,66 @@ class CRxStats(CTRexStats): def calculate_bw_for_pg (self, pg_current, pg_prev = None, diff_sec = 0.0): - - # if no previous values - its None + # no previous values if (pg_prev == None) or not (diff_sec > 0): - pg_current['tx_pps'] = None - pg_current['tx_bps'] = None - pg_current['tx_bps_L1'] = None - pg_current['rx_pps'] = None - pg_current['rx_bps'] = None + pg_current['tx_pps'] = {} + pg_current['tx_bps'] = {} + pg_current['tx_bps_L1'] = {} + pg_current['tx_line_util'] = {} + pg_current['rx_pps'] = {} + pg_current['rx_bps'] = {} + pg_current['rx_bps_L1'] = {} + pg_current['rx_line_util'] = {} + + pg_current['tx_pps_lpf'] = {} + pg_current['tx_bps_lpf'] = {} + pg_current['tx_bps_L1_lpf'] = {} + pg_current['rx_pps_lpf'] = {} + pg_current['rx_bps_lpf'] = {} + pg_current['rx_bps_L1_lpf'] = {} return - - # read the current values - now_tx_pkts = pg_current['tx_pkts']['total'] - now_tx_bytes = pg_current['tx_bytes']['total'] - now_rx_pkts = pg_current['rx_pkts']['total'] - now_rx_bytes = pg_current['rx_bytes']['total'] - - # prev values - prev_tx_pkts = pg_prev['tx_pkts']['total'] - prev_tx_bytes = pg_prev['tx_bytes']['total'] - prev_rx_pkts = pg_prev['rx_pkts']['total'] - prev_rx_bytes = pg_prev['rx_bytes']['total'] - - # prev B/W - prev_tx_pps = pg_prev['tx_pps'] - prev_tx_bps = pg_prev['tx_bps'] - prev_rx_pps = pg_prev['rx_pps'] - prev_rx_bps = pg_prev['rx_bps'] - - - #assert(now_tx_pkts >= prev_tx_pkts) - pg_current['tx_pps'] = self.calc_pps(prev_tx_pps, now_tx_pkts, prev_tx_pkts, diff_sec) - pg_current['tx_bps'] = self.calc_bps(prev_tx_bps, now_tx_bytes, prev_tx_bytes, diff_sec) - pg_current['rx_pps'] = self.calc_pps(prev_rx_pps, now_rx_pkts, prev_rx_pkts, diff_sec) - pg_current['rx_bps'] = self.calc_bps(prev_rx_bps, now_rx_bytes, prev_rx_bytes, diff_sec) - - if pg_current['tx_bps'] != None and pg_current['tx_pps'] != None: - pg_current['tx_bps_L1'] = calc_bps_L1(pg_current['tx_bps'], pg_current['tx_pps']) - else: - pg_current['tx_bps_L1'] = None + # 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) + pg_current['tx_pps'][port], pg_current['tx_pps_lpf'][port] = self.calc_pps(prev_tx_pps, now_tx_pkts, prev_tx_pkts, diff_sec) + + 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) + pg_current['rx_pps'][port], pg_current['rx_pps_lpf'][port] = self.calc_pps(prev_rx_pps, now_rx_pkts, prev_rx_pkts, diff_sec) + + prev_rx_bps = pg_prev['rx_bps'].get(port) + now_rx_bytes = pg_current['rx_bytes'].get(port) + prev_rx_bytes = pg_prev['rx_bytes'].get(port) + pg_current['rx_bps'][port], pg_current['rx_bps_lpf'][port] = self.calc_bps(prev_rx_bps, now_rx_bytes, prev_rx_bytes, diff_sec) + 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): @@ -918,11 +935,11 @@ class CRxStats(CTRexStats): def calc_bps (self, prev_bw, now, prev, diff_sec): return self.calc_bw(prev_bw, now, prev, diff_sec, True) - + # returns tuple - first value is real, second is low pass filtered def calc_bw (self, prev_bw, now, prev, diff_sec, is_bps): # B/W is not valid when the values are None if (now is None) or (prev is None): - return None + return (None, None) # calculate the B/W for current snapshot current_bw = (now - prev) / diff_sec @@ -933,7 +950,7 @@ class CRxStats(CTRexStats): if prev_bw is None: prev_bw = 0 - return ( (0.5 * prev_bw) + (0.5 * current_bw) ) + return (current_bw, 0.5 * prev_bw + 0.5 * current_bw) @@ -960,22 +977,29 @@ class CRxStats(CTRexStats): # skip non ints if not is_intable(pg_id): continue - + # bare counters stats[int(pg_id)] = {} - for field in ['tx_pkts', 'tx_bytes', 'rx_pkts']: - stats[int(pg_id)][field] = {'total': self.get_rel([pg_id, field, 'total'])} - - for port, pv in value[field].items(): - try: - int(port) - except ValueError: - continue - stats[int(pg_id)][field][int(port)] = self.get_rel([pg_id, field, port]) + for field in ['tx_pkts', 'tx_bytes', 'rx_pkts', 'rx_bytes']: + val = self.get_rel([pg_id, field, 'total']) + stats[int(pg_id)][field] = {'total': val if val != 'N/A' else StatNotAvailable(field)} + for port in value[field].keys(): + if is_intable(port): + val = self.get_rel([pg_id, field, port]) + 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']: + 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(): + if is_intable(port): + val = self.get([pg_id, field, port]) + stats[int(pg_id)][field][int(port)] = val if val != 'N/A' else StatNotAvailable(field) return stats - + # for Console def generate_stats (self): # for TUI - maximum 4 @@ -1005,13 +1029,13 @@ class CRxStats(CTRexStats): # maximum 4 for pg_id in pg_ids: - formatted_stats['Tx pps'].append(self.get([pg_id, 'tx_pps'], format = True, suffix = "pps")) - formatted_stats['Tx bps L2'].append(self.get([pg_id, 'tx_bps'], format = True, suffix = "bps")) + formatted_stats['Tx pps'].append(self.get([pg_id, 'tx_pps_lpf', 'total'], format = True, suffix = "pps")) + formatted_stats['Tx bps L2'].append(self.get([pg_id, 'tx_bps_lpf', 'total'], format = True, suffix = "bps")) - formatted_stats['Tx bps L1'].append(self.get([pg_id, 'tx_bps_L1'], format = True, suffix = "bps")) + formatted_stats['Tx bps L1'].append(self.get([pg_id, 'tx_bps_L1_lpf', 'total'], format = True, suffix = "bps")) - formatted_stats['Rx pps'].append(self.get([pg_id, 'rx_pps'], format = True, suffix = "pps")) - formatted_stats['Rx bps'].append(self.get([pg_id, 'rx_bps'], format = True, suffix = "bps")) + formatted_stats['Rx pps'].append(self.get([pg_id, 'rx_pps_lpf', 'total'], format = True, suffix = "pps")) + formatted_stats['Rx bps'].append(self.get([pg_id, 'rx_bps_lpf', 'total'], format = True, suffix = "bps")) formatted_stats['opackets'].append(self.get_rel([pg_id, 'tx_pkts', 'total'])) formatted_stats['ipackets'].append(self.get_rel([pg_id, 'rx_pkts', 'total'])) diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_types.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_types.py index cd15b831..f3ac5c65 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_types.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_types.py @@ -139,4 +139,16 @@ def validate_type(arg_name, arg, valid_types): def verify_exclusive_arg (args_list): if not (len(list(filter(lambda x: x is not None, args_list))) == 1): raise STLError('exactly one parameter from {0} should be provided'.format(args_list)) - + + +# shows as 'N/A', but does not let any compares for user to not mistake in automation +class StatNotAvailable(object): + def __init__(self, stat_name): + self.stat_name = stat_name + + def __repr__(self, *args, **kwargs): + return 'N/A' + + def __cmp__(self, *args, **kwargs): + raise Exception("Stat '%s' not available at this setup" % self.stat_name) + |