diff options
Diffstat (limited to 'scripts/automation/trex_control_plane/stl/console')
-rwxr-xr-x | scripts/automation/trex_control_plane/stl/console/trex_console.py | 1 | ||||
-rw-r--r-- | scripts/automation/trex_control_plane/stl/console/trex_tui.py | 312 |
2 files changed, 220 insertions, 93 deletions
diff --git a/scripts/automation/trex_control_plane/stl/console/trex_console.py b/scripts/automation/trex_control_plane/stl/console/trex_console.py index 110457d6..5d23d8da 100755 --- a/scripts/automation/trex_control_plane/stl/console/trex_console.py +++ b/scripts/automation/trex_control_plane/stl/console/trex_console.py @@ -440,6 +440,7 @@ class TRexConsole(TRexGeneralCmd): if (l > 2) and (s[l - 2] in file_flags): return TRexConsole.tree_autocomplete(s[l - 1]) + complete_push = complete_start @verify_connected def do_start(self, line): 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 a69c4165..e769b9b2 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,89 @@ 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 + self.update_flag = False + +# a policer class to make sure no too-fast redraws +# occurs - it filters fast bursts of redraws +class RedrawPolicer(): + def __init__ (self, rate): + self.ts = 0 + self.marked = False + self.rate = rate + self.force = False + + def mark_for_redraw (self, force = False): + self.marked = True + if force: + self.force = True + + def should_redraw (self): + dt = time.time() - self.ts + return self.force or (self.marked and (dt > self.rate)) + + def reset (self, restart = False): + self.ts = time.time() + self.marked = restart + self.force = False + + # shows a textual top style window class TrexTUI(): @@ -463,6 +553,7 @@ class TrexTUI(): MIN_ROWS = 50 MIN_COLS = 111 + class ScreenSizeException(Exception): def __init__ (self, cols, rows): msg = "TUI requires console screen size of at least {0}x{1}, current is {2}x{3}".format(TrexTUI.MIN_COLS, @@ -471,11 +562,18 @@ class TrexTUI(): rows) super(TrexTUI.ScreenSizeException, self).__init__(msg) + def __init__ (self, stateless_client): self.stateless_client = stateless_client + self.tui_global_lock = threading.Lock() self.pm = TrexTUIPanelManager(self) - + self.sb = ScreenBuffer(self.redraw_handler) + + def redraw_handler (self, buffer): + # this is executed by the screen buffer - should be protected against TUI commands + with self.tui_global_lock: + self.pm.show(show_legend = self.async_keys.is_legend_mode(), buffer = buffer) def clear_screen (self, lines = 50): # reposition the cursor @@ -490,7 +588,6 @@ class TrexTUI(): # reposition the cursor sys.stdout.write("\x1b[0;0H") - #sys.stdout.write("\x1b[2J\x1b[H") def show (self, client, save_console_history, show_log = False, locked = False): @@ -499,7 +596,7 @@ class TrexTUI(): if (int(rows) < TrexTUI.MIN_ROWS) or (int(cols) < TrexTUI.MIN_COLS): raise self.ScreenSizeException(rows = rows, cols = cols) - with AsyncKeys(client, save_console_history, locked) as async_keys: + with AsyncKeys(client, save_console_history, self.tui_global_lock, locked) as async_keys: sys.stdout.write("\x1bc") self.async_keys = async_keys self.show_internal(show_log, locked) @@ -511,84 +608,107 @@ class TrexTUI(): self.pm.init(show_log, locked) self.state = self.STATE_ACTIVE - self.last_redraw_ts = 0 + + # create print policers + self.full_redraw = RedrawPolicer(0.5) + self.keys_redraw = RedrawPolicer(0.05) + self.full_redraw.mark_for_redraw() + try: + self.sb.start() + while True: # draw and handle user input status = self.async_keys.tick(self.pm) - self.draw_screen(status) + # prepare the next frame + self.prepare(status) + time.sleep(0.01) + self.draw_screen() - # speedup for keys, slower for no keys - if status == AsyncKeys.STATUS_NONE: - time.sleep(0.01) - else: - time.sleep(0.001) + with self.tui_global_lock: + self.handle_state_machine() - # regular state - if self.state == self.STATE_ACTIVE: - # if no connectivity - move to lost connecitivty - if not self.stateless_client.async_client.is_alive(): - self.stateless_client._invalidate_stats(self.pm.ports) - self.state = self.STATE_LOST_CONT + except TUIQuit: + print("\nExiting TUI...") + finally: + self.sb.stop() - # lost connectivity - elif self.state == self.STATE_LOST_CONT: - # got it back - if self.stateless_client.async_client.is_alive(): - # move to state reconnect - self.state = self.STATE_RECONNECT + print("") + - # restored connectivity - try to reconnect - elif self.state == self.STATE_RECONNECT: + # handle state machine + def handle_state_machine (self): + # regular state + if self.state == self.STATE_ACTIVE: + # if no connectivity - move to lost connecitivty + if not self.stateless_client.async_client.is_alive(): + self.stateless_client._invalidate_stats(self.pm.ports) + self.state = self.STATE_LOST_CONT - try: - self.stateless_client.connect() - self.state = self.STATE_ACTIVE - except STLError: - self.state = self.STATE_LOST_CONT + # lost connectivity + elif self.state == self.STATE_LOST_CONT: + # got it back + if self.stateless_client.async_client.is_alive(): + # move to state reconnect + self.state = self.STATE_RECONNECT - except TUIQuit: - print("\nExiting TUI...") - print("") + # restored connectivity - try to reconnect + elif self.state == self.STATE_RECONNECT: + try: + self.stateless_client.connect() + self.stateless_client.acquire() + self.state = self.STATE_ACTIVE + except STLError: + self.state = self.STATE_LOST_CONT - # 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() + # logic before printing + def prepare (self, status): + if status == AsyncKeys.STATUS_REDRAW_ALL: + self.full_redraw.mark_for_redraw(force = True) - self.async_keys.draw() - sys.stdout = old_stdout + elif status == AsyncKeys.STATUS_REDRAW_KEYS: + self.keys_redraw.mark_for_redraw() - self.clear_screen() + if self.full_redraw.should_redraw(): + self.sb.update() + self.full_redraw.reset(restart = True) - sys.stdout.write(mystdout.getvalue()) - + return + + + # draw once + def draw_screen (self): + + # check for screen buffer's new screen + x = self.sb.get() + + # we have a new screen to draw + if x: + self.clear_screen() + + self.async_keys.draw(x) + sys.stdout.write(x.getvalue()) sys.stdout.flush() - self.last_redraw_ts = time.time() - elif status == AsyncKeys.STATUS_REDRAW_KEYS: + # maybe we need to redraw the keys + elif self.keys_redraw.should_redraw(): sys.stdout.write("\x1b[4A") - - self.async_keys.draw() + self.async_keys.draw(sys.stdout) sys.stdout.flush() - return + # reset the policer for next time + self.keys_redraw.reset() + def get_state (self): return self.state @@ -607,7 +727,9 @@ class AsyncKeys: STATUS_REDRAW_KEYS = 1 STATUS_REDRAW_ALL = 2 - def __init__ (self, client, save_console_history, locked = False): + def __init__ (self, client, save_console_history, tui_global_lock, locked = False): + self.tui_global_lock = tui_global_lock + self.engine_console = AsyncKeysEngineConsole(self, client, save_console_history) self.engine_legend = AsyncKeysEngineLegend(self) self.locked = locked @@ -679,8 +801,8 @@ class AsyncKeys: return self.engine.tick(seq, pm) - def draw (self): - self.engine.draw() + def draw (self, buffer): + self.engine.draw(buffer) @@ -704,7 +826,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 @@ -720,8 +842,10 @@ class AsyncKeysEngineConsole: self.ac = {'start' : client.start_line, 'stop' : client.stop_line, 'pause' : client.pause_line, + 'clear' : client.clear_stats_line, 'push' : client.push_line, 'resume' : client.resume_line, + 'reset' : client.reset_line, 'update' : client.update_line, 'connect' : client.connect_line, 'disconnect' : client.disconnect_line, @@ -804,7 +928,6 @@ class AsyncKeysEngineConsole: def handle_single_key (self, ch): - # newline if ch == '\n': self.handle_cmd() @@ -921,6 +1044,7 @@ class AsyncKeysEngineConsole: def handle_cmd (self): + cmd = self.lines[self.line_index].get().strip() if not cmd: return @@ -929,7 +1053,8 @@ class AsyncKeysEngineConsole: func = self.ac.get(op) if func: - func_rc = func(param) + with self.async.tui_global_lock: + func_rc = func(param) # take out the empty line empty_line = self.lines.popleft() @@ -962,7 +1087,6 @@ class AsyncKeysEngineConsole: # success if func_rc: self.last_status = format_text("[OK]", 'green') - # errors else: err_msgs = ascii_split(str(func_rc)) @@ -971,16 +1095,18 @@ class AsyncKeysEngineConsole: self.last_status += " [{0} more errors messages]".format(len(err_msgs) - 1) color = 'red' + + # trim too long lines if ansi_len(self.last_status) > TrexTUI.MIN_COLS: 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 @@ -1055,7 +1181,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)) |