pw_system: Console log files and busy wait fix
- Add host and device logfile options
- Merge host and device into single logfile option
- Remove ANSI colors from logfiles
- Add IPython option in case the pw_console TUI is not needed
- Set pyserial timeout to small timespan to avoid 100% CPU usage
Change-Id: I308c78e7a157d914359b2a77c511768f23932d68
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/108555
Commit-Queue: Anthony DiGirolamo <tonymd@google.com>
Reviewed-by: Armando Montanez <amontanez@google.com>
diff --git a/pw_system/py/pw_system/console.py b/pw_system/py/pw_system/console.py
index ba491a1..90450ed 100644
--- a/pw_system/py/pw_system/console.py
+++ b/pw_system/py/pw_system/console.py
@@ -52,22 +52,24 @@
import socket
import serial # type: ignore
+import IPython # type: ignore
import pw_cli.log
-import pw_console.python_logging
-from pw_console import PwConsoleEmbed
-from pw_console.pyserial_wrapper import SerialWithLogging
-from pw_console.plugins.bandwidth_toolbar import BandwidthToolbar
+from pw_console.embed import PwConsoleEmbed
from pw_console.log_store import LogStore
+from pw_console.plugins.bandwidth_toolbar import BandwidthToolbar
+from pw_console.pyserial_wrapper import SerialWithLogging
+import pw_console.python_logging
+from pw_rpc.console_tools.console import flattened_rpc_completions
+from pw_system.device import Device
+from pw_tokenizer.detokenize import AutoUpdatingDetokenizer
+
+# Default proto imports:
from pw_log.proto import log_pb2
from pw_metric_proto import metric_service_pb2
from pw_thread_protos import thread_snapshot_service_pb2
-from pw_rpc.console_tools.console import flattened_rpc_completions
-from pw_tokenizer.detokenize import AutoUpdatingDetokenizer
from pw_unit_test_proto import unit_test_pb2
-from pw_system.device import Device
-
_LOG = logging.getLogger('tools')
_DEVICE_LOG = logging.getLogger('rpc_device')
_SERIAL_DEBUG = logging.getLogger('pw_console.serial_debug_logger')
@@ -102,7 +104,34 @@
default=sys.stdout.buffer,
help=('The file to which to write device output (HDLC channel 1); '
'provide - or omit for stdout.'))
- parser.add_argument('--logfile', help='Console debug log file.')
+
+ # Log file options
+ parser.add_argument(
+ '--logfile',
+ default='pw_console-logs.txt',
+ help=('Default log file. This will contain host side '
+ 'log messages only unles the '
+ '--merge-device-and-host-logs argument is used.'),
+ )
+
+ parser.add_argument(
+ '--merge-device-and-host-logs',
+ action='store_true',
+ help=('Include device logs in the default --logfile.'
+ 'These are normally shown in a separate device '
+ 'only log file.'),
+ )
+
+ parser.add_argument(
+ '--host-logfile',
+ help=('Additional host only log file. Normally all logs in the '
+ 'default logfile are host only.'),
+ )
+
+ parser.add_argument('--device-logfile',
+ default='pw_console-device-logs.txt',
+ help='Device only log file.')
+
group.add_argument('-s',
'--socket-addr',
type=str,
@@ -119,11 +148,16 @@
parser.add_argument('--proto-globs',
nargs='+',
default=[],
- help='glob pattern for .proto files')
+ help='glob pattern for .proto files.')
parser.add_argument('-v',
'--verbose',
action='store_true',
- help='Enables debug logging when set')
+ help='Enables debug logging when set.')
+ parser.add_argument('--ipython',
+ action='store_true',
+ dest='use_ipython',
+ help='Use IPython instead of pw_console.')
+
return parser.parse_args()
@@ -133,16 +167,19 @@
yield Path(file)
-def _start_ipython_terminal(
+def _start_python_terminal(
device: Device,
device_log_store: LogStore,
root_log_store: LogStore,
serial_debug_log_store: LogStore,
log_file: str,
+ host_logfile: str,
+ device_logfile: str,
serial_debug: bool = False,
config_file_path: Optional[Path] = None,
+ use_ipython: bool = False,
) -> None:
- """Starts an interactive IPython terminal with preset variables."""
+ """Starts an interactive Python terminal with preset variables."""
local_variables = dict(
client=device.client,
device=device,
@@ -166,6 +203,19 @@
DEVICE_LOG.warning('Message appears in Device Logs window.')
""")
+ welcome_message += '\n\nLogs are being saved to:\n ' + log_file
+ if host_logfile:
+ welcome_message += '\nHost logs are being saved to:\n ' + host_logfile
+ if device_logfile:
+ welcome_message += ('\nDevice logs are being saved to:\n ' +
+ device_logfile)
+
+ if use_ipython:
+ print(welcome_message)
+ IPython.terminal.embed.InteractiveShellEmbed().mainloop(
+ local_ns=local_variables, module=argparse.Namespace())
+ return
+
client_info = device.info()
completions = flattened_rpc_completions([client_info])
@@ -219,18 +269,22 @@
return self.socket.recv(num_bytes)
-#pylint: disable=too-many-arguments
+#pylint: disable=too-many-arguments,too-many-locals
def console(device: str,
baudrate: int,
proto_globs: Collection[str],
token_databases: Collection[Path],
socket_addr: str,
logfile: str,
+ host_logfile: str,
+ device_logfile: str,
output: Any,
serial_debug: bool = False,
config_file: Optional[Path] = None,
verbose: bool = False,
- compiled_protos: Optional[List[ModuleType]] = None) -> int:
+ compiled_protos: Optional[List[ModuleType]] = None,
+ merge_device_and_host_logs: bool = False,
+ use_ipython: bool = False) -> int:
"""Starts an interactive RPC console for HDLC."""
# argparse.FileType doesn't correctly handle '-' for binary files.
if output is sys.stdout:
@@ -256,11 +310,37 @@
logfile = pw_console.python_logging.create_temp_log_file()
log_level = logging.DEBUG if verbose else logging.INFO
- pw_cli.log.install(log_level, True, False, logfile)
+
+ pw_cli.log.install(level=log_level,
+ use_color=False,
+ hide_timestamp=False,
+ log_file=logfile)
+
+ if device_logfile:
+ pw_cli.log.install(level=log_level,
+ use_color=False,
+ hide_timestamp=False,
+ log_file=device_logfile,
+ logger=_DEVICE_LOG)
+ if host_logfile:
+ pw_cli.log.install(level=log_level,
+ use_color=False,
+ hide_timestamp=False,
+ log_file=host_logfile,
+ logger=_ROOT_LOG)
+
+ if merge_device_and_host_logs:
+ # Add device logs to the default logfile.
+ pw_cli.log.install(level=log_level,
+ use_color=False,
+ hide_timestamp=False,
+ log_file=logfile,
+ logger=_DEVICE_LOG)
+
_LOG.setLevel(log_level)
_DEVICE_LOG.setLevel(log_level)
_ROOT_LOG.setLevel(log_level)
- _SERIAL_DEBUG.setLevel(log_level)
+ _SERIAL_DEBUG.setLevel(logging.DEBUG)
detokenizer = None
if token_databases:
@@ -299,7 +379,11 @@
serial_device = serial_impl(
device,
baudrate,
- timeout=0, # Non-blocking mode
+ # Timeout in seconds. This should be a very small value. Setting to
+ # zero makes pyserial read() non-blocking which will cause the host
+ # machine to busy loop and 100% CPU usage.
+ # https://pythonhosted.org/pyserial/pyserial_api.html#serial.Serial
+ timeout=0.1,
)
read = lambda: serial_device.read(8192)
write = serial_device.write
@@ -327,9 +411,18 @@
timestamp_decoder=timestamp_decoder,
rpc_timeout_s=5)
- _start_ipython_terminal(device_client, device_log_store, root_log_store,
- serial_debug_log_store, logfile, serial_debug,
- config_file)
+ _start_python_terminal(
+ device=device_client,
+ device_log_store=device_log_store,
+ root_log_store=root_log_store,
+ serial_debug_log_store=serial_debug_log_store,
+ log_file=logfile,
+ host_logfile=host_logfile,
+ device_logfile=device_logfile,
+ serial_debug=serial_debug,
+ config_file_path=config_file,
+ use_ipython=use_ipython,
+ )
return 0