You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
142 lines
4.9 KiB
142 lines
4.9 KiB
#!/usr/bin/env python3
|
|
#
|
|
# Copyright 2020 - The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
import psutil
|
|
import re
|
|
import subprocess
|
|
from typing import Container
|
|
from collections import deque
|
|
|
|
|
|
class TerminalColor:
|
|
RED = "\033[31;1m"
|
|
BLUE = "\033[34;1m"
|
|
YELLOW = "\033[33;1m"
|
|
MAGENTA = "\033[35;1m"
|
|
END = "\033[0m"
|
|
|
|
|
|
def is_subprocess_alive(process, timeout_seconds=1):
|
|
"""
|
|
Check if a process is alive for at least timeout_seconds
|
|
:param process: a Popen object that represent a subprocess
|
|
:param timeout_seconds: process needs to be alive for at least
|
|
timeout_seconds
|
|
:return: True if process is alive for at least timeout_seconds
|
|
"""
|
|
try:
|
|
process.wait(timeout=timeout_seconds)
|
|
return False
|
|
except subprocess.TimeoutExpired as exp:
|
|
return True
|
|
|
|
|
|
def get_gd_root():
|
|
"""
|
|
Return the root of the GD test library
|
|
|
|
GD root is the parent directory of cert
|
|
:return: root directory string of gd test library
|
|
"""
|
|
return str(Path(__file__).absolute().parents[1])
|
|
|
|
|
|
def make_ports_available(ports: Container[int], timeout_seconds=10):
|
|
"""Make sure a list of ports are available
|
|
kill occupying process if possible
|
|
:param ports: list of target ports
|
|
:param timeout_seconds: number of seconds to wait when killing processes
|
|
:return: True on success, False on failure
|
|
"""
|
|
if not ports:
|
|
logging.warning("Empty ports is given to make_ports_available()")
|
|
return True
|
|
# Get connections whose state are in LISTEN only
|
|
# Connections in other states won't affect binding as SO_REUSEADDR is used
|
|
listening_conns_for_port = filter(
|
|
lambda conn: (conn and conn.status == psutil.CONN_LISTEN and conn.laddr and conn.laddr.port in ports),
|
|
psutil.net_connections())
|
|
success = True
|
|
for conn in listening_conns_for_port:
|
|
logging.warning("Freeing port %d used by %s" % (conn.laddr.port, str(conn)))
|
|
if not conn.pid:
|
|
logging.error("Failed to kill process occupying port %d due to lack of pid" % conn.laddr.port)
|
|
success = False
|
|
continue
|
|
logging.warning("Killing pid %d that is using port port %d" % (conn.pid, conn.laddr.port))
|
|
process = psutil.Process(conn.pid)
|
|
process.kill()
|
|
try:
|
|
process.wait(timeout=timeout_seconds)
|
|
except psutil.TimeoutExpired:
|
|
logging.error("SIGKILL timeout after %d seconds for pid %d" % (timeout_seconds, conn.pid))
|
|
continue
|
|
return success
|
|
|
|
|
|
# e.g. 2020-05-06 16:02:04.216 bt - system/bt/gd/facade/facade_main.cc:79 - crash_callback: #03 pc 0000000000013520 /lib/x86_64-linux-gnu/libpthread-2.29.so
|
|
HOST_CRASH_LINE_REGEX = re.compile(r"^.* - crash_callback: (?P<line>.*)$")
|
|
HOST_ABORT_HEADER = "Process crashed, signal: Aborted"
|
|
ASAN_OUTPUT_START_REGEX = re.compile(r"^==.*AddressSanitizer.*$")
|
|
|
|
|
|
def read_crash_snippet_and_log_tail(logpath):
|
|
"""
|
|
Get crash snippet if regex matched or last 20 lines of log
|
|
:return: crash_snippet, log_tail_20
|
|
1) crash snippet without timestamp in one string;
|
|
2) last 20 lines of log in one string;
|
|
"""
|
|
gd_root_prefix = get_gd_root() + "/"
|
|
abort_line = None
|
|
last_20_lines = deque(maxlen=20)
|
|
crash_log_lines = []
|
|
asan = False
|
|
asan_lines = []
|
|
|
|
with open(logpath) as f:
|
|
for _, line in enumerate(f):
|
|
last_20_lines.append(line)
|
|
asan_match = ASAN_OUTPUT_START_REGEX.match(line)
|
|
if asan or asan_match:
|
|
asan_lines.append(line)
|
|
asan = True
|
|
continue
|
|
|
|
host_crash_match = HOST_CRASH_LINE_REGEX.match(line)
|
|
if host_crash_match:
|
|
crash_line = host_crash_match.group("line").replace(gd_root_prefix, "")
|
|
if HOST_ABORT_HEADER in crash_line \
|
|
and len(last_20_lines) > 1:
|
|
abort_line = last_20_lines[-2]
|
|
crash_log_lines.append(crash_line)
|
|
|
|
log_tail_20 = "".join(last_20_lines)
|
|
crash_snippet = ""
|
|
if abort_line is not None:
|
|
crash_snippet += "abort log line:\n\n%s\n" % abort_line
|
|
crash_snippet += "\n".join(crash_log_lines)
|
|
|
|
if len(asan_lines) > 0:
|
|
return "".join(asan_lines), log_tail_20
|
|
|
|
if len(crash_log_lines) > 0:
|
|
return crash_snippet, log_tail_20
|
|
|
|
return None, log_tail_20
|