Skip to content
Snippets Groups Projects
Verified Commit a9807fbe authored by Karel Koci's avatar Karel Koci :metal:
Browse files

nsfarm/cli: implement logging with one thread less using poll

parent e1a30f22
Branches
1 merge request!3Feature: realtime logs on serial console
...@@ -26,10 +26,8 @@ class Board(abc.ABC): ...@@ -26,10 +26,8 @@ class Board(abc.ABC):
self.config = target_config self.config = target_config
# Open serial console to board # Open serial console to board
self._serial = serial.Serial(self.config['serial'], 115200) self._serial = serial.Serial(self.config['serial'], 115200)
#self._fdlogging = cli.FDLogging(self._serial, logging.getLogger(f"{__package__}[{target}]")) self._fdlogging = cli.FDLogging(self._serial, logging.getLogger(f"{__package__}[{target}]"))
#self._pexpect = fdpexpect.fdspawn(self._fdlogging.socket()) self._pexpect = fdpexpect.fdspawn(self._fdlogging.socket)
self._pexpect = fdpexpect.fdspawn(self._serial)
self._pexpect.logfile_read = cli.PexpectLogging(logging.getLogger(f"{__package__}[{target}]"))
# Set board to some known state # Set board to some known state
self.reset(True) # Hold in reset state self.reset(True) # Hold in reset state
......
...@@ -14,6 +14,7 @@ import abc ...@@ -14,6 +14,7 @@ import abc
import logging import logging
import base64 import base64
import fcntl import fcntl
import select
import socket import socket
import threading import threading
import typing import typing
...@@ -207,77 +208,78 @@ class FDLogging: ...@@ -207,77 +208,78 @@ class FDLogging:
""" """
_EXPECTED_EOL = b'\n\r' _EXPECTED_EOL = b'\n\r'
def __init__(self, fd, logger, in_level=logging.INFO, out_level=logging.DEBUG): def __init__(self, fileno, logger, in_level=logging.INFO, out_level=logging.DEBUG):
self.logger = logger self._logger = logger
self.in_level = in_level self._in_level = in_level
self.out_level = out_level self._out_level = out_level
self.fileno = fd.fileno() self._fileno = fileno if isinstance(fileno, int) else fileno.fileno()
self.our_sock, self.user_sock = socket.socketpair() self._our_sock, self._user_sock = socket.socketpair()
# Input self._orig_filestatus = fcntl.fcntl(self._fileno, fcntl.F_GETFL)
self.inputthread = threading.Thread(target=self._input, daemon=True) fcntl.fcntl(self._fileno, fcntl.F_SETFL, self._orig_filestatus | os.O_NONBLOCK)
self.inputthread.start() self._our_sock.setblocking(False)
# Output
self.outputthread = threading.Thread(target=self._output, daemon=True)
self.outputthread.start()
self._thread = threading.Thread(target=self._thread_func, daemon=True)
self._thread.start()
@property
def socket(self): def socket(self):
"""Returns socket for user to use to communicate trough this logged passtrough. """Returns socket for user to use to communicate trough this logged passtrough.
""" """
return self.user_sock return self._user_sock
def close(self): def close(self):
"""Close socket and stop logging. """Close socket and stop logging.
""" """
if self.our_sock is not None: if self._our_sock is None:
# Terminates and cleans _input return
_orig_filestatus = fcntl.fcntl(self.fileno, fcntl.F_GETFL) self._our_sock.close()
fcntl.fcntl(self.fileno, fcntl.F_SETFL, _orig_filestatus | os.O_NONBLOCK) self._our_sock = None
self.inputthread.join() self._thread.join()
fcntl.fcntl(self.fileno, fcntl.F_SETFL, _orig_filestatus) fcntl.fcntl(self._fileno, fcntl.F_SETFL, self._orig_filestatus)
# Terminates and cleans _output
self.our_sock.close()
self.outputthread.join()
self.our_sock = None
def __del__(self): def __del__(self):
self.close() self.close()
def _log_line(self, line, level): def _log_line(self, line, level):
self.logger.log(level, repr(line.rstrip(self._EXPECTED_EOL).expandtabs())[2:-1]) self._logger.log(level, repr(line.rstrip(self._EXPECTED_EOL).expandtabs())[2:-1])
def _log(self, prev_data, new_data, level): def _log(self, prev_data, new_data, level):
data = prev_data + new_data data = prev_data + new_data
lines = data.splitlines(keepends=True) lines = data.splitlines(keepends=True)
if not lines: if not lines:
return return data
# The last line does not have to be terminated (no new line character) so just preserve it # The last line does not have to be terminated (no new line character) so just preserve it
reminder = lines.pop() if lines[-1] or lines[-1][-1] not in self._EXPECTED_EOL else b'' reminder = lines.pop() if lines[-1] and lines[-1][-1] not in self._EXPECTED_EOL else b''
for line in lines: for line in lines:
self._log_line(line, level) self._log_line(line, level)
return reminder return reminder
def _input(self): def _thread_func(self):
data = b'' data = {
while True: self._fileno: b'',
try: self._our_sock.fileno(): b'',
new_data = os.read(self.fileno, io.DEFAULT_BUFFER_SIZE) }
except io.BlockingIOError: level = {
self._log_line(data, self.in_level) self._fileno: self._in_level,
return self._our_sock.fileno(): self._out_level,
self.our_sock.sendall(new_data) }
data = self._log(data, new_data, self.in_level) output = {
self._fileno: self._our_sock.fileno(),
def _output(self): self._our_sock.fileno(): self._fileno,
data = b'' }
poll = select.poll()
poll.register(self._fileno, select.POLLIN)
poll.register(self._our_sock.fileno(), select.POLLIN | select.POLLNVAL)
while True: while True:
try: for poll_event in poll.poll():
new_data = self.our_sock.recv(io.DEFAULT_BUFFER_SIZE) fileno, event = poll_event
except io.BlockingIOError: if event == select.POLLNVAL:
self._log_line(data, self.out_level) return
return new_data = os.read(fileno, io.DEFAULT_BUFFER_SIZE)
os.write(self.fileno, new_data) os.write(output[fileno], new_data)
data = self._log(data, new_data, self.out_level) data[fileno] = self._log(data[fileno], new_data, level[fileno])
class PexpectLogging: class PexpectLogging:
...@@ -288,7 +290,7 @@ class PexpectLogging: ...@@ -288,7 +290,7 @@ class PexpectLogging:
_EXPECTED_EOL = b'\n\r' _EXPECTED_EOL = b'\n\r'
def __init__(self, logger): def __init__(self, logger):
self._level = logging.DEBUG self._level = logging.INFO
self.logger = logger self.logger = logger
self.linebuf = b'' self.linebuf = b''
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment