From 91f6c24f45cbb0cbf8568a9938059a1a934e6ae6 Mon Sep 17 00:00:00 2001 From: Dan Klein Date: Thu, 26 Nov 2015 13:06:36 +0200 Subject: Initial implementation of stats prompting --- .../client/trex_stateless_client.py | 108 ++++++++++++++++++--- 1 file changed, 95 insertions(+), 13 deletions(-) (limited to 'scripts/automation/trex_control_plane/client/trex_stateless_client.py') diff --git a/scripts/automation/trex_control_plane/client/trex_stateless_client.py b/scripts/automation/trex_control_plane/client/trex_stateless_client.py index 7bcbf2c7..4cb70483 100755 --- a/scripts/automation/trex_control_plane/client/trex_stateless_client.py +++ b/scripts/automation/trex_control_plane/client/trex_stateless_client.py @@ -10,11 +10,13 @@ except ImportError: from client_utils.jsonrpc_client import JsonRpcClient, BatchMessage from client_utils.packet_builder import CTRexPktBuilder import json -from common.trex_stats import * + from common.trex_streams import * from collections import namedtuple from common.text_opts import * -import parsing_opts +# import trex_stats +from common import trex_stats +from client_utils import parsing_opts, text_tables import time from trex_async_client import CTRexAsyncClient @@ -29,7 +31,7 @@ class RpcResponseStatus(namedtuple('RpcResponseStatus', ['success', 'id', 'msg'] stat="success" if self.success else "fail") # simple class to represent complex return value -class RC: +class RC(): def __init__ (self, rc = None, data = None): self.rc_list = [] @@ -74,7 +76,7 @@ class RC: def RC_OK(): return RC(True, "") -def RC_ERR (err): +def RC_ERR(err): return RC(False, err) @@ -86,7 +88,7 @@ class CStreamsDB(object): def __init__(self): self.stream_packs = {} - def load_yaml_file (self, filename): + def load_yaml_file(self, filename): stream_pack_name = filename if stream_pack_name in self.get_loaded_streams_names(): @@ -376,6 +378,7 @@ class CTRexStatelessClient(object): def __init__(self, username, server="localhost", sync_port = 5050, async_port = 4500, virtual=False): super(CTRexStatelessClient, self).__init__() self.user = username + self.system_info = None self.comm_link = CTRexStatelessClient.CCommLink(server, sync_port, virtual) self.verbose = False self.ports = [] @@ -388,6 +391,11 @@ class CTRexStatelessClient(object): self._async_client = CTRexAsyncClient(server, async_port, self) self.streams_db = CStreamsDB() + self.info_and_stats = trex_stats.CTRexInformationCenter({"server": server, + "sync_port": sync_port, + "async_port": async_port}, + self.ports, + self.get_stats_async()) self.connected = False @@ -444,13 +452,15 @@ class CTRexStatelessClient(object): return RC_ERR(data) self.server_version = data + self.info_and_stats.server_version = data # cache system info + # self.get_system_info(refresh=True) rc, data = self.transmit("get_system_info") if not rc: return RC_ERR(data) - self.system_info = data + self.info_and_stats.system_info = data # cache supported commands rc, data = self.transmit("get_supported_cmds") @@ -508,7 +518,7 @@ class CTRexStatelessClient(object): else: return port_ids - def get_stats_async (self): + def get_stats_async(self): return self._async_client.get_stats() def get_connection_port (self): @@ -548,6 +558,9 @@ class CTRexStatelessClient(object): return RC_OK() + def get_global_stats(self): + rc, info = self.transmit("get_global_stats") + return RC(rc, info) ########## port commands ############## @@ -787,7 +800,7 @@ class CTRexStatelessClient(object): opts = parser.parse_args(line.split()) if opts is None: - return RC_ERR("bad command line paramters") + return RC_ERR("bad command line parameters") return self.cmd_pause(opts.ports) @@ -820,7 +833,7 @@ class CTRexStatelessClient(object): opts = parser.parse_args(line.split()) if opts is None: - return RC_ERR("bad command line paramters") + return RC_ERR("bad command line parameters") return self.cmd_resume(opts.ports) @@ -861,6 +874,18 @@ class CTRexStatelessClient(object): return RC_OK() + def cmd_stats(self, port_id_list, stats_mask=set()): + print port_id_list + print stats_mask + stats_opts = trex_stats.ALL_STATS_OPTS.intersection(stats_mask) + print stats_opts + + stats_obj = {} + for stats_type in stats_opts: + stats_obj.update(self.info_and_stats.generate_single_statistic(stats_type)) + return stats_obj + pass + ############## High Level API With Parser ################ def cmd_start_line (self, line): '''Start selected traffic in specified ports on TRex\n''' @@ -877,10 +902,10 @@ class CTRexStatelessClient(object): opts = parser.parse_args(line.split()) if opts is None: - return RC_ERR("bad command line paramters") + return RC_ERR("bad command line parameters") if opts.db: - stream_list = self.stream_db.get_stream_pack(opts.db) + stream_list = self.streams_db.get_stream_pack(opts.db) rc = RC(stream_list != None) rc.annotate("Load stream pack (from DB):") if rc.bad(): @@ -906,7 +931,7 @@ class CTRexStatelessClient(object): opts = parser.parse_args(line.split()) if opts is None: - return RC_ERR("bad command line paramters") + return RC_ERR("bad command line parameters") return self.cmd_stop(opts.ports) @@ -915,6 +940,49 @@ class CTRexStatelessClient(object): return self.cmd_reset() + def cmd_stats_line (self, line): + '''Fetch statistics from TRex server by port\n''' + # define a parser + parser = parsing_opts.gen_parser(self, + "stats", + self.cmd_stats_line.__doc__, + parsing_opts.PORT_LIST_WITH_ALL, + parsing_opts.STATS_MASK) + + opts = parser.parse_args(line.split()) + + if opts is None: + return RC_ERR("bad command line parameters") + + print opts + print self.get_global_stats() + # determine stats mask + mask = self._get_mask_keys(**self._filter_namespace_args(opts, ['p', 'g', 'ps'])) + # get stats objects, as dictionary + stats = self.cmd_stats(opts.ports, mask) + # print stats to screen + for stat_type, stat_data in stats.iteritems(): + text_tables.print_table_with_header(stat_data.text_table, stat_type) + return + + # if opts.db: + # stream_list = self.streams_db.get_stream_pack(opts.db) + # rc = RC(stream_list != None) + # rc.annotate("Load stream pack (from DB):") + # if rc.bad(): + # return RC_ERR("Failed to load stream pack") + # + # else: + # # load streams from file + # stream_list = self.streams_db.load_yaml_file(opts.file[0]) + # rc = RC(stream_list != None) + # rc.annotate("Load stream pack (from file):") + # if stream_list == None: + # return RC_ERR("Failed to load stream pack") + # + # + # return self.cmd_start(opts.ports, stream_list, opts.mult, opts.force, opts.duration) + def cmd_exit_line (self, line): print format_text("Exiting\n", 'bold') # a way to exit @@ -931,7 +999,7 @@ class CTRexStatelessClient(object): opts = parser.parse_args(line.split()) if opts is None: - return RC_ERR("bad command line paramters") + return RC_ERR("bad command line parameters") delay_sec = opts.duration if (opts.duration > 0) else 1 @@ -990,6 +1058,20 @@ class CTRexStatelessClient(object): return True + ################################# + # ------ private methods ------ # + @staticmethod + def _get_mask_keys(ok_values={True}, **kwargs): + masked_keys = set() + for key, val in kwargs.iteritems(): + if val in ok_values: + masked_keys.add(key) + return masked_keys + + @staticmethod + def _filter_namespace_args(namespace, ok_values): + return {k: v for k, v in namespace.__dict__.items() if k in ok_values} + ################################# # ------ private classes ------ # class CCommLink(object): -- cgit 1.2.3-korg From a609111bc37ef88f14d4f2ebf7cd186b04b86402 Mon Sep 17 00:00:00 2001 From: Dan Klein Date: Sun, 29 Nov 2015 00:25:07 +0200 Subject: Supports all desired stats option, plus clearing option --- .../trex_control_plane/client/trex_async_client.py | 3 + .../client/trex_stateless_client.py | 48 +++++-- .../trex_control_plane/common/trex_stats.py | 154 ++++++++++++++++++--- .../trex_control_plane/console/trex_console.py | 7 + 4 files changed, 180 insertions(+), 32 deletions(-) (limited to 'scripts/automation/trex_control_plane/client/trex_stateless_client.py') diff --git a/scripts/automation/trex_control_plane/client/trex_async_client.py b/scripts/automation/trex_control_plane/client/trex_async_client.py index 0a3afbe8..a2bb4752 100644 --- a/scripts/automation/trex_control_plane/client/trex_async_client.py +++ b/scripts/automation/trex_control_plane/client/trex_async_client.py @@ -46,6 +46,9 @@ class CTRexAsyncStats(object): if self.ref_point == None: self.ref_point = self.current + + def clear(self): + self.ref_point = self.current def get(self, field, format = False, suffix = ""): diff --git a/scripts/automation/trex_control_plane/client/trex_stateless_client.py b/scripts/automation/trex_control_plane/client/trex_stateless_client.py index 2db30daf..7be7392e 100755 --- a/scripts/automation/trex_control_plane/client/trex_stateless_client.py +++ b/scripts/automation/trex_control_plane/client/trex_stateless_client.py @@ -148,6 +148,13 @@ class Port(object): STATE_STREAMS = 2 STATE_TX = 3 STATE_PAUSE = 4 + PortState = namedtuple('PortState', ['state_id', 'state_name']) + STATES_MAP = {STATE_DOWN: "DOWN", + STATE_IDLE: "IDLE", + STATE_STREAMS: "STREAMS", + STATE_TX: "ACTIVE", + STATE_PAUSE: "PAUSE"} + def __init__ (self, port_id, speed, driver, user, transmit): self.port_id = port_id @@ -399,6 +406,10 @@ class Port(object): return self.ok() + def get_port_state_name(self): + return self.STATES_MAP.get(self.state, "Unknown") + + ################# events handler ###################### def async_event_port_stopped (self): self.state = self.STATE_STREAMS @@ -423,7 +434,8 @@ class CTRexStatelessClient(object): self._async_client = CTRexAsyncClient(server, async_port, self) self.streams_db = CStreamsDB() - self.info_and_stats = trex_stats.CTRexInformationCenter({"server": server, + self.info_and_stats = trex_stats.CTRexInformationCenter(self.user, + {"server": server, "sync_port": sync_port, "async_port": async_port}, self.ports, @@ -885,7 +897,7 @@ class CTRexStatelessClient(object): active_ports = list(set(self.get_active_ports()).intersection(port_id_list)) if not active_ports: - msg = "No active traffic on porvided ports" + msg = "No active traffic on provided ports" print format_text(msg, 'bold') return RC_ERR(msg) @@ -896,6 +908,11 @@ class CTRexStatelessClient(object): return RC_OK() + def cmd_clear(self, port_id_list): + self.info_and_stats.clear(port_id_list) + return RC_OK() + + # pause cmd def cmd_pause (self, port_id_list): @@ -999,16 +1016,13 @@ class CTRexStatelessClient(object): return RC_OK() def cmd_stats(self, port_id_list, stats_mask=set()): - print port_id_list - print stats_mask stats_opts = trex_stats.ALL_STATS_OPTS.intersection(stats_mask) - print stats_opts stats_obj = {} for stats_type in stats_opts: - stats_obj.update(self.info_and_stats.generate_single_statistic(stats_type)) + stats_obj.update(self.info_and_stats.generate_single_statistic(port_id_list, stats_type)) return stats_obj - pass + ############## High Level API With Parser ################ def cmd_start_line (self, line): @@ -1090,6 +1104,19 @@ class CTRexStatelessClient(object): def cmd_reset_line (self, line): return self.cmd_reset() + def cmd_clear_line (self, line): + '''Clear cached local statistics\n''' + # define a parser + parser = parsing_opts.gen_parser(self, + "clear", + self.cmd_clear_line.__doc__, + parsing_opts.PORT_LIST_WITH_ALL) + + opts = parser.parse_args(line.split()) + + if opts is None: + return RC_ERR("bad command line parameters") + return self.cmd_clear(opts.ports) def cmd_stats_line (self, line): '''Fetch statistics from TRex server by port\n''' @@ -1105,10 +1132,11 @@ class CTRexStatelessClient(object): if opts is None: return RC_ERR("bad command line parameters") - print opts - print self.get_global_stats() # determine stats mask - mask = self._get_mask_keys(**self._filter_namespace_args(opts, ['p', 'g', 'ps'])) + mask = self._get_mask_keys(**self._filter_namespace_args(opts, trex_stats.ALL_STATS_OPTS)) + if not mask: + # set to show all stats if no filter was given + mask = trex_stats.ALL_STATS_OPTS # get stats objects, as dictionary stats = self.cmd_stats(opts.ports, mask) # print stats to screen diff --git a/scripts/automation/trex_control_plane/common/trex_stats.py b/scripts/automation/trex_control_plane/common/trex_stats.py index bf5ba2bb..ec455730 100755 --- a/scripts/automation/trex_control_plane/common/trex_stats.py +++ b/scripts/automation/trex_control_plane/common/trex_stats.py @@ -1,6 +1,8 @@ #!/router/bin/python from collections import namedtuple, OrderedDict from client_utils import text_tables +from common.text_opts import format_text +from client.trex_async_client import CTRexAsyncStats import copy GLOBAL_STATS = 'g' @@ -12,7 +14,8 @@ ExportableStats = namedtuple('ExportableStats', ['raw_data', 'text_table']) class CTRexInformationCenter(object): - def __init__(self, connection_info, ports_ref, async_stats_ref): + def __init__(self, username, connection_info, ports_ref, async_stats_ref): + self.user = username self.connection_info = connection_info self.server_version = None self.system_info = None @@ -26,14 +29,20 @@ class CTRexInformationCenter(object): # else: # return None - def generate_single_statistic(self, statistic_type): + def clear(self, port_id_list): + self._async_stats.get_general_stats().clear() + for port_id in port_id_list: + self._async_stats.get_port_stats(port_id).clear() + pass + + def generate_single_statistic(self, port_id_list, statistic_type): if statistic_type == GLOBAL_STATS: return self._generate_global_stats() elif statistic_type == PORT_STATS: - # return generate_global_stats() + return self._generate_port_stats(port_id_list) pass elif statistic_type == PORT_STATUS: - pass + return self._generate_port_status(port_id_list) else: # ignore by returning empty object return {} @@ -43,26 +52,11 @@ class CTRexInformationCenter(object): return_stats_data = \ OrderedDict([("connection", "{host}, Port {port}".format(host=self.connection_info.get("server"), port=self.connection_info.get("sync_port"))), - ("version", self.server_version.get("version", "N/A")), - ("cpu_util", stats_obj.get("m_cpu_util")), + ("version", "{ver}, UUID: {uuid}".format(ver=self.server_version.get("version", "N/A"), + uuid="N/A")), + ("cpu_util", "{0}%".format(stats_obj.get("m_cpu_util"))), ("total_tx", stats_obj.get("m_tx_bps", format=True, suffix="b/sec")), - # {'m_tx_bps': stats_obj.get("m_tx_bps", format= True, suffix= "b/sec"), - # 'm_tx_pps': stats_obj.get("m_tx_pps", format= True, suffix= "pkt/sec"), - # 'm_total_tx_bytes':stats_obj.get_rel("m_total_tx_bytes", - # format= True, - # suffix = "B"), - # 'm_total_tx_pkts': stats_obj.get_rel("m_total_tx_pkts", - # format= True, - # suffix = "pkts")}, ("total_rx", stats_obj.get("m_rx_bps", format=True, suffix="b/sec")), - # {'m_rx_bps': stats_obj.get("m_rx_bps", format= True, suffix= "b/sec"), - # 'm_rx_pps': stats_obj.get("m_rx_pps", format= True, suffix= "pkt/sec"), - # 'm_total_rx_bytes': stats_obj.get_rel("m_total_rx_bytes", - # format= True, - # suffix = "B"), - # 'm_total_rx_pkts': stats_obj.get_rel("m_total_rx_pkts", - # format= True, - # suffix = "pkts")}, ("total_pps", stats_obj.format_num(stats_obj.get("m_tx_pps") + stats_obj.get("m_rx_pps"), suffix="pkt/sec")), ("total_streams", sum([len(port.streams) @@ -81,6 +75,122 @@ class CTRexInformationCenter(object): return {"global_statistics": ExportableStats(return_stats_data, stats_table)} + def _generate_port_stats(self, port_id_list): + relevant_ports = self.__get_relevant_ports(port_id_list) + + return_stats_data = {} + per_field_stats = OrderedDict([("owner", []), + ("active", []), + ("tx-bytes", []), + ("rx-bytes", []), + ("tx-pkts", []), + ("rx-pkts", []), + ("tx-errors", []), + ("rx-errors", []), + ("tx-BW", []), + ("rx-BW", []) + ] + ) + + for port_obj in relevant_ports: + # fetch port data + port_stats = self._async_stats.get_port_stats(port_obj.port_id) + + owner = self.user + active = "YES" if port_obj.is_active() else "NO" + tx_bytes = port_stats.get_rel("obytes", format = True, suffix = "B") + rx_bytes = port_stats.get_rel("ibytes", format = True, suffix = "B") + tx_pkts = port_stats.get_rel("opackets", format = True, suffix = "pkts") + rx_pkts = port_stats.get_rel("ipackets", format = True, suffix = "pkts") + tx_errors = port_stats.get_rel("oerrors", format = True) + rx_errors = port_stats.get_rel("ierrors", format = True) + tx_bw = port_stats.get("m_total_tx_bps", format = True, suffix = "bps") + rx_bw = port_stats.get("m_total_rx_bps", format = True, suffix = "bps") + + # populate to data structures + return_stats_data[port_obj.port_id] = {"owner": owner, + "active": active, + "tx-bytes": tx_bytes, + "rx-bytes": rx_bytes, + "tx-pkts": tx_pkts, + "rx-pkts": rx_pkts, + "tx-errors": tx_errors, + "rx-errors": rx_errors, + "Tx-BW": tx_bw, + "Rx-BW": rx_bw + } + per_field_stats["owner"].append(owner) + per_field_stats["active"].append(active) + per_field_stats["tx-bytes"].append(tx_bytes) + per_field_stats["rx-bytes"].append(rx_bytes) + per_field_stats["tx-pkts"].append(tx_pkts) + per_field_stats["rx-pkts"].append(rx_pkts) + per_field_stats["tx-errors"].append(tx_errors) + per_field_stats["rx-errors"].append(rx_errors) + per_field_stats["tx-BW"].append(tx_bw) + per_field_stats["rx-BW"].append(rx_bw) + + stats_table = text_tables.TRexTextTable() + stats_table.set_cols_align(["l"] + ["r"]*len(relevant_ports)) + stats_table.add_rows([[k] + v + for k, v in per_field_stats.iteritems()], + header=False) + stats_table.header(["port"] + [port.port_id + for port in relevant_ports]) + + return {"port_statistics": ExportableStats(return_stats_data, stats_table)} + + def _generate_port_status(self, port_id_list): + relevant_ports = self.__get_relevant_ports(port_id_list) + + return_stats_data = {} + per_field_status = OrderedDict([("port-type", []), + ("maximum", []), + ("port-status", []) + ] + ) + + for port_obj in relevant_ports: + # fetch port data + port_stats = self._async_stats.get_port_stats(port_obj.port_id) + + + port_type = port_obj.driver + maximum = "{speed} Gb/s".format(speed=port_obj.speed)#CTRexAsyncStats.format_num(port_obj.get_speed_bps(), suffix="bps") + port_status = port_obj.get_port_state_name() + + # populate to data structures + return_stats_data[port_obj.port_id] = {"port-type": port_type, + "maximum": maximum, + "port-status": port_status, + } + per_field_status["port-type"].append(port_type) + per_field_status["maximum"].append(maximum) + per_field_status["port-status"].append(port_status) + + stats_table = text_tables.TRexTextTable() + stats_table.set_cols_align(["l"] + ["c"]*len(relevant_ports)) + stats_table.add_rows([[k] + v + for k, v in per_field_status.iteritems()], + header=False) + stats_table.header(["port"] + [port.port_id + for port in relevant_ports]) + + return {"port_status": ExportableStats(return_stats_data, stats_table)} + + def __get_relevant_ports(self, port_id_list): + # fetch owned ports + ports = [port_obj + for port_obj in self._ports + if port_obj.is_acquired() and port_obj.port_id in port_id_list] + # display only the first FOUR options, by design + if len(ports) > 4: + print format_text("[WARNING]: ", 'magenta', 'bold'), format_text("displaying up to 4 ports", 'magenta') + ports = ports[:4] + return ports + + + class CTRexStatsManager(object): diff --git a/scripts/automation/trex_control_plane/console/trex_console.py b/scripts/automation/trex_control_plane/console/trex_console.py index 3ddfd8c6..9236ce98 100755 --- a/scripts/automation/trex_control_plane/console/trex_console.py +++ b/scripts/automation/trex_control_plane/console/trex_console.py @@ -350,6 +350,13 @@ class TRexConsole(TRexGeneralCmd): def help_stats(self): self.do_stats("-h") + def do_clear(self, line): + '''Clear cached local statistics\n''' + self.stateless_client.cmd_clear_line(line) + + def help_clear(self): + self.do_clear("-h") + def help_events (self): self.do_events("-h") -- cgit 1.2.3-korg From 503c10b024aa2ed6d4d8dc7fb5debf4a64bd9b1e Mon Sep 17 00:00:00 2001 From: Dan Klein Date: Mon, 7 Dec 2015 08:29:17 +0200 Subject: Re-designed the statistic building model based on agreed diagram. WORKING: all polling stats --- .../trex_control_plane/client/trex_async_client.py | 8 +- .../client/trex_stateless_client.py | 139 +++++++---- .../trex_control_plane/common/trex_stats.py | 261 +++++++++++---------- .../trex_control_plane/console/trex_status.py | 2 +- 4 files changed, 233 insertions(+), 177 deletions(-) (limited to 'scripts/automation/trex_control_plane/client/trex_stateless_client.py') diff --git a/scripts/automation/trex_control_plane/client/trex_async_client.py b/scripts/automation/trex_control_plane/client/trex_async_client.py index a2bb4752..8b274134 100644 --- a/scripts/automation/trex_control_plane/client/trex_async_client.py +++ b/scripts/automation/trex_control_plane/client/trex_async_client.py @@ -51,7 +51,7 @@ class CTRexAsyncStats(object): self.ref_point = self.current - def get(self, field, format = False, suffix = ""): + def get(self, field, format=False, suffix=""): if not field in self.current: return "N/A" @@ -61,8 +61,7 @@ class CTRexAsyncStats(object): else: return self.format_num(self.current[field], suffix) - - def get_rel (self, field, format = False, suffix = ""): + def get_rel (self, field, format=False, suffix=""): if not field in self.current: return "N/A" @@ -204,7 +203,8 @@ class CTRexAsyncClient(): def __dispatch (self, name, type, data): # stats if name == "trex-global": - self.stats.update(data) + # self.stats.update(data) + self.stateless_client.handle_async_stats_update(data) # events elif name == "trex-event": self.stateless_client.handle_async_event(type, data) diff --git a/scripts/automation/trex_control_plane/client/trex_stateless_client.py b/scripts/automation/trex_control_plane/client/trex_stateless_client.py index 7be7392e..e02620a6 100755 --- a/scripts/automation/trex_control_plane/client/trex_stateless_client.py +++ b/scripts/automation/trex_control_plane/client/trex_stateless_client.py @@ -19,6 +19,7 @@ from common import trex_stats from client_utils import parsing_opts, text_tables import time import datetime +import re from trex_async_client import CTRexAsyncClient @@ -165,6 +166,7 @@ class Port(object): self.driver = driver self.speed = speed self.streams = {} + self.port_stats = trex_stats.CPortStats(self) def err(self, msg): return RC_ERR("port {0} : {1}".format(self.port_id, msg)) @@ -189,7 +191,6 @@ class Port(object): else: return self.err(rc.data) - # release the port def release(self): params = {"port_id": self.port_id, @@ -409,6 +410,16 @@ class Port(object): def get_port_state_name(self): return self.STATES_MAP.get(self.state, "Unknown") + ################# stats handler ###################### + def generate_port_stats(self): + return self.port_stats.generate_stats() + pass + + def generate_port_status(self): + return {"port-type": self.driver, + "maximum": "{speed} Gb/s".format(speed=self.speed), + "port-status": self.get_port_state_name() + } ################# events handler ###################### def async_event_port_stopped (self): @@ -424,22 +435,24 @@ class CTRexStatelessClient(object): self.system_info = None self.comm_link = CTRexStatelessClient.CCommLink(server, sync_port, virtual) self.verbose = False - self.ports = [] - self._conn_handler = {} - self._active_ports = set() - self._system_info = None - self._server_version = None + self.ports = {} + # self._conn_handler = {} + # self._active_ports = set() + self._connection_info = {"server": server, + "sync_port": sync_port, + "async_port": async_port} + self.system_info = {} + self.server_version = {} self.__err_log = None self._async_client = CTRexAsyncClient(server, async_port, self) self.streams_db = CStreamsDB() - self.info_and_stats = trex_stats.CTRexInformationCenter(self.user, - {"server": server, - "sync_port": sync_port, - "async_port": async_port}, - self.ports, - self.get_stats_async()) + self.global_stats = trex_stats.CGlobalStats(self._connection_info, + self.server_version, + self.ports) + self.stats_generator = trex_stats.CTRexStatsGenerator(self.global_stats, + self.ports) self.connected = False @@ -447,6 +460,33 @@ class CTRexStatelessClient(object): ################# events handler ###################### + def handle_async_stats_update(self, dump_data): + global_stats = {} + port_stats = {} + + # filter the values per port and general + for key, value in dump_data.iteritems(): + # match a pattern of ports + m = re.search('(.*)\-([0-8])', key) + if m: + port_id = int(m.group(2)) + field_name = m.group(1) + if self.ports.has_key(port_id): + if not port_id in port_stats: + port_stats[port_id] = {} + port_stats[port_id][field_name] = value + else: + continue + else: + # no port match - general stats + global_stats[key] = value + + # update the general object with the snapshot + self.global_stats.update(global_stats) + # update all ports + for port_id, data in port_stats.iteritems(): + self.ports[port_id].port_stats.update(data) + def handle_async_event (self, type, data): # DP stopped @@ -556,7 +596,7 @@ class CTRexStatelessClient(object): return RC_ERR(data) self.server_version = data - self.info_and_stats.server_version = data + self.global_stats.server_version = data # cache system info # self.get_system_info(refresh=True) @@ -564,7 +604,6 @@ class CTRexStatelessClient(object): if not rc: return RC_ERR(data) self.system_info = data - self.info_and_stats.system_info = data # cache supported commands rc, data = self.transmit("get_supported_cmds") @@ -577,7 +616,7 @@ class CTRexStatelessClient(object): for port_id in xrange(self.get_port_count()): speed = self.system_info['ports'][port_id]['speed'] driver = self.system_info['ports'][port_id]['driver'] - self.ports.append(Port(port_id, speed, driver, self.user, self.transmit)) + self.ports[port_id] = Port(port_id, speed, driver, self.user, self.transmit) # acquire all ports rc = self.acquire() @@ -634,11 +673,14 @@ class CTRexStatelessClient(object): return self.comm_link.server def get_acquired_ports(self): - return [port.port_id for port in self.ports if port.is_acquired()] - + return [port_id + for port_id, port_obj in self.ports.iteritems() + if port_obj.is_acquired()] def get_active_ports(self): - return [port.port_id for port in self.ports if port.is_active()] + return [port_id + for port_id, port_obj in self.ports.iteritems() + if port_obj.is_active()] def set_verbose(self, mode): self.comm_link.set_verbose(mode) @@ -893,7 +935,7 @@ class CTRexStatelessClient(object): # update cmd def cmd_update (self, port_id_list, mult): - # find the relveant ports + # find the relevant ports active_ports = list(set(self.get_active_ports()).intersection(port_id_list)) if not active_ports: @@ -909,15 +951,14 @@ class CTRexStatelessClient(object): return RC_OK() def cmd_clear(self, port_id_list): - self.info_and_stats.clear(port_id_list) + # self.info_and_stats.clear(port_id_list) return RC_OK() - # pause cmd def cmd_pause (self, port_id_list): - # find the relveant ports + # find the relevant ports active_ports = list(set(self.get_active_ports()).intersection(port_id_list)) if not active_ports: @@ -932,19 +973,6 @@ class CTRexStatelessClient(object): return RC_OK() - def cmd_pause_line (self, line): - '''Pause active traffic in specified ports on TRex\n''' - parser = parsing_opts.gen_parser(self, - "pause", - self.cmd_stop_line.__doc__, - parsing_opts.PORT_LIST_WITH_ALL) - - opts = parser.parse_args(line.split()) - if opts is None: - return RC_ERR("bad command line parameters") - - return self.cmd_pause(opts.ports) - # resume cmd def cmd_resume (self, port_id_list): @@ -965,20 +993,6 @@ class CTRexStatelessClient(object): return RC_OK() - def cmd_resume_line (self, line): - '''Resume active traffic in specified ports on TRex\n''' - parser = parsing_opts.gen_parser(self, - "resume", - self.cmd_stop_line.__doc__, - parsing_opts.PORT_LIST_WITH_ALL) - - opts = parser.parse_args(line.split()) - if opts is None: - return RC_ERR("bad command line parameters") - - return self.cmd_resume(opts.ports) - - # start cmd def cmd_start (self, port_id_list, stream_list, mult, force, duration): @@ -1020,7 +1034,7 @@ class CTRexStatelessClient(object): stats_obj = {} for stats_type in stats_opts: - stats_obj.update(self.info_and_stats.generate_single_statistic(port_id_list, stats_type)) + stats_obj.update(self.stats_generator.generate_single_statistic(port_id_list, stats_type)) return stats_obj @@ -1066,6 +1080,19 @@ class CTRexStatelessClient(object): return self.cmd_start(opts.ports, stream_list, opts.mult, opts.force, opts.duration) + def cmd_resume_line (self, line): + '''Resume active traffic in specified ports on TRex\n''' + parser = parsing_opts.gen_parser(self, + "resume", + self.cmd_stop_line.__doc__, + parsing_opts.PORT_LIST_WITH_ALL) + + opts = parser.parse_args(line.split()) + if opts is None: + return RC_ERR("bad command line parameters") + + return self.cmd_resume(opts.ports) + def cmd_stop_line (self, line): '''Stop active traffic in specified ports on TRex\n''' parser = parsing_opts.gen_parser(self, @@ -1079,6 +1106,18 @@ class CTRexStatelessClient(object): return self.cmd_stop(opts.ports) + def cmd_pause_line (self, line): + '''Pause active traffic in specified ports on TRex\n''' + parser = parsing_opts.gen_parser(self, + "pause", + self.cmd_stop_line.__doc__, + parsing_opts.PORT_LIST_WITH_ALL) + + opts = parser.parse_args(line.split()) + if opts is None: + return RC_ERR("bad command line parameters") + + return self.cmd_pause(opts.ports) def cmd_update_line (self, line): '''Update port(s) speed currently active\n''' diff --git a/scripts/automation/trex_control_plane/common/trex_stats.py b/scripts/automation/trex_control_plane/common/trex_stats.py index ec455730..1f9d59e3 100755 --- a/scripts/automation/trex_control_plane/common/trex_stats.py +++ b/scripts/automation/trex_control_plane/common/trex_stats.py @@ -4,6 +4,8 @@ from client_utils import text_tables from common.text_opts import format_text from client.trex_async_client import CTRexAsyncStats import copy +import datetime +import re GLOBAL_STATS = 'g' PORT_STATS = 'p' @@ -12,28 +14,15 @@ ALL_STATS_OPTS = {GLOBAL_STATS, PORT_STATS, PORT_STATUS} ExportableStats = namedtuple('ExportableStats', ['raw_data', 'text_table']) -class CTRexInformationCenter(object): +class CTRexStatsGenerator(object): + """ + This object is responsible of generating stats from objects maintained at + CTRexStatelessClient and the ports. + """ - def __init__(self, username, connection_info, ports_ref, async_stats_ref): - self.user = username - self.connection_info = connection_info - self.server_version = None - self.system_info = None - self._ports = ports_ref - self._async_stats = async_stats_ref - - # def __getitem__(self, item): - # stats_obj = getattr(self, item) - # if stats_obj: - # return stats_obj.get_stats() - # else: - # return None - - def clear(self, port_id_list): - self._async_stats.get_general_stats().clear() - for port_id in port_id_list: - self._async_stats.get_port_stats(port_id).clear() - pass + def __init__(self, global_stats_ref, ports_dict_ref): + self._global_stats = global_stats_ref + self._ports_dict = ports_dict_ref def generate_single_statistic(self, port_id_list, statistic_type): if statistic_type == GLOBAL_STATS: @@ -48,32 +37,17 @@ class CTRexInformationCenter(object): return {} def _generate_global_stats(self): - stats_obj = self._async_stats.get_general_stats() - return_stats_data = \ - OrderedDict([("connection", "{host}, Port {port}".format(host=self.connection_info.get("server"), - port=self.connection_info.get("sync_port"))), - ("version", "{ver}, UUID: {uuid}".format(ver=self.server_version.get("version", "N/A"), - uuid="N/A")), - ("cpu_util", "{0}%".format(stats_obj.get("m_cpu_util"))), - ("total_tx", stats_obj.get("m_tx_bps", format=True, suffix="b/sec")), - ("total_rx", stats_obj.get("m_rx_bps", format=True, suffix="b/sec")), - ("total_pps", stats_obj.format_num(stats_obj.get("m_tx_pps") + stats_obj.get("m_rx_pps"), - suffix="pkt/sec")), - ("total_streams", sum([len(port.streams) - for port in self._ports])), - ("active_ports", sum([port.is_active() - for port in self._ports])) - ] - ) + # stats_obj = self._async_stats.get_general_stats() + stats_data = self._global_stats.generate_stats() # build table representation stats_table = text_tables.TRexTextInfo() stats_table.set_cols_align(["l", "l"]) stats_table.add_rows([[k.replace("_", " ").title(), v] - for k, v in return_stats_data.iteritems()], + for k, v in stats_data.iteritems()], header=False) - return {"global_statistics": ExportableStats(return_stats_data, stats_table)} + return {"global_statistics": ExportableStats(stats_data, stats_table)} def _generate_port_stats(self, port_id_list): relevant_ports = self.__get_relevant_ports(port_id_list) @@ -94,41 +68,11 @@ class CTRexInformationCenter(object): for port_obj in relevant_ports: # fetch port data - port_stats = self._async_stats.get_port_stats(port_obj.port_id) - - owner = self.user - active = "YES" if port_obj.is_active() else "NO" - tx_bytes = port_stats.get_rel("obytes", format = True, suffix = "B") - rx_bytes = port_stats.get_rel("ibytes", format = True, suffix = "B") - tx_pkts = port_stats.get_rel("opackets", format = True, suffix = "pkts") - rx_pkts = port_stats.get_rel("ipackets", format = True, suffix = "pkts") - tx_errors = port_stats.get_rel("oerrors", format = True) - rx_errors = port_stats.get_rel("ierrors", format = True) - tx_bw = port_stats.get("m_total_tx_bps", format = True, suffix = "bps") - rx_bw = port_stats.get("m_total_rx_bps", format = True, suffix = "bps") + port_stats = port_obj.generate_port_stats() # populate to data structures - return_stats_data[port_obj.port_id] = {"owner": owner, - "active": active, - "tx-bytes": tx_bytes, - "rx-bytes": rx_bytes, - "tx-pkts": tx_pkts, - "rx-pkts": rx_pkts, - "tx-errors": tx_errors, - "rx-errors": rx_errors, - "Tx-BW": tx_bw, - "Rx-BW": rx_bw - } - per_field_stats["owner"].append(owner) - per_field_stats["active"].append(active) - per_field_stats["tx-bytes"].append(tx_bytes) - per_field_stats["rx-bytes"].append(rx_bytes) - per_field_stats["tx-pkts"].append(tx_pkts) - per_field_stats["rx-pkts"].append(rx_pkts) - per_field_stats["tx-errors"].append(tx_errors) - per_field_stats["rx-errors"].append(rx_errors) - per_field_stats["tx-BW"].append(tx_bw) - per_field_stats["rx-BW"].append(rx_bw) + return_stats_data[port_obj.port_id] = port_stats + self.__update_per_field_dict(port_stats, per_field_stats) stats_table = text_tables.TRexTextTable() stats_table.set_cols_align(["l"] + ["r"]*len(relevant_ports)) @@ -152,21 +96,13 @@ class CTRexInformationCenter(object): for port_obj in relevant_ports: # fetch port data - port_stats = self._async_stats.get_port_stats(port_obj.port_id) - - - port_type = port_obj.driver - maximum = "{speed} Gb/s".format(speed=port_obj.speed)#CTRexAsyncStats.format_num(port_obj.get_speed_bps(), suffix="bps") - port_status = port_obj.get_port_state_name() + # port_stats = self._async_stats.get_port_stats(port_obj.port_id) + port_status = port_obj.generate_port_status() # populate to data structures - return_stats_data[port_obj.port_id] = {"port-type": port_type, - "maximum": maximum, - "port-status": port_status, - } - per_field_status["port-type"].append(port_type) - per_field_status["maximum"].append(maximum) - per_field_status["port-status"].append(port_status) + return_stats_data[port_obj.port_id] = port_status + + self.__update_per_field_dict(port_status, per_field_status) stats_table = text_tables.TRexTextTable() stats_table.set_cols_align(["l"] + ["c"]*len(relevant_ports)) @@ -181,7 +117,7 @@ class CTRexInformationCenter(object): def __get_relevant_ports(self, port_id_list): # fetch owned ports ports = [port_obj - for port_obj in self._ports + for _, port_obj in self._ports_dict.iteritems() if port_obj.is_acquired() and port_obj.port_id in port_id_list] # display only the first FOUR options, by design if len(ports) > 4: @@ -189,61 +125,142 @@ class CTRexInformationCenter(object): ports = ports[:4] return ports + def __update_per_field_dict(self, dict_src_data, dict_dest_ref): + for key, val in dict_src_data.iteritems(): + if key in dict_dest_ref: + dict_dest_ref[key].append(val) -class CTRexStatsManager(object): - def __init__(self, *args): - for stat_type in args: - # register stat handler for each stats type - setattr(self, stat_type, CTRexStatsManager.CSingleStatsHandler()) +class CTRexStats(object): + """ This is an abstract class to represent a stats object """ + + def __init__(self): + self.reference_stats = None + self.latest_stats = {} + self.last_update_ts = datetime.datetime.now() + def __getitem__(self, item): - stats_obj = getattr(self, item) - if stats_obj: - return stats_obj.get_stats() - else: - return None + # override this to allow quick and clean access to fields + if not item in self.latest_stats: + return "N/A" + + # item must exist + m = re.search('_(([a-z])ps)$', item) + if m: + # this is a non-relative item + unit = m.group(2) + if unit == "b": + return self.get(item, format=True, suffix="b/sec") + elif unit == "p": + return self.get(item, format=True, suffix="pkt/sec") + else: + return self.get(item, format=True, suffix=m.group(1)) + + m = re.search('^[i|o](a-z+)$', item) + if m: + # this is a non-relative item + type = m.group(1) + if type == "bytes": + return self.get_rel(item, format=True, suffix="B") + elif type == "packets": + return self.get_rel(item, format=True, suffix="pkts") + else: + # do not format with suffix + return self.get_rel(item, format=True) - class CSingleStatsHandler(object): + # can't match to any known pattern, return N/A + return "N/A" - def __init__(self): - self._stats = {} + @staticmethod + def format_num(size, suffix = ""): + for unit in ['','K','M','G','T','P']: + if abs(size) < 1000.0: + return "%3.2f %s%s" % (size, unit, suffix) + size /= 1000.0 + return "NaN" - def update(self, obj_id, stats_obj): - assert isinstance(stats_obj, CTRexStats) - self._stats[obj_id] = stats_obj + def generate_stats(self): + # must be implemented by designated classes (such as port/ global stats) + raise NotImplementedError() - def get_stats(self, obj_id=None): - if obj_id: - return copy.copy(self._stats.pop(obj_id)) - else: - return copy.copy(self._stats) + def update(self, snapshot): + # update + self.last_update_ts = datetime.datetime.now() + self.latest_stats = snapshot -class CTRexStats(object): - def __init__(self, **kwargs): - for k, v in kwargs.items(): - setattr(self, k, v) + if self.reference_stats == None: + self.reference_stats = self.latest_stats + + def clear_stats(self): + self.reference_stats = self.latest_stats + + def get(self, field, format=False, suffix=""): + if not field in self.latest_stats: + return "N/A" + if not format: + return self.latest_stats[field] + else: + return self.format_num(self.latest_stats[field], suffix) + + def get_rel(self, field, format=False, suffix=""): + if not field in self.latest_stats: + return "N/A" + if not format: + return (self.latest_stats[field] - self.reference_stats[field]) + else: + return self.format_num(self.latest_stats[field] - self.reference_stats[field], suffix) class CGlobalStats(CTRexStats): - def __init__(self, **kwargs): - super(CGlobalStats, self).__init__(kwargs) - pass + pass + + def __init__(self, connection_info, server_version, ports_dict_ref): + super(CGlobalStats, self).__init__() + self.connection_info = connection_info + self.server_version = server_version + self._ports_dict = ports_dict_ref + def generate_stats(self): + return OrderedDict([("connection", "{host}, Port {port}".format(host=self.connection_info.get("server"), + port=self.connection_info.get("sync_port"))), + ("version", "{ver}, UUID: {uuid}".format(ver=self.server_version.get("version", "N/A"), + uuid="N/A")), + ("cpu_util", "{0}%".format(self.get("m_cpu_util"))), + ("total_tx", self.get("m_tx_bps", format=True, suffix="b/sec")), + ("total_rx", self.get("m_rx_bps", format=True, suffix="b/sec")), + ("total_pps", self.format_num(self.get("m_tx_pps") + self.get("m_rx_pps"), + suffix="pkt/sec")), + ("total_streams", sum([len(port_obj.streams) + for _, port_obj in self._ports_dict.iteritems()])), + ("active_ports", sum([port_obj.is_active() + for _, port_obj in self._ports_dict.iteritems()])) + ] + ) class CPortStats(CTRexStats): - def __init__(self, **kwargs): - super(CPortStats, self).__init__(kwargs) - pass + pass + def __init__(self, port_obj): + super(CPortStats, self).__init__() + self._port_obj = port_obj + + def generate_stats(self): + return {"owner": self._port_obj.user, + "active": "YES" if self._port_obj.is_active() else "NO", + "tx-bytes": self.get_rel("obytes", format = True, suffix = "B"), + "rx-bytes": self.get_rel("ibytes", format = True, suffix = "B"), + "tx-pkts": self.get_rel("opackets", format = True, suffix = "pkts"), + "rx-pkts": self.get_rel("ipackets", format = True, suffix = "pkts"), + "tx-errors": self.get_rel("oerrors", format = True), + "rx-errors": self.get_rel("ierrors", format = True), + "tx-BW": self.get("m_total_tx_bps", format = True, suffix = "bps"), + "rx-BW": self.get("m_total_rx_bps", format = True, suffix = "bps") + } -class CStreamStats(CTRexStats): - def __init__(self, **kwargs): - super(CStreamStats, self).__init__(kwargs) - pass if __name__ == "__main__": diff --git a/scripts/automation/trex_control_plane/console/trex_status.py b/scripts/automation/trex_control_plane/console/trex_status.py index 10ac75c9..cdf3fb69 100644 --- a/scripts/automation/trex_control_plane/console/trex_status.py +++ b/scripts/automation/trex_control_plane/console/trex_status.py @@ -494,7 +494,7 @@ class CTRexStatus(): self.stats_panel.panel.top() self.stats_panel.draw() - panel.update_panels(); + panel.update_panels() self.stdscr.refresh() sleep(0.01) -- cgit 1.2.3-korg From 1895d21485621c3428d045fa0f5b9daf165c8260 Mon Sep 17 00:00:00 2001 From: Dan Klein Date: Mon, 7 Dec 2015 08:35:46 +0200 Subject: clear command now works as well.. --- .../trex_control_plane/client/trex_stateless_client.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'scripts/automation/trex_control_plane/client/trex_stateless_client.py') diff --git a/scripts/automation/trex_control_plane/client/trex_stateless_client.py b/scripts/automation/trex_control_plane/client/trex_stateless_client.py index e02620a6..748817da 100755 --- a/scripts/automation/trex_control_plane/client/trex_stateless_client.py +++ b/scripts/automation/trex_control_plane/client/trex_stateless_client.py @@ -421,6 +421,9 @@ class Port(object): "port-status": self.get_port_state_name() } + def clear_stats(self): + return self.port_stats.clear_stats() + ################# events handler ###################### def async_event_port_stopped (self): self.state = self.STATE_STREAMS @@ -951,10 +954,11 @@ class CTRexStatelessClient(object): return RC_OK() def cmd_clear(self, port_id_list): - # self.info_and_stats.clear(port_id_list) + for port_id in port_id_list: + self.ports[port_id].clear_stats() + self.global_stats.clear_stats() return RC_OK() - # pause cmd def cmd_pause (self, port_id_list): @@ -962,7 +966,7 @@ class CTRexStatelessClient(object): active_ports = list(set(self.get_active_ports()).intersection(port_id_list)) if not active_ports: - msg = "No active traffic on porvided ports" + msg = "No active traffic on provided ports" print format_text(msg, 'bold') return RC_ERR(msg) -- cgit 1.2.3-korg