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 --- .../trex_control_plane/client/trex_async_client.py | 48 ++--- .../client/trex_stateless_client.py | 108 +++++++++-- .../client_utils/external_packages.py | 3 +- .../client_utils/jsonrpc_client.py | 4 +- .../client_utils/parsing_opts.py | 213 +++++++++++++++++++++ .../trex_control_plane/client_utils/text_tables.py | 34 ++++ .../trex_control_plane/common/trex_stats.py | 80 ++++++++ .../trex_control_plane/console/parsing_opts.py | 193 ------------------- .../trex_control_plane/console/trex_console.py | 11 +- .../trex_control_plane/console/trex_status.py | 4 +- 10 files changed, 462 insertions(+), 236 deletions(-) create mode 100755 scripts/automation/trex_control_plane/client_utils/parsing_opts.py create mode 100644 scripts/automation/trex_control_plane/client_utils/text_tables.py delete mode 100755 scripts/automation/trex_control_plane/console/parsing_opts.py (limited to 'scripts/automation/trex_control_plane') 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 adb91d97..12c89c1a 100644 --- a/scripts/automation/trex_control_plane/client/trex_async_client.py +++ b/scripts/automation/trex_control_plane/client/trex_async_client.py @@ -21,13 +21,14 @@ from common.trex_stats import * from common.trex_streams import * # basic async stats class -class TrexAsyncStats(object): +class CTRexAsyncStats(object): def __init__ (self): self.ref_point = None self.current = {} self.last_update_ts = datetime.datetime.now() - def __format_num (self, size, suffix = ""): + @staticmethod + def format_num (size, suffix = ""): for unit in ['','K','M','G','T','P']: if abs(size) < 1000.0: @@ -47,7 +48,7 @@ class TrexAsyncStats(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" @@ -55,7 +56,7 @@ class TrexAsyncStats(object): if not format: return self.current[field] else: - return self.__format_num(self.current[field], suffix) + return self.format_num(self.current[field], suffix) def get_rel (self, field, format = False, suffix = ""): @@ -65,7 +66,7 @@ class TrexAsyncStats(object): if not format: return (self.current[field] - self.ref_point[field]) else: - return self.__format_num(self.current[field] - self.ref_point[field], suffix) + return self.format_num(self.current[field] - self.ref_point[field], suffix) # return true if new data has arrived in the past 2 seconds @@ -74,28 +75,28 @@ class TrexAsyncStats(object): return (delta_ms < 2000) # describes the general stats provided by TRex -class TrexAsyncStatsGeneral(TrexAsyncStats): +class CTRexAsyncStatsGeneral(CTRexAsyncStats): def __init__ (self): - super(TrexAsyncStatsGeneral, self).__init__() + super(CTRexAsyncStatsGeneral, self).__init__() # per port stats -class TrexAsyncStatsPort(TrexAsyncStats): +class CTRexAsyncStatsPort(CTRexAsyncStats): def __init__ (self): - super(TrexAsyncStatsPort, self).__init__() + super(CTRexAsyncStatsPort, self).__init__() def get_stream_stats (self, stream_id): return None # stats manager -class TrexAsyncStatsManager(): +class CTRexAsyncStatsManager(): def __init__ (self): - self.general_stats = TrexAsyncStatsGeneral() + self.general_stats = CTRexAsyncStatsGeneral() self.port_stats = {} - def get_general_stats (self): + def get_general_stats(self): return self.general_stats def get_port_stats (self, port_id): @@ -106,10 +107,10 @@ class TrexAsyncStatsManager(): return self.port_stats[str(port_id)] - def update (self, data): + def update(self, data): self.__handle_snapshot(data) - def __handle_snapshot (self, snapshot): + def __handle_snapshot(self, snapshot): general_stats = {} port_stats = {} @@ -140,7 +141,7 @@ class TrexAsyncStatsManager(): for port_id, data in port_stats.iteritems(): if not port_id in self.port_stats: - self.port_stats[port_id] = TrexAsyncStatsPort() + self.port_stats[port_id] = CTRexAsyncStatsPort() self.port_stats[port_id].update(data) @@ -157,22 +158,20 @@ class CTRexAsyncClient(): self.raw_snapshot = {} - self.stats = TrexAsyncStatsManager() + self.stats = CTRexAsyncStatsManager() self.tr = "tcp://{0}:{1}".format(self.server, self.port) print "\nConnecting To ZMQ Publisher At {0}".format(self.tr) self.active = True - self.t = threading.Thread(target = self.run) + self.t = threading.Thread(target= self.run) # kill this thread on exit and don't add it to the join list self.t.setDaemon(True) self.t.start() - - - def run (self): + def run(self): # Socket to talk to server self.context = zmq.Context() @@ -182,7 +181,7 @@ class CTRexAsyncClient(): self.socket.setsockopt(zmq.SUBSCRIBE, '') while self.active: - line = self.socket.recv_string(); + line = self.socket.recv_string() msg = json.loads(line) name = msg['name'] @@ -192,15 +191,13 @@ class CTRexAsyncClient(): self.__dispatch(name, type, data) - - def get_stats (self): + def get_stats(self): return self.stats def get_raw_snapshot (self): #return str(self.stats.global_stats.get('m_total_tx_bytes')) + " / " + str(self.stats.global_stats.get_rel('m_total_tx_bytes')) return self.raw_snapshot - # dispatch the message to the right place def __dispatch (self, name, type, data): # stats @@ -225,3 +222,6 @@ class CTRexAsyncClient(): self.active = False self.t.join() + +if __name__ == "__main__": + pass \ No newline at end of file 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): diff --git a/scripts/automation/trex_control_plane/client_utils/external_packages.py b/scripts/automation/trex_control_plane/client_utils/external_packages.py index e2bb37a5..3c6eb449 100755 --- a/scripts/automation/trex_control_plane/client_utils/external_packages.py +++ b/scripts/automation/trex_control_plane/client_utils/external_packages.py @@ -9,7 +9,8 @@ PATH_TO_PYTHON_LIB = os.path.abspath(os.path.join(ROOT_PATH, os.pardir, os.pard CLIENT_UTILS_MODULES = ['zmq', 'dpkt-1.8.6', - 'PyYAML-3.01/lib' + 'PyYAML-3.01/lib', + 'texttable-0.8.4' ] def import_client_utils_modules(): diff --git a/scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py b/scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py index b826f02f..dd208da4 100755 --- a/scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py +++ b/scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py @@ -174,7 +174,7 @@ class JsonRpcClient(object): def process_single_response (self, response_json): if (response_json.get("jsonrpc") != "2.0"): - return False, "Malfromed Response ({0})".format(str(response)) + return False, "Malformed Response ({0})".format(str(response_json)) # error reported by server if ("error" in response_json): @@ -185,7 +185,7 @@ class JsonRpcClient(object): # if no error there should be a result if ("result" not in response_json): - return False, "Malformed Response ({0})".format(str(response)) + return False, "Malformed Response ({0})".format(str(response_json)) return True, response_json["result"] diff --git a/scripts/automation/trex_control_plane/client_utils/parsing_opts.py b/scripts/automation/trex_control_plane/client_utils/parsing_opts.py new file mode 100755 index 00000000..c110983b --- /dev/null +++ b/scripts/automation/trex_control_plane/client_utils/parsing_opts.py @@ -0,0 +1,213 @@ +import argparse +from collections import namedtuple +import sys +import re +import os + +ArgumentPack = namedtuple('ArgumentPack', ['name_or_flags', 'options']) +ArgumentGroup = namedtuple('ArgumentGroup', ['type', 'args', 'options']) + + +# list of available parsing options +MULTIPLIER = 1 +PORT_LIST = 2 +ALL_PORTS = 3 +PORT_LIST_WITH_ALL = 4 +FILE_PATH = 5 +FILE_FROM_DB = 6 +SERVER_IP = 7 +STREAM_FROM_PATH_OR_FILE = 8 +DURATION = 9 +FORCE = 10 +GLOBAL_STATS = 11 +PORT_STATS = 12 +PORT_STATUS = 13 +STATS_MASK = 14 + +# list of ArgumentGroup types +MUTEX = 1 + + +def match_time_unit(val): + '''match some val against time shortcut inputs ''' + match = re.match("^(\d+)([m|h]?)$", val) + if match: + digit = int(match.group(1)) + unit = match.group(2) + if not unit: + return digit + elif unit == 'm': + return digit*60 + else: + return digit*60*60 + else: + raise argparse.ArgumentTypeError("Duration should be passed in the following format: \n" + "-d 100 : in sec \n" + "-d 10m : in min \n" + "-d 1h : in hours") + +def match_multiplier(val): + '''match some val against multiplier shortcut inputs ''' + match = re.match("^(\d+)(gb|kpps|%?)$", val) + if match: + digit = int(match.group(1)) + unit = match.group(2) + if not unit: + return digit + elif unit == 'gb': + raise NotImplementedError("gb units are not supported yet") + else: + raise NotImplementedError("kpps units are not supported yet") + else: + raise argparse.ArgumentTypeError("Multiplier should be passed in the following format: \n" + "-m 100 : multiply stream file by this factor \n" + "-m 10gb : from graph calculate the maximum rate as this bandwidth (for each port)\n" + "-m 10kpps : from graph calculate the maximum rate as this pps (for each port)\n" + "-m 40% : from graph calculate the maximum rate as this percent from total port (for each port)") + + + +def is_valid_file(filename): + if not os.path.isfile(filename): + raise argparse.ArgumentTypeError("The file '%s' does not exist" % filename) + + return filename + + +OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], + {'help': "Set multiplier for stream", + 'dest': "mult", + 'default': 1.0, + 'type': match_multiplier}), + + PORT_LIST: ArgumentPack(['--port'], + {"nargs": '+', + 'dest':'ports', + 'metavar': 'PORTS', + 'type': int, + 'help': "A list of ports on which to apply the command", + 'default': []}), + + ALL_PORTS: ArgumentPack(['-a'], + {"action": "store_true", + "dest": "all_ports", + 'help': "Set this flag to apply the command on all available ports"}), + DURATION: ArgumentPack(['-d'], + {'action': "store", + 'metavar': 'TIME', + 'dest': 'duration', + 'type': match_time_unit, + 'default': -1.0, + 'help': "Set duration time for TRex."}), + + FORCE: ArgumentPack(['--force'], + {"action": "store_true", + 'default': False, + 'help': "Set if you want to stop active ports before applying new TRex run on them."}), + + FILE_PATH: ArgumentPack(['-f'], + {'metavar': 'FILE', + 'dest': 'file', + 'nargs': 1, + 'type': is_valid_file, + 'help': "File path to YAML file that describes a stream pack. "}), + + FILE_FROM_DB: ArgumentPack(['--db'], + {'metavar': 'LOADED_STREAM_PACK', + 'help': "A stream pack which already loaded into console cache."}), + + SERVER_IP: ArgumentPack(['--server'], + {'metavar': 'SERVER', + 'help': "server IP"}), + + GLOBAL_STATS: ArgumentPack(['-g'], + {'action': 'store_true', + 'help': "Fetch only global statistics"}), + + PORT_STATS: ArgumentPack(['-p'], + {'action': 'store_true', + 'help': "Fetch only port statistics"}), + + PORT_STATUS: ArgumentPack(['--ps'], + {'action': 'store_true', + 'help': "Fetch only port status data"}), + + # advanced options + PORT_LIST_WITH_ALL: ArgumentGroup(MUTEX, [PORT_LIST, + ALL_PORTS], + {'required': True}), + STREAM_FROM_PATH_OR_FILE: ArgumentGroup(MUTEX, [FILE_PATH, + FILE_FROM_DB], + {'required': True}), + STATS_MASK: ArgumentGroup(MUTEX, [GLOBAL_STATS, + PORT_STATS, + PORT_STATUS], + {}) + } + + +class CCmdArgParser(argparse.ArgumentParser): + + def __init__(self, stateless_client, *args, **kwargs): + super(CCmdArgParser, self).__init__(*args, **kwargs) + self.stateless_client = stateless_client + + def parse_args(self, args=None, namespace=None): + try: + opts = super(CCmdArgParser, self).parse_args(args, namespace) + if opts is None: + return None + + if getattr(opts, "all_ports", None): + opts.ports = self.stateless_client.get_port_ids() + + if getattr(opts, "ports", None): + for port in opts.ports: + if not self.stateless_client.validate_port_list([port]): + self.error("port id '{0}' is not a valid port id\n".format(port)) + + return opts + + except SystemExit: + # recover from system exit scenarios, such as "help", or bad arguments. + return None + + +def get_flags (opt): + return OPTIONS_DB[opt].name_or_flags + +def gen_parser(stateless_client, op_name, description, *args): + parser = CCmdArgParser(stateless_client, prog=op_name, conflict_handler='resolve', + description=description) + for param in args: + try: + + if isinstance(param, int): + argument = OPTIONS_DB[param] + else: + argument = param + + if isinstance(argument, ArgumentGroup): + if argument.type == MUTEX: + # handle as mutually exclusive group + group = parser.add_mutually_exclusive_group(**argument.options) + for sub_argument in argument.args: + group.add_argument(*OPTIONS_DB[sub_argument].name_or_flags, + **OPTIONS_DB[sub_argument].options) + else: + # ignore invalid objects + continue + elif isinstance(argument, ArgumentPack): + parser.add_argument(*argument.name_or_flags, + **argument.options) + else: + # ignore invalid objects + continue + except KeyError as e: + cause = e.args[0] + raise KeyError("The attribute '{0}' is missing as a field of the {1} option.\n".format(cause, param)) + return parser + + +if __name__ == "__main__": + pass \ No newline at end of file diff --git a/scripts/automation/trex_control_plane/client_utils/text_tables.py b/scripts/automation/trex_control_plane/client_utils/text_tables.py new file mode 100644 index 00000000..2debca38 --- /dev/null +++ b/scripts/automation/trex_control_plane/client_utils/text_tables.py @@ -0,0 +1,34 @@ + +import external_packages +from texttable import Texttable +from common.text_opts import format_text + +class TRexTextTable(Texttable): + + def __init__(self): + Texttable.__init__(self) + # set class attributes so that it'll be more like TRex standard output + self.set_chars(['-', '|', '-', '-']) + self.set_deco(Texttable.HEADER | Texttable.VLINES) + +class TRexTextInfo(Texttable): + + def __init__(self): + Texttable.__init__(self) + # set class attributes so that it'll be more like TRex standard output + self.set_chars(['-', ':', '-', '-']) + self.set_deco(Texttable.VLINES) + +def generate_trex_stats_table(): + pass + +def print_table_with_header(texttable_obj, header=""): + header = header.replace("_", " ").title() + print format_text(header, 'cyan', 'underline') + "\n" + print texttable_obj.draw() + "\n" + + pass + +if __name__ == "__main__": + pass + diff --git a/scripts/automation/trex_control_plane/common/trex_stats.py b/scripts/automation/trex_control_plane/common/trex_stats.py index b7e768c1..bf5ba2bb 100755 --- a/scripts/automation/trex_control_plane/common/trex_stats.py +++ b/scripts/automation/trex_control_plane/common/trex_stats.py @@ -1,6 +1,86 @@ #!/router/bin/python +from collections import namedtuple, OrderedDict +from client_utils import text_tables import copy +GLOBAL_STATS = 'g' +PORT_STATS = 'p' +PORT_STATUS = 'ps' +ALL_STATS_OPTS = {GLOBAL_STATS, PORT_STATS, PORT_STATUS} +ExportableStats = namedtuple('ExportableStats', ['raw_data', 'text_table']) + + +class CTRexInformationCenter(object): + + def __init__(self, connection_info, ports_ref, async_stats_ref): + 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 generate_single_statistic(self, statistic_type): + if statistic_type == GLOBAL_STATS: + return self._generate_global_stats() + elif statistic_type == PORT_STATS: + # return generate_global_stats() + pass + elif statistic_type == PORT_STATUS: + pass + else: + # ignore by returning empty 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", self.server_version.get("version", "N/A")), + ("cpu_util", 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) + for port in self._ports])), + ("active_ports", sum([port.is_active() + for port in self._ports])) + ] + ) + + # 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()], + header=False) + + return {"global_statistics": ExportableStats(return_stats_data, stats_table)} + class CTRexStatsManager(object): diff --git a/scripts/automation/trex_control_plane/console/parsing_opts.py b/scripts/automation/trex_control_plane/console/parsing_opts.py deleted file mode 100755 index d5c21af0..00000000 --- a/scripts/automation/trex_control_plane/console/parsing_opts.py +++ /dev/null @@ -1,193 +0,0 @@ -import argparse -from collections import namedtuple -import sys -import re -import os - -ArgumentPack = namedtuple('ArgumentPack', ['name_or_flags', 'options']) -ArgumentGroup = namedtuple('ArgumentGroup', ['type', 'args', 'options']) - - -# list of available parsing options -MULTIPLIER = 1 -PORT_LIST = 2 -ALL_PORTS = 3 -PORT_LIST_WITH_ALL = 4 -FILE_PATH = 5 -FILE_FROM_DB = 6 -SERVER_IP = 7 -STREAM_FROM_PATH_OR_FILE = 8 -DURATION = 9 -FORCE = 10 - -# list of ArgumentGroup types -MUTEX = 1 - - -def match_time_unit(val): - '''match some val against time shortcut inputs ''' - match = re.match("^(\d+)([m|h]?)$", val) - if match: - digit = int(match.group(1)) - unit = match.group(2) - if not unit: - return digit - elif unit == 'm': - return digit*60 - else: - return digit*60*60 - else: - raise argparse.ArgumentTypeError("Duration should be passed in the following format: \n" - "-d 100 : in sec \n" - "-d 10m : in min \n" - "-d 1h : in hours") - -def match_multiplier(val): - '''match some val against multiplier shortcut inputs ''' - match = re.match("^(\d+)(gb|kpps|%?)$", val) - if match: - digit = int(match.group(1)) - unit = match.group(2) - if not unit: - return digit - elif unit == 'gb': - raise NotImplementedError("gb units are not supported yet") - else: - raise NotImplementedError("kpps units are not supported yet") - else: - raise argparse.ArgumentTypeError("Multiplier should be passed in the following format: \n" - "-m 100 : multiply stream file by this factor \n" - "-m 10gb : from graph calculate the maximum rate as this bandwidth (for each port)\n" - "-m 10kpps : from graph calculate the maximum rate as this pps (for each port)\n" - "-m 40% : from graph calculate the maximum rate as this percent from total port (for each port)") - - - -def is_valid_file(filename): - if not os.path.isfile(filename): - raise argparse.ArgumentTypeError("The file '%s' does not exist" % filename) - - return filename - - -OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], - {'help': "Set multiplier for stream", - 'dest': "mult", - 'default': 1.0, - 'type': match_multiplier}), - - PORT_LIST: ArgumentPack(['--port'], - {"nargs": '+', - 'dest':'ports', - 'metavar': 'PORTS', - 'type': int, - 'help': "A list of ports on which to apply the command", - 'default': []}), - - ALL_PORTS: ArgumentPack(['-a'], - {"action": "store_true", - "dest": "all_ports", - 'help': "Set this flag to apply the command on all available ports"}), - DURATION: ArgumentPack(['-d'], - {'action': "store", - 'metavar': 'TIME', - 'dest': 'duration', - 'type': match_time_unit, - 'default': -1.0, - 'help': "Set duration time for TRex."}), - - FORCE: ArgumentPack(['--force'], - {"action": "store_true", - 'default': False, - 'help': "Set if you want to stop active ports before applying new TRex run on them."}), - - FILE_PATH: ArgumentPack(['-f'], - {'metavar': 'FILE', - 'dest': 'file', - 'nargs': 1, - 'type': is_valid_file, - 'help': "File path to YAML file that describes a stream pack. "}), - - FILE_FROM_DB: ArgumentPack(['--db'], - {'metavar': 'LOADED_STREAM_PACK', - 'help': "A stream pack which already loaded into console cache."}), - - SERVER_IP: ArgumentPack(['--server'], - {'metavar': 'SERVER', - 'help': "server IP"}), - - # advanced options - PORT_LIST_WITH_ALL: ArgumentGroup(MUTEX, [PORT_LIST, - ALL_PORTS], - {'required': True}), - STREAM_FROM_PATH_OR_FILE: ArgumentGroup(MUTEX, [FILE_PATH, - FILE_FROM_DB], - {'required': True}) - } - - -class CCmdArgParser(argparse.ArgumentParser): - - def __init__(self, stateless_client, *args, **kwargs): - super(CCmdArgParser, self).__init__(*args, **kwargs) - self.stateless_client = stateless_client - - def parse_args(self, args=None, namespace=None): - try: - opts = super(CCmdArgParser, self).parse_args(args, namespace) - if opts is None: - return None - - if getattr(opts, "all_ports", None): - opts.ports = self.stateless_client.get_port_ids() - - if getattr(opts, "ports", None): - for port in opts.ports: - if not self.stateless_client.validate_port_list([port]): - self.error("port id '{0}' is not a valid port id\n".format(port)) - - return opts - - except SystemExit: - # recover from system exit scenarios, such as "help", or bad arguments. - return None - - -def get_flags (opt): - return OPTIONS_DB[opt].name_or_flags - -def gen_parser(stateless_client, op_name, description, *args): - parser = CCmdArgParser(stateless_client, prog=op_name, conflict_handler='resolve', - description=description) - for param in args: - try: - - if isinstance(param, int): - argument = OPTIONS_DB[param] - else: - argument = param - - if isinstance(argument, ArgumentGroup): - if argument.type == MUTEX: - # handle as mutually exclusive group - group = parser.add_mutually_exclusive_group(**argument.options) - for sub_argument in argument.args: - group.add_argument(*OPTIONS_DB[sub_argument].name_or_flags, - **OPTIONS_DB[sub_argument].options) - else: - # ignore invalid objects - continue - elif isinstance(argument, ArgumentPack): - parser.add_argument(*argument.name_or_flags, - **argument.options) - else: - # ignore invalid objects - continue - except KeyError as e: - cause = e.args[0] - raise KeyError("The attribute '{0}' is missing as a field of the {1} option.\n".format(cause, param)) - return parser - - -if __name__ == "__main__": - pass \ No newline at end of file diff --git a/scripts/automation/trex_control_plane/console/trex_console.py b/scripts/automation/trex_control_plane/console/trex_console.py index c03f2a82..7d4f3c27 100755 --- a/scripts/automation/trex_control_plane/console/trex_console.py +++ b/scripts/automation/trex_control_plane/console/trex_console.py @@ -33,8 +33,9 @@ from common.trex_streams import * from client.trex_stateless_client import CTRexStatelessClient from common.text_opts import * from client_utils.general_utils import user_input, get_current_user +from client_utils import parsing_opts import trex_status -import parsing_opts + __version__ = "1.1" @@ -283,6 +284,14 @@ class TRexConsole(TRexGeneralCmd): '''force stop all ports\n''' self.stateless_client.cmd_reset() + def do_stats(self, line): + '''Fetch statistics from TRex server by port\n''' + self.stateless_client.cmd_stats_line(line) + pass + + def help_stats(self): + self.do_stats("-h") + # tui def do_tui (self, line): diff --git a/scripts/automation/trex_control_plane/console/trex_status.py b/scripts/automation/trex_control_plane/console/trex_status.py index 869812a1..10ac75c9 100644 --- a/scripts/automation/trex_control_plane/console/trex_status.py +++ b/scripts/automation/trex_control_plane/console/trex_status.py @@ -385,7 +385,7 @@ class TrexStatusCommands(): # # # -class TrexStatus(): +class CTRexStatus(): def __init__ (self, stdscr, stateless_client): self.stdscr = stdscr @@ -506,7 +506,7 @@ def show_trex_status_internal (stdscr, stateless_client): global trex_status if trex_status == None: - trex_status = TrexStatus(stdscr, stateless_client) + trex_status = CTRexStatus(stdscr, stateless_client) trex_status.run() -- cgit 1.2.3-korg