1*90c8c64dSAndroid Build Coastguard Worker#!/usr/bin/env python3 2*90c8c64dSAndroid Build Coastguard Worker# 3*90c8c64dSAndroid Build Coastguard Worker# Copyright (C) 2021 The Android Open Source Project 4*90c8c64dSAndroid Build Coastguard Worker# 5*90c8c64dSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*90c8c64dSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*90c8c64dSAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*90c8c64dSAndroid Build Coastguard Worker# 9*90c8c64dSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*90c8c64dSAndroid Build Coastguard Worker# 11*90c8c64dSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*90c8c64dSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*90c8c64dSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*90c8c64dSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*90c8c64dSAndroid Build Coastguard Worker# limitations under the License. 16*90c8c64dSAndroid Build Coastguard Worker 17*90c8c64dSAndroid Build Coastguard Worker# acov-llvm.py is a tool for gathering coverage information from a device and 18*90c8c64dSAndroid Build Coastguard Worker# generating an LLVM coverage report from that information. To use: 19*90c8c64dSAndroid Build Coastguard Worker# 20*90c8c64dSAndroid Build Coastguard Worker# This script would work only when the device image was built with the following 21*90c8c64dSAndroid Build Coastguard Worker# build variables: 22*90c8c64dSAndroid Build Coastguard Worker# CLANG_COVERAGE=true NATIVE_COVERAGE_PATHS="<list-of-paths>" 23*90c8c64dSAndroid Build Coastguard Worker# 24*90c8c64dSAndroid Build Coastguard Worker# 1. [optional] Reset coverage information on the device 25*90c8c64dSAndroid Build Coastguard Worker# $ acov-llvm.py clean-device 26*90c8c64dSAndroid Build Coastguard Worker# 27*90c8c64dSAndroid Build Coastguard Worker# 2. Run tests 28*90c8c64dSAndroid Build Coastguard Worker# 29*90c8c64dSAndroid Build Coastguard Worker# 3. Flush coverage 30*90c8c64dSAndroid Build Coastguard Worker# from select daemons and system processes on the device 31*90c8c64dSAndroid Build Coastguard Worker# $ acov-llvm.py flush [list of process names] 32*90c8c64dSAndroid Build Coastguard Worker# $ acov-llvm.py flush -p [list of process pids] 33*90c8c64dSAndroid Build Coastguard Worker# or from all processes on the device: 34*90c8c64dSAndroid Build Coastguard Worker# $ acov-llvm.py flush 35*90c8c64dSAndroid Build Coastguard Worker# 36*90c8c64dSAndroid Build Coastguard Worker# 4. Pull coverage from device and generate coverage report 37*90c8c64dSAndroid Build Coastguard Worker# $ acov-llvm.py report -s <one-or-more-source-paths-in-$ANDROID_BUILD_TOP> \ 38*90c8c64dSAndroid Build Coastguard Worker# -b <one-or-more-binaries-in-$OUT> \ 39*90c8c64dSAndroid Build Coastguard Worker# E.g.: 40*90c8c64dSAndroid Build Coastguard Worker# acov-llvm.py report \ 41*90c8c64dSAndroid Build Coastguard Worker# -s bionic \ 42*90c8c64dSAndroid Build Coastguard Worker# -b \ 43*90c8c64dSAndroid Build Coastguard Worker# $OUT/symbols/apex/com.android.runtime/lib/bionic/libc.so \ 44*90c8c64dSAndroid Build Coastguard Worker# $OUT/symbols/apex/com.android.runtime/lib/bionic/libm.so 45*90c8c64dSAndroid Build Coastguard Worker 46*90c8c64dSAndroid Build Coastguard Workerimport argparse 47*90c8c64dSAndroid Build Coastguard Workerimport logging 48*90c8c64dSAndroid Build Coastguard Workerimport os 49*90c8c64dSAndroid Build Coastguard Workerimport re 50*90c8c64dSAndroid Build Coastguard Workerimport subprocess 51*90c8c64dSAndroid Build Coastguard Workerimport time 52*90c8c64dSAndroid Build Coastguard Workerimport tempfile 53*90c8c64dSAndroid Build Coastguard Worker 54*90c8c64dSAndroid Build Coastguard Workerfrom pathlib import Path 55*90c8c64dSAndroid Build Coastguard Worker 56*90c8c64dSAndroid Build Coastguard WorkerFLUSH_SLEEP = 60 57*90c8c64dSAndroid Build Coastguard Worker 58*90c8c64dSAndroid Build Coastguard Worker 59*90c8c64dSAndroid Build Coastguard Workerdef android_build_top(): 60*90c8c64dSAndroid Build Coastguard Worker return Path(os.environ.get('ANDROID_BUILD_TOP', None)) 61*90c8c64dSAndroid Build Coastguard Worker 62*90c8c64dSAndroid Build Coastguard Worker 63*90c8c64dSAndroid Build Coastguard Workerdef _get_clang_revision(): 64*90c8c64dSAndroid Build Coastguard Worker version_output = subprocess.check_output( 65*90c8c64dSAndroid Build Coastguard Worker android_build_top() / 'build/soong/scripts/get_clang_version.py', 66*90c8c64dSAndroid Build Coastguard Worker text=True) 67*90c8c64dSAndroid Build Coastguard Worker return version_output.strip() 68*90c8c64dSAndroid Build Coastguard Worker 69*90c8c64dSAndroid Build Coastguard Worker 70*90c8c64dSAndroid Build Coastguard WorkerCLANG_TOP = android_build_top() / 'prebuilts/clang/host/linux-x86/' \ 71*90c8c64dSAndroid Build Coastguard Worker / _get_clang_revision() 72*90c8c64dSAndroid Build Coastguard WorkerLLVM_PROFDATA_PATH = CLANG_TOP / 'bin' / 'llvm-profdata' 73*90c8c64dSAndroid Build Coastguard WorkerLLVM_COV_PATH = CLANG_TOP / 'bin' / 'llvm-cov' 74*90c8c64dSAndroid Build Coastguard Worker 75*90c8c64dSAndroid Build Coastguard Worker 76*90c8c64dSAndroid Build Coastguard Workerdef check_output(cmd, *args, **kwargs): 77*90c8c64dSAndroid Build Coastguard Worker """subprocess.check_output with logging.""" 78*90c8c64dSAndroid Build Coastguard Worker cmd_str = cmd if isinstance(cmd, str) else ' '.join(cmd) 79*90c8c64dSAndroid Build Coastguard Worker logging.debug(cmd_str) 80*90c8c64dSAndroid Build Coastguard Worker return subprocess.run( 81*90c8c64dSAndroid Build Coastguard Worker cmd, *args, **kwargs, check=True, stdout=subprocess.PIPE).stdout 82*90c8c64dSAndroid Build Coastguard Worker 83*90c8c64dSAndroid Build Coastguard Worker 84*90c8c64dSAndroid Build Coastguard Workerdef adb(cmd, *args, **kwargs): 85*90c8c64dSAndroid Build Coastguard Worker """call 'adb <cmd>' with logging.""" 86*90c8c64dSAndroid Build Coastguard Worker return check_output(['adb'] + cmd, *args, **kwargs) 87*90c8c64dSAndroid Build Coastguard Worker 88*90c8c64dSAndroid Build Coastguard Worker 89*90c8c64dSAndroid Build Coastguard Workerdef adb_root(*args, **kwargs): 90*90c8c64dSAndroid Build Coastguard Worker """call 'adb root' with logging.""" 91*90c8c64dSAndroid Build Coastguard Worker return adb(['root'], *args, **kwargs) 92*90c8c64dSAndroid Build Coastguard Worker 93*90c8c64dSAndroid Build Coastguard Worker 94*90c8c64dSAndroid Build Coastguard Workerdef adb_shell(cmd, *args, **kwargs): 95*90c8c64dSAndroid Build Coastguard Worker """call 'adb shell <cmd>' with logging.""" 96*90c8c64dSAndroid Build Coastguard Worker return adb(['shell'] + cmd, *args, **kwargs) 97*90c8c64dSAndroid Build Coastguard Worker 98*90c8c64dSAndroid Build Coastguard Worker 99*90c8c64dSAndroid Build Coastguard Workerdef send_flush_signal(pids=None): 100*90c8c64dSAndroid Build Coastguard Worker 101*90c8c64dSAndroid Build Coastguard Worker def _has_handler_sig37(pid): 102*90c8c64dSAndroid Build Coastguard Worker try: 103*90c8c64dSAndroid Build Coastguard Worker status = adb_shell(['cat', f'/proc/{pid}/status'], 104*90c8c64dSAndroid Build Coastguard Worker text=True, 105*90c8c64dSAndroid Build Coastguard Worker stderr=subprocess.DEVNULL) 106*90c8c64dSAndroid Build Coastguard Worker except subprocess.CalledProcessError: 107*90c8c64dSAndroid Build Coastguard Worker logging.warning(f'Process {pid} is no longer active') 108*90c8c64dSAndroid Build Coastguard Worker return False 109*90c8c64dSAndroid Build Coastguard Worker 110*90c8c64dSAndroid Build Coastguard Worker status = status.split('\n') 111*90c8c64dSAndroid Build Coastguard Worker sigcgt = [ 112*90c8c64dSAndroid Build Coastguard Worker line.split(':\t')[1] for line in status if line.startswith('SigCgt') 113*90c8c64dSAndroid Build Coastguard Worker ] 114*90c8c64dSAndroid Build Coastguard Worker if not sigcgt: 115*90c8c64dSAndroid Build Coastguard Worker logging.warning(f'Cannot find \'SigCgt:\' in /proc/{pid}/status') 116*90c8c64dSAndroid Build Coastguard Worker return False 117*90c8c64dSAndroid Build Coastguard Worker return int(sigcgt[0], base=16) & (1 << 36) 118*90c8c64dSAndroid Build Coastguard Worker 119*90c8c64dSAndroid Build Coastguard Worker if not pids: 120*90c8c64dSAndroid Build Coastguard Worker output = adb_shell(['ps', '-eo', 'pid'], text=True) 121*90c8c64dSAndroid Build Coastguard Worker pids = [pid.strip() for pid in output.split()] 122*90c8c64dSAndroid Build Coastguard Worker pids = pids[1:] # ignore the column header 123*90c8c64dSAndroid Build Coastguard Worker pids = [pid for pid in pids if _has_handler_sig37(pid)] 124*90c8c64dSAndroid Build Coastguard Worker 125*90c8c64dSAndroid Build Coastguard Worker if not pids: 126*90c8c64dSAndroid Build Coastguard Worker logging.warning( 127*90c8c64dSAndroid Build Coastguard Worker f'couldn\'t find any process with handler for signal 37') 128*90c8c64dSAndroid Build Coastguard Worker 129*90c8c64dSAndroid Build Coastguard Worker # Some processes may have exited after we run `ps` command above - ignore failures when 130*90c8c64dSAndroid Build Coastguard Worker # sending flush signal. 131*90c8c64dSAndroid Build Coastguard Worker # We rely on kill(1) sending the signal to all pids on the command line even if some don't 132*90c8c64dSAndroid Build Coastguard Worker # exist. This is true of toybox and "probably implied" by POSIX, even if not explicitly called 133*90c8c64dSAndroid Build Coastguard Worker # out [https://pubs.opengroup.org/onlinepubs/9699919799/utilities/kill.html]. 134*90c8c64dSAndroid Build Coastguard Worker try: 135*90c8c64dSAndroid Build Coastguard Worker adb_shell(['kill', '-37'] + pids) 136*90c8c64dSAndroid Build Coastguard Worker except subprocess.CalledProcessError: 137*90c8c64dSAndroid Build Coastguard Worker logging.warning('Sending flush signal failed - some pids no longer active') 138*90c8c64dSAndroid Build Coastguard Worker 139*90c8c64dSAndroid Build Coastguard Worker 140*90c8c64dSAndroid Build Coastguard Workerdef do_clean_device(args): 141*90c8c64dSAndroid Build Coastguard Worker adb_root() 142*90c8c64dSAndroid Build Coastguard Worker 143*90c8c64dSAndroid Build Coastguard Worker logging.info('resetting coverage on device') 144*90c8c64dSAndroid Build Coastguard Worker send_flush_signal() 145*90c8c64dSAndroid Build Coastguard Worker 146*90c8c64dSAndroid Build Coastguard Worker logging.info( 147*90c8c64dSAndroid Build Coastguard Worker f'sleeping for {FLUSH_SLEEP} seconds for coverage to be written') 148*90c8c64dSAndroid Build Coastguard Worker time.sleep(FLUSH_SLEEP) 149*90c8c64dSAndroid Build Coastguard Worker 150*90c8c64dSAndroid Build Coastguard Worker logging.info('deleting coverage data from device') 151*90c8c64dSAndroid Build Coastguard Worker adb_shell(['rm', '-rf', '/data/misc/trace/*.profraw']) 152*90c8c64dSAndroid Build Coastguard Worker 153*90c8c64dSAndroid Build Coastguard Worker 154*90c8c64dSAndroid Build Coastguard Workerdef do_flush(args): 155*90c8c64dSAndroid Build Coastguard Worker adb_root() 156*90c8c64dSAndroid Build Coastguard Worker 157*90c8c64dSAndroid Build Coastguard Worker if args.procnames: 158*90c8c64dSAndroid Build Coastguard Worker pids = adb_shell(['pidof'] + args.procnames, text=True).split() 159*90c8c64dSAndroid Build Coastguard Worker logging.info(f'flushing coverage for pids: {pids}') 160*90c8c64dSAndroid Build Coastguard Worker elif args.pids: 161*90c8c64dSAndroid Build Coastguard Worker pids = args.pids 162*90c8c64dSAndroid Build Coastguard Worker logging.info(f'flushing coverage for pids: {pids}') 163*90c8c64dSAndroid Build Coastguard Worker else: 164*90c8c64dSAndroid Build Coastguard Worker pids = None 165*90c8c64dSAndroid Build Coastguard Worker logging.info('flushing coverage for all processes on device') 166*90c8c64dSAndroid Build Coastguard Worker 167*90c8c64dSAndroid Build Coastguard Worker send_flush_signal(pids) 168*90c8c64dSAndroid Build Coastguard Worker 169*90c8c64dSAndroid Build Coastguard Worker logging.info( 170*90c8c64dSAndroid Build Coastguard Worker f'sleeping for {FLUSH_SLEEP} seconds for coverage to be written') 171*90c8c64dSAndroid Build Coastguard Worker time.sleep(FLUSH_SLEEP) 172*90c8c64dSAndroid Build Coastguard Worker 173*90c8c64dSAndroid Build Coastguard Worker 174*90c8c64dSAndroid Build Coastguard Workerdef do_report(args): 175*90c8c64dSAndroid Build Coastguard Worker adb_root() 176*90c8c64dSAndroid Build Coastguard Worker 177*90c8c64dSAndroid Build Coastguard Worker temp_dir = tempfile.mkdtemp( 178*90c8c64dSAndroid Build Coastguard Worker prefix='covreport-', dir=os.environ.get('ANDROID_BUILD_TOP', None)) 179*90c8c64dSAndroid Build Coastguard Worker logging.info(f'generating coverage report in {temp_dir}') 180*90c8c64dSAndroid Build Coastguard Worker 181*90c8c64dSAndroid Build Coastguard Worker # Pull coverage files from /data/misc/trace on the device 182*90c8c64dSAndroid Build Coastguard Worker compressed = adb_shell(['tar', '-czf', '-', '-C', '/data/misc', 'trace']) 183*90c8c64dSAndroid Build Coastguard Worker check_output(['tar', 'zxvf', '-', '-C', temp_dir], input=compressed) 184*90c8c64dSAndroid Build Coastguard Worker 185*90c8c64dSAndroid Build Coastguard Worker # Call llvm-profdata followed by llvm-cov 186*90c8c64dSAndroid Build Coastguard Worker profdata = f'{temp_dir}/merged.profdata' 187*90c8c64dSAndroid Build Coastguard Worker check_output( 188*90c8c64dSAndroid Build Coastguard Worker f'{LLVM_PROFDATA_PATH} merge --failure-mode=all --output={profdata} {temp_dir}/trace/*.profraw', 189*90c8c64dSAndroid Build Coastguard Worker shell=True) 190*90c8c64dSAndroid Build Coastguard Worker 191*90c8c64dSAndroid Build Coastguard Worker object_flags = [args.binary[0]] + ['--object=' + b for b in args.binary[1:]] 192*90c8c64dSAndroid Build Coastguard Worker source_dirs = ['/proc/self/cwd/' + s for s in args.source_dir] 193*90c8c64dSAndroid Build Coastguard Worker 194*90c8c64dSAndroid Build Coastguard Worker output_dir = f'{temp_dir}/html' 195*90c8c64dSAndroid Build Coastguard Worker 196*90c8c64dSAndroid Build Coastguard Worker check_output([ 197*90c8c64dSAndroid Build Coastguard Worker str(LLVM_COV_PATH), 'show', f'--instr-profile={profdata}', 198*90c8c64dSAndroid Build Coastguard Worker '--format=html', f'--output-dir={output_dir}', 199*90c8c64dSAndroid Build Coastguard Worker '--show-region-summary=false' 200*90c8c64dSAndroid Build Coastguard Worker ] + object_flags + source_dirs) 201*90c8c64dSAndroid Build Coastguard Worker 202*90c8c64dSAndroid Build Coastguard Worker check_output(['chmod', '+rx', temp_dir]) 203*90c8c64dSAndroid Build Coastguard Worker 204*90c8c64dSAndroid Build Coastguard Worker print(f'Coverage report data written in {output_dir}') 205*90c8c64dSAndroid Build Coastguard Worker 206*90c8c64dSAndroid Build Coastguard Worker 207*90c8c64dSAndroid Build Coastguard Workerdef parse_args(): 208*90c8c64dSAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 209*90c8c64dSAndroid Build Coastguard Worker parser.add_argument( 210*90c8c64dSAndroid Build Coastguard Worker '-v', 211*90c8c64dSAndroid Build Coastguard Worker '--verbose', 212*90c8c64dSAndroid Build Coastguard Worker action='store_true', 213*90c8c64dSAndroid Build Coastguard Worker default=False, 214*90c8c64dSAndroid Build Coastguard Worker help='enable debug logging') 215*90c8c64dSAndroid Build Coastguard Worker 216*90c8c64dSAndroid Build Coastguard Worker subparsers = parser.add_subparsers(dest='command', required=True) 217*90c8c64dSAndroid Build Coastguard Worker 218*90c8c64dSAndroid Build Coastguard Worker clean_device = subparsers.add_parser( 219*90c8c64dSAndroid Build Coastguard Worker 'clean-device', help='reset coverage on device') 220*90c8c64dSAndroid Build Coastguard Worker clean_device.set_defaults(func=do_clean_device) 221*90c8c64dSAndroid Build Coastguard Worker 222*90c8c64dSAndroid Build Coastguard Worker flush = subparsers.add_parser( 223*90c8c64dSAndroid Build Coastguard Worker 'flush', help='flush coverage for processes on device') 224*90c8c64dSAndroid Build Coastguard Worker flush.add_argument( 225*90c8c64dSAndroid Build Coastguard Worker 'procnames', 226*90c8c64dSAndroid Build Coastguard Worker nargs='*', 227*90c8c64dSAndroid Build Coastguard Worker metavar='PROCNAME', 228*90c8c64dSAndroid Build Coastguard Worker help='flush coverage for one or more processes with name PROCNAME') 229*90c8c64dSAndroid Build Coastguard Worker flush.add_argument( 230*90c8c64dSAndroid Build Coastguard Worker '-p', 231*90c8c64dSAndroid Build Coastguard Worker '--pids', 232*90c8c64dSAndroid Build Coastguard Worker nargs='+', 233*90c8c64dSAndroid Build Coastguard Worker metavar='PROCID', 234*90c8c64dSAndroid Build Coastguard Worker required=False, 235*90c8c64dSAndroid Build Coastguard Worker help='flush coverage for one or more processes with name PROCID') 236*90c8c64dSAndroid Build Coastguard Worker flush.set_defaults(func=do_flush) 237*90c8c64dSAndroid Build Coastguard Worker 238*90c8c64dSAndroid Build Coastguard Worker report = subparsers.add_parser( 239*90c8c64dSAndroid Build Coastguard Worker 'report', help='fetch coverage from device and generate report') 240*90c8c64dSAndroid Build Coastguard Worker report.add_argument( 241*90c8c64dSAndroid Build Coastguard Worker '-b', 242*90c8c64dSAndroid Build Coastguard Worker '--binary', 243*90c8c64dSAndroid Build Coastguard Worker nargs='+', 244*90c8c64dSAndroid Build Coastguard Worker metavar='BINARY', 245*90c8c64dSAndroid Build Coastguard Worker action='extend', 246*90c8c64dSAndroid Build Coastguard Worker required=True, 247*90c8c64dSAndroid Build Coastguard Worker help='generate coverage report for BINARY') 248*90c8c64dSAndroid Build Coastguard Worker report.add_argument( 249*90c8c64dSAndroid Build Coastguard Worker '-s', 250*90c8c64dSAndroid Build Coastguard Worker '--source-dir', 251*90c8c64dSAndroid Build Coastguard Worker nargs='+', 252*90c8c64dSAndroid Build Coastguard Worker action='extend', 253*90c8c64dSAndroid Build Coastguard Worker metavar='PATH', 254*90c8c64dSAndroid Build Coastguard Worker required=True, 255*90c8c64dSAndroid Build Coastguard Worker help='generate coverage report for source files in PATH') 256*90c8c64dSAndroid Build Coastguard Worker report.set_defaults(func=do_report) 257*90c8c64dSAndroid Build Coastguard Worker return parser.parse_args() 258*90c8c64dSAndroid Build Coastguard Worker 259*90c8c64dSAndroid Build Coastguard Worker 260*90c8c64dSAndroid Build Coastguard Workerdef main(): 261*90c8c64dSAndroid Build Coastguard Worker args = parse_args() 262*90c8c64dSAndroid Build Coastguard Worker if args.verbose: 263*90c8c64dSAndroid Build Coastguard Worker logging.basicConfig(level=logging.DEBUG) 264*90c8c64dSAndroid Build Coastguard Worker 265*90c8c64dSAndroid Build Coastguard Worker args.func(args) 266*90c8c64dSAndroid Build Coastguard Worker 267*90c8c64dSAndroid Build Coastguard Worker 268*90c8c64dSAndroid Build Coastguard Workerif __name__ == '__main__': 269*90c8c64dSAndroid Build Coastguard Worker main() 270