diff options
Diffstat (limited to 'scripts/automation/trex_control_plane')
-rw-r--r-- | scripts/automation/trex_control_plane/client/trex_async_client.py | 48 | ||||
-rwxr-xr-x | scripts/automation/trex_control_plane/client/trex_stateless_client.py | 108 | ||||
-rwxr-xr-x | scripts/automation/trex_control_plane/client_utils/external_packages.py | 3 | ||||
-rwxr-xr-x | scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py | 4 | ||||
-rwxr-xr-x | scripts/automation/trex_control_plane/client_utils/parsing_opts.py (renamed from scripts/automation/trex_control_plane/console/parsing_opts.py) | 22 | ||||
-rw-r--r-- | scripts/automation/trex_control_plane/client_utils/text_tables.py | 34 | ||||
-rwxr-xr-x | scripts/automation/trex_control_plane/common/trex_stats.py | 80 | ||||
-rwxr-xr-x | scripts/automation/trex_control_plane/console/trex_console.py | 11 | ||||
-rw-r--r-- | scripts/automation/trex_control_plane/console/trex_status.py | 4 |
9 files changed, 270 insertions, 44 deletions
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 @@ -991,6 +1059,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): """describes the connectivity of the stateless client method""" 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/console/parsing_opts.py b/scripts/automation/trex_control_plane/client_utils/parsing_opts.py index d5c21af0..c110983b 100755 --- a/scripts/automation/trex_control_plane/console/parsing_opts.py +++ b/scripts/automation/trex_control_plane/client_utils/parsing_opts.py @@ -19,6 +19,10 @@ 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 @@ -116,13 +120,29 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], {'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}) + {'required': True}), + STATS_MASK: ArgumentGroup(MUTEX, [GLOBAL_STATS, + PORT_STATS, + PORT_STATUS], + {}) } 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/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() |