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 cert 54 :return: root directory string of gd test library 55 """ 56 return str(Path(__file__).absolute().parents[1]) 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 for conn in listening_conns_for_port: 76 logging.warning("Freeing port %d used by %s" % (conn.laddr.port, str(conn))) 77 if not conn.pid: 78 logging.error("Failed to kill process occupying port %d due to lack of pid" % conn.laddr.port) 79 success = False 80 continue 81 logging.warning("Killing pid %d that is using port port %d" % (conn.pid, conn.laddr.port)) 82 process = psutil.Process(conn.pid) 83 process.kill() 84 try: 85 process.wait(timeout=timeout_seconds) 86 except psutil.TimeoutExpired: 87 logging.error("SIGKILL timeout after %d seconds for pid %d" % (timeout_seconds, conn.pid)) 88 continue 89 return success 90 91 92# 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 93HOST_CRASH_LINE_REGEX = re.compile(r"^.* - crash_callback: (?P<line>.*)$") 94HOST_ABORT_HEADER = "Process crashed, signal: Aborted" 95ASAN_OUTPUT_START_REGEX = re.compile(r"^==.*AddressSanitizer.*$") 96 97 98def read_crash_snippet_and_log_tail(logpath): 99 """ 100 Get crash snippet if regex matched or last 20 lines of log 101 :return: crash_snippet, log_tail_20 102 1) crash snippet without timestamp in one string; 103 2) last 20 lines of log in one string; 104 """ 105 gd_root_prefix = get_gd_root() + "/" 106 abort_line = None 107 last_20_lines = deque(maxlen=20) 108 crash_log_lines = [] 109 asan = False 110 asan_lines = [] 111 112 with open(logpath) as f: 113 for _, line in enumerate(f): 114 last_20_lines.append(line) 115 asan_match = ASAN_OUTPUT_START_REGEX.match(line) 116 if asan or asan_match: 117 asan_lines.append(line) 118 asan = True 119 continue 120 121 host_crash_match = HOST_CRASH_LINE_REGEX.match(line) 122 if host_crash_match: 123 crash_line = host_crash_match.group("line").replace(gd_root_prefix, "") 124 if HOST_ABORT_HEADER in crash_line \ 125 and len(last_20_lines) > 1: 126 abort_line = last_20_lines[-2] 127 crash_log_lines.append(crash_line) 128 129 log_tail_20 = "".join(last_20_lines) 130 crash_snippet = "" 131 if abort_line is not None: 132 crash_snippet += "abort log line:\n\n%s\n" % abort_line 133 crash_snippet += "\n".join(crash_log_lines) 134 135 if len(asan_lines) > 0: 136 return "".join(asan_lines), log_tail_20 137 138 if len(crash_log_lines) > 0: 139 return crash_snippet, log_tail_20 140 141 return None, log_tail_20 142