summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorimarom <imarom@cisco.com>2015-12-07 10:38:37 -0500
committerimarom <imarom@cisco.com>2015-12-07 10:38:37 -0500
commit24b895f6843210b1bbe8046c639ed9da436c8012 (patch)
treedb293baa76ed838d7828859301a07e79b9fc852a
parent0fc30adae2fc5708baef74d36e97a174b078f332 (diff)
parent1895d21485621c3428d045fa0f5b9daf165c8260 (diff)
Merge branch 'dan_stateless' into a test branch
Conflicts: scripts/automation/trex_control_plane/client/trex_async_client.py scripts/automation/trex_control_plane/client/trex_stateless_client.py scripts/automation/trex_control_plane/client_utils/parsing_opts.py scripts/automation/trex_control_plane/console/trex_console.py
-rw-r--r--scripts/automation/trex_control_plane/client/trex_async_client.py45
-rwxr-xr-xscripts/automation/trex_control_plane/client/trex_stateless_client.py237
-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)24
-rw-r--r--scripts/automation/trex_control_plane/client_utils/text_tables.py34
-rwxr-xr-xscripts/automation/trex_control_plane/common/trex_stats.py275
-rwxr-xr-xscripts/automation/trex_control_plane/console/trex_console.py19
-rw-r--r--scripts/automation/trex_control_plane/console/trex_status.py6
-rw-r--r--scripts/external_libs/texttable-0.8.4/LICENSE165
-rw-r--r--scripts/external_libs/texttable-0.8.4/PKG-INFO23
-rw-r--r--scripts/external_libs/texttable-0.8.4/README.md223
-rw-r--r--scripts/external_libs/texttable-0.8.4/setup.py59
-rw-r--r--scripts/external_libs/texttable-0.8.4/texttable.py607
14 files changed, 1634 insertions, 90 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 e38c6ca7..8fdf7c9b 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:
@@ -45,9 +46,12 @@ class TrexAsyncStats(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 = ""):
+ def get(self, field, format=False, suffix=""):
if not field in self.current:
return "N/A"
@@ -55,17 +59,16 @@ 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 = ""):
+ def get_rel (self, field, format=False, suffix=""):
if not field in self.current:
return "N/A"
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 +77,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 +109,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 +143,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,7 +160,7 @@ class CTRexAsyncClient():
self.raw_snapshot = {}
- self.stats = TrexAsyncStatsManager()
+ self.stats = CTRexAsyncStatsManager()
self.connected = False
@@ -227,7 +230,7 @@ class CTRexAsyncClient():
while self.active:
try:
- line = self.socket.recv_string();
+ line = self.socket.recv_string()
if not self.alive:
self.stateless_client.on_async_alive()
@@ -267,15 +270,15 @@ class CTRexAsyncClient():
def get_raw_snapshot (self):
return self.raw_snapshot
-
# dispatch the message to the right place
def __dispatch (self, name, type, data):
# stats
if name == "trex-global":
- 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)
else:
pass
+
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 ca2ad0bb..2925e7eb 100755
--- a/scripts/automation/trex_control_plane/client/trex_stateless_client.py
+++ b/scripts/automation/trex_control_plane/client/trex_stateless_client.py
@@ -10,13 +10,15 @@ 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
+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
@@ -30,7 +32,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 = []
@@ -78,7 +80,6 @@ def RC_OK(data = None):
def RC_ERR (err):
return RC(False, err)
-
LoadedStreamList = namedtuple('LoadedStreamList', ['loaded', 'compiled'])
########## utlity ############
@@ -103,7 +104,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():
@@ -162,6 +163,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, comm_link):
self.port_id = port_id
@@ -176,6 +184,8 @@ class Port(object):
self.streams = {}
self.profile = None
+ self.port_stats = trex_stats.CPortStats(self)
+
def err(self, msg):
return RC_ERR("port {0} : {1}".format(self.port_id, msg))
@@ -199,7 +209,6 @@ class Port(object):
else:
return self.err(rc.data)
-
# release the port
def release(self):
params = {"port_id": self.port_id,
@@ -492,6 +501,25 @@ class Port(object):
format_time(exp_time_factor_sec))
print "\n"
+
+ 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()
+ }
+
+ def clear_stats(self):
+ return self.port_stats.clear_stats()
+
+
################# events handler ######################
def async_event_port_stopped (self):
self.state = self.STATE_STREAMS
@@ -505,16 +533,22 @@ class CTRexStatelessClient(object):
self.user = username
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._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.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.events = []
@@ -534,6 +568,36 @@ class CTRexStatelessClient(object):
if show:
print format_text("\n{:^8} - {:}".format(prefix, format_text(msg, 'bold')))
+
+
+ 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
@@ -663,12 +727,12 @@ class CTRexStatelessClient(object):
return RC_ERR(data)
self.server_version = data
+ self.global_stats.server_version = data
# cache system info
rc, data = self.transmit("get_system_info")
if not rc:
return RC_ERR(data)
-
self.system_info = data
# cache supported commands
@@ -682,7 +746,9 @@ 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.comm_link))
+
+ self.ports[port_id] = Port(port_id, speed, driver, self.user, self.comm_link)
+
# acquire all ports
rc = self.acquire()
@@ -747,11 +813,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)
@@ -777,6 +846,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 ##############
@@ -1018,11 +1090,11 @@ 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:
- msg = "No active traffic on porvided ports"
+ msg = "No active traffic on provided ports"
print format_text(msg, 'bold')
return RC_ERR(msg)
@@ -1031,24 +1103,34 @@ class CTRexStatelessClient(object):
return rc
+ # clear stats
+ def cmd_clear(self, 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):
- # find the relveant ports
+ # find the relevant ports
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)
rc = self.pause_traffic(active_ports)
rc.annotate("Pausing traffic on port(s) {0}:".format(port_id_list))
-
return rc
+
# resume cmd
def cmd_resume (self, port_id_list):
@@ -1062,7 +1144,6 @@ class CTRexStatelessClient(object):
rc = self.resume_traffic(active_ports)
rc.annotate("Resume traffic on port(s) {0}:".format(port_id_list))
-
return rc
@@ -1112,12 +1193,23 @@ class CTRexStatelessClient(object):
return rc
-
+ # validate port(s) profile
def cmd_validate (self, port_id_list):
rc = self.validate(port_id_list)
rc.annotate("Validating streams on port(s) {0}:".format(port_id_list))
return rc
+
+ # stats
+ def cmd_stats(self, port_id_list, stats_mask=set()):
+ stats_opts = trex_stats.ALL_STATS_OPTS.intersection(stats_mask)
+
+ stats_obj = {}
+ for stats_type in stats_opts:
+ stats_obj.update(self.stats_generator.generate_single_statistic(port_id_list, stats_type))
+ return stats_obj
+
+
############## High Level API With Parser ################
@timing
def cmd_start_line (self, line):
@@ -1137,14 +1229,14 @@ 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.dry:
print format_text("\n*** DRY RUN ***", 'bold')
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():
@@ -1167,6 +1259,21 @@ class CTRexStatelessClient(object):
return self.cmd_start(opts.ports, stream_list, opts.mult, opts.force, opts.duration, opts.dry)
@timing
+ 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)
+
+
+ @timing
def cmd_stop_line (self, line):
'''Stop active traffic in specified ports on TRex\n'''
parser = parsing_opts.gen_parser(self,
@@ -1176,10 +1283,26 @@ 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)
+
+ @timing
+ 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)
+
+
@timing
def cmd_update_line (self, line):
'''Update port(s) speed currently active\n'''
@@ -1206,6 +1329,52 @@ class CTRexStatelessClient(object):
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'''
+ # 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")
+
+ # determine stats mask
+ 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
+ for stat_type, stat_data in stats.iteritems():
+ text_tables.print_table_with_header(stat_data.text_table, stat_type)
+
+ return RC_OK()
+
+
+
+
@timing
def cmd_pause_line (self, line):
'''Pause active traffic in specified ports on TRex\n'''
@@ -1269,7 +1438,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
@@ -1330,6 +1499,20 @@ class CTRexStatelessClient(object):
#################################
+ # ------ 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 90d7f8e8..f55d7798 100755
--- a/scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py
+++ b/scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py
@@ -172,7 +172,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):
@@ -183,7 +183,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 57ff06e0..7ac9e312 100755
--- a/scripts/automation/trex_control_plane/console/parsing_opts.py
+++ b/scripts/automation/trex_control_plane/client_utils/parsing_opts.py
@@ -23,6 +23,11 @@ FORCE = 11
DRY_RUN = 12
TOTAL = 13
+GLOBAL_STATS = 14
+PORT_STATS = 15
+PORT_STATUS = 16
+STATS_MASK = 17
+
# list of ArgumentGroup types
MUTEX = 1
@@ -205,13 +210,30 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'],
'default': False,
'help': "Dry run - no traffic will be injected"}),
+ 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..1f9d59e3 100755
--- a/scripts/automation/trex_control_plane/common/trex_stats.py
+++ b/scripts/automation/trex_control_plane/common/trex_stats.py
@@ -1,59 +1,266 @@
#!/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
+import datetime
+import re
+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 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 CTRexStatsGenerator(object):
+ """
+ This object is responsible of generating stats from objects maintained at
+ CTRexStatelessClient and the ports.
+ """
- def __getitem__(self, item):
- stats_obj = getattr(self, item)
- if stats_obj:
- return stats_obj.get_stats()
+ 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:
+ return self._generate_global_stats()
+ elif statistic_type == PORT_STATS:
+ return self._generate_port_stats(port_id_list)
+ pass
+ elif statistic_type == PORT_STATUS:
+ return self._generate_port_status(port_id_list)
else:
- return None
+ # ignore by returning empty object
+ return {}
- class CSingleStatsHandler(object):
+ def _generate_global_stats(self):
+ # stats_obj = self._async_stats.get_general_stats()
+ stats_data = self._global_stats.generate_stats()
- def __init__(self):
- self._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 stats_data.iteritems()],
+ header=False)
+
+ 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)
+
+ 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 = port_obj.generate_port_stats()
+
+ # populate to data structures
+ 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))
+ 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_status = port_obj.generate_port_status()
+
+ # populate to data structures
+ 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))
+ 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_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:
+ print format_text("[WARNING]: ", 'magenta', 'bold'), format_text("displaying up to 4 ports", 'magenta')
+ 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)
- def update(self, obj_id, stats_obj):
- assert isinstance(stats_obj, CTRexStats)
- self._stats[obj_id] = stats_obj
- def get_stats(self, obj_id=None):
- if obj_id:
- return copy.copy(self._stats.pop(obj_id))
- else:
- return copy.copy(self._stats)
class CTRexStats(object):
- def __init__(self, **kwargs):
- for k, v in kwargs.items():
- setattr(self, k, v)
+ """ 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):
+ # 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)
+
+ # can't match to any known pattern, return N/A
+ return "N/A"
+
+ @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 generate_stats(self):
+ # must be implemented by designated classes (such as port/ global stats)
+ raise NotImplementedError()
+
+ def update(self, snapshot):
+ # update
+ self.last_update_ts = datetime.datetime.now()
+
+ self.latest_stats = snapshot
+
+ 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_console.py b/scripts/automation/trex_control_plane/console/trex_console.py
index 9d855f98..e187c8c2 100755
--- a/scripts/automation/trex_control_plane/console/trex_console.py
+++ b/scripts/automation/trex_control_plane/console/trex_console.py
@@ -33,10 +33,12 @@ 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
from functools import wraps
+
__version__ = "1.1"
@@ -362,7 +364,6 @@ class TRexConsole(TRexGeneralCmd):
@verify_connected
def do_reset (self, line):
'''force stop all ports\n'''
-
self.stateless_client.cmd_reset_line(line)
@@ -374,6 +375,22 @@ class TRexConsole(TRexGeneralCmd):
self.stateless_client.cmd_validate_line(line)
+ def do_stats(self, line):
+ '''Fetch statistics from TRex server by port\n'''
+ self.stateless_client.cmd_stats_line(line)
+
+
+ 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")
diff --git a/scripts/automation/trex_control_plane/console/trex_status.py b/scripts/automation/trex_control_plane/console/trex_status.py
index 869812a1..cdf3fb69 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
@@ -494,7 +494,7 @@ class TrexStatus():
self.stats_panel.panel.top()
self.stats_panel.draw()
- panel.update_panels();
+ panel.update_panels()
self.stdscr.refresh()
sleep(0.01)
@@ -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()
diff --git a/scripts/external_libs/texttable-0.8.4/LICENSE b/scripts/external_libs/texttable-0.8.4/LICENSE
new file mode 100644
index 00000000..65c5ca88
--- /dev/null
+++ b/scripts/external_libs/texttable-0.8.4/LICENSE
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/scripts/external_libs/texttable-0.8.4/PKG-INFO b/scripts/external_libs/texttable-0.8.4/PKG-INFO
new file mode 100644
index 00000000..a3079ad9
--- /dev/null
+++ b/scripts/external_libs/texttable-0.8.4/PKG-INFO
@@ -0,0 +1,23 @@
+Metadata-Version: 1.0
+Name: texttable
+Version: 0.8.4
+Summary: module for creating simple ASCII tables
+Home-page: https://github.com/foutaise/texttable/
+Author: Gerome Fournier
+Author-email: jef(at)foutaise.org
+License: LGPL
+Download-URL: https://github.com/foutaise/texttable/archive/v0.8.4.tar.gz
+Description: texttable is a module to generate a formatted text table, using ASCII
+ characters.
+Platform: any
+Classifier: Development Status :: 4 - Beta
+Classifier: Environment :: Console
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: End Users/Desktop
+Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: MacOS
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Text Processing
+Classifier: Topic :: Utilities
diff --git a/scripts/external_libs/texttable-0.8.4/README.md b/scripts/external_libs/texttable-0.8.4/README.md
new file mode 100644
index 00000000..0d9895e8
--- /dev/null
+++ b/scripts/external_libs/texttable-0.8.4/README.md
@@ -0,0 +1,223 @@
+# texttable
+
+Python module for creating simple ASCII tables
+
+## Availability
+
+This module is available on [PypI](https://pypi.python.org/pypi/texttable/0.8.4), and has been packaged for several Linux/Unix platforms
+([Debian](https://packages.debian.org/search?&searchon=names&keywords=python-texttable+),
+[FreeBSD](https://www.freebsd.org/cgi/ports.cgi?query=texttable&stype=all), Fedora, Suse...).
+
+## Documentation
+
+```
+NAME
+ texttable - module for creating simple ASCII tables
+
+FILE
+ /usr/lib/python2.3/site-packages/texttable.py
+
+DESCRIPTION
+
+ Example:
+
+ table = Texttable()
+ table.set_cols_align(["l", "r", "c"])
+ table.set_cols_valign(["t", "m", "b"])
+ table.add_rows([["Name", "Age", "Nickname"],
+ ["Mr\nXavier\nHuon", 32, "Xav'"],
+ ["Mr\nBaptiste\nClement", 1, "Baby"]])
+ print table.draw() + "\n"
+
+ table = Texttable()
+ table.set_deco(Texttable.HEADER)
+ table.set_cols_dtype(['t', # text
+ 'f', # float (decimal)
+ 'e', # float (exponent)
+ 'i', # integer
+ 'a']) # automatic
+ table.set_cols_align(["l", "r", "r", "r", "l"])
+ table.add_rows([["text", "float", "exp", "int", "auto"],
+ ["abcd", "67", 654, 89, 128.001],
+ ["efghijk", 67.5434, .654, 89.6, 12800000000000000000000.00023],
+ ["lmn", 5e-78, 5e-78, 89.4, .000000000000128],
+ ["opqrstu", .023, 5e+78, 92., 12800000000000000000000]])
+ print table.draw()
+
+ Result:
+
+ +----------+-----+----------+
+ | Name | Age | Nickname |
+ +==========+=====+==========+
+ | Mr | | |
+ | Xavier | 32 | |
+ | Huon | | Xav' |
+ +----------+-----+----------+
+ | Mr | | |
+ | Baptiste | 1 | |
+ | Clement | | Baby |
+ +----------+-----+----------+
+
+ text float exp int auto
+ ===========================================
+ abcd 67.000 6.540e+02 89 128.001
+ efgh 67.543 6.540e-01 90 1.280e+22
+ ijkl 0.000 5.000e-78 89 0.000
+ mnop 0.023 5.000e+78 92 1.280e+22
+
+CLASSES
+ class Texttable
+ | Methods defined here:
+ |
+ | __init__(self, max_width=80)
+ | Constructor
+ |
+ | - max_width is an integer, specifying the maximum width of the table
+ | - if set to 0, size is unlimited, therefore cells won't be wrapped
+ |
+ | add_row(self, array)
+ | Add a row in the rows stack
+ |
+ | - cells can contain newlines and tabs
+ |
+ | add_rows(self, rows, header=True)
+ | Add several rows in the rows stack
+ |
+ | - The 'rows' argument can be either an iterator returning arrays,
+ | or a by-dimensional array
+ | - 'header' specifies if the first row should be used as the header
+ | of the table
+ |
+ | draw(self)
+ | Draw the table
+ |
+ | - the table is returned as a whole string
+ |
+ | header(self, array)
+ | Specify the header of the table
+ |
+ | reset(self)
+ | Reset the instance
+ |
+ | - reset rows and header
+ |
+ | set_chars(self, array)
+ | Set the characters used to draw lines between rows and columns
+ |
+ | - the array should contain 4 fields:
+ |
+ | [horizontal, vertical, corner, header]
+ |
+ | - default is set to:
+ |
+ | ['-', '|', '+', '=']
+ |
+ | set_cols_align(self, array)
+ | Set the desired columns alignment
+ |
+ | - the elements of the array should be either "l", "c" or "r":
+ |
+ | * "l": column flushed left
+ | * "c": column centered
+ | * "r": column flushed right
+ |
+ | set_cols_dtype(self, array)
+ | Set the desired columns datatype for the cols.
+ |
+ | - the elements of the array should be either "a", "t", "f", "e" or "i":
+ |
+ | * "a": automatic (try to use the most appropriate datatype)
+ | * "t": treat as text
+ | * "f": treat as float in decimal format
+ | * "e": treat as float in exponential format
+ | * "i": treat as int
+ |
+ | - by default, automatic datatyping is used for each column
+ |
+ | set_cols_valign(self, array)
+ | Set the desired columns vertical alignment
+ |
+ | - the elements of the array should be either "t", "m" or "b":
+ |
+ | * "t": column aligned on the top of the cell
+ | * "m": column aligned on the middle of the cell
+ | * "b": column aligned on the bottom of the cell
+ |
+ | set_cols_width(self, array)
+ | Set the desired columns width
+ |
+ | - the elements of the array should be integers, specifying the
+ | width of each column. For example:
+ |
+ | [10, 20, 5]
+ |
+ | set_deco(self, deco)
+ | Set the table decoration
+ |
+ | - 'deco' can be a combinaison of:
+ |
+ | Texttable.BORDER: Border around the table
+ | Texttable.HEADER: Horizontal line below the header
+ | Texttable.HLINES: Horizontal lines between rows
+ | Texttable.VLINES: Vertical lines between columns
+ |
+ | All of them are enabled by default
+ |
+ | - example:
+ |
+ | Texttable.BORDER | Texttable.HEADER
+ |
+ | set_precision(self, width)
+ | Set the desired precision for float/exponential formats
+ |
+ | - width must be an integer >= 0
+ |
+ | - default value is set to 3
+ |
+ | ----------------------------------------------------------------------
+ | Data and other attributes defined here:
+ |
+ | BORDER = 1
+ |
+ | HEADER = 2
+ |
+ | HLINES = 4
+ |
+ | VLINES = 8
+
+DATA
+ __all__ = ['Texttable', 'ArraySizeError']
+ __author__ = 'Gerome Fournier <jef(at)foutaise.org>'
+ __credits__ = 'Jeff Kowalczyk:\n - textwrap improved import\n ...:\...
+ __license__ = 'LGPL'
+ __version__ = '0.8.4'
+
+VERSION
+ 0.8.4
+
+AUTHOR
+ Gerome Fournier <jef(at)foutaise.org>
+
+CREDITS
+ Jeff Kowalczyk:
+ - textwrap improved import
+ - comment concerning header output
+
+ Anonymous:
+ - add_rows method, for adding rows in one go
+
+ Sergey Simonenko:
+ - redefined len() function to deal with non-ASCII characters
+
+ Roger Lew:
+ - columns datatype specifications
+
+ Brian Peterson:
+ - better handling of unicode errors
+
+ Frank Sachsenheim:
+ - add Python 2/3-compatibility
+
+ Maximilian Hils:
+ - fix minor bug for Python 3 compatibility
+```
diff --git a/scripts/external_libs/texttable-0.8.4/setup.py b/scripts/external_libs/texttable-0.8.4/setup.py
new file mode 100644
index 00000000..eb8c9e3a
--- /dev/null
+++ b/scripts/external_libs/texttable-0.8.4/setup.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+#
+# texttable - module for creating simple ASCII tables
+# Copyright (C) 2003-2015 Gerome Fournier <jef(at)foutaise.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+DESCRIPTION = "module for creating simple ASCII tables"
+
+LONG_DESCRIPTION = """\
+texttable is a module to generate a formatted text table, using ASCII
+characters."""
+
+import sys
+
+from distutils.core import setup
+if sys.version < '2.2.3':
+ from distutils.dist import DistributionMetadata
+ DistributionMetadata.classifiers = None
+ DistributionMetadata.download_url = None
+
+setup(
+ name = "texttable",
+ version = "0.8.4",
+ author = "Gerome Fournier",
+ author_email = "jef(at)foutaise.org",
+ url = "https://github.com/foutaise/texttable/",
+ download_url = "https://github.com/foutaise/texttable/archive/v0.8.4.tar.gz",
+ license = "LGPL",
+ py_modules = ["texttable"],
+ description = DESCRIPTION,
+ long_description = LONG_DESCRIPTION,
+ platforms = "any",
+ classifiers = [
+ 'Development Status :: 4 - Beta',
+ 'Environment :: Console',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: End Users/Desktop',
+ 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
+ 'Operating System :: Microsoft :: Windows',
+ 'Operating System :: POSIX',
+ 'Operating System :: MacOS',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ 'Topic :: Text Processing',
+ 'Topic :: Utilities',
+ ]
+)
diff --git a/scripts/external_libs/texttable-0.8.4/texttable.py b/scripts/external_libs/texttable-0.8.4/texttable.py
new file mode 100644
index 00000000..775a43e5
--- /dev/null
+++ b/scripts/external_libs/texttable-0.8.4/texttable.py
@@ -0,0 +1,607 @@
+#!/usr/bin/env python
+#
+# texttable - module for creating simple ASCII tables
+# Copyright (C) 2003-2015 Gerome Fournier <jef(at)foutaise.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+"""module for creating simple ASCII tables
+
+
+Example:
+
+ table = Texttable()
+ table.set_cols_align(["l", "r", "c"])
+ table.set_cols_valign(["t", "m", "b"])
+ table.add_rows([["Name", "Age", "Nickname"],
+ ["Mr\\nXavier\\nHuon", 32, "Xav'"],
+ ["Mr\\nBaptiste\\nClement", 1, "Baby"]])
+ print table.draw() + "\\n"
+
+ table = Texttable()
+ table.set_deco(Texttable.HEADER)
+ table.set_cols_dtype(['t', # text
+ 'f', # float (decimal)
+ 'e', # float (exponent)
+ 'i', # integer
+ 'a']) # automatic
+ table.set_cols_align(["l", "r", "r", "r", "l"])
+ table.add_rows([["text", "float", "exp", "int", "auto"],
+ ["abcd", "67", 654, 89, 128.001],
+ ["efghijk", 67.5434, .654, 89.6, 12800000000000000000000.00023],
+ ["lmn", 5e-78, 5e-78, 89.4, .000000000000128],
+ ["opqrstu", .023, 5e+78, 92., 12800000000000000000000]])
+ print table.draw()
+
+Result:
+
+ +----------+-----+----------+
+ | Name | Age | Nickname |
+ +==========+=====+==========+
+ | Mr | | |
+ | Xavier | 32 | |
+ | Huon | | Xav' |
+ +----------+-----+----------+
+ | Mr | | |
+ | Baptiste | 1 | |
+ | Clement | | Baby |
+ +----------+-----+----------+
+
+ text float exp int auto
+ ===========================================
+ abcd 67.000 6.540e+02 89 128.001
+ efgh 67.543 6.540e-01 90 1.280e+22
+ ijkl 0.000 5.000e-78 89 0.000
+ mnop 0.023 5.000e+78 92 1.280e+22
+"""
+
+__all__ = ["Texttable", "ArraySizeError"]
+
+__author__ = 'Gerome Fournier <jef(at)foutaise.org>'
+__license__ = 'LGPL'
+__version__ = '0.8.4'
+__credits__ = """\
+Jeff Kowalczyk:
+ - textwrap improved import
+ - comment concerning header output
+
+Anonymous:
+ - add_rows method, for adding rows in one go
+
+Sergey Simonenko:
+ - redefined len() function to deal with non-ASCII characters
+
+Roger Lew:
+ - columns datatype specifications
+
+Brian Peterson:
+ - better handling of unicode errors
+
+Frank Sachsenheim:
+ - add Python 2/3-compatibility
+
+Maximilian Hils:
+ - fix minor bug for Python 3 compatibility
+"""
+
+import sys
+import string
+
+try:
+ if sys.version >= '2.3':
+ import textwrap
+ elif sys.version >= '2.2':
+ from optparse import textwrap
+ else:
+ from optik import textwrap
+except ImportError:
+ sys.stderr.write("Can't import textwrap module!\n")
+ raise
+
+if sys.version >= '2.7':
+ from functools import reduce
+
+def len(iterable):
+ """Redefining len here so it will be able to work with non-ASCII characters
+ """
+ if not isinstance(iterable, str):
+ return iterable.__len__()
+
+ try:
+ if sys.version >= '3.0':
+ return len(str)
+ else:
+ return len(unicode(iterable, 'utf'))
+ except:
+ return iterable.__len__()
+
+
+class ArraySizeError(Exception):
+ """Exception raised when specified rows don't fit the required size
+ """
+
+ def __init__(self, msg):
+ self.msg = msg
+ Exception.__init__(self, msg, '')
+
+ def __str__(self):
+ return self.msg
+
+
+class Texttable:
+
+ BORDER = 1
+ HEADER = 1 << 1
+ HLINES = 1 << 2
+ VLINES = 1 << 3
+
+ def __init__(self, max_width=80):
+ """Constructor
+
+ - max_width is an integer, specifying the maximum width of the table
+ - if set to 0, size is unlimited, therefore cells won't be wrapped
+ """
+
+ if max_width <= 0:
+ max_width = False
+ self._max_width = max_width
+ self._precision = 3
+
+ self._deco = Texttable.VLINES | Texttable.HLINES | Texttable.BORDER | \
+ Texttable.HEADER
+ self.set_chars(['-', '|', '+', '='])
+ self.reset()
+
+ def reset(self):
+ """Reset the instance
+
+ - reset rows and header
+ """
+
+ self._hline_string = None
+ self._row_size = None
+ self._header = []
+ self._rows = []
+
+ def set_chars(self, array):
+ """Set the characters used to draw lines between rows and columns
+
+ - the array should contain 4 fields:
+
+ [horizontal, vertical, corner, header]
+
+ - default is set to:
+
+ ['-', '|', '+', '=']
+ """
+
+ if len(array) != 4:
+ raise ArraySizeError("array should contain 4 characters")
+ array = [ x[:1] for x in [ str(s) for s in array ] ]
+ (self._char_horiz, self._char_vert,
+ self._char_corner, self._char_header) = array
+
+ def set_deco(self, deco):
+ """Set the table decoration
+
+ - 'deco' can be a combinaison of:
+
+ Texttable.BORDER: Border around the table
+ Texttable.HEADER: Horizontal line below the header
+ Texttable.HLINES: Horizontal lines between rows
+ Texttable.VLINES: Vertical lines between columns
+
+ All of them are enabled by default
+
+ - example:
+
+ Texttable.BORDER | Texttable.HEADER
+ """
+
+ self._deco = deco
+
+ def set_cols_align(self, array):
+ """Set the desired columns alignment
+
+ - the elements of the array should be either "l", "c" or "r":
+
+ * "l": column flushed left
+ * "c": column centered
+ * "r": column flushed right
+ """
+
+ self._check_row_size(array)
+ self._align = array
+
+ def set_cols_valign(self, array):
+ """Set the desired columns vertical alignment
+
+ - the elements of the array should be either "t", "m" or "b":
+
+ * "t": column aligned on the top of the cell
+ * "m": column aligned on the middle of the cell
+ * "b": column aligned on the bottom of the cell
+ """
+
+ self._check_row_size(array)
+ self._valign = array
+
+ def set_cols_dtype(self, array):
+ """Set the desired columns datatype for the cols.
+
+ - the elements of the array should be either "a", "t", "f", "e" or "i":
+
+ * "a": automatic (try to use the most appropriate datatype)
+ * "t": treat as text
+ * "f": treat as float in decimal format
+ * "e": treat as float in exponential format
+ * "i": treat as int
+
+ - by default, automatic datatyping is used for each column
+ """
+
+ self._check_row_size(array)
+ self._dtype = array
+
+ def set_cols_width(self, array):
+ """Set the desired columns width
+
+ - the elements of the array should be integers, specifying the
+ width of each column. For example:
+
+ [10, 20, 5]
+ """
+
+ self._check_row_size(array)
+ try:
+ array = list(map(int, array))
+ if reduce(min, array) <= 0:
+ raise ValueError
+ except ValueError:
+ sys.stderr.write("Wrong argument in column width specification\n")
+ raise
+ self._width = array
+
+ def set_precision(self, width):
+ """Set the desired precision for float/exponential formats
+
+ - width must be an integer >= 0
+
+ - default value is set to 3
+ """
+
+ if not type(width) is int or width < 0:
+ raise ValueError('width must be an integer greater then 0')
+ self._precision = width
+
+ def header(self, array):
+ """Specify the header of the table
+ """
+
+ self._check_row_size(array)
+ self._header = list(map(str, array))
+
+ def add_row(self, array):
+ """Add a row in the rows stack
+
+ - cells can contain newlines and tabs
+ """
+
+ self._check_row_size(array)
+
+ if not hasattr(self, "_dtype"):
+ self._dtype = ["a"] * self._row_size
+
+ cells = []
+ for i, x in enumerate(array):
+ cells.append(self._str(i, x))
+ self._rows.append(cells)
+
+ def add_rows(self, rows, header=True):
+ """Add several rows in the rows stack
+
+ - The 'rows' argument can be either an iterator returning arrays,
+ or a by-dimensional array
+ - 'header' specifies if the first row should be used as the header
+ of the table
+ """
+
+ # nb: don't use 'iter' on by-dimensional arrays, to get a
+ # usable code for python 2.1
+ if header:
+ if hasattr(rows, '__iter__') and hasattr(rows, 'next'):
+ self.header(rows.next())
+ else:
+ self.header(rows[0])
+ rows = rows[1:]
+ for row in rows:
+ self.add_row(row)
+
+ def draw(self):
+ """Draw the table
+
+ - the table is returned as a whole string
+ """
+
+ if not self._header and not self._rows:
+ return
+ self._compute_cols_width()
+ self._check_align()
+ out = ""
+ if self._has_border():
+ out += self._hline()
+ if self._header:
+ out += self._draw_line(self._header, isheader=True)
+ if self._has_header():
+ out += self._hline_header()
+ length = 0
+ for row in self._rows:
+ length += 1
+ out += self._draw_line(row)
+ if self._has_hlines() and length < len(self._rows):
+ out += self._hline()
+ if self._has_border():
+ out += self._hline()
+ return out[:-1]
+
+ def _str(self, i, x):
+ """Handles string formatting of cell data
+
+ i - index of the cell datatype in self._dtype
+ x - cell data to format
+ """
+ try:
+ f = float(x)
+ except:
+ return str(x)
+
+ n = self._precision
+ dtype = self._dtype[i]
+
+ if dtype == 'i':
+ return str(int(round(f)))
+ elif dtype == 'f':
+ return '%.*f' % (n, f)
+ elif dtype == 'e':
+ return '%.*e' % (n, f)
+ elif dtype == 't':
+ return str(x)
+ else:
+ if f - round(f) == 0:
+ if abs(f) > 1e8:
+ return '%.*e' % (n, f)
+ else:
+ return str(int(round(f)))
+ else:
+ if abs(f) > 1e8:
+ return '%.*e' % (n, f)
+ else:
+ return '%.*f' % (n, f)
+
+ def _check_row_size(self, array):
+ """Check that the specified array fits the previous rows size
+ """
+
+ if not self._row_size:
+ self._row_size = len(array)
+ elif self._row_size != len(array):
+ raise ArraySizeError("array should contain %d elements" \
+ % self._row_size)
+
+ def _has_vlines(self):
+ """Return a boolean, if vlines are required or not
+ """
+
+ return self._deco & Texttable.VLINES > 0
+
+ def _has_hlines(self):
+ """Return a boolean, if hlines are required or not
+ """
+
+ return self._deco & Texttable.HLINES > 0
+
+ def _has_border(self):
+ """Return a boolean, if border is required or not
+ """
+
+ return self._deco & Texttable.BORDER > 0
+
+ def _has_header(self):
+ """Return a boolean, if header line is required or not
+ """
+
+ return self._deco & Texttable.HEADER > 0
+
+ def _hline_header(self):
+ """Print header's horizontal line
+ """
+
+ return self._build_hline(True)
+
+ def _hline(self):
+ """Print an horizontal line
+ """
+
+ if not self._hline_string:
+ self._hline_string = self._build_hline()
+ return self._hline_string
+
+ def _build_hline(self, is_header=False):
+ """Return a string used to separated rows or separate header from
+ rows
+ """
+ horiz = self._char_horiz
+ if (is_header):
+ horiz = self._char_header
+ # compute cell separator
+ s = "%s%s%s" % (horiz, [horiz, self._char_corner][self._has_vlines()],
+ horiz)
+ # build the line
+ l = s.join([horiz * n for n in self._width])
+ # add border if needed
+ if self._has_border():
+ l = "%s%s%s%s%s\n" % (self._char_corner, horiz, l, horiz,
+ self._char_corner)
+ else:
+ l += "\n"
+ return l
+
+ def _len_cell(self, cell):
+ """Return the width of the cell
+
+ Special characters are taken into account to return the width of the
+ cell, such like newlines and tabs
+ """
+
+ cell_lines = cell.split('\n')
+ maxi = 0
+ for line in cell_lines:
+ length = 0
+ parts = line.split('\t')
+ for part, i in zip(parts, list(range(1, len(parts) + 1))):
+ length = length + len(part)
+ if i < len(parts):
+ length = (length//8 + 1) * 8
+ maxi = max(maxi, length)
+ return maxi
+
+ def _compute_cols_width(self):
+ """Return an array with the width of each column
+
+ If a specific width has been specified, exit. If the total of the
+ columns width exceed the table desired width, another width will be
+ computed to fit, and cells will be wrapped.
+ """
+
+ if hasattr(self, "_width"):
+ return
+ maxi = []
+ if self._header:
+ maxi = [ self._len_cell(x) for x in self._header ]
+ for row in self._rows:
+ for cell,i in zip(row, list(range(len(row)))):
+ try:
+ maxi[i] = max(maxi[i], self._len_cell(cell))
+ except (TypeError, IndexError):
+ maxi.append(self._len_cell(cell))
+ items = len(maxi)
+ length = reduce(lambda x, y: x+y, maxi)
+ if self._max_width and length + items * 3 + 1 > self._max_width:
+ maxi = [(self._max_width - items * 3 -1) // items \
+ for n in range(items)]
+ self._width = maxi
+
+ def _check_align(self):
+ """Check if alignment has been specified, set default one if not
+ """
+
+ if not hasattr(self, "_align"):
+ self._align = ["l"] * self._row_size
+ if not hasattr(self, "_valign"):
+ self._valign = ["t"] * self._row_size
+
+ def _draw_line(self, line, isheader=False):
+ """Draw a line
+
+ Loop over a single cell length, over all the cells
+ """
+
+ line = self._splitit(line, isheader)
+ space = " "
+ out = ""
+ for i in range(len(line[0])):
+ if self._has_border():
+ out += "%s " % self._char_vert
+ length = 0
+ for cell, width, align in zip(line, self._width, self._align):
+ length += 1
+ cell_line = cell[i]
+ fill = width - len(cell_line)
+ if isheader:
+ align = "c"
+ if align == "r":
+ out += "%s " % (fill * space + cell_line)
+ elif align == "c":
+ out += "%s " % (int(fill/2) * space + cell_line \
+ + int(fill/2 + fill%2) * space)
+ else:
+ out += "%s " % (cell_line + fill * space)
+ if length < len(line):
+ out += "%s " % [space, self._char_vert][self._has_vlines()]
+ out += "%s\n" % ['', self._char_vert][self._has_border()]
+ return out
+
+ def _splitit(self, line, isheader):
+ """Split each element of line to fit the column width
+
+ Each element is turned into a list, result of the wrapping of the
+ string to the desired width
+ """
+
+ line_wrapped = []
+ for cell, width in zip(line, self._width):
+ array = []
+ for c in cell.split('\n'):
+ try:
+ if sys.version >= '3.0':
+ c = str(c)
+ else:
+ c = unicode(c, 'utf')
+ except UnicodeDecodeError as strerror:
+ sys.stderr.write("UnicodeDecodeError exception for string '%s': %s\n" % (c, strerror))
+ if sys.version >= '3.0':
+ c = str(c, 'utf', 'replace')
+ else:
+ c = unicode(c, 'utf', 'replace')
+ array.extend(textwrap.wrap(c, width))
+ line_wrapped.append(array)
+ max_cell_lines = reduce(max, list(map(len, line_wrapped)))
+ for cell, valign in zip(line_wrapped, self._valign):
+ if isheader:
+ valign = "t"
+ if valign == "m":
+ missing = max_cell_lines - len(cell)
+ cell[:0] = [""] * int(missing / 2)
+ cell.extend([""] * int(missing / 2 + missing % 2))
+ elif valign == "b":
+ cell[:0] = [""] * (max_cell_lines - len(cell))
+ else:
+ cell.extend([""] * (max_cell_lines - len(cell)))
+ return line_wrapped
+
+
+if __name__ == '__main__':
+ table = Texttable()
+ table.set_cols_align(["l", "r", "c"])
+ table.set_cols_valign(["t", "m", "b"])
+ table.add_rows([["Name", "Age", "Nickname"],
+ ["Mr\nXavier\nHuon", 32, "Xav'"],
+ ["Mr\nBaptiste\nClement", 1, "Baby"]])
+ print(table.draw() + "\n")
+
+ table = Texttable()
+ table.set_deco(Texttable.HEADER)
+ table.set_cols_dtype(['t', # text
+ 'f', # float (decimal)
+ 'e', # float (exponent)
+ 'i', # integer
+ 'a']) # automatic
+ table.set_cols_align(["l", "r", "r", "r", "l"])
+ table.add_rows([["text", "float", "exp", "int", "auto"],
+ ["abcd", "67", 654, 89, 128.001],
+ ["efghijk", 67.5434, .654, 89.6, 12800000000000000000000.00023],
+ ["lmn", 5e-78, 5e-78, 89.4, .000000000000128],
+ ["opqrstu", .023, 5e+78, 92., 12800000000000000000000]])
+ print(table.draw())