summaryrefslogtreecommitdiffstats
path: root/scripts/automation/trex_control_plane
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/automation/trex_control_plane')
-rw-r--r--scripts/automation/trex_control_plane/client/trex_async_client.py48
-rwxr-xr-xscripts/automation/trex_control_plane/client/trex_stateless_client.py108
-rwxr-xr-xscripts/automation/trex_control_plane/client_utils/external_packages.py3
-rwxr-xr-xscripts/automation/trex_control_plane/client_utils/jsonrpc_client.py4
-rwxr-xr-xscripts/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.py34
-rwxr-xr-xscripts/automation/trex_control_plane/common/trex_stats.py80
-rwxr-xr-xscripts/automation/trex_control_plane/console/trex_console.py11
-rw-r--r--scripts/automation/trex_control_plane/console/trex_status.py4
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()