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.py1
-rw-r--r--scripts/automation/trex_control_plane/stl/console/trex_tui.py312
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))