From 25aa665b7e5a5e8747735aaaa5a00dba11b21067 Mon Sep 17 00:00:00 2001 From: imarom Date: Mon, 15 Aug 2016 15:33:51 +0300 Subject: TUI screen buffer --- .../trex_control_plane/stl/console/trex_tui.py | 179 +++++++++++++++------ .../stl/trex_stl_lib/trex_stl_client.py | 61 +++++-- .../stl/trex_stl_lib/trex_stl_port.py | 4 +- .../stl/trex_stl_lib/trex_stl_stats.py | 4 - .../stl/trex_stl_lib/utils/parsing_opts.py | 42 ++++- .../stl/trex_stl_lib/utils/text_tables.py | 9 +- 6 files changed, 218 insertions(+), 81 deletions(-) (limited to 'scripts') 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 -- cgit 1.2.3-korg