1*387f9dfdSAndroid Build Coastguard Worker#!/usr/bin/python 2*387f9dfdSAndroid Build Coastguard Worker# 3*387f9dfdSAndroid Build Coastguard Worker# memleak Trace and display outstanding allocations to detect 4*387f9dfdSAndroid Build Coastguard Worker# memory leaks in user-mode processes and the kernel. 5*387f9dfdSAndroid Build Coastguard Worker# 6*387f9dfdSAndroid Build Coastguard Worker# USAGE: memleak [-h] [-p PID] [-t] [-a] [-o OLDER] [-c COMMAND] 7*387f9dfdSAndroid Build Coastguard Worker# [-s SAMPLE_RATE] [-d STACK_DEPTH] [-T TOP] [-z MIN_SIZE] 8*387f9dfdSAndroid Build Coastguard Worker# [-Z MAX_SIZE] 9*387f9dfdSAndroid Build Coastguard Worker# [interval] [count] 10*387f9dfdSAndroid Build Coastguard Worker# 11*387f9dfdSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License") 12*387f9dfdSAndroid Build Coastguard Worker# Copyright (C) 2016 Sasha Goldshtein. 13*387f9dfdSAndroid Build Coastguard Worker 14*387f9dfdSAndroid Build Coastguard Workerfrom bcc import BPF 15*387f9dfdSAndroid Build Coastguard Workerfrom time import sleep 16*387f9dfdSAndroid Build Coastguard Workerfrom datetime import datetime 17*387f9dfdSAndroid Build Coastguard Workerimport argparse 18*387f9dfdSAndroid Build Coastguard Workerimport subprocess 19*387f9dfdSAndroid Build Coastguard Workerimport os 20*387f9dfdSAndroid Build Coastguard Worker 21*387f9dfdSAndroid Build Coastguard Workerdef decode_stack(bpf, pid, info): 22*387f9dfdSAndroid Build Coastguard Worker stack = "" 23*387f9dfdSAndroid Build Coastguard Worker if info.num_frames <= 0: 24*387f9dfdSAndroid Build Coastguard Worker return "???" 25*387f9dfdSAndroid Build Coastguard Worker for i in range(0, info.num_frames): 26*387f9dfdSAndroid Build Coastguard Worker addr = info.callstack[i] 27*387f9dfdSAndroid Build Coastguard Worker stack += " %s ;" % bpf.sym(addr, pid, show_offset=True) 28*387f9dfdSAndroid Build Coastguard Worker return stack 29*387f9dfdSAndroid Build Coastguard Worker 30*387f9dfdSAndroid Build Coastguard Workerdef run_command_get_output(command): 31*387f9dfdSAndroid Build Coastguard Worker p = subprocess.Popen(command.split(), 32*387f9dfdSAndroid Build Coastguard Worker stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 33*387f9dfdSAndroid Build Coastguard Worker return iter(p.stdout.readline, b'') 34*387f9dfdSAndroid Build Coastguard Worker 35*387f9dfdSAndroid Build Coastguard Workerdef run_command_get_pid(command): 36*387f9dfdSAndroid Build Coastguard Worker p = subprocess.Popen(command.split()) 37*387f9dfdSAndroid Build Coastguard Worker return p.pid 38*387f9dfdSAndroid Build Coastguard Worker 39*387f9dfdSAndroid Build Coastguard Workerexamples = """ 40*387f9dfdSAndroid Build Coastguard WorkerEXAMPLES: 41*387f9dfdSAndroid Build Coastguard Worker 42*387f9dfdSAndroid Build Coastguard Worker./memleak -p $(pidof allocs) 43*387f9dfdSAndroid Build Coastguard Worker Trace allocations and display a summary of "leaked" (outstanding) 44*387f9dfdSAndroid Build Coastguard Worker allocations every 5 seconds 45*387f9dfdSAndroid Build Coastguard Worker./memleak -p $(pidof allocs) -t 46*387f9dfdSAndroid Build Coastguard Worker Trace allocations and display each individual call to malloc/free 47*387f9dfdSAndroid Build Coastguard Worker./memleak -ap $(pidof allocs) 10 48*387f9dfdSAndroid Build Coastguard Worker Trace allocations and display allocated addresses, sizes, and stacks 49*387f9dfdSAndroid Build Coastguard Worker every 10 seconds for outstanding allocations 50*387f9dfdSAndroid Build Coastguard Worker./memleak -c "./allocs" 51*387f9dfdSAndroid Build Coastguard Worker Run the specified command and trace its allocations 52*387f9dfdSAndroid Build Coastguard Worker./memleak 53*387f9dfdSAndroid Build Coastguard Worker Trace allocations in kernel mode and display a summary of outstanding 54*387f9dfdSAndroid Build Coastguard Worker allocations every 5 seconds 55*387f9dfdSAndroid Build Coastguard Worker./memleak -o 60000 56*387f9dfdSAndroid Build Coastguard Worker Trace allocations in kernel mode and display a summary of outstanding 57*387f9dfdSAndroid Build Coastguard Worker allocations that are at least one minute (60 seconds) old 58*387f9dfdSAndroid Build Coastguard Worker./memleak -s 5 59*387f9dfdSAndroid Build Coastguard Worker Trace roughly every 5th allocation, to reduce overhead 60*387f9dfdSAndroid Build Coastguard Worker""" 61*387f9dfdSAndroid Build Coastguard Worker 62*387f9dfdSAndroid Build Coastguard Workerdescription = """ 63*387f9dfdSAndroid Build Coastguard WorkerTrace outstanding memory allocations that weren't freed. 64*387f9dfdSAndroid Build Coastguard WorkerSupports both user-mode allocations made with malloc/free and kernel-mode 65*387f9dfdSAndroid Build Coastguard Workerallocations made with kmalloc/kfree. 66*387f9dfdSAndroid Build Coastguard Worker""" 67*387f9dfdSAndroid Build Coastguard Worker 68*387f9dfdSAndroid Build Coastguard Workerparser = argparse.ArgumentParser(description=description, 69*387f9dfdSAndroid Build Coastguard Worker formatter_class=argparse.RawDescriptionHelpFormatter, 70*387f9dfdSAndroid Build Coastguard Worker epilog=examples) 71*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-p", "--pid", type=int, default=-1, 72*387f9dfdSAndroid Build Coastguard Worker help="the PID to trace; if not specified, trace kernel allocs") 73*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-t", "--trace", action="store_true", 74*387f9dfdSAndroid Build Coastguard Worker help="print trace messages for each alloc/free call") 75*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("interval", nargs="?", default=5, type=int, 76*387f9dfdSAndroid Build Coastguard Worker help="interval in seconds to print outstanding allocations") 77*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("count", nargs="?", type=int, 78*387f9dfdSAndroid Build Coastguard Worker help="number of times to print the report before exiting") 79*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-a", "--show-allocs", default=False, action="store_true", 80*387f9dfdSAndroid Build Coastguard Worker help="show allocation addresses and sizes as well as call stacks") 81*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-o", "--older", default=500, type=int, 82*387f9dfdSAndroid Build Coastguard Worker help="prune allocations younger than this age in milliseconds") 83*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-c", "--command", 84*387f9dfdSAndroid Build Coastguard Worker help="execute and trace the specified command") 85*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-s", "--sample-rate", default=1, type=int, 86*387f9dfdSAndroid Build Coastguard Worker help="sample every N-th allocation to decrease the overhead") 87*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-d", "--stack-depth", default=10, type=int, 88*387f9dfdSAndroid Build Coastguard Worker help="maximum stack depth to capture") 89*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-T", "--top", type=int, default=10, 90*387f9dfdSAndroid Build Coastguard Worker help="display only this many top allocating stacks (by size)") 91*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-z", "--min-size", type=int, 92*387f9dfdSAndroid Build Coastguard Worker help="capture only allocations larger than this size") 93*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-Z", "--max-size", type=int, 94*387f9dfdSAndroid Build Coastguard Worker help="capture only allocations smaller than this size") 95*387f9dfdSAndroid Build Coastguard Worker 96*387f9dfdSAndroid Build Coastguard Workerargs = parser.parse_args() 97*387f9dfdSAndroid Build Coastguard Worker 98*387f9dfdSAndroid Build Coastguard Workerpid = args.pid 99*387f9dfdSAndroid Build Coastguard Workercommand = args.command 100*387f9dfdSAndroid Build Coastguard Workerkernel_trace = (pid == -1 and command is None) 101*387f9dfdSAndroid Build Coastguard Workertrace_all = args.trace 102*387f9dfdSAndroid Build Coastguard Workerinterval = args.interval 103*387f9dfdSAndroid Build Coastguard Workermin_age_ns = 1e6 * args.older 104*387f9dfdSAndroid Build Coastguard Workersample_every_n = args.sample_rate 105*387f9dfdSAndroid Build Coastguard Workernum_prints = args.count 106*387f9dfdSAndroid Build Coastguard Workermax_stack_size = args.stack_depth + 2 107*387f9dfdSAndroid Build Coastguard Workertop_stacks = args.top 108*387f9dfdSAndroid Build Coastguard Workermin_size = args.min_size 109*387f9dfdSAndroid Build Coastguard Workermax_size = args.max_size 110*387f9dfdSAndroid Build Coastguard Worker 111*387f9dfdSAndroid Build Coastguard Workerif min_size is not None and max_size is not None and min_size > max_size: 112*387f9dfdSAndroid Build Coastguard Worker print("min_size (-z) can't be greater than max_size (-Z)") 113*387f9dfdSAndroid Build Coastguard Worker exit(1) 114*387f9dfdSAndroid Build Coastguard Worker 115*387f9dfdSAndroid Build Coastguard Workerif command is not None: 116*387f9dfdSAndroid Build Coastguard Worker print("Executing '%s' and tracing the resulting process." % command) 117*387f9dfdSAndroid Build Coastguard Worker pid = run_command_get_pid(command) 118*387f9dfdSAndroid Build Coastguard Worker 119*387f9dfdSAndroid Build Coastguard Workerbpf_source = """ 120*387f9dfdSAndroid Build Coastguard Worker#include <uapi/linux/ptrace.h> 121*387f9dfdSAndroid Build Coastguard Worker 122*387f9dfdSAndroid Build Coastguard Workerstruct alloc_info_t { 123*387f9dfdSAndroid Build Coastguard Worker u64 size; 124*387f9dfdSAndroid Build Coastguard Worker u64 timestamp_ns; 125*387f9dfdSAndroid Build Coastguard Worker int num_frames; 126*387f9dfdSAndroid Build Coastguard Worker u64 callstack[MAX_STACK_SIZE]; 127*387f9dfdSAndroid Build Coastguard Worker}; 128*387f9dfdSAndroid Build Coastguard Worker 129*387f9dfdSAndroid Build Coastguard WorkerBPF_HASH(sizes, u64); 130*387f9dfdSAndroid Build Coastguard WorkerBPF_HASH(allocs, u64, struct alloc_info_t); 131*387f9dfdSAndroid Build Coastguard Worker 132*387f9dfdSAndroid Build Coastguard Worker// Adapted from https://github.com/iovisor/bcc/tools/offcputime.py 133*387f9dfdSAndroid Build Coastguard Workerstatic u64 get_frame(u64 *bp) { 134*387f9dfdSAndroid Build Coastguard Worker if (*bp) { 135*387f9dfdSAndroid Build Coastguard Worker // The following stack walker is x86_64 specific 136*387f9dfdSAndroid Build Coastguard Worker u64 ret = 0; 137*387f9dfdSAndroid Build Coastguard Worker if (bpf_probe_read(&ret, sizeof(ret), (void *)(*bp+8))) 138*387f9dfdSAndroid Build Coastguard Worker return 0; 139*387f9dfdSAndroid Build Coastguard Worker if (bpf_probe_read(bp, sizeof(*bp), (void *)*bp)) 140*387f9dfdSAndroid Build Coastguard Worker *bp = 0; 141*387f9dfdSAndroid Build Coastguard Worker return ret; 142*387f9dfdSAndroid Build Coastguard Worker } 143*387f9dfdSAndroid Build Coastguard Worker return 0; 144*387f9dfdSAndroid Build Coastguard Worker} 145*387f9dfdSAndroid Build Coastguard Workerstatic int grab_stack(struct pt_regs *ctx, struct alloc_info_t *info) 146*387f9dfdSAndroid Build Coastguard Worker{ 147*387f9dfdSAndroid Build Coastguard Worker int depth = 0; 148*387f9dfdSAndroid Build Coastguard Worker u64 bp = ctx->bp; 149*387f9dfdSAndroid Build Coastguard Worker GRAB_ONE_FRAME 150*387f9dfdSAndroid Build Coastguard Worker return depth; 151*387f9dfdSAndroid Build Coastguard Worker} 152*387f9dfdSAndroid Build Coastguard Worker 153*387f9dfdSAndroid Build Coastguard Workerint alloc_enter(struct pt_regs *ctx, size_t size) 154*387f9dfdSAndroid Build Coastguard Worker{ 155*387f9dfdSAndroid Build Coastguard Worker SIZE_FILTER 156*387f9dfdSAndroid Build Coastguard Worker if (SAMPLE_EVERY_N > 1) { 157*387f9dfdSAndroid Build Coastguard Worker u64 ts = bpf_ktime_get_ns(); 158*387f9dfdSAndroid Build Coastguard Worker if (ts % SAMPLE_EVERY_N != 0) 159*387f9dfdSAndroid Build Coastguard Worker return 0; 160*387f9dfdSAndroid Build Coastguard Worker } 161*387f9dfdSAndroid Build Coastguard Worker 162*387f9dfdSAndroid Build Coastguard Worker u64 pid = bpf_get_current_pid_tgid(); 163*387f9dfdSAndroid Build Coastguard Worker u64 size64 = size; 164*387f9dfdSAndroid Build Coastguard Worker sizes.update(&pid, &size64); 165*387f9dfdSAndroid Build Coastguard Worker 166*387f9dfdSAndroid Build Coastguard Worker if (SHOULD_PRINT) 167*387f9dfdSAndroid Build Coastguard Worker bpf_trace_printk("alloc entered, size = %u\\n", size); 168*387f9dfdSAndroid Build Coastguard Worker return 0; 169*387f9dfdSAndroid Build Coastguard Worker} 170*387f9dfdSAndroid Build Coastguard Worker 171*387f9dfdSAndroid Build Coastguard Workerint alloc_exit(struct pt_regs *ctx) 172*387f9dfdSAndroid Build Coastguard Worker{ 173*387f9dfdSAndroid Build Coastguard Worker u64 address = ctx->ax; 174*387f9dfdSAndroid Build Coastguard Worker u64 pid = bpf_get_current_pid_tgid(); 175*387f9dfdSAndroid Build Coastguard Worker u64* size64 = sizes.lookup(&pid); 176*387f9dfdSAndroid Build Coastguard Worker struct alloc_info_t info = {0}; 177*387f9dfdSAndroid Build Coastguard Worker 178*387f9dfdSAndroid Build Coastguard Worker if (size64 == 0) 179*387f9dfdSAndroid Build Coastguard Worker return 0; // missed alloc entry 180*387f9dfdSAndroid Build Coastguard Worker 181*387f9dfdSAndroid Build Coastguard Worker info.size = *size64; 182*387f9dfdSAndroid Build Coastguard Worker sizes.delete(&pid); 183*387f9dfdSAndroid Build Coastguard Worker 184*387f9dfdSAndroid Build Coastguard Worker info.timestamp_ns = bpf_ktime_get_ns(); 185*387f9dfdSAndroid Build Coastguard Worker info.num_frames = grab_stack(ctx, &info) - 2; 186*387f9dfdSAndroid Build Coastguard Worker allocs.update(&address, &info); 187*387f9dfdSAndroid Build Coastguard Worker 188*387f9dfdSAndroid Build Coastguard Worker if (SHOULD_PRINT) { 189*387f9dfdSAndroid Build Coastguard Worker bpf_trace_printk("alloc exited, size = %lu, result = %lx," 190*387f9dfdSAndroid Build Coastguard Worker "frames = %d\\n", info.size, address, 191*387f9dfdSAndroid Build Coastguard Worker info.num_frames); 192*387f9dfdSAndroid Build Coastguard Worker } 193*387f9dfdSAndroid Build Coastguard Worker return 0; 194*387f9dfdSAndroid Build Coastguard Worker} 195*387f9dfdSAndroid Build Coastguard Worker 196*387f9dfdSAndroid Build Coastguard Workerint free_enter(struct pt_regs *ctx, void *address) 197*387f9dfdSAndroid Build Coastguard Worker{ 198*387f9dfdSAndroid Build Coastguard Worker u64 addr = (u64)address; 199*387f9dfdSAndroid Build Coastguard Worker struct alloc_info_t *info = allocs.lookup(&addr); 200*387f9dfdSAndroid Build Coastguard Worker if (info == 0) 201*387f9dfdSAndroid Build Coastguard Worker return 0; 202*387f9dfdSAndroid Build Coastguard Worker 203*387f9dfdSAndroid Build Coastguard Worker allocs.delete(&addr); 204*387f9dfdSAndroid Build Coastguard Worker 205*387f9dfdSAndroid Build Coastguard Worker if (SHOULD_PRINT) { 206*387f9dfdSAndroid Build Coastguard Worker bpf_trace_printk("free entered, address = %lx, size = %lu\\n", 207*387f9dfdSAndroid Build Coastguard Worker address, info->size); 208*387f9dfdSAndroid Build Coastguard Worker } 209*387f9dfdSAndroid Build Coastguard Worker return 0; 210*387f9dfdSAndroid Build Coastguard Worker} 211*387f9dfdSAndroid Build Coastguard Worker""" 212*387f9dfdSAndroid Build Coastguard Workerbpf_source = bpf_source.replace("SHOULD_PRINT", "1" if trace_all else "0") 213*387f9dfdSAndroid Build Coastguard Workerbpf_source = bpf_source.replace("SAMPLE_EVERY_N", str(sample_every_n)) 214*387f9dfdSAndroid Build Coastguard Workerbpf_source = bpf_source.replace("GRAB_ONE_FRAME", max_stack_size * 215*387f9dfdSAndroid Build Coastguard Worker "\tif (!(info->callstack[depth++] = get_frame(&bp))) return depth;\n") 216*387f9dfdSAndroid Build Coastguard Workerbpf_source = bpf_source.replace("MAX_STACK_SIZE", str(max_stack_size)) 217*387f9dfdSAndroid Build Coastguard Worker 218*387f9dfdSAndroid Build Coastguard Workersize_filter = "" 219*387f9dfdSAndroid Build Coastguard Workerif min_size is not None and max_size is not None: 220*387f9dfdSAndroid Build Coastguard Worker size_filter = "if (size < %d || size > %d) return 0;" % \ 221*387f9dfdSAndroid Build Coastguard Worker (min_size, max_size) 222*387f9dfdSAndroid Build Coastguard Workerelif min_size is not None: 223*387f9dfdSAndroid Build Coastguard Worker size_filter = "if (size < %d) return 0;" % min_size 224*387f9dfdSAndroid Build Coastguard Workerelif max_size is not None: 225*387f9dfdSAndroid Build Coastguard Worker size_filter = "if (size > %d) return 0;" % max_size 226*387f9dfdSAndroid Build Coastguard Workerbpf_source = bpf_source.replace("SIZE_FILTER", size_filter) 227*387f9dfdSAndroid Build Coastguard Worker 228*387f9dfdSAndroid Build Coastguard Workerbpf_program = BPF(text=bpf_source) 229*387f9dfdSAndroid Build Coastguard Worker 230*387f9dfdSAndroid Build Coastguard Workerif not kernel_trace: 231*387f9dfdSAndroid Build Coastguard Worker print("Attaching to malloc and free in pid %d, Ctrl+C to quit." % pid) 232*387f9dfdSAndroid Build Coastguard Worker bpf_program.attach_uprobe(name="c", sym="malloc", 233*387f9dfdSAndroid Build Coastguard Worker fn_name="alloc_enter", pid=pid) 234*387f9dfdSAndroid Build Coastguard Worker bpf_program.attach_uretprobe(name="c", sym="malloc", 235*387f9dfdSAndroid Build Coastguard Worker fn_name="alloc_exit", pid=pid) 236*387f9dfdSAndroid Build Coastguard Worker bpf_program.attach_uprobe(name="c", sym="free", 237*387f9dfdSAndroid Build Coastguard Worker fn_name="free_enter", pid=pid) 238*387f9dfdSAndroid Build Coastguard Workerelse: 239*387f9dfdSAndroid Build Coastguard Worker print("Attaching to kmalloc and kfree, Ctrl+C to quit.") 240*387f9dfdSAndroid Build Coastguard Worker bpf_program.attach_kprobe(event="__kmalloc", fn_name="alloc_enter") 241*387f9dfdSAndroid Build Coastguard Worker bpf_program.attach_kretprobe(event="__kmalloc", fn_name="alloc_exit") 242*387f9dfdSAndroid Build Coastguard Worker bpf_program.attach_kprobe(event="kfree", fn_name="free_enter") 243*387f9dfdSAndroid Build Coastguard Worker 244*387f9dfdSAndroid Build Coastguard Workerdef print_outstanding(): 245*387f9dfdSAndroid Build Coastguard Worker stacks = {} 246*387f9dfdSAndroid Build Coastguard Worker print("[%s] Top %d stacks with outstanding allocations:" % 247*387f9dfdSAndroid Build Coastguard Worker (datetime.now().strftime("%H:%M:%S"), top_stacks)) 248*387f9dfdSAndroid Build Coastguard Worker allocs = bpf_program.get_table("allocs") 249*387f9dfdSAndroid Build Coastguard Worker for address, info in sorted(allocs.items(), key=lambda a: a[1].size): 250*387f9dfdSAndroid Build Coastguard Worker if BPF.monotonic_time() - min_age_ns < info.timestamp_ns: 251*387f9dfdSAndroid Build Coastguard Worker continue 252*387f9dfdSAndroid Build Coastguard Worker stack = decode_stack(bpf_program, pid, info) 253*387f9dfdSAndroid Build Coastguard Worker if stack in stacks: 254*387f9dfdSAndroid Build Coastguard Worker stacks[stack] = (stacks[stack][0] + 1, 255*387f9dfdSAndroid Build Coastguard Worker stacks[stack][1] + info.size) 256*387f9dfdSAndroid Build Coastguard Worker else: 257*387f9dfdSAndroid Build Coastguard Worker stacks[stack] = (1, info.size) 258*387f9dfdSAndroid Build Coastguard Worker if args.show_allocs: 259*387f9dfdSAndroid Build Coastguard Worker print("\taddr = %x size = %s" % 260*387f9dfdSAndroid Build Coastguard Worker (address.value, info.size)) 261*387f9dfdSAndroid Build Coastguard Worker to_show = sorted(stacks.items(), key=lambda s: s[1][1])[-top_stacks:] 262*387f9dfdSAndroid Build Coastguard Worker for stack, (count, size) in to_show: 263*387f9dfdSAndroid Build Coastguard Worker print("\t%d bytes in %d allocations from stack\n\t\t%s" % 264*387f9dfdSAndroid Build Coastguard Worker (size, count, stack.replace(";", "\n\t\t"))) 265*387f9dfdSAndroid Build Coastguard Worker 266*387f9dfdSAndroid Build Coastguard Workercount_so_far = 0 267*387f9dfdSAndroid Build Coastguard Workerwhile True: 268*387f9dfdSAndroid Build Coastguard Worker if trace_all: 269*387f9dfdSAndroid Build Coastguard Worker print(bpf_program.trace_fields()) 270*387f9dfdSAndroid Build Coastguard Worker else: 271*387f9dfdSAndroid Build Coastguard Worker try: 272*387f9dfdSAndroid Build Coastguard Worker sleep(interval) 273*387f9dfdSAndroid Build Coastguard Worker except KeyboardInterrupt: 274*387f9dfdSAndroid Build Coastguard Worker exit() 275*387f9dfdSAndroid Build Coastguard Worker print_outstanding() 276*387f9dfdSAndroid Build Coastguard Worker count_so_far += 1 277*387f9dfdSAndroid Build Coastguard Worker if num_prints is not None and count_so_far >= num_prints: 278*387f9dfdSAndroid Build Coastguard Worker exit() 279