summaryrefslogtreecommitdiffstats
path: root/scripts/automation/trex_control_plane
diff options
context:
space:
mode:
authorimarom <imarom@cisco.com>2016-08-15 15:33:51 +0300
committerimarom <imarom@cisco.com>2016-08-15 16:33:41 +0300
commit25aa665b7e5a5e8747735aaaa5a00dba11b21067 (patch)
tree75f6fc67584dbcba8a72b44ed5384f24e0eef698 /scripts/automation/trex_control_plane
parentba7b5dff853a3b11b0cc2e7b29cfc1cd99e606f7 (diff)
TUI screen buffer
Diffstat (limited to 'scripts/automation/trex_control_plane')
-rw-r--r--scripts/automation/trex_control_plane/stl/console/trex_tui.py179
-rwxr-xr-xscripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py61
-rw-r--r--scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py4
-rw-r--r--scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py4
-rwxr-xr-xscripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py42
-rw-r--r--scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_tables.py9
6 files changed, 218 insertions, 81 deletions
diff --git a/scripts/automation/trex_control_plane/stl/console/trex_tui.py b/scripts/automation/trex_control_plane/stl/console/trex_tui.py
index 0ac2f6a2..95202acd 100644
--- a/scripts/automation/trex_control_plane/stl/console/trex_tui.py
+++ b/scripts/automation/trex_control_plane/stl/console/trex_tui.py
@@ -1,11 +1,18 @@
+from __future__ import print_function
+
import termios
import sys
import os
import time
+import threading
+
from collections import OrderedDict, deque
+from texttable import ansi_len
+
+
import datetime
import readline
-from texttable import ansi_len
+
if sys.version_info > (3,0):
from io import StringIO
@@ -41,11 +48,11 @@ class SimpleBar(object):
self.pattern_len = len(pattern)
self.index = 0
- def show (self):
+ def show (self, buffer):
if self.desc:
- print(format_text("{0} {1}".format(self.desc, self.pattern[self.index]), 'bold'))
+ print(format_text("{0} {1}".format(self.desc, self.pattern[self.index]), 'bold'), file = buffer)
else:
- print(format_text("{0}".format(self.pattern[self.index]), 'bold'))
+ print(format_text("{0}".format(self.pattern[self.index]), 'bold'), file = buffer)
self.index = (self.index + 1) % self.pattern_len
@@ -59,7 +66,7 @@ class TrexTUIPanel(object):
self.stateless_client = mng.stateless_client
self.is_graph = False
- def show (self):
+ def show (self, buffer):
raise NotImplementedError("must implement this")
def get_key_actions (self):
@@ -108,11 +115,11 @@ class TrexTUIDashBoard(TrexTUIPanel):
return self.toggle_filter.filter_items()
- def show (self):
+ def show (self, buffer):
stats = self.stateless_client._get_formatted_stats(self.get_showed_ports())
# print stats to screen
for stat_type, stat_data in stats.items():
- text_tables.print_table_with_header(stat_data.text_table, stat_type)
+ text_tables.print_table_with_header(stat_data.text_table, stat_type, buffer = buffer)
def get_key_actions (self):
@@ -203,11 +210,11 @@ class TrexTUIStreamsStats(TrexTUIPanel):
self.key_actions['c'] = {'action': self.action_clear, 'legend': 'clear', 'show': True}
- def show (self):
+ def show (self, buffer):
stats = self.stateless_client._get_formatted_stats(port_id_list = None, stats_mask = trex_stl_stats.SS_COMPAT)
# print stats to screen
for stat_type, stat_data in stats.items():
- text_tables.print_table_with_header(stat_data.text_table, stat_type)
+ text_tables.print_table_with_header(stat_data.text_table, stat_type, buffer = buffer)
pass
@@ -230,7 +237,7 @@ class TrexTUILatencyStats(TrexTUIPanel):
self.is_histogram = False
- def show (self):
+ def show (self, buffer):
if self.is_histogram:
stats = self.stateless_client._get_formatted_stats(port_id_list = None, stats_mask = trex_stl_stats.LH_COMPAT)
else:
@@ -241,7 +248,7 @@ class TrexTUILatencyStats(TrexTUIPanel):
untouched_header = ' (usec)'
else:
untouched_header = ''
- text_tables.print_table_with_header(stat_data.text_table, stat_type, untouched_header = untouched_header)
+ text_tables.print_table_with_header(stat_data.text_table, stat_type, untouched_header = untouched_header, buffer = buffer)
def get_key_actions (self):
return self.key_actions
@@ -261,11 +268,11 @@ class TrexTUIUtilizationStats(TrexTUIPanel):
super(TrexTUIUtilizationStats, self).__init__(mng, "ustats")
self.key_actions = {}
- def show (self):
+ def show (self, buffer):
stats = self.stateless_client._get_formatted_stats(port_id_list = None, stats_mask = trex_stl_stats.UT_COMPAT)
# print stats to screen
for stat_type, stat_data in stats.items():
- text_tables.print_table_with_header(stat_data.text_table, stat_type)
+ text_tables.print_table_with_header(stat_data.text_table, stat_type, buffer = buffer)
def get_key_actions (self):
return self.key_actions
@@ -279,16 +286,16 @@ class TrexTUILog():
def add_event (self, msg):
self.log.append("[{0}] {1}".format(str(datetime.datetime.now().time()), msg))
- def show (self, max_lines = 4):
+ def show (self, buffer, max_lines = 4):
cut = len(self.log) - max_lines
if cut < 0:
cut = 0
- print(format_text("\nLog:", 'bold', 'underline'))
+ print(format_text("\nLog:", 'bold', 'underline'), file = buffer)
for msg in self.log[cut:]:
- print(msg)
+ print(msg, file = buffer)
# a predicate to wrap function as a bool
@@ -366,14 +373,14 @@ class TrexTUIPanelManager():
self.legend += "{:}".format(x)
- def print_connection_status (self):
+ def print_connection_status (self, buffer):
if self.tui.get_state() == self.tui.STATE_ACTIVE:
- self.conn_bar.show()
+ self.conn_bar.show(buffer = buffer)
else:
- self.dis_bar.show()
+ self.dis_bar.show(buffer = buffer)
- def print_legend (self):
- print(format_text(self.legend, 'bold'))
+ def print_legend (self, buffer):
+ print(format_text(self.legend, 'bold'), file = buffer)
# on window switch or turn on / off of the TUI we call this
@@ -382,16 +389,16 @@ class TrexTUIPanelManager():
self.locked = locked
self.generate_legend()
- def show (self, show_legend):
- self.main_panel.show()
- self.print_connection_status()
+ def show (self, show_legend, buffer):
+ self.main_panel.show(buffer)
+ self.print_connection_status(buffer)
if show_legend:
self.generate_legend()
- self.print_legend()
+ self.print_legend(buffer)
if self.show_log:
- self.log.show()
+ self.log.show(buffer)
def handle_key (self, ch):
@@ -452,6 +459,66 @@ class TrexTUIPanelManager():
self.init(self.show_log)
return ""
+
+
+# ScreenBuffer is a class designed to
+# avoid inline delays when reprinting the screen
+class ScreenBuffer():
+ def __init__ (self, redraw_cb):
+ self.snapshot = ''
+ self.lock = threading.Lock()
+
+ self.redraw_cb = redraw_cb
+ self.update_flag = False
+
+
+ def start (self):
+ self.active = True
+ self.t = threading.Thread(target = self.__handler)
+ self.t.setDaemon(True)
+ self.t.start()
+
+ def stop (self):
+ self.active = False
+ self.t.join()
+
+
+ # request an update
+ def update (self):
+ self.update_flag = True
+
+ # fetch the screen, return None if no new screen exists yet
+ def get (self):
+
+ if not self.snapshot:
+ return None
+
+ # we have a snapshot - fetch it
+ with self.lock:
+ x = self.snapshot
+ self.snapshot = None
+ return x
+
+
+ def __handler (self):
+
+ while self.active:
+ if self.update_flag:
+ self.__redraw()
+
+ time.sleep(0.01)
+
+ # redraw the next screen
+ def __redraw (self):
+ buffer = StringIO()
+ self.redraw_cb(buffer)
+
+ with self.lock:
+ self.snapshot = buffer.getvalue()
+ self.update_flag = False
+
+
+
# shows a textual top style window
class TrexTUI():
@@ -475,7 +542,10 @@ class TrexTUI():
self.stateless_client = stateless_client
self.pm = TrexTUIPanelManager(self)
-
+ self.sb = ScreenBuffer(self.redraw_handler)
+
+ def redraw_handler (self, buffer):
+ self.pm.show(show_legend = self.async_keys.is_legend_mode(), buffer = buffer)
def clear_screen (self, lines = 50):
# reposition the cursor
@@ -513,7 +583,10 @@ class TrexTUI():
self.state = self.STATE_ACTIVE
self.last_redraw_ts = 0
+
try:
+ self.sb.start()
+
while True:
# draw and handle user input
status = self.async_keys.tick(self.pm)
@@ -522,8 +595,6 @@ class TrexTUI():
# speedup for keys, slower for no keys
if status == AsyncKeys.STATUS_NONE:
- time.sleep(0.01)
- else:
time.sleep(0.001)
# regular state
@@ -556,40 +627,42 @@ class TrexTUI():
except TUIQuit:
print("\nExiting TUI...")
+ finally:
+ self.sb.stop()
+
print("")
# draw once
def draw_screen (self, status):
+
t = time.time() - self.last_redraw_ts
redraw = (t >= 0.5) or (status == AsyncKeys.STATUS_REDRAW_ALL)
-
if redraw:
- # capture stdout to a string
- old_stdout = sys.stdout
- sys.stdout = mystdout = StringIO()
- self.pm.show(show_legend = self.async_keys.is_legend_mode())
- self.last_snap = mystdout.getvalue()
+ self.sb.update()
+ self.last_redraw_ts = time.time()
+
- self.async_keys.draw()
- sys.stdout = old_stdout
+ x = self.sb.get()
+ # we have a new screen to draw
+ if x:
self.clear_screen()
- sys.stdout.write(mystdout.getvalue())
-
+ sys.stdout.write(x)
+ self.async_keys.draw(sys.stdout)
sys.stdout.flush()
- self.last_redraw_ts = time.time()
+ # we only need to redraw the keys
elif status == AsyncKeys.STATUS_REDRAW_KEYS:
sys.stdout.write("\x1b[4A")
- self.async_keys.draw()
+ self.async_keys.draw(sys.stdout)
sys.stdout.flush()
return
-
+
def get_state (self):
return self.state
@@ -680,8 +753,8 @@ class AsyncKeys:
return self.engine.tick(seq, pm)
- def draw (self):
- self.engine.draw()
+ def draw (self, buffer):
+ self.engine.draw(buffer)
@@ -705,7 +778,7 @@ class AsyncKeysEngineLegend:
rc = pm.handle_key(seq)
return AsyncKeys.STATUS_REDRAW_ALL if rc else AsyncKeys.STATUS_NONE
- def draw (self):
+ def draw (self, buffer):
pass
@@ -979,11 +1052,11 @@ class AsyncKeysEngineConsole:
self.last_status = format_text(self.last_status[:TrexTUI.MIN_COLS] + "...", color, 'bold')
- def draw (self):
- sys.stdout.write("\nPress 'ESC' for navigation panel...\n")
- sys.stdout.write("status: \x1b[0K{0}\n".format(self.last_status))
- sys.stdout.write("\n{0}\x1b[0K".format(self.generate_prompt(prefix = 'tui')))
- self.lines[self.line_index].draw()
+ def draw (self, buffer):
+ buffer.write("\nPress 'ESC' for navigation panel...\n")
+ buffer.write("status: \x1b[0K{0}\n".format(self.last_status))
+ buffer.write("\n{0}\x1b[0K".format(self.generate_prompt(prefix = 'tui')))
+ self.lines[self.line_index].draw(buffer)
# a readline alike command line - can be modified during edit
@@ -1058,7 +1131,7 @@ class CmdLine(object):
def go_right (self):
self.cursor_index = min(len(self.get()), self.cursor_index + 1)
- def draw (self):
- sys.stdout.write(self.get())
- sys.stdout.write('\b' * (len(self.get()) - self.cursor_index))
+ def draw (self, buffer):
+ buffer.write(self.get())
+ buffer.write('\b' * (len(self.get()) - self.cursor_index))
diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py
index f0201d6c..3bc507e5 100755
--- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py
+++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py
@@ -690,20 +690,12 @@ class STLClient(object):
rc = RC()
- ports_mask = {}
- for port_id in port_id_list:
- # a pin mode was requested and we have
- # the second port from the group in the start list
- if (core_mask == self.CORE_MASK_PIN) and ( (port_id ^ 0x1) in port_id_list ):
- ports_mask[port_id] = 0x55555555 if( port_id % 2) == 0 else 0xAAAAAAAA
- else:
- ports_mask[port_id] = None
for port_id in port_id_list:
rc.add(self.ports[port_id].start(multiplier,
duration,
force,
- ports_mask[port_id]))
+ core_mask[port_id]))
return rc
@@ -929,6 +921,37 @@ class STLClient(object):
return stats
+ def __decode_core_mask (self, ports, core_mask):
+
+ validate_type('core_mask', core_mask, (int, list))
+
+ # predefined modes
+ if isinstance(core_mask, int):
+ if core_mask not in [self.CORE_MASK_PIN, self.CORE_MASK_SPLIT]:
+ raise STLError("'core_mask' can be either CORE_MASK_PIN, CORE_MASK_SPLIT or a list of masks")
+
+ mask = {}
+ for port in ports:
+ # a pin mode was requested and we have
+ # the second port from the group in the start list
+ if (core_mask == self.CORE_MASK_PIN) and ( (port ^ 0x1) in ports ):
+ mask[port] = 0x55555555 if( port % 2) == 0 else 0xAAAAAAAA
+ else:
+ mask[port] = None
+
+ return mask
+
+ # list of masks
+ elif isinstance(core_mask, list):
+ if not ports:
+ raise STLError("'ports' must be specified explicitly when providing 'core_mask'")
+ if len(ports) != len(core_mask):
+ raise STLError("'core_mask' list must be the same length as 'ports' list")
+
+ return core_mask
+
+
+
############ functions used by other classes but not users ##############
def _validate_port_list (self, port_id_list):
@@ -1950,7 +1973,7 @@ class STLClient(object):
True: Divide bandwidth among the ports
False: Duplicate
- core_mask: CORE_MASK_SPLIT, CORE_MASK_PIN
+ core_mask: CORE_MASK_SPLIT, CORE_MASK_PIN or a list of masks (one per port)
Determine the allocation of cores per port
In CORE_MASK_SPLIT all the traffic will be divided equally between all the cores
associated with each port
@@ -1962,6 +1985,10 @@ class STLClient(object):
"""
+ #########################
+ # decode core mask argument
+ core_mask = self.__decode_core_mask(ports, core_mask)
+ #######################
ports = ports if ports is not None else self.get_acquired_ports()
ports = self._validate_port_list(ports)
@@ -2677,12 +2704,21 @@ class STLClient(object):
parsing_opts.TUNABLES,
parsing_opts.MULTIPLIER_STRICT,
parsing_opts.DRY_RUN,
- parsing_opts.PIN_CORES)
+ parsing_opts.CORE_MASK_GROUP)
opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True)
if not opts:
return opts
+ # core mask
+ if opts.core_mask is not None:
+ core_mask = opts.core_mask
+ else:
+ core_mask = self.CORE_MASK_PIN if opts.pin_cores else self.CORE_MASK_SPLIT
+
+ # just for sanity - will be checked on the API as well
+ self.__decode_core_mask(opts.ports, core_mask)
+
active_ports = list_intersect(self.get_active_ports(), opts.ports)
if active_ports:
if not opts.force:
@@ -2738,12 +2774,13 @@ class STLClient(object):
if opts.dry:
self.validate(opts.ports, opts.mult, opts.duration, opts.total)
else:
+
self.start(opts.ports,
opts.mult,
opts.force,
opts.duration,
opts.total,
- core_mask = self.CORE_MASK_PIN if opts.pin_cores else self.CORE_MASK_SPLIT)
+ core_mask)
return RC_OK()
diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py
index 556a14d8..2074080a 100644
--- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py
+++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py
@@ -36,6 +36,8 @@ class Port(object):
STATE_PAUSE = 4
STATE_PCAP_TX = 5
+ MASK_ALL = ((1 << 64) - 1)
+
PortState = namedtuple('PortState', ['state_id', 'state_name'])
STATES_MAP = {STATE_DOWN: "DOWN",
STATE_IDLE: "IDLE",
@@ -406,7 +408,7 @@ class Port(object):
"mul": mul,
"duration": duration,
"force": force,
- "core_mask": mask if mask is not None else ((1 << 64) - 1)}
+ "core_mask": mask if mask is not None else self.MASK_ALL}
# must set this before to avoid race with the async response
last_state = self.state
diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py
index b321c00b..5e4bdfda 100644
--- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py
+++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py
@@ -442,10 +442,6 @@ class CTRexInfoGenerator(object):
for i in range(min(14, len(cpu_stats))):
history = cpu_stats[i]["history"]
ports = cpu_stats[i]["ports"]
- if not len(ports) == 2:
- sys.__stdout__.write(str(util_stats["cpu"]))
- exit(-1)
-
avg = int(round(sum(history[:avg_len]) / avg_len))
# decode active ports for core
diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py
index 51265252..9ed6c0f8 100755
--- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py
+++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py
@@ -38,6 +38,7 @@ TUNABLES = 22
REMOTE_FILE = 23
LOCKED = 24
PIN_CORES = 25
+CORE_MASK = 26
GLOBAL_STATS = 50
PORT_STATS = 51
@@ -48,6 +49,8 @@ CPU_STATS = 55
MBUF_STATS = 56
STREAMS_MASK = 60
+CORE_MASK_GROUP = 61
+
# ALL_STREAMS = 61
# STREAM_LIST_WITH_ALL = 62
@@ -193,6 +196,14 @@ def match_multiplier_strict(val):
return val
+def hex_int (val):
+ pattern = r"0x[1-9a-fA-F][0-9a-fA-F]*"
+
+ if not re.match(pattern, val):
+ raise argparse.ArgumentTypeError("{0} is not a valid positive HEX formatted number".format(val))
+
+ return int(val, 16)
+
def is_valid_file(filename):
if not os.path.isfile(filename):
@@ -274,7 +285,7 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'],
{"nargs": '+',
'dest':'ports',
'metavar': 'PORTS',
- 'type': int,
+ 'type': int,
'help': "A list of ports on which to apply the command",
'default': []}),
@@ -324,12 +335,6 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'],
'default': False,
'help': "Dry run - no traffic will be injected"}),
- PIN_CORES: ArgumentPack(['--pin'],
- {'action': 'store_true',
- 'dest': 'pin_cores',
- 'default': False,
- 'help': "Pin cores to interfaces - cores will be divided between interfaces (performance boot for symetric profiles)"}),
-
XTERM: ArgumentPack(['-x', '--xterm'],
{'action': 'store_true',
'dest': 'xterm',
@@ -379,6 +384,21 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'],
'default': []}),
+ PIN_CORES: ArgumentPack(['--pin'],
+ {'action': 'store_true',
+ 'dest': 'pin_cores',
+ 'default': False,
+ 'help': "Pin cores to interfaces - cores will be divided between interfaces (performance boot for symetric profiles)"}),
+
+ CORE_MASK: ArgumentPack(['--core_mask'],
+ {'action': 'store',
+ 'nargs': '+',
+ 'type': hex_int,
+ 'dest': 'core_mask',
+ 'default': None,
+ 'help': "Core mask - only cores responding to the bit mask will be active"}),
+
+
# promiscuous
PROMISCUOUS_SWITCH: ArgumentGroup(MUTEX, [PROMISCUOUS,
NO_PROMISCUOUS],
@@ -398,7 +418,13 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'],
STREAMS_STATS,
CPU_STATS,
MBUF_STATS],
- {})
+ {}),
+
+
+ CORE_MASK_GROUP: ArgumentGroup(MUTEX, [PIN_CORES,
+ CORE_MASK],
+ {'required': False}),
+
}
diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_tables.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_tables.py
index 4b7e9b3e..393ba111 100644
--- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_tables.py
+++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_tables.py
@@ -1,7 +1,10 @@
+from __future__ import print_function
+
import sys
from texttable import Texttable
from .text_opts import format_text
+
class TRexTextTable(Texttable):
def __init__(self):
@@ -21,11 +24,11 @@ class TRexTextInfo(Texttable):
def generate_trex_stats_table():
pass
-def print_table_with_header(texttable_obj, header="", untouched_header=""):
+def print_table_with_header(texttable_obj, header="", untouched_header="", buffer=sys.stdout):
header = header.replace("_", " ").title() + untouched_header
- print(format_text(header, 'cyan', 'underline') + "\n")
+ print(format_text(header, 'cyan', 'underline') + "\n", file=buffer)
- print((texttable_obj.draw() + "\n"))
+ print((texttable_obj.draw() + "\n"), file=buffer)
if __name__ == "__main__":
pass