summaryrefslogtreecommitdiffstats
path: root/scripts/automation/trex_control_plane/stl/console
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/automation/trex_control_plane/stl/console')
-rwxr-xr-xscripts/automation/trex_control_plane/stl/console/trex_console.py9
-rw-r--r--scripts/automation/trex_control_plane/stl/console/trex_tui.py505
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))
+