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 | 9 | ||||
-rw-r--r-- | scripts/automation/trex_control_plane/stl/console/trex_tui.py | 505 |
2 files changed, 460 insertions, 54 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 ab70d357..9e3f2600 100755 --- a/scripts/automation/trex_control_plane/stl/console/trex_console.py +++ b/scripts/automation/trex_control_plane/stl/console/trex_console.py @@ -459,7 +459,6 @@ class TRexConsole(TRexGeneralCmd): self.stateless_client.start_line(line) - def help_start(self): @@ -567,7 +566,8 @@ class TRexConsole(TRexGeneralCmd): parser = parsing_opts.gen_parser(self, "tui", self.do_tui.__doc__, - parsing_opts.XTERM) + parsing_opts.XTERM, + parsing_opts.LOCKED) opts = parser.parse_args(line.split()) if opts is None: @@ -590,7 +590,7 @@ class TRexConsole(TRexGeneralCmd): with self.stateless_client.logger.supress(): - self.tui.show() + self.tui.show(self.stateless_client, locked = opts.locked) def help_tui (self): @@ -872,7 +872,8 @@ def main(): # TUI if options.tui: - console.do_tui("-x" if options.xtui else "") + console.do_tui("-x" if options.xtui else "-l") + else: console.start() 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 d3be4435..32c6b1e0 100644 --- a/scripts/automation/trex_control_plane/stl/console/trex_tui.py +++ b/scripts/automation/trex_control_plane/stl/console/trex_tui.py @@ -4,6 +4,7 @@ import os import time from collections import OrderedDict, deque import datetime +import readline if sys.version_info > (3,0): from io import StringIO @@ -15,6 +16,10 @@ from trex_stl_lib.utils import text_tables from trex_stl_lib import trex_stl_stats from trex_stl_lib.utils.filters import ToggleFilter +class TUIQuit(Exception): + pass + + # for STL exceptions from trex_stl_lib.api import * @@ -210,7 +215,7 @@ class TrexTUILatencyStats(TrexTUIPanel): super(TrexTUILatencyStats, self).__init__(mng, "lstats") self.key_actions = OrderedDict() self.key_actions['c'] = {'action': self.action_clear, 'legend': 'clear', 'show': True} - self.key_actions['t'] = {'action': self.action_toggle_histogram, 'legend': 'toggle histogram', 'show': True} + self.key_actions['h'] = {'action': self.action_toggle_histogram, 'legend': 'histogram toggle', 'show': True} self.is_histogram = False @@ -238,6 +243,23 @@ class TrexTUILatencyStats(TrexTUIPanel): self.stateless_client.latency_stats.clear_stats() return "" + +# utilization stats +class TrexTUIUtilizationStats(TrexTUIPanel): + def __init__ (self, mng): + super(TrexTUIUtilizationStats, self).__init__(mng, "ustats") + self.key_actions = {} + + def show (self): + 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) + + def get_key_actions (self): + return self.key_actions + + # log class TrexTUILog(): def __init__ (self): @@ -258,24 +280,42 @@ class TrexTUILog(): print(msg) +# a predicate to wrap function as a bool +class Predicate(object): + def __init__ (self, func): + self.func = func + + def __nonzero__ (self): + return True if self.func() else False + def __bool__ (self): + return True if self.func() else False + + # Panels manager (contains server panels) class TrexTUIPanelManager(): def __init__ (self, tui): self.tui = tui self.stateless_client = tui.stateless_client self.ports = self.stateless_client.get_all_ports() - + self.locked = False self.panels = {} self.panels['dashboard'] = TrexTUIDashBoard(self) self.panels['sstats'] = TrexTUIStreamsStats(self) self.panels['lstats'] = TrexTUILatencyStats(self) + self.panels['ustats'] = TrexTUIUtilizationStats(self) self.key_actions = OrderedDict() - self.key_actions['q'] = {'action': self.action_quit, 'legend': 'quit', 'show': True} + + # we allow console only when ports are acquired + self.key_actions['ESC'] = {'action': self.action_none, 'legend': 'console', 'show': Predicate(lambda : not self.locked)} + + self.key_actions['q'] = {'action': self.action_none, 'legend': 'quit', 'show': True} self.key_actions['d'] = {'action': self.action_show_dash, 'legend': 'dashboard', 'show': True} self.key_actions['s'] = {'action': self.action_show_sstats, 'legend': 'streams', 'show': True} self.key_actions['l'] = {'action': self.action_show_lstats, 'legend': 'latency', 'show': True} + self.key_actions['u'] = {'action': self.action_show_ustats, 'legend': 'util', 'show': True} + # start with dashboard self.main_panel = self.panels['dashboard'] @@ -290,7 +330,6 @@ class TrexTUIPanelManager(): self.show_log = False - def generate_legend (self): self.legend = "\n{:<12}".format("browse:") @@ -327,14 +366,18 @@ class TrexTUIPanelManager(): # on window switch or turn on / off of the TUI we call this - def init (self, show_log = False): + def init (self, show_log = False, locked = False): self.show_log = show_log + self.locked = locked self.generate_legend() - def show (self): + def show (self, show_legend): self.main_panel.show() self.print_connection_status() - self.print_legend() + + if show_legend: + self.generate_legend() + self.print_legend() if self.show_log: self.log.show() @@ -350,21 +393,22 @@ class TrexTUIPanelManager(): msg = self.main_panel.get_key_actions()[ch]['action']() else: - msg = "" + return False self.generate_legend() - - if msg == None: - return False - else: - if msg: - self.log.add_event(msg) - return True + return True + + #if msg == None: + # return False + #else: + # if msg: + # self.log.add_event(msg) + # return True # actions - def action_quit (self): + def action_none (self): return None def action_show_dash (self): @@ -392,6 +436,11 @@ class TrexTUIPanelManager(): self.init(self.show_log) return "" + def action_show_ustats(self): + self.main_panel = self.panels['ustats'] + self.init(self.show_log) + return "" + # shows a textual top style window class TrexTUI(): @@ -404,18 +453,7 @@ class TrexTUI(): self.stateless_client = stateless_client self.pm = TrexTUIPanelManager(self) - - - - def handle_key_input (self): - # try to read a single key - ch = os.read(sys.stdin.fileno(), 1).decode() - if ch != None and len(ch) > 0: - return (self.pm.handle_key(ch), True) - - else: - return (True, False) - + def clear_screen (self): #os.system('clear') @@ -423,17 +461,16 @@ class TrexTUI(): sys.stdout.write("\x1b[2J\x1b[H") + def show (self, client, show_log = False, locked = False): + with AsyncKeys(client, locked) as async_keys: + self.async_keys = async_keys + self.show_internal(show_log, locked) - def show (self, show_log = False): - # init termios - old_settings = termios.tcgetattr(sys.stdin) - new_settings = termios.tcgetattr(sys.stdin) - new_settings[3] = new_settings[3] & ~(termios.ECHO | termios.ICANON) # lflags - new_settings[6][termios.VMIN] = 0 # cc - new_settings[6][termios.VTIME] = 0 # cc - termios.tcsetattr(sys.stdin, termios.TCSADRAIN, new_settings) - self.pm.init(show_log) + + def show_internal (self, show_log, locked): + + self.pm.init(show_log, locked) self.state = self.STATE_ACTIVE self.draw_policer = 0 @@ -441,11 +478,11 @@ class TrexTUI(): try: while True: # draw and handle user input - cont, force_draw = self.handle_key_input() - self.draw_screen(force_draw) - if not cont: - break - time.sleep(0.1) + status = self.async_keys.tick(self.pm) + + self.draw_screen(status) + if status == AsyncKeys.STATUS_NONE: + time.sleep(0.001) # regular state if self.state == self.STATE_ACTIVE: @@ -473,34 +510,402 @@ class TrexTUI(): self.state = self.STATE_LOST_CONT - finally: - # restore - termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings) + except TUIQuit: + print("\nExiting TUI...") print("") # draw once - def draw_screen (self, force_draw = False): + def draw_screen (self, status): - if (self.draw_policer >= 5) or (force_draw): + # only redraw the keys line + if status == AsyncKeys.STATUS_REDRAW_KEYS: + self.clear_screen() + sys.stdout.write(self.last_snap) + self.async_keys.draw() + sys.stdout.flush() + return + + if (self.draw_policer >= 500) or (status == AsyncKeys.STATUS_REDRAW_ALL): # capture stdout to a string old_stdout = sys.stdout sys.stdout = mystdout = StringIO() - self.pm.show() + self.pm.show(show_legend = self.async_keys.is_legend_mode()) + self.last_snap = mystdout.getvalue() + + self.async_keys.draw() sys.stdout = old_stdout self.clear_screen() - print(mystdout.getvalue()) - + sys.stdout.write(mystdout.getvalue()) + sys.stdout.flush() self.draw_policer = 0 else: self.draw_policer += 1 + def get_state (self): return self.state + + + + +# handles async IO +class AsyncKeys: + + MODE_LEGEND = 1 + MODE_CONSOLE = 2 + + STATUS_NONE = 0 + STATUS_REDRAW_KEYS = 1 + STATUS_REDRAW_ALL = 2 + + def __init__ (self, client, locked = False): + self.engine_console = AsyncKeysEngineConsole(self, client) + self.engine_legend = AsyncKeysEngineLegend(self) + self.locked = locked + + if locked: + self.engine = self.engine_legend + self.locked = True + else: + self.engine = self.engine_console + self.locked = False + + def __enter__ (self): + # init termios + self.old_settings = termios.tcgetattr(sys.stdin) + new_settings = termios.tcgetattr(sys.stdin) + new_settings[3] = new_settings[3] & ~(termios.ECHO | termios.ICANON) # lflags + new_settings[6][termios.VMIN] = 0 # cc + new_settings[6][termios.VTIME] = 0 # cc + termios.tcsetattr(sys.stdin, termios.TCSADRAIN, new_settings) + return self + + def __exit__ (self, type, value, traceback): + termios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.old_settings) + + + def is_legend_mode (self): + return self.engine.get_type() == AsyncKeys.MODE_LEGEND + + def is_console_mode (self): + return self.engine.get_type == AsyncKeys.MODE_CONSOLE + + def switch (self): + if self.is_legend_mode(): + self.engine = self.engine_console + else: + self.engine = self.engine_legend + + + def tick (self, pm): + seq = '' + # drain all chars + while True: + ch = os.read(sys.stdin.fileno(), 1).decode() + if not ch: + break + seq += ch + + if not seq: + return self.STATUS_NONE + + # ESC for switch + if seq == '\x1b': + if not self.locked: + self.switch() + return self.STATUS_REDRAW_ALL + + # EOF (ctrl + D) + if seq == '\x04': + raise TUIQuit() + + # pass tick to engine + return self.engine.tick(seq, pm) + + + def draw (self): + self.engine.draw() + + + +# Legend engine +class AsyncKeysEngineLegend: + def __init__ (self, async): + self.async = async + + def get_type (self): + return self.async.MODE_LEGEND + + def tick (self, seq, pm): + + if seq == 'q': + raise TUIQuit() + + # ignore escapes + if len(seq) > 1: + return AsyncKeys.STATUS_NONE + + rc = pm.handle_key(seq) + return AsyncKeys.STATUS_REDRAW_ALL if rc else AsyncKeys.STATUS_NONE + + def draw (self): + pass + + +# console engine +class AsyncKeysEngineConsole: + def __init__ (self, async, client): + self.async = async + self.lines = deque(maxlen = 100) + + self.ac = {'start' : client.start_line, + 'stop' : client.stop_line, + 'pause' : client.pause_line, + 'resume': client.resume_line, + 'update': client.update_line, + 'quit' : self.action_quit, + 'q' : self.action_quit, + 'exit' : self.action_quit, + 'help' : self.action_help, + '?' : self.action_help} + + # fetch readline history and add relevants + for i in range(0, readline.get_current_history_length()): + cmd = readline.get_history_item(i) + if cmd and cmd.split()[0] in self.ac: + self.lines.appendleft(CmdLine(cmd)) + + # new line + self.lines.appendleft(CmdLine('')) + self.line_index = 0 + self.last_status = '' + + def action_quit (self, _): + raise TUIQuit() + + def action_help (self, _): + return ' '.join([format_text(cmd, 'bold') for cmd in self.ac.keys()]) + + def get_type (self): + return self.async.MODE_CONSOLE + + + def handle_escape_char (self, seq): + # up + if seq == '\x1b[A': + self.line_index = min(self.line_index + 1, len(self.lines) - 1) + + # down + elif seq == '\x1b[B': + self.line_index = max(self.line_index - 1, 0) + + # left + elif seq == '\x1b[D': + self.lines[self.line_index].go_left() + + # right + elif seq == '\x1b[C': + self.lines[self.line_index].go_right() + + # del + elif seq == '\x1b[3~': + self.lines[self.line_index].del_key() + + # home + elif seq == '\x1b[H': + self.lines[self.line_index].home_key() + + # end + elif seq == '\x1b[F': + self.lines[self.line_index].end_key() + return True + + # unknown key + else: + return AsyncKeys.STATUS_NONE + + return AsyncKeys.STATUS_REDRAW_KEYS + + + def tick (self, seq, _): + + # handle escape chars + if len(seq) > 1: + return self.handle_escape_char(seq) + + # handle each char + for ch in seq: + return self.handle_single_key(ch) + + + + def handle_single_key (self, ch): + + # newline + if ch == '\n': + self.handle_cmd() + + # backspace + elif ch == '\x7f': + self.lines[self.line_index].backspace() + + # TAB + elif ch == '\t': + cur = self.lines[self.line_index].get() + if not cur: + return + + matching_cmds = [x for x in self.ac if x.startswith(cur)] + + common = os.path.commonprefix([x for x in self.ac if x.startswith(cur)]) + if common: + if len(matching_cmds) == 1: + self.lines[self.line_index].set(common + ' ') + self.last_status = '' + else: + self.lines[self.line_index].set(common) + self.last_status = 'ambigious: '+ ' '.join([format_text(cmd, 'bold') for cmd in matching_cmds]) + + + # simple char + else: + self.lines[self.line_index] += ch + + return AsyncKeys.STATUS_REDRAW_KEYS + + + def split_cmd (self, cmd): + s = cmd.split(' ', 1) + op = s[0] + param = s[1] if len(s) == 2 else '' + return op, param + + + def handle_cmd (self): + cmd = self.lines[self.line_index].get().strip() + if not cmd: + return + + op, param = self.split_cmd(cmd) + + func = self.ac.get(op) + if func: + func_rc = func(param) + + # take out the empty line + empty_line = self.lines.popleft() + assert(empty_line.ro_line == '') + + if not self.lines or self.lines[0].ro_line != cmd: + self.lines.appendleft(CmdLine(cmd)) + + # back in + self.lines.appendleft(empty_line) + self.line_index = 0 + readline.add_history(cmd) + + # back to readonly + for line in self.lines: + line.invalidate() + + assert(self.lines[0].modified == False) + if not func: + self.last_status = "unknown command: '{0}'".format(format_text(cmd.split()[0], 'bold')) + else: + if isinstance(func_rc, str): + self.last_status = func_rc + else: + self.last_status = format_text("[OK]", 'green') if func_rc else format_text(str(func_rc).replace('\n', ''), 'red') + + + def draw (self): + sys.stdout.write("\nPress 'ESC' for navigation panel...\n") + sys.stdout.write("status: {0}\n".format(self.last_status)) + sys.stdout.write("\ntui>") + self.lines[self.line_index].draw() + + +# a readline alike command line - can be modified during edit +class CmdLine(object): + def __init__ (self, line): + self.ro_line = line + self.w_line = None + self.modified = False + self.cursor_index = len(line) + + def get (self): + if self.modified: + return self.w_line + else: + return self.ro_line + + def set (self, line, cursor_pos = None): + self.w_line = line + self.modified = True + + if cursor_pos is None: + self.cursor_index = len(self.w_line) + else: + self.cursor_index = cursor_pos + + + def __add__ (self, other): + assert(0) + + + def __str__ (self): + return self.get() + + + def __iadd__ (self, other): + + self.set(self.get()[:self.cursor_index] + other + self.get()[self.cursor_index:], + cursor_pos = self.cursor_index + len(other)) + + return self + + + def backspace (self): + if self.cursor_index == 0: + return + + self.set(self.get()[:self.cursor_index - 1] + self.get()[self.cursor_index:], + self.cursor_index - 1) + + + def del_key (self): + if self.cursor_index == len(self.get()): + return + + self.set(self.get()[:self.cursor_index] + self.get()[self.cursor_index + 1:], + self.cursor_index) + + def home_key (self): + self.cursor_index = 0 + + def end_key (self): + self.cursor_index = len(self.get()) + + def invalidate (self): + self.modified = False + self.w_line = None + self.cursor_index = len(self.ro_line) + + def go_left (self): + self.cursor_index = max(0, self.cursor_index - 1) + + 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)) + |