diff options
23 files changed, 647 insertions, 297 deletions
diff --git a/scripts/automation/regression/functional_tests/stl_basic_tests.py b/scripts/automation/regression/functional_tests/stl_basic_tests.py index a4e28ca9..e03c0742 100644 --- a/scripts/automation/regression/functional_tests/stl_basic_tests.py +++ b/scripts/automation/regression/functional_tests/stl_basic_tests.py @@ -85,7 +85,7 @@ class CStlBasic_Test(functional_general_test.CGeneralFunctional_Test): pkts1 = list(RawPcapReader(output)) pkts2 = list(RawPcapReader(golden)) - assert_equal(len(pkts1), len(pkts2)) + assert_equal(len(pkts1), len(pkts2), 'Lengths of generated pcap (%s) and golden (%s) are different' % (output, golden)) for pkt1, pkt2, i in zip(pkts1, pkts2, range(1, len(pkts1))): ts1 = float(pkt1[1][0]) + (float(pkt1[1][1]) / 1e6) @@ -143,7 +143,7 @@ class CStlBasic_Test(functional_general_test.CGeneralFunctional_Test): os.unlink(output_cap) try: rc = self.run_sim(input_file, output_cap, options, silent) - assert_equal(rc, True) + assert_equal(rc, True, 'Simulation on profile %s failed.' % profile) #s='cp '+output_cap+' '+golden_file; #print s #os.system(s) @@ -165,7 +165,7 @@ class CStlBasic_Test(functional_general_test.CGeneralFunctional_Test): profile.dump_to_code(generated_filename) rc = self.run_sim(generated_filename, output_cap, options, silent) - assert_equal(rc, True) + assert_equal(rc, True, 'Simulation on profile %s (generated) failed.' % profile) if compare: self.compare_caps(output_cap, golden_file) diff --git a/scripts/automation/regression/setups/trex07/benchmark.yaml b/scripts/automation/regression/setups/trex07/benchmark.yaml index 4778de91..98bbaf14 100644 --- a/scripts/automation/regression/setups/trex07/benchmark.yaml +++ b/scripts/automation/regression/setups/trex07/benchmark.yaml @@ -2,169 +2,241 @@ #### TRex benchmark configuration file #### ############################################################### -test_nbar_simple : - multiplier : 7.5 - cores : 2 - exp_gbps : 3.5 - cpu_to_core_ratio : 20800000 - cpu2core_custom_dev: YES - cpu2core_dev : 0.07 - exp_max_latency : 1000 - - nbar_classification: - rtp : 32.57 - http : 30.25 - oracle-sqlnet : 11.23 - exchange : 10.80 - citrix : 5.62 - rtsp : 2.84 - dns : 1.95 - smtp : 0.57 - pop3 : 0.36 - ssl : 0.17 - sctp : 0.13 - sip : 0.09 - unknown : 3.41 - -test_rx_check : - multiplier : 13 - cores : 3 - rx_sample_rate : 128 - exp_gbps : 6 - cpu_to_core_ratio : 37270000 - exp_bw : 13 - exp_latency : 1 - -test_nat_simple : &test_nat_simple - stat_route_dict : +#### common templates ### + +stat_route_dict: &stat_route_dict clients_start : 16.0.0.1 servers_start : 48.0.0.1 dual_port_mask : 1.0.0.0 client_destination_mask : 255.0.0.0 server_destination_mask : 255.0.0.0 - nat_dict : + +nat_dict: &nat_dict clients_net_start : 16.0.0.0 client_acl_wildcard_mask : 0.0.0.255 dual_port_mask : 1.0.0.0 pool_start : 200.0.0.0 pool_netmask : 255.255.255.0 - multiplier : 12000 - cores : 1 - cpu_to_core_ratio : 37270000 - exp_bw : 1 - exp_latency : 1 - allow_timeout_dev : YES - -test_nat_simple_mode1 : *test_nat_simple -test_nat_simple_mode2 : *test_nat_simple - -test_nat_learning : - stat_route_dict : - clients_start : 16.0.0.1 - servers_start : 48.0.0.1 - dual_port_mask : 1.0.0.0 - client_destination_mask : 255.0.0.0 - server_destination_mask : 255.0.0.0 - multiplier : 12000 - cores : 1 - nat_opened : 40000 - cpu_to_core_ratio : 270 - exp_bw : 8 - exp_latency : 1 - allow_timeout_dev : YES - -test_routing_imix_64 : - multiplier : 430 - cores : 1 - cpu_to_core_ratio : 280 - exp_latency : 1 - -test_routing_imix : - multiplier : 10 - cores : 1 - cpu_to_core_ratio : 1800 - exp_latency : 1 - -test_static_routing_imix : - stat_route_dict : - clients_start : 16.0.0.1 - servers_start : 48.0.0.1 - dual_port_mask : 1.0.0.0 - client_destination_mask : 255.0.0.0 - server_destination_mask : 255.0.0.0 - multiplier : 8 - cores : 1 - cpu_to_core_ratio : 1800 - exp_latency : 1 -test_static_routing_imix_asymmetric: - stat_route_dict : - clients_start : 16.0.0.1 - servers_start : 48.0.0.1 - dual_port_mask : 1.0.0.0 - client_destination_mask : 255.0.0.0 - server_destination_mask : 255.0.0.0 - multiplier : 8 - cores : 1 - cpu_to_core_ratio : 1800 - exp_latency : 1 - -test_ipv6_simple : - multiplier : 9 - cores : 2 - cpu_to_core_ratio : 30070000 - cpu2core_custom_dev: YES - cpu2core_dev : 0.07 - - -test_rx_check_sfr: - multiplier : 10 - cores : 2 - rx_sample_rate : 16 - # allow 0.03% errors, bad router - error_tolerance : 0.03 - -test_rx_check_http: - multiplier : 15000 - cores : 1 - rx_sample_rate : 16 - # allow 0.03% errors, bad routerifconfig - error_tolerance : 0.03 -test_rx_check_sfr_ipv6: - multiplier : 10 - cores : 2 - rx_sample_rate : 16 - # allow 0.03% errors, bad router - error_tolerance : 0.03 +### stateful ### + +test_jumbo: + multiplier : 17 + cores : 1 + bw_per_core : 543.232 + + +test_routing_imix: + multiplier : 10 + cores : 1 + bw_per_core : 34.128 + + +test_routing_imix_64: + multiplier : 430 + cores : 1 + bw_per_core : 5.893 + + +test_static_routing_imix: &test_static_routing_imix + stat_route_dict : *stat_route_dict + multiplier : 8 + cores : 1 + bw_per_core : 34.339 + +test_static_routing_imix_asymmetric: *test_static_routing_imix + + +test_ipv6_simple: + multiplier : 9 + cores : 2 + bw_per_core : 19.064 + + +test_nat_simple_mode1: &test_nat_simple + stat_route_dict : *stat_route_dict + nat_dict : *nat_dict + multiplier : 6000 + cores : 1 + nat_opened : 500000 + allow_timeout_dev : True + bw_per_core : 44.445 + +test_nat_simple_mode2: *test_nat_simple + +test_nat_learning: *test_nat_simple + + +test_nbar_simple: + multiplier : 7.5 + cores : 2 + bw_per_core : 17.174 + nbar_classification: + rtp : 32.57 + http : 30.25 + oracle_sqlnet : 11.23 + exchange : 10.80 + citrix : 5.62 + rtsp : 2.84 + dns : 1.95 + smtp : 0.57 + pop3 : 0.36 + ssl : 0.17 + sctp : 0.13 + sip : 0.09 + unknown : 3.41 + + +test_rx_check_http: &rx_http + multiplier : 15000 + cores : 1 + rx_sample_rate : 16 + bw_per_core : 39.560 test_rx_check_http_ipv6: - multiplier : 15000 - cores : 1 - rx_sample_rate : 16 - # allow 0.03% errors, bad router - error_tolerance : 0.03 + << : *rx_http + bw_per_core : 49.237 test_rx_check_http_negative: - multiplier : 13000 - cores : 1 - rx_sample_rate : 16 - # allow 0.03% errors, bad router - error_tolerance : 0.03 - stat_route_dict : - clients_start : 16.0.0.1 - servers_start : 48.0.0.1 - dual_port_mask : 1.0.0.0 - client_destination_mask : 255.0.0.0 - server_destination_mask : 255.0.0.0 - nat_dict : - clients_net_start : 16.0.0.0 - client_acl_wildcard_mask : 0.0.0.255 - dual_port_mask : 1.0.0.0 - pool_start : 200.0.0.0 - pool_netmask : 255.255.255.0 + << : *rx_http + stat_route_dict : *stat_route_dict + nat_dict : *nat_dict + + +test_rx_check_sfr: &rx_sfr + multiplier : 10 + cores : 3 + rx_sample_rate : 16 + bw_per_core : 16.082 + +test_rx_check_sfr_ipv6: + << : *rx_sfr + bw_per_core : 19.198 + + + +### stateless ### + +test_CPU_benchmark: + profiles: + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 64} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 64, stream_count: 10} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 64, stream_count: 100} + cpu_util : 1 + bw_per_core : 1 + +# causes queue full +# - name : stl/udp_for_benchmarks.py +# kwargs : {packet_len: 64, stream_count: 1000} +# cpu_util : 1 +# bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 128} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 256} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 512} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 1500} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 4000} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 9000} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 9000, stream_count: 10} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 9000, stream_count: 100} + cpu_util : 1 + bw_per_core : 1 + +# not enough memory + queue full if memory increase +# - name : stl/udp_for_benchmarks.py +# kwargs : {packet_len: 9000, stream_count: 1000} +# cpu_util : 1 +# bw_per_core : 1 + + - name : stl/imix.py + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_1pkt_tuple_gen.py + kwargs : {packet_len: 64} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_1pkt_tuple_gen.py + kwargs : {packet_len: 128} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_1pkt_tuple_gen.py + kwargs : {packet_len: 256} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_1pkt_tuple_gen.py + kwargs : {packet_len: 512} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_1pkt_tuple_gen.py + kwargs : {packet_len: 1500} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_1pkt_tuple_gen.py + kwargs : {packet_len: 4000} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_1pkt_tuple_gen.py + kwargs : {packet_len: 9000} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/pcap.py + kwargs : {ipg_usec: 2, loop_count: 0} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_rand_len_9k.py + cpu_util : 1 + bw_per_core : 1 + + - name : stl/hlt/hlt_udp_rand_len_9k.py + cpu_util : 1 + bw_per_core : 1 -test_jumbo: - multiplier : 17 - cores : 1 diff --git a/scripts/automation/regression/setups/trex14/benchmark.yaml b/scripts/automation/regression/setups/trex14/benchmark.yaml index aa4ac2d4..8380cbb1 100644 --- a/scripts/automation/regression/setups/trex14/benchmark.yaml +++ b/scripts/automation/regression/setups/trex14/benchmark.yaml @@ -57,17 +57,15 @@ test_ipv6_simple: test_nat_simple_mode1: &test_nat_simple stat_route_dict : *stat_route_dict nat_dict : *nat_dict - multiplier : 12000 + multiplier : 6000 cores : 1 - nat_opened : 40000 + nat_opened : 500000 allow_timeout_dev : True bw_per_core : 44.445 test_nat_simple_mode2: *test_nat_simple -test_nat_learning: - << : *test_nat_simple - nat_opened : 40000 +test_nat_learning: *test_nat_simple test_nbar_simple: diff --git a/scripts/automation/regression/stateful_tests/trex_general_test.py b/scripts/automation/regression/stateful_tests/trex_general_test.py index 82b1d9d1..8ff4fdaf 100755 --- a/scripts/automation/regression/stateful_tests/trex_general_test.py +++ b/scripts/automation/regression/stateful_tests/trex_general_test.py @@ -139,41 +139,42 @@ 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 = 25, minimal_cpu = 30, maximal_cpu = 85): - #cpu_util = float(trex_res.get_last_value("trex-global.data.m_cpu_util")) - cpu_util = sum(trex_res.get_value_list("trex-global.data.m_cpu_util")[-4:-1]) / 3.0 # mean of 3 values before last - - if '1G' in self.modes: - minimal_cpu /= 10.0 - - if not self.is_virt_nics: - if cpu_util > maximal_cpu: - self.fail("CPU is too high (%s%%), probably queue full." % cpu_util ) - #if cpu_util < minimal_cpu: - # self.fail("CPU is too low (%s%%), can't verify performance in such low CPU%%." % cpu_util ) - - test_norm_cpu = sum(trex_res.get_value_list("trex-global.data.m_bw_per_core")[-4:-1]) / 3.0 - - print("TRex CPU utilization: %g%%, norm_cpu is : %g Gb/core" % (round(cpu_util, 2), round(test_norm_cpu))) - - expected_norm_cpu = self.get_benchmark_param('bw_per_core') - if not expected_norm_cpu: - expected_norm_cpu = 1 - - calc_error_precent = abs(100.0 * test_norm_cpu / expected_norm_cpu - 100) - print('Err percent: %s' % calc_error_precent) - #if calc_error_precent > err and cpu_util > 10: - # self.fail('Excepted bw_per_core ratio: %s, got: %g' % (expected_norm_cpu, round(test_norm_cpu))) - - # report benchmarks - if self.GAManager: - try: - setup_test = '%s.%s' % (CTRexScenario.setup_name, self.get_name()) - self.GAManager.gaAddAction(Event = 'stateful_test', action = setup_test, label = 'bw_per_core', value = int(test_norm_cpu)) - self.GAManager.gaAddAction(Event = 'stateful_test', action = setup_test, label = 'bw_per_core_exp', value = int(expected_norm_cpu)) - self.GAManager.emptyAndReportQ() - except Exception as e: - print('Sending GA failed: %s' % e) + def check_CPU_benchmark (self, trex_res, err = 25, minimal_cpu = 10, maximal_cpu = 85): + cpu_util = trex_res.get_avg_steady_state_value('trex-global.data.m_cpu_util_raw') + trex_tx_bps = trex_res.get_avg_steady_state_value('trex-global.data.m_tx_bps') + expected_norm_cpu = self.get_benchmark_param('bw_per_core') + cores = self.get_benchmark_param('cores') + ports_count = trex_res.get_ports_count() + test_norm_cpu = trex_tx_bps / (cpu_util * ports_count * cores * 2.5e6) + + if '1G' in self.modes: + minimal_cpu /= 10.0 + + if not self.is_virt_nics: + if cpu_util > maximal_cpu: + self.fail("CPU is too high (%s%%), probably queue full." % cpu_util ) + #if cpu_util < minimal_cpu: + # self.fail("CPU is too low (%s%%), can't verify performance in such low CPU%%." % cpu_util ) + + print("TRex CPU utilization: %g%%, norm_cpu is : %g Gb/core" % (round(cpu_util, 2), round(test_norm_cpu, 2))) + + if not expected_norm_cpu: + expected_norm_cpu = 1 + + calc_error_precent = abs(100.0 * test_norm_cpu / expected_norm_cpu - 100) + print('Err percent: %s' % calc_error_precent) + #if calc_error_precent > err and cpu_util > 10: + # self.fail('Excepted bw_per_core ratio: %s, got: %g' % (expected_norm_cpu, round(test_norm_cpu))) + + # report benchmarks + if self.GAManager: + try: + setup_test = '%s.%s' % (CTRexScenario.setup_name, self.get_name()) + self.GAManager.gaAddAction(Event = 'stateful_test', action = setup_test, label = 'bw_per_core', value = int(test_norm_cpu)) + self.GAManager.gaAddAction(Event = 'stateful_test', action = setup_test, label = 'bw_per_core_exp', value = int(expected_norm_cpu)) + self.GAManager.emptyAndReportQ() + except Exception as e: + print('Sending GA failed: %s' % e) def check_results_gt (self, res, name, val): if res is None: diff --git a/scripts/automation/regression/stateful_tests/trex_rx_test.py b/scripts/automation/regression/stateful_tests/trex_rx_test.py index 40528d16..c08ad1ea 100755 --- a/scripts/automation/regression/stateful_tests/trex_rx_test.py +++ b/scripts/automation/regression/stateful_tests/trex_rx_test.py @@ -250,7 +250,7 @@ class CTRexRx_Test(CTRexGeneral_Test): print('Run until finish, expect errors') old_errors = copy.deepcopy(self.fail_reasons) - nat_dict = self.get_benchmark_param('nat_dict', test_name = 'test_nat_simple') + nat_dict = self.get_benchmark_param('nat_dict', test_name = 'test_nat_simple_mode1') nat_obj = CNatConfig(nat_dict) self.router.config_nat(nat_obj) self.router.config_zbf() diff --git a/scripts/automation/regression/trex.py b/scripts/automation/regression/trex.py index 44f2faba..7286b166 100644 --- a/scripts/automation/regression/trex.py +++ b/scripts/automation/regression/trex.py @@ -40,6 +40,7 @@ class CTRexScenario: no_daemon = False router_image = None debug_image = False + test = None class CTRexRunner: """This is an instance for generating a CTRexRunner""" diff --git a/scripts/automation/regression/trex_unit_test.py b/scripts/automation/regression/trex_unit_test.py index 915cd682..4ff21b80 100755 --- a/scripts/automation/regression/trex_unit_test.py +++ b/scripts/automation/regression/trex_unit_test.py @@ -30,7 +30,7 @@ import outer_packages import nose from nose.plugins import Plugin -import logging +from nose.selector import Selector import CustomLogger import misc_methods from rednose import RedNose @@ -43,11 +43,30 @@ from trex_stl_lib.utils.GAObjClass import GAmanager import trex import socket from pprint import pprint -import subprocess -import re import time from distutils.dir_util import mkpath +# nose overrides + +# option to select wanted test by name without file, class etc. +def new_Selector_wantMethod(self, method, orig_Selector_wantMethod = Selector.wantMethod): + result = orig_Selector_wantMethod(self, method) + if not CTRexScenario.test: + return result + else: + return CTRexScenario.test in getattr(method, '__name__', '') + +Selector.wantMethod = new_Selector_wantMethod + +def new_Selector_wantFunction(self, function, orig_Selector_wantFunction = Selector.wantFunction): + result = orig_Selector_wantFunction(self, function) + if not CTRexScenario.test: + return result + else: + return CTRexScenario.test in getattr(function, '__name__', '') + +Selector.wantFunction = new_Selector_wantFunction + # override nose's strange representation of setUpClass errors def __suite_repr__(self): if hasattr(self.context, '__module__'): # inside class, setUpClass etc. @@ -59,6 +78,8 @@ def __suite_repr__(self): nose.suite.ContextSuite.__repr__ = __suite_repr__ nose.suite.ContextSuite.__str__ = __suite_repr__ +# /nose overrides + def check_trex_path(trex_path): if os.path.isfile('%s/trex_daemon_server' % trex_path): return os.path.abspath(trex_path) @@ -78,7 +99,7 @@ def get_trex_path(): def address_to_ip(address): - for i in range(10): + for i in range(5): try: return socket.gethostbyname(address) except: @@ -149,6 +170,8 @@ class CTRexTestConfiguringPlugin(Plugin): parser.add_option('--trex-args', action='store', default = '', dest="trex_args", help="Additional TRex arguments (--no-watchdog etc.).") + parser.add_option('-t', '--test', action='store', default = '', dest='test', + help='Test name to run (without file, class etc.)') def configure(self, options, conf): @@ -160,6 +183,7 @@ class CTRexTestConfiguringPlugin(Plugin): self.json_verbose = options.json_verbose self.telnet_verbose = options.telnet_verbose self.no_daemon = options.no_daemon + CTRexScenario.test = options.test if self.collect_only or self.functional: return if CTRexScenario.setup_dir and options.config_path: @@ -181,7 +205,7 @@ class CTRexTestConfiguringPlugin(Plugin): self.loggerPath = options.log_path # initialize CTRexScenario global testing class, to be used by all tests CTRexScenario.configuration = self.configuration - CTRexScenario.no_daemon = self.no_daemon + CTRexScenario.no_daemon = options.no_daemon CTRexScenario.benchmark = self.benchmark CTRexScenario.modes = set(self.modes) CTRexScenario.server_logs = self.server_logs @@ -347,15 +371,14 @@ if __name__ == "__main__": nose_argv += sys_args - config_plugin = CTRexTestConfiguringPlugin() - red_nose = RedNose() + addplugins = [RedNose(), CTRexTestConfiguringPlugin()] result = True try: if len(CTRexScenario.test_types['functional_tests']): additional_args = ['--func'] + CTRexScenario.test_types['functional_tests'] if xml_arg: additional_args += ['--with-xunit', xml_arg.replace('.xml', '_functional.xml')] - result = nose.run(argv = nose_argv + additional_args, addplugins = [red_nose, config_plugin]) + result = nose.run(argv = nose_argv + additional_args, addplugins = addplugins) if len(CTRexScenario.test_types['stateful_tests']): additional_args = ['--stf'] if '--warmup' in sys.argv: @@ -365,14 +388,14 @@ if __name__ == "__main__": additional_args.extend(['-a', '!client_package']) if xml_arg: additional_args += ['--with-xunit', xml_arg.replace('.xml', '_stateful.xml')] - result = nose.run(argv = nose_argv + additional_args, addplugins = [red_nose, config_plugin]) and result + result = nose.run(argv = nose_argv + additional_args, addplugins = addplugins) and result if len(CTRexScenario.test_types['stateless_tests']): additional_args = ['--stl', 'stateless_tests/stl_general_test.py:STLBasic_Test.test_connectivity'] + CTRexScenario.test_types['stateless_tests'] if not test_client_package: additional_args.extend(['-a', '!client_package']) if xml_arg: additional_args += ['--with-xunit', xml_arg.replace('.xml', '_stateless.xml')] - result = nose.run(argv = nose_argv + additional_args, addplugins = [red_nose, config_plugin]) and result + result = nose.run(argv = nose_argv + additional_args, addplugins = addplugins) and result #except Exception as e: # result = False # print(e) 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 f044f623..c8827afe 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 @@ -535,7 +535,7 @@ class CTRexClient(object): finally: self.prompt_verbose_data() - def sample_until_condition (self, condition_func, time_between_samples = 5): + def sample_until_condition (self, condition_func, time_between_samples = 1): """ Automatically sets ongoing sampling of TRex data, with sampling rate described by time_between_samples. @@ -549,7 +549,7 @@ class CTRexClient(object): time_between_samples : int determines the time between each sample of the server - default value : **5** + default value : **1** :return: the first result object (see :class:`CTRexResult` for further details) of the TRex run on which the condition has been met. @@ -579,7 +579,7 @@ class CTRexClient(object): # this could come from provided method 'condition_func' raise - def sample_to_run_finish (self, time_between_samples = 5): + def sample_to_run_finish (self, time_between_samples = 1): """ Automatically sets automatically sampling of TRex data with sampling rate described by time_between_samples until TRex run finished. @@ -587,7 +587,7 @@ class CTRexClient(object): time_between_samples : int determines the time between each sample of the server - default value : **5** + default value : **1** :return: the latest result object (see :class:`CTRexResult` for further details) with sampled data. @@ -609,7 +609,7 @@ class CTRexClient(object): results = self.get_result_obj() return results - def sample_x_seconds (self, sample_time, time_between_samples = 5): + def sample_x_seconds (self, sample_time, time_between_samples = 1): """ Automatically sets ongoing sampling of TRex data for sample_time seconds, with sampling rate described by time_between_samples. Does not stop the TRex afterwards! @@ -623,7 +623,7 @@ class CTRexClient(object): time_between_samples : int determines the time between each sample of the server - default value : **5** + default value : **1** :return: the first result object (see :class:`CTRexResult` for further details) of the TRex run after given sample_time. @@ -1271,7 +1271,7 @@ class CTRexResult(object): .. tip:: | Use '.' to enter one level deeper in dictionary hierarchy. | Use '[i]' to access the i'th indexed object of an array. - tree_path_to_key : regex + regex : regex apply a regex to filter results out from a multiple results set. Filter applies only on keys of dictionary type. @@ -1299,7 +1299,7 @@ class CTRexResult(object): .. tip:: | Use '.' to enter one level deeper in dictionary hierarchy. | Use '[i]' to access the i'th indexed object of an array. - tree_path_to_key : regex + regex : regex apply a regex to filter results out from a multiple results set. Filter applies only on keys of dictionary type. @@ -1352,7 +1352,7 @@ class CTRexResult(object): if not len(self._history): return -1 - return len(self.__get_value_by_path(self._history[-1], 'trex-global.data', 'opackets-\d+')) + return len(self.get_last_value('trex-global.data', 'opackets-\d+')) def update_result_data (self, latest_dump): @@ -1383,6 +1383,7 @@ class CTRexResult(object): # check for up to 2% change between expected and actual if (self._current_tx_rate['m_tx_bps'] > 0.98 * self._expected_tx_rate['m_tx_expected_bps']): self._done_warmup = True + latest_dump['warmup_barrier'] = True # handle latency data if self.latency_checked: @@ -1427,12 +1428,12 @@ class CTRexResult(object): for i, p in re.findall(r'(\d+)|([\w|-]+)', tree_path): dct = dct[p or int(i)] if regex is not None and isinstance(dct, dict): - res = {} - for key,val in dct.items(): - match = re.match(regex, key) - if match: - res[key]=val - return res + res = {} + for key,val in dct.items(): + match = re.match(regex, key) + if match: + res[key]=val + return res else: return dct except (KeyError, TypeError): @@ -1499,6 +1500,42 @@ class CTRexResult(object): return result + # history iterator after warmup period + def _get_steady_state_history_iterator(self): + if not self.is_done_warmup(): + raise Exception('Warm-up period not finished') + for index, res in enumerate(self._history): + if 'warmup_barrier' in res: + for steady_state_index in range(index, max(index, len(self._history) - 1)): + yield self._history[steady_state_index] + return + for index in range(len(self._history) - 1): + yield self._history[index] + + + def get_avg_steady_state_value(self, tree_path_to_key): + ''' + Gets average value after warmup period. + For example: <result object>.get_avg_steady_state_value('trex-global.data.m_tx_bps') + Usually more accurate than latest history value. + + :parameters: + tree_path_to_key : str + defines a path to desired data. + + :return: + average value at steady state + + :raises: + Exception in case steady state period was not reached or tree_path_to_key was not found in result. + ''' + values_arr = [self.__get_value_by_path(res, tree_path_to_key) for res in self._get_steady_state_history_iterator()] + values_arr = list(filter(lambda x: x is not None, values_arr)) + if not values_arr: + raise Exception('All the keys are None, probably wrong tree_path_to_key: %s' % tree_path_to_key) + return sum(values_arr) / float(len(values_arr)) + + if __name__ == "__main__": c = CTRexClient('127.0.0.1') print('restarting daemon') 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 d3be4435..a2ffcad6 100644 --- a/scripts/automation/trex_control_plane/stl/console/trex_tui.py +++ b/scripts/automation/trex_control_plane/stl/console/trex_tui.py @@ -210,7 +210,7 @@ class TrexTUILatencyStats(TrexTUIPanel): super(TrexTUILatencyStats, self).__init__(mng, "lstats") self.key_actions = OrderedDict() self.key_actions['c'] = {'action': self.action_clear, 'legend': 'clear', 'show': True} - self.key_actions['t'] = {'action': self.action_toggle_histogram, 'legend': 'toggle histogram', 'show': True} + self.key_actions['h'] = {'action': self.action_toggle_histogram, 'legend': 'histogram toggle', 'show': True} self.is_histogram = False @@ -238,6 +238,23 @@ class TrexTUILatencyStats(TrexTUIPanel): self.stateless_client.latency_stats.clear_stats() return "" + +# utilization stats +class TrexTUIUtilizationStats(TrexTUIPanel): + def __init__ (self, mng): + super(TrexTUIUtilizationStats, self).__init__(mng, "ustats") + self.key_actions = {} + + def show (self): + stats = self.stateless_client._get_formatted_stats(port_id_list = None, stats_mask = trex_stl_stats.UT_COMPAT) + # print stats to screen + 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): + return self.key_actions + + # log class TrexTUILog(): def __init__ (self): @@ -270,12 +287,14 @@ class TrexTUIPanelManager(): self.panels['dashboard'] = TrexTUIDashBoard(self) self.panels['sstats'] = TrexTUIStreamsStats(self) self.panels['lstats'] = TrexTUILatencyStats(self) + self.panels['ustats'] = TrexTUIUtilizationStats(self) self.key_actions = OrderedDict() self.key_actions['q'] = {'action': self.action_quit, 'legend': 'quit', 'show': True} self.key_actions['d'] = {'action': self.action_show_dash, 'legend': 'dashboard', 'show': True} self.key_actions['s'] = {'action': self.action_show_sstats, 'legend': 'streams', 'show': True} self.key_actions['l'] = {'action': self.action_show_lstats, 'legend': 'latency', 'show': True} + self.key_actions['u'] = {'action': self.action_show_ustats, 'legend': 'util', 'show': True} # start with dashboard self.main_panel = self.panels['dashboard'] @@ -392,6 +411,11 @@ class TrexTUIPanelManager(): self.init(self.show_log) return "" + def action_show_ustats(self): + self.main_panel = self.panels['ustats'] + self.init(self.show_log) + return "" + # shows a textual top style window class TrexTUI(): 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 acedf24f..ae7c23f2 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 ########### @@ -1688,6 +1691,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): @@ -1902,8 +1922,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 f74c8fa5..af4d6f69 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 +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): @@ -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,73 @@ 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(use_1sec_cache = True) + stats_table = text_tables.TRexTextTable() + 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) + stats_table = text_tables.TRexTextTable() + 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: + 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 def _get_rational_block_char(value, range_start, interval): # in Konsole, utf-8 is sometimes printed with artifacts, return ascii for now @@ -1294,7 +1377,27 @@ 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 + 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: + 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__": 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 bb28d3af..ceaf1af8 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 @@ -340,6 +342,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', @@ -365,7 +375,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], {}) } diff --git a/scripts/master_daemon.py b/scripts/master_daemon.py index aa49f207..a44f55a8 100755 --- a/scripts/master_daemon.py +++ b/scripts/master_daemon.py @@ -181,7 +181,7 @@ parser.add_argument('--type', '--daemon-type', '--daemon_type', choices = daemon action = 'store', help = 'Specify daemon type to start/stop etc.\nDefault is master_daemon.') args = parser.parse_args() -args.trex_dir = os.path.normpath(args.trex_dir) +args.trex_dir = os.path.abspath(args.trex_dir) args.daemon_type = args.daemon_type or 'master_daemon' stl_rpc_proxy_dir = os.path.join(args.trex_dir, 'automation', 'trex_control_plane', 'stl', 'examples') diff --git a/src/bp_sim.cpp b/src/bp_sim.cpp index 758778d9..b93c0461 100755 --- a/src/bp_sim.cpp +++ b/src/bp_sim.cpp @@ -498,40 +498,23 @@ void CRteMemPool::dump_in_case_of_error(FILE *fd){ dump(fd); } -std::string CRteMemPool::add_to_json( - std::string name, - rte_mempool_t * pool, - bool last){ +void CRteMemPool::add_to_json(Json::Value &json, std::string name, rte_mempool_t * pool){ uint32_t p_free = rte_mempool_count(pool); uint32_t p_size = pool->size; - char buff[200]; - sprintf(buff,"\"%s\":[%llu,%llu]",name.c_str(),(unsigned long long)p_free,(unsigned long long)p_size); - std::string json = std::string(buff) + (last?std::string(""):std::string(",")); - return (json); + json[name].append((unsigned long long)p_free); + json[name].append((unsigned long long)p_size); } -std::string CRteMemPool::dump_as_json(uint8_t id,bool last){ - - char buff[200]; - sprintf(buff,"\"socket-%d\":{", (int)id); - - std::string json=std::string(buff); - - json+=add_to_json("64b",m_small_mbuf_pool); - json+=add_to_json("128b",m_mbuf_pool_128); - json+=add_to_json("256b",m_mbuf_pool_256); - json+=add_to_json("512b",m_mbuf_pool_512); - json+=add_to_json("1024b",m_mbuf_pool_1024); - json+=add_to_json("2048b",m_mbuf_pool_2048); - json+=add_to_json("4096b",m_mbuf_pool_4096); - json+=add_to_json("9kb",m_mbuf_pool_9k,true); - - json+="}" ; - if (last==false) { - json+="," ; - } - return (json); +void CRteMemPool::dump_as_json(Json::Value &json){ + add_to_json(json, "64b", m_small_mbuf_pool); + add_to_json(json, "128b", m_mbuf_pool_128); + add_to_json(json, "256b", m_mbuf_pool_256); + add_to_json(json, "512b", m_mbuf_pool_512); + add_to_json(json, "1024b", m_mbuf_pool_1024); + add_to_json(json, "2048b", m_mbuf_pool_2048); + add_to_json(json, "4096b", m_mbuf_pool_4096); + add_to_json(json, "9kb", m_mbuf_pool_9k); } @@ -548,30 +531,24 @@ void CRteMemPool::dump(FILE *fd){ DUMP_MBUF("mbuf_9k",m_mbuf_pool_9k); } -//////////////////////////////////////// +//////////////////////////////////////// -std::string CGlobalInfo::dump_pool_as_json(void){ - - std::string json="\"mbuf_stats\":{"; +void CGlobalInfo::dump_pool_as_json(Json::Value &json){ CPlatformSocketInfo * lpSocket =&m_socket; - int last_socket=-1; - /* calc the last socket */ - int i; - for (i=0; i<(int)MAX_SOCKETS_SUPPORTED; i++) { + for (int i=0; i<(int)MAX_SOCKETS_SUPPORTED; i++) { if (lpSocket->is_sockets_enable((socket_id_t)i)) { - last_socket=i; + std::string socket_id = "cpu-socket-" + std::to_string(i); + m_mem_pool[i].dump_as_json(json["mbuf_stats"][socket_id]); } } +} - for (i=0; i<(int)MAX_SOCKETS_SUPPORTED; i++) { - if (lpSocket->is_sockets_enable((socket_id_t)i)) { - json+=m_mem_pool[i].dump_as_json(i,last_socket==i?true:false); - } - } - json+="},"; - return json; +std::string CGlobalInfo::dump_pool_as_json_str(void){ + Json::Value json; + dump_pool_as_json(json); + return (json.toStyledString()); } void CGlobalInfo::free_pools(){ @@ -4482,6 +4459,16 @@ double CFlowGenList::GetCpuUtil(){ return (c/m_threads_info.size()); } +double CFlowGenList::GetCpuUtilRaw(){ + int i; + double c=0.0; + for (i=0; i<(int)m_threads_info.size(); i++) { + CFlowGenListPerThread * lp=m_threads_info[i]; + c+=lp->m_cpu_cp_u.GetValRaw(); + } + return (c/m_threads_info.size()); +} + void CFlowGenList::UpdateFast(){ diff --git a/src/bp_sim.h b/src/bp_sim.h index bfccc817..23e9f8d5 100755 --- a/src/bp_sim.h +++ b/src/bp_sim.h @@ -1182,13 +1182,10 @@ public: void dump_in_case_of_error(FILE *fd); - std::string dump_as_json(uint8_t id,bool last); + void dump_as_json(Json::Value &json); private: - std::string add_to_json(std::string name, - rte_mempool_t * pool, - bool last=false); - + void add_to_json(Json::Value &json, std::string name, rte_mempool_t * pool); public: rte_mempool_t * m_small_mbuf_pool; /* pool for start packets */ @@ -1282,7 +1279,8 @@ public: } - static std::string dump_pool_as_json(void); + static void dump_pool_as_json(Json::Value &json); + static std::string dump_pool_as_json_str(void); public: @@ -3881,6 +3879,7 @@ public: void DumpPktSize(); void UpdateFast(); double GetCpuUtil(); + double GetCpuUtilRaw(); public: double get_total_kcps(); diff --git a/src/internal_api/trex_platform_api.h b/src/internal_api/trex_platform_api.h index 4f19ec9d..7037584b 100644 --- a/src/internal_api/trex_platform_api.h +++ b/src/internal_api/trex_platform_api.h @@ -28,6 +28,7 @@ limitations under the License. #include <string.h> #include "flow_stat_parser.h" #include "trex_defs.h" +#include <json/json.h> /** * Global stats @@ -157,6 +158,8 @@ public: virtual bool get_promiscuous(uint8_t port_id) const = 0; virtual void flush_dp_messages() const = 0; virtual int get_active_pgids(flow_stat_active_t &result) const = 0; + virtual int get_cpu_util_full(cpu_util_full_t &result) const = 0; + virtual int get_mbuf_util(Json::Value &result) const = 0; virtual CFlowStatParser *get_flow_stat_parser() const = 0; virtual ~TrexPlatformApi() {} }; @@ -190,6 +193,8 @@ public: bool get_promiscuous(uint8_t port_id) const; void flush_dp_messages() const; int get_active_pgids(flow_stat_active_t &result) const; + int get_cpu_util_full(cpu_util_full_t &result) const; + int get_mbuf_util(Json::Value &result) const; CFlowStatParser *get_flow_stat_parser() const; }; @@ -255,6 +260,8 @@ public: void flush_dp_messages() const { } int get_active_pgids(flow_stat_active_t &result) const {return 0;} + int get_cpu_util_full(cpu_util_full_t &result) const {return 0;} + int get_mbuf_util(Json::Value &result) const {return 0;} CFlowStatParser *get_flow_stat_parser() const {return new CFlowStatParser();} private: diff --git a/src/main_dpdk.cpp b/src/main_dpdk.cpp index c4ba7fa5..c36c3eae 100644 --- a/src/main_dpdk.cpp +++ b/src/main_dpdk.cpp @@ -2452,6 +2452,7 @@ public: float m_active_flows; float m_open_flows; float m_cpu_util; + float m_cpu_util_raw; float m_rx_cpu_util; float m_bw_per_core; uint8_t m_threads; @@ -2461,7 +2462,7 @@ public: public: void Dump(FILE *fd,DumpFormat mode); void DumpAllPorts(FILE *fd); - void dump_json(std::string & json, bool baseline,uint32_t stats_tick); + void dump_json(std::string & json, bool baseline); private: std::string get_field(std::string name,float &f); std::string get_field(std::string name,uint64_t &f); @@ -2501,7 +2502,7 @@ std::string CGlobalStats::get_field_port(int port,std::string name,uint64_t &f){ } -void CGlobalStats::dump_json(std::string & json, bool baseline,uint32_t stats_tick){ +void CGlobalStats::dump_json(std::string & json, bool baseline){ /* refactor this to JSON */ json="{\"name\":\"trex-global\",\"type\":0,"; @@ -2519,6 +2520,7 @@ void CGlobalStats::dump_json(std::string & json, bool baseline,uint32_t stats_ti #define GET_FIELD_PORT(p,f) get_field_port(p,std::string(#f),lp->f) json+=GET_FIELD(m_cpu_util); + json+=GET_FIELD(m_cpu_util_raw); json+=GET_FIELD(m_bw_per_core); json+=GET_FIELD(m_rx_cpu_util); json+=GET_FIELD(m_platform_factor); @@ -2568,10 +2570,6 @@ void CGlobalStats::dump_json(std::string & json, bool baseline,uint32_t stats_ti json+=GET_FIELD_PORT(i,m_total_rx_pps); } json+=m_template.dump_as_json("template"); - if ( stats_tick %4==0){ - json+=CGlobalInfo::dump_pool_as_json(); /* no need a feq update beacuse it trash the cores D cache, once in 2 sec */ - } - json+="\"unknown\":0}}" ; } @@ -3588,6 +3586,7 @@ void CGlobalTRex::get_stats(CGlobalStats & stats){ stats.m_num_of_ports = m_max_ports; stats.m_cpu_util = m_fl.GetCpuUtil(); + stats.m_cpu_util_raw = m_fl.GetCpuUtilRaw(); if (get_is_stateless()) { stats.m_rx_cpu_util = m_rx_sl.get_cpu_util(); } @@ -3819,7 +3818,7 @@ CGlobalTRex::publish_async_data(bool sync_now, bool baseline) { get_stats(m_stats); } - m_stats.dump_json(json, baseline,m_stats_cnt); + m_stats.dump_json(json, baseline); m_zmq_publisher.publish_json(json); /* generator json , all cores are the same just sample the first one */ @@ -3913,7 +3912,7 @@ CGlobalTRex::handle_slow_path(bool &was_stopped) { if (m_io_modes.m_g_mode == CTrexGlobalIoMode::gMem) { if ( m_stats_cnt%4==0) { - fprintf (stdout," %s \n",CGlobalInfo::dump_pool_as_json().c_str()); + fprintf (stdout," %s \n",CGlobalInfo::dump_pool_as_json_str().c_str()); } } @@ -5812,6 +5811,20 @@ int TrexDpdkPlatformApi::get_active_pgids(flow_stat_active_t &result) const { return g_trex.m_trex_stateless->m_rx_flow_stat.get_active_pgids(result); } +int TrexDpdkPlatformApi::get_cpu_util_full(cpu_util_full_t &cpu_util_full) const { + cpu_util_full.resize((int)g_trex.m_fl.m_threads_info.size()); + for (int thread_id=0; thread_id<(int)g_trex.m_fl.m_threads_info.size(); thread_id++) { + CFlowGenListPerThread * lp=g_trex.m_fl.m_threads_info[thread_id]; + lp->m_cpu_cp_u.GetHistory(cpu_util_full[thread_id]); + } + return 0; +} + +int TrexDpdkPlatformApi::get_mbuf_util(Json::Value &mbuf_pool) const { + CGlobalInfo::dump_pool_as_json(mbuf_pool); + return 0; +} + CFlowStatParser *TrexDpdkPlatformApi::get_flow_stat_parser() const { return CTRexExtendedDriverDb::Ins()->get_drv()->get_flow_stat_parser(); } diff --git a/src/rpc-server/commands/trex_rpc_cmd_general.cpp b/src/rpc-server/commands/trex_rpc_cmd_general.cpp index 68ea2587..27010e0e 100644 --- a/src/rpc-server/commands/trex_rpc_cmd_general.cpp +++ b/src/rpc-server/commands/trex_rpc_cmd_general.cpp @@ -157,6 +157,29 @@ TrexRpcCmdGetActivePGIds::_run(const Json::Value ¶ms, Json::Value &result) { return (TREX_RPC_CMD_OK); } +// get utilization of CPU per thread with up to 20 latest values + mbufs per socket +trex_rpc_cmd_rc_e +TrexRpcCmdGetUtilization::_run(const Json::Value ¶ms, Json::Value &result) { + cpu_util_full_t cpu_util_full; + + Json::Value §ion = result["result"]; + + if (get_stateless_obj()->get_platform_api()->get_mbuf_util(section) != 0) { + return TREX_RPC_CMD_INTERNAL_ERR; + } + + if (get_stateless_obj()->get_platform_api()->get_cpu_util_full(cpu_util_full) != 0) { + return TREX_RPC_CMD_INTERNAL_ERR; + } + + for (int thread_id = 0; thread_id < cpu_util_full.size(); thread_id++) { + for (int history_id = 0; history_id < cpu_util_full[thread_id].size(); history_id++) { + section["cpu"][thread_id].append(cpu_util_full[thread_id][history_id]); + } + } + return (TREX_RPC_CMD_OK); +} + /** * get the CPU model * diff --git a/src/rpc-server/commands/trex_rpc_cmds.h b/src/rpc-server/commands/trex_rpc_cmds.h index affa65c1..2776727d 100644 --- a/src/rpc-server/commands/trex_rpc_cmds.h +++ b/src/rpc-server/commands/trex_rpc_cmds.h @@ -67,6 +67,7 @@ TREX_RPC_CMD_DEFINE(TrexRpcPublishNow, "publish_now", 2, false, TREX_RPC_CMD_DEFINE(TrexRpcCmdGetCmds, "get_supported_cmds", 0, false, APIClass::API_CLASS_TYPE_CORE); TREX_RPC_CMD_DEFINE(TrexRpcCmdGetVersion, "get_version", 0, false, APIClass::API_CLASS_TYPE_CORE); TREX_RPC_CMD_DEFINE(TrexRpcCmdGetActivePGIds, "get_active_pgids", 0, false, APIClass::API_CLASS_TYPE_CORE); +TREX_RPC_CMD_DEFINE(TrexRpcCmdGetUtilization, "get_utilization", 0, false, APIClass::API_CLASS_TYPE_CORE); TREX_RPC_CMD_DEFINE_EXTENDED(TrexRpcCmdGetSysInfo, "get_system_info", 0, false, APIClass::API_CLASS_TYPE_CORE, diff --git a/src/rpc-server/trex_rpc_cmds_table.cpp b/src/rpc-server/trex_rpc_cmds_table.cpp index 7104792e..6144d265 100644 --- a/src/rpc-server/trex_rpc_cmds_table.cpp +++ b/src/rpc-server/trex_rpc_cmds_table.cpp @@ -39,6 +39,7 @@ TrexRpcCommandsTable::TrexRpcCommandsTable() { register_command(new TrexRpcCmdGetCmds()); register_command(new TrexRpcCmdGetVersion()); register_command(new TrexRpcCmdGetActivePGIds()); + register_command(new TrexRpcCmdGetUtilization()); register_command(new TrexRpcCmdGetSysInfo()); register_command(new TrexRpcCmdGetOwner()); register_command(new TrexRpcCmdAcquire()); diff --git a/src/trex_defs.h b/src/trex_defs.h index 665c1edc..bbf3f3ba 100644 --- a/src/trex_defs.h +++ b/src/trex_defs.h @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ #include <set> +#include <queue> +#include <vector> #ifndef __TREX_DEFS_H__ #define __TREX_DEFS_H__ @@ -36,5 +38,7 @@ limitations under the License. typedef std::set<uint32_t> flow_stat_active_t; typedef std::set<uint32_t>::iterator flow_stat_active_it_t; +typedef std::vector<std::vector<uint8_t>> cpu_util_full_t; +typedef std::vector<uint8_t> cpu_vct_t; #endif diff --git a/src/utl_cpuu.cpp b/src/utl_cpuu.cpp index 4d75cf6f..7786356e 100755 --- a/src/utl_cpuu.cpp +++ b/src/utl_cpuu.cpp @@ -25,7 +25,9 @@ limitations under the License. void CCpuUtlCp::Create(CCpuUtlDp * cdp){ m_dpcpu=cdp; - m_cpu_util=0.0; + memset(m_cpu_util, 0, sizeof(m_cpu_util)); + m_history_latest_index = 0; + m_cpu_util_lpf=0.0; m_ticks=0; m_work=0; } @@ -41,17 +43,34 @@ void CCpuUtlCp::Update(){ m_work++; } if (m_ticks==100) { - double window_cpu_u = ((double)m_work/(double)m_ticks); /* LPF*/ - m_cpu_util = (m_cpu_util*0.75)+(window_cpu_u*0.25); + m_cpu_util_lpf = (m_cpu_util_lpf*0.75)+((double)m_work*0.25); + AppendHistory(m_work); m_ticks=0; m_work=0; - } } -/* return cpu % */ +/* return cpu % Smoothed */ double CCpuUtlCp::GetVal(){ - return (m_cpu_util*100); + return (m_cpu_util_lpf); +} + +/* return cpu % Raw */ +uint8_t CCpuUtlCp::GetValRaw(){ + return (m_cpu_util[m_history_latest_index]); } +/* get cpu % utilization history */ +void CCpuUtlCp::GetHistory(cpu_vct_t &cpu_vct){ + cpu_vct.clear(); + for (int i = m_history_latest_index + m_history_size; i > m_history_latest_index; i--) { + cpu_vct.push_back(m_cpu_util[i % m_history_size]); + } +} + +/* save last CPU % util in history */ +void CCpuUtlCp::AppendHistory(uint8_t val){ + m_history_latest_index = (m_history_latest_index + 1) % m_history_size; + m_cpu_util[m_history_latest_index] = val; +} diff --git a/src/utl_cpuu.h b/src/utl_cpuu.h index e5305783..109fff4f 100755 --- a/src/utl_cpuu.h +++ b/src/utl_cpuu.h @@ -22,6 +22,8 @@ limitations under the License. */ #include <stdint.h> +#include <cstring> +#include "trex_defs.h" #include "os_time.h" #include "mbuf.h" @@ -56,13 +58,18 @@ public: void Update(); /* return cpu % */ double GetVal(); - + uint8_t GetValRaw(); + void GetHistory(cpu_vct_t &cpu_vct); private: - CCpuUtlDp * m_dpcpu; - uint16_t m_ticks; - uint16_t m_work; + void AppendHistory(uint8_t); + CCpuUtlDp * m_dpcpu; + uint8_t m_ticks; + uint8_t m_work; - double m_cpu_util; + static const int m_history_size=20; + uint8_t m_cpu_util[m_history_size]; // history as cyclic array + uint8_t m_history_latest_index; + double m_cpu_util_lpf; }; #endif |