1#!/usr/bin/env python3 2# 3# Copyright 2020 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import logging 18from pathlib import Path 19import psutil 20import re 21import subprocess 22from typing import Container 23from collections import deque 24 25 26class TerminalColor: 27 RED = "\033[31;1m" 28 BLUE = "\033[34;1m" 29 YELLOW = "\033[33;1m" 30 MAGENTA = "\033[35;1m" 31 END = "\033[0m" 32 33 34def is_subprocess_alive(process, timeout_seconds=1): 35 """ 36 Check if a process is alive for at least timeout_seconds 37 :param process: a Popen object that represent a subprocess 38 :param timeout_seconds: process needs to be alive for at least 39 timeout_seconds 40 :return: True if process is alive for at least timeout_seconds 41 """ 42 try: 43 process.wait(timeout=timeout_seconds) 44 return False 45 except subprocess.TimeoutExpired as exp: 46 return True 47 48 49def get_gd_root(): 50 """ 51 Return the root of the GD test library 52 53 GD root is the parent directory of blueberry/tests/gd/cert 54 :return: root directory string of gd test library 55 """ 56 return str(Path(__file__).absolute().parents[4]) 57 58 59def make_ports_available(ports: Container[int], timeout_seconds=10): 60 """Make sure a list of ports are available 61 kill occupying process if possible 62 :param ports: list of target ports 63 :param timeout_seconds: number of seconds to wait when killing processes 64 :return: True on success, False on failure 65 """ 66 if not ports: 67 logging.warning("Empty ports is given to make_ports_available()") 68 return True 69 # Get connections whose state are in LISTEN only 70 # Connections in other states won't affect binding as SO_REUSEADDR is used 71 listening_conns_for_port = filter( 72 lambda conn: (conn and conn.status == psutil.CONN_LISTEN and conn.laddr and conn.laddr.port in ports), 73 psutil.net_connections()) 74 success = True 75 killed_pids = set() 76 for conn in listening_conns_for_port: 77 logging.warning("Freeing port %d used by %s" % (conn.laddr.port, str(conn))) 78 if not conn.pid: 79 logging.error("Failed to kill process occupying port %d due to lack of pid" % conn.laddr.port) 80 continue 81 logging.warning("Killing pid %d that is using port port %d" % (conn.pid, conn.laddr.port)) 82 if conn.pid in killed_pids: 83 logging.warning("Pid %d is already killed in previous iteration" % (conn.pid)) 84 continue 85 try: 86 process = psutil.Process(conn.pid) 87 process.kill() 88 process.wait(timeout=timeout_seconds) 89 killed_pids.add(conn.pid) 90 except psutil.NoSuchProcess: 91 logging.warning("Pid %d is already dead before trying to kill it" % (conn.pid)) 92 killed_pids.add(conn.pid) 93 continue 94 except psutil.TimeoutExpired: 95 logging.error("SIGKILL timeout after %d seconds for pid %d" % (timeout_seconds, conn.pid)) 96 success = False 97 break 98 return success 99 100 101# e.g. 2020-05-06 16:02:04.216 bt - packages/modules/Bluetooth/system/gd/facade/facade_main.cc:79 - crash_callback: #03 pc 0000000000013520 /lib/x86_64-linux-gnu/libpthread-2.29.so 102HOST_CRASH_LINE_REGEX = re.compile(r"^.* - crash_callback: (?P<line>.*)$") 103HOST_ABORT_HEADER = "Process crashed, signal: Aborted" 104ASAN_OUTPUT_START_REGEX = re.compile(r"^==.*AddressSanitizer.*$") 105 106 107def read_crash_snippet_and_log_tail(logpath): 108 """ 109 Get crash snippet if regex matched or last 20 lines of log 110 :return: crash_snippet, log_tail_20 111 1) crash snippet without timestamp in one string; 112 2) last 20 lines of log in one string; 113 """ 114 gd_root_prefix = get_gd_root() + "/" 115 abort_line = None 116 last_20_lines = deque(maxlen=20) 117 crash_log_lines = [] 118 asan = False 119 asan_lines = [] 120 121 try: 122 with open(logpath) as f: 123 for _, line in enumerate(f): 124 last_20_lines.append(line) 125 asan_match = ASAN_OUTPUT_START_REGEX.match(line) 126 if asan or asan_match: 127 asan_lines.append(line) 128 asan = True 129 continue 130 131 host_crash_match = HOST_CRASH_LINE_REGEX.match(line) 132 if host_crash_match: 133 crash_line = host_crash_match.group("line").replace(gd_root_prefix, "") 134 if HOST_ABORT_HEADER in crash_line \ 135 and len(last_20_lines) > 1: 136 abort_line = last_20_lines[-2] 137 crash_log_lines.append(crash_line) 138 except EnvironmentError: 139 logging.error("Cannot open backing log file at {}".format(logpath)) 140 return None, None 141 142 log_tail_20 = "".join(last_20_lines) 143 crash_snippet = "" 144 if abort_line is not None: 145 crash_snippet += "abort log line:\n\n%s\n" % abort_line 146 crash_snippet += "\n".join(crash_log_lines) 147 148 if len(asan_lines) > 0: 149 return "".join(asan_lines), log_tail_20 150 151 if len(crash_log_lines) > 0: 152 return crash_snippet, log_tail_20 153 154 return None, log_tail_20 155