summaryrefslogtreecommitdiffstats
path: root/scripts/automation
diff options
context:
space:
mode:
authorYaroslav Brustinov <ybrustin@cisco.com>2016-04-11 01:47:32 +0300
committerYaroslav Brustinov <ybrustin@cisco.com>2016-04-11 01:47:32 +0300
commitaf49c70aaaecb897c7d3b4bddb18f504d32b0546 (patch)
tree4b30e6a2533379fd3febd512aab4e6bc359c7ba7 /scripts/automation
parent9b790cd6df9545ad69515560f6af62a6638a4093 (diff)
hltapi stream stats, port tx/rx graphs at console
Diffstat (limited to 'scripts/automation')
-rwxr-xr-xscripts/automation/regression/stateful_tests/trex_general_test.py10
-rwxr-xr-xscripts/automation/regression/stateful_tests/trex_nat_test.py2
-rwxr-xr-xscripts/automation/trex_control_plane/stf/trex_stf_lib/trex_client.py5
-rwxr-xr-xscripts/automation/trex_control_plane/stl/console/trex_console.py1
-rw-r--r--scripts/automation/trex_control_plane/stl/console/trex_tui.py32
-rwxr-xr-xscripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py2
-rwxr-xr-xscripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_hltapi.py202
-rw-r--r--scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py274
-rw-r--r--scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_types.py14
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)
+