From 62d6e59ef252d4ed81180ec25d97fbf841732030 Mon Sep 17 00:00:00 2001 From: uael Date: Tue, 28 Nov 2023 06:56:06 +0000 Subject: [PATCH 1/2] Reapply "android: forward all logs to adb logs" This reverts commit 01857d725e3536dada059bb942de250bdd25510a. --- avatar/pandora_server.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/avatar/pandora_server.py b/avatar/pandora_server.py index aafc3fc..ee17604 100644 --- a/avatar/pandora_server.py +++ b/avatar/pandora_server.py @@ -19,7 +19,11 @@ import avatar.aio import grpc import grpc.aio +import logging +import os import portpicker +import re +import shlex import threading import types @@ -112,6 +116,8 @@ class AndroidPandoraServer(PandoraServer[AndroidDevice]): _instrumentation: Optional[threading.Thread] = None _port: int + _logger: logging.Logger + _handler: logging.Handler def start(self) -> PandoraClient: """Sets up and starts the Pandora server on the Android device.""" @@ -132,6 +138,31 @@ def start(self) -> PandoraClient: self._instrumentation.start() self.device.adb.forward([f'tcp:{self._port}', f'tcp:{ANDROID_SERVER_GRPC_PORT}']) # type: ignore + # Forward all logging to ADB logs + adb = self.device.adb + + class AdbLoggingHandler(logging.Handler): + def emit(self, record: logging.LogRecord) -> None: + if record.levelno <= logging.DEBUG: + return + ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') + msg = self.format(record) + msg = ansi_escape.sub('', msg) + level = { + logging.FATAL: 'f', + logging.ERROR: 'e', + logging.WARN: 'w', + logging.INFO: 'i', + logging.DEBUG: 'd', + logging.NOTSET: 'd', + }[record.levelno] + for msg in msg.splitlines(): + os.system(f'adb -s {adb.serial} shell "log -t Avatar -p {level} {shlex.quote(msg)}"') + + self._logger = logging.getLogger() + self._handler = AdbLoggingHandler() + self._logger.addHandler(self._handler) + return PandoraClient(f'localhost:{self._port}', 'android') def stop(self) -> None: @@ -143,6 +174,9 @@ def stop(self) -> None: 'shell', f'am force-stop {ANDROID_SERVER_PACKAGE}', shell=False, timeout=None, stderr=None ) + # Remove ADB logging handler + self._logger.removeHandler(self._handler) + self.device.adb.forward(['--remove', f'tcp:{self._port}']) # type: ignore self._instrumentation.join() self._instrumentation = None From 68ceab18c02a5621ea50fe9ac0e2c7f7405dfd74 Mon Sep 17 00:00:00 2001 From: uael Date: Tue, 28 Nov 2023 06:57:10 +0000 Subject: [PATCH 2/2] android: fix logs forwarding latency Instead of executing an adb shell command for each log line, spawn one adb shell per android device and write one log command per log to its stdin. This considerably reduce the forwarding latency. --- avatar/pandora_server.py | 44 ++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/avatar/pandora_server.py b/avatar/pandora_server.py index ee17604..5f54363 100644 --- a/avatar/pandora_server.py +++ b/avatar/pandora_server.py @@ -20,10 +20,10 @@ import grpc import grpc.aio import logging -import os import portpicker import re import shlex +import subprocess import threading import types @@ -118,6 +118,7 @@ class AndroidPandoraServer(PandoraServer[AndroidDevice]): _port: int _logger: logging.Logger _handler: logging.Handler + _adb_shell: subprocess.Popen[bytes] def start(self) -> PandoraClient: """Sets up and starts the Pandora server on the Android device.""" @@ -140,27 +141,37 @@ def start(self) -> PandoraClient: # Forward all logging to ADB logs adb = self.device.adb + self._adb_shell = subprocess.Popen(['adb', '-s', adb.serial, 'shell'], stdin=subprocess.PIPE) + + # This regex match all ANSI escape sequences (colors, style, ..). + ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') class AdbLoggingHandler(logging.Handler): + LOGGING_TO_ANDROID_LEVELS = { + logging.FATAL: 'f', + logging.ERROR: 'e', + logging.WARN: 'w', + logging.INFO: 'i', + logging.DEBUG: 'd', + logging.NOTSET: 'd', + } + + def __init__(self, adb_shell: subprocess.Popen[bytes]) -> None: + super().__init__() + self.adb_shell = adb_shell + def emit(self, record: logging.LogRecord) -> None: if record.levelno <= logging.DEBUG: return - ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') - msg = self.format(record) - msg = ansi_escape.sub('', msg) - level = { - logging.FATAL: 'f', - logging.ERROR: 'e', - logging.WARN: 'w', - logging.INFO: 'i', - logging.DEBUG: 'd', - logging.NOTSET: 'd', - }[record.levelno] - for msg in msg.splitlines(): - os.system(f'adb -s {adb.serial} shell "log -t Avatar -p {level} {shlex.quote(msg)}"') + # Format and remove all ANSI escape sequences. + msg = ansi_escape.sub('', self.format(record)) + level = AdbLoggingHandler.LOGGING_TO_ANDROID_LEVELS[record.levelno] + assert self.adb_shell.stdin + self.adb_shell.stdin.write(f'log -t Avatar -p {level} {shlex.quote(msg)}\n'.encode('utf-8')) + self.adb_shell.stdin.flush() self._logger = logging.getLogger() - self._handler = AdbLoggingHandler() + self._handler = AdbLoggingHandler(self._adb_shell) self._logger.addHandler(self._handler) return PandoraClient(f'localhost:{self._port}', 'android') @@ -176,6 +187,9 @@ def stop(self) -> None: # Remove ADB logging handler self._logger.removeHandler(self._handler) + assert self._adb_shell.stdin + self._adb_shell.stdin.close() + self._adb_shell.wait() self.device.adb.forward(['--remove', f'tcp:{self._port}']) # type: ignore self._instrumentation.join()