1*6dbdd20aSAndroid Build Coastguard Worker#!/usr/bin/env python3 2*6dbdd20aSAndroid Build Coastguard Worker# Copyright (C) 2022 The Android Open Source Project 3*6dbdd20aSAndroid Build Coastguard Worker# 4*6dbdd20aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 5*6dbdd20aSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 6*6dbdd20aSAndroid Build Coastguard Worker# You may obtain a copy of the License at 7*6dbdd20aSAndroid Build Coastguard Worker# 8*6dbdd20aSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 9*6dbdd20aSAndroid Build Coastguard Worker# 10*6dbdd20aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 11*6dbdd20aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 12*6dbdd20aSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*6dbdd20aSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 14*6dbdd20aSAndroid Build Coastguard Worker# limitations under the License. 15*6dbdd20aSAndroid Build Coastguard Worker"""Runs tracing with CPU profiling enabled, and symbolizes traces if requested. 16*6dbdd20aSAndroid Build Coastguard Worker 17*6dbdd20aSAndroid Build Coastguard WorkerFor usage instructions, please see: 18*6dbdd20aSAndroid Build Coastguard Workerhttps://perfetto.dev/docs/quickstart/callstack-sampling 19*6dbdd20aSAndroid Build Coastguard Worker 20*6dbdd20aSAndroid Build Coastguard WorkerAdapted in large part from `heap_profile`. 21*6dbdd20aSAndroid Build Coastguard Worker""" 22*6dbdd20aSAndroid Build Coastguard Worker 23*6dbdd20aSAndroid Build Coastguard Workerimport argparse 24*6dbdd20aSAndroid Build Coastguard Workerimport os 25*6dbdd20aSAndroid Build Coastguard Workerimport shutil 26*6dbdd20aSAndroid Build Coastguard Workerimport signal 27*6dbdd20aSAndroid Build Coastguard Workerimport subprocess 28*6dbdd20aSAndroid Build Coastguard Workerimport sys 29*6dbdd20aSAndroid Build Coastguard Workerimport tempfile 30*6dbdd20aSAndroid Build Coastguard Workerimport textwrap 31*6dbdd20aSAndroid Build Coastguard Workerimport time 32*6dbdd20aSAndroid Build Coastguard Workerimport uuid 33*6dbdd20aSAndroid Build Coastguard Worker 34*6dbdd20aSAndroid Build Coastguard Workerfrom perfetto.prebuilts.manifests.traceconv import * 35*6dbdd20aSAndroid Build Coastguard Workerfrom perfetto.prebuilts.perfetto_prebuilts import * 36*6dbdd20aSAndroid Build Coastguard Worker 37*6dbdd20aSAndroid Build Coastguard Worker# Used for creating directories, etc. 38*6dbdd20aSAndroid Build Coastguard WorkerUUID = str(uuid.uuid4())[-6:] 39*6dbdd20aSAndroid Build Coastguard Worker 40*6dbdd20aSAndroid Build Coastguard Worker# See `sigint_handler` below. 41*6dbdd20aSAndroid Build Coastguard WorkerIS_INTERRUPTED = False 42*6dbdd20aSAndroid Build Coastguard Worker 43*6dbdd20aSAndroid Build Coastguard Worker 44*6dbdd20aSAndroid Build Coastguard Workerdef sigint_handler(signal, frame): 45*6dbdd20aSAndroid Build Coastguard Worker """Useful for cleanly interrupting tracing.""" 46*6dbdd20aSAndroid Build Coastguard Worker global IS_INTERRUPTED 47*6dbdd20aSAndroid Build Coastguard Worker IS_INTERRUPTED = True 48*6dbdd20aSAndroid Build Coastguard Worker 49*6dbdd20aSAndroid Build Coastguard Worker 50*6dbdd20aSAndroid Build Coastguard Workerdef exit_with_no_profile(): 51*6dbdd20aSAndroid Build Coastguard Worker sys.exit("No profiles generated.") 52*6dbdd20aSAndroid Build Coastguard Worker 53*6dbdd20aSAndroid Build Coastguard Worker 54*6dbdd20aSAndroid Build Coastguard Workerdef exit_with_bug_report(error): 55*6dbdd20aSAndroid Build Coastguard Worker sys.exit( 56*6dbdd20aSAndroid Build Coastguard Worker "{}\n\n If this is unexpected, please consider filing a bug at: \n" 57*6dbdd20aSAndroid Build Coastguard Worker "https://perfetto.dev/docs/contributing/getting-started#bugs.".format( 58*6dbdd20aSAndroid Build Coastguard Worker error)) 59*6dbdd20aSAndroid Build Coastguard Worker 60*6dbdd20aSAndroid Build Coastguard Worker 61*6dbdd20aSAndroid Build Coastguard Workerdef adb_check_output(command): 62*6dbdd20aSAndroid Build Coastguard Worker """Runs an `adb` command and returns its output.""" 63*6dbdd20aSAndroid Build Coastguard Worker try: 64*6dbdd20aSAndroid Build Coastguard Worker return subprocess.check_output(command).decode('utf-8') 65*6dbdd20aSAndroid Build Coastguard Worker except FileNotFoundError: 66*6dbdd20aSAndroid Build Coastguard Worker sys.exit("`adb` not found: Is it installed or on PATH?") 67*6dbdd20aSAndroid Build Coastguard Worker except subprocess.CalledProcessError as error: 68*6dbdd20aSAndroid Build Coastguard Worker sys.exit("`adb` error: Are any (or multiple) devices connected?\n" 69*6dbdd20aSAndroid Build Coastguard Worker "If multiple devices are connected, please select one by " 70*6dbdd20aSAndroid Build Coastguard Worker "setting `ANDROID_SERIAL=device_id`.\n" 71*6dbdd20aSAndroid Build Coastguard Worker "{}".format(error)) 72*6dbdd20aSAndroid Build Coastguard Worker except Exception as error: 73*6dbdd20aSAndroid Build Coastguard Worker exit_with_bug_report(error) 74*6dbdd20aSAndroid Build Coastguard Worker 75*6dbdd20aSAndroid Build Coastguard Worker 76*6dbdd20aSAndroid Build Coastguard Workerdef parse_and_validate_args(): 77*6dbdd20aSAndroid Build Coastguard Worker """Parses, validates, and returns command-line arguments for this script.""" 78*6dbdd20aSAndroid Build Coastguard Worker DESCRIPTION = """Runs tracing with CPU profiling enabled, and symbolizes 79*6dbdd20aSAndroid Build Coastguard Worker traces if requested. 80*6dbdd20aSAndroid Build Coastguard Worker 81*6dbdd20aSAndroid Build Coastguard Worker For usage instructions, please see: 82*6dbdd20aSAndroid Build Coastguard Worker https://perfetto.dev/docs/quickstart/callstack-sampling 83*6dbdd20aSAndroid Build Coastguard Worker """ 84*6dbdd20aSAndroid Build Coastguard Worker parser = argparse.ArgumentParser(description=DESCRIPTION) 85*6dbdd20aSAndroid Build Coastguard Worker parser.add_argument( 86*6dbdd20aSAndroid Build Coastguard Worker "-f", 87*6dbdd20aSAndroid Build Coastguard Worker "--frequency", 88*6dbdd20aSAndroid Build Coastguard Worker help="Sampling frequency (Hz). " 89*6dbdd20aSAndroid Build Coastguard Worker "Default: 100 Hz.", 90*6dbdd20aSAndroid Build Coastguard Worker metavar="FREQUENCY", 91*6dbdd20aSAndroid Build Coastguard Worker type=int, 92*6dbdd20aSAndroid Build Coastguard Worker default=100) 93*6dbdd20aSAndroid Build Coastguard Worker parser.add_argument( 94*6dbdd20aSAndroid Build Coastguard Worker "-d", 95*6dbdd20aSAndroid Build Coastguard Worker "--duration", 96*6dbdd20aSAndroid Build Coastguard Worker help="Duration of profile (ms). 0 to run until interrupted. " 97*6dbdd20aSAndroid Build Coastguard Worker "Default: until interrupted by user.", 98*6dbdd20aSAndroid Build Coastguard Worker metavar="DURATION", 99*6dbdd20aSAndroid Build Coastguard Worker type=int, 100*6dbdd20aSAndroid Build Coastguard Worker default=0) 101*6dbdd20aSAndroid Build Coastguard Worker # Profiling using hardware counters. 102*6dbdd20aSAndroid Build Coastguard Worker parser.add_argument( 103*6dbdd20aSAndroid Build Coastguard Worker "-e", 104*6dbdd20aSAndroid Build Coastguard Worker "--event", 105*6dbdd20aSAndroid Build Coastguard Worker help="Use the specified hardware counter event for sampling.", 106*6dbdd20aSAndroid Build Coastguard Worker metavar="EVENT", 107*6dbdd20aSAndroid Build Coastguard Worker action="append", 108*6dbdd20aSAndroid Build Coastguard Worker # See: '//perfetto/protos/perfetto/trace/perfetto_trace.proto'. 109*6dbdd20aSAndroid Build Coastguard Worker choices=['HW_CPU_CYCLES', 'HW_INSTRUCTIONS', 'HW_CACHE_REFERENCES', 110*6dbdd20aSAndroid Build Coastguard Worker 'HW_CACHE_MISSES', 'HW_BRANCH_INSTRUCTIONS', 'HW_BRANCH_MISSES', 111*6dbdd20aSAndroid Build Coastguard Worker 'HW_BUS_CYCLES', 'HW_STALLED_CYCLES_FRONTEND', 112*6dbdd20aSAndroid Build Coastguard Worker 'HW_STALLED_CYCLES_BACKEND'], 113*6dbdd20aSAndroid Build Coastguard Worker default=[]) 114*6dbdd20aSAndroid Build Coastguard Worker parser.add_argument( 115*6dbdd20aSAndroid Build Coastguard Worker "-k", 116*6dbdd20aSAndroid Build Coastguard Worker "--kernel-frames", 117*6dbdd20aSAndroid Build Coastguard Worker help="Collect kernel frames. Default: false.", 118*6dbdd20aSAndroid Build Coastguard Worker action="store_true", 119*6dbdd20aSAndroid Build Coastguard Worker default=False) 120*6dbdd20aSAndroid Build Coastguard Worker parser.add_argument( 121*6dbdd20aSAndroid Build Coastguard Worker "-n", 122*6dbdd20aSAndroid Build Coastguard Worker "--name", 123*6dbdd20aSAndroid Build Coastguard Worker help="Comma-separated list of names of processes to be profiled.", 124*6dbdd20aSAndroid Build Coastguard Worker metavar="NAMES", 125*6dbdd20aSAndroid Build Coastguard Worker default=None) 126*6dbdd20aSAndroid Build Coastguard Worker parser.add_argument( 127*6dbdd20aSAndroid Build Coastguard Worker "-p", 128*6dbdd20aSAndroid Build Coastguard Worker "--partial-matching", 129*6dbdd20aSAndroid Build Coastguard Worker help="If set, enables \"partial matching\" on the strings in --names/-n." 130*6dbdd20aSAndroid Build Coastguard Worker "Processes that are already running when profiling is started, and whose " 131*6dbdd20aSAndroid Build Coastguard Worker "names include any of the values in --names/-n as substrings will be " 132*6dbdd20aSAndroid Build Coastguard Worker "profiled.", 133*6dbdd20aSAndroid Build Coastguard Worker action="store_true") 134*6dbdd20aSAndroid Build Coastguard Worker parser.add_argument( 135*6dbdd20aSAndroid Build Coastguard Worker "-c", 136*6dbdd20aSAndroid Build Coastguard Worker "--config", 137*6dbdd20aSAndroid Build Coastguard Worker help="A custom configuration file, if any, to be used for profiling. " 138*6dbdd20aSAndroid Build Coastguard Worker "If provided, --frequency/-f, --duration/-d, and --name/-n are not used.", 139*6dbdd20aSAndroid Build Coastguard Worker metavar="CONFIG", 140*6dbdd20aSAndroid Build Coastguard Worker default=None) 141*6dbdd20aSAndroid Build Coastguard Worker parser.add_argument( 142*6dbdd20aSAndroid Build Coastguard Worker "--no-annotations", 143*6dbdd20aSAndroid Build Coastguard Worker help="Do not suffix the pprof function names with Android ART mode " 144*6dbdd20aSAndroid Build Coastguard Worker "annotations such as [jit].", 145*6dbdd20aSAndroid Build Coastguard Worker action="store_true") 146*6dbdd20aSAndroid Build Coastguard Worker parser.add_argument( 147*6dbdd20aSAndroid Build Coastguard Worker "--print-config", 148*6dbdd20aSAndroid Build Coastguard Worker action="store_true", 149*6dbdd20aSAndroid Build Coastguard Worker help="Print config instead of running. For debugging.") 150*6dbdd20aSAndroid Build Coastguard Worker parser.add_argument( 151*6dbdd20aSAndroid Build Coastguard Worker "-o", 152*6dbdd20aSAndroid Build Coastguard Worker "--output", 153*6dbdd20aSAndroid Build Coastguard Worker help="Output directory for recorded trace.", 154*6dbdd20aSAndroid Build Coastguard Worker metavar="DIRECTORY", 155*6dbdd20aSAndroid Build Coastguard Worker default=None) 156*6dbdd20aSAndroid Build Coastguard Worker 157*6dbdd20aSAndroid Build Coastguard Worker args = parser.parse_args() 158*6dbdd20aSAndroid Build Coastguard Worker if args.config is not None: 159*6dbdd20aSAndroid Build Coastguard Worker if args.name is not None: 160*6dbdd20aSAndroid Build Coastguard Worker sys.exit("--name/-n should not be specified with --config/-c.") 161*6dbdd20aSAndroid Build Coastguard Worker elif args.event: 162*6dbdd20aSAndroid Build Coastguard Worker sys.exit("-e/--event should not be specified with --config/-c.") 163*6dbdd20aSAndroid Build Coastguard Worker elif args.config is None and args.name is None: 164*6dbdd20aSAndroid Build Coastguard Worker sys.exit("One of --names/-n or --config/-c is required.") 165*6dbdd20aSAndroid Build Coastguard Worker 166*6dbdd20aSAndroid Build Coastguard Worker return args 167*6dbdd20aSAndroid Build Coastguard Worker 168*6dbdd20aSAndroid Build Coastguard Worker 169*6dbdd20aSAndroid Build Coastguard Workerdef get_matching_processes(args, names_to_match): 170*6dbdd20aSAndroid Build Coastguard Worker """Returns a list of currently-running processes whose names match 171*6dbdd20aSAndroid Build Coastguard Worker `names_to_match`. 172*6dbdd20aSAndroid Build Coastguard Worker 173*6dbdd20aSAndroid Build Coastguard Worker Args: 174*6dbdd20aSAndroid Build Coastguard Worker args: The command-line arguments provided to this script. 175*6dbdd20aSAndroid Build Coastguard Worker names_to_match: The list of process names provided by the user. 176*6dbdd20aSAndroid Build Coastguard Worker """ 177*6dbdd20aSAndroid Build Coastguard Worker # Returns names as they are. 178*6dbdd20aSAndroid Build Coastguard Worker if not args.partial_matching: 179*6dbdd20aSAndroid Build Coastguard Worker return names_to_match 180*6dbdd20aSAndroid Build Coastguard Worker 181*6dbdd20aSAndroid Build Coastguard Worker # Attempt to match names to names of currently running processes. 182*6dbdd20aSAndroid Build Coastguard Worker PS_PROCESS_OFFSET = 8 183*6dbdd20aSAndroid Build Coastguard Worker matching_processes = [] 184*6dbdd20aSAndroid Build Coastguard Worker for line in adb_check_output(['adb', 'shell', 'ps', '-A']).splitlines(): 185*6dbdd20aSAndroid Build Coastguard Worker line_split = line.split() 186*6dbdd20aSAndroid Build Coastguard Worker if len(line_split) <= PS_PROCESS_OFFSET: 187*6dbdd20aSAndroid Build Coastguard Worker continue 188*6dbdd20aSAndroid Build Coastguard Worker process = line_split[PS_PROCESS_OFFSET] 189*6dbdd20aSAndroid Build Coastguard Worker for name in names_to_match: 190*6dbdd20aSAndroid Build Coastguard Worker if name in process: 191*6dbdd20aSAndroid Build Coastguard Worker matching_processes.append(process) 192*6dbdd20aSAndroid Build Coastguard Worker break 193*6dbdd20aSAndroid Build Coastguard Worker 194*6dbdd20aSAndroid Build Coastguard Worker return matching_processes 195*6dbdd20aSAndroid Build Coastguard Worker 196*6dbdd20aSAndroid Build Coastguard Worker 197*6dbdd20aSAndroid Build Coastguard Workerdef get_perfetto_config(args): 198*6dbdd20aSAndroid Build Coastguard Worker """Returns a Perfetto config with CPU profiling enabled for the selected 199*6dbdd20aSAndroid Build Coastguard Worker processes. 200*6dbdd20aSAndroid Build Coastguard Worker 201*6dbdd20aSAndroid Build Coastguard Worker Args: 202*6dbdd20aSAndroid Build Coastguard Worker args: The command-line arguments provided to this script. 203*6dbdd20aSAndroid Build Coastguard Worker """ 204*6dbdd20aSAndroid Build Coastguard Worker if args.config is not None: 205*6dbdd20aSAndroid Build Coastguard Worker try: 206*6dbdd20aSAndroid Build Coastguard Worker with open(args.config, 'r') as config_file: 207*6dbdd20aSAndroid Build Coastguard Worker return config_file.read() 208*6dbdd20aSAndroid Build Coastguard Worker except IOError as error: 209*6dbdd20aSAndroid Build Coastguard Worker sys.exit("Unable to read config file: {}".format(error)) 210*6dbdd20aSAndroid Build Coastguard Worker 211*6dbdd20aSAndroid Build Coastguard Worker CONFIG_INDENT = ' ' 212*6dbdd20aSAndroid Build Coastguard Worker CONFIG = textwrap.dedent('''\ 213*6dbdd20aSAndroid Build Coastguard Worker buffers {{ 214*6dbdd20aSAndroid Build Coastguard Worker size_kb: 2048 215*6dbdd20aSAndroid Build Coastguard Worker }} 216*6dbdd20aSAndroid Build Coastguard Worker 217*6dbdd20aSAndroid Build Coastguard Worker buffers {{ 218*6dbdd20aSAndroid Build Coastguard Worker size_kb: 63488 219*6dbdd20aSAndroid Build Coastguard Worker }} 220*6dbdd20aSAndroid Build Coastguard Worker 221*6dbdd20aSAndroid Build Coastguard Worker data_sources {{ 222*6dbdd20aSAndroid Build Coastguard Worker config {{ 223*6dbdd20aSAndroid Build Coastguard Worker name: "linux.process_stats" 224*6dbdd20aSAndroid Build Coastguard Worker target_buffer: 0 225*6dbdd20aSAndroid Build Coastguard Worker process_stats_config {{ 226*6dbdd20aSAndroid Build Coastguard Worker proc_stats_poll_ms: 100 227*6dbdd20aSAndroid Build Coastguard Worker }} 228*6dbdd20aSAndroid Build Coastguard Worker }} 229*6dbdd20aSAndroid Build Coastguard Worker }} 230*6dbdd20aSAndroid Build Coastguard Worker 231*6dbdd20aSAndroid Build Coastguard Worker duration_ms: {duration} 232*6dbdd20aSAndroid Build Coastguard Worker write_into_file: true 233*6dbdd20aSAndroid Build Coastguard Worker flush_timeout_ms: 30000 234*6dbdd20aSAndroid Build Coastguard Worker flush_period_ms: 604800000 235*6dbdd20aSAndroid Build Coastguard Worker ''') 236*6dbdd20aSAndroid Build Coastguard Worker 237*6dbdd20aSAndroid Build Coastguard Worker matching_processes = [] 238*6dbdd20aSAndroid Build Coastguard Worker if args.name is not None: 239*6dbdd20aSAndroid Build Coastguard Worker names_to_match = [name.strip() for name in args.name.split(',')] 240*6dbdd20aSAndroid Build Coastguard Worker matching_processes = get_matching_processes(args, names_to_match) 241*6dbdd20aSAndroid Build Coastguard Worker 242*6dbdd20aSAndroid Build Coastguard Worker if not matching_processes: 243*6dbdd20aSAndroid Build Coastguard Worker sys.exit("No running processes matched for profiling.") 244*6dbdd20aSAndroid Build Coastguard Worker 245*6dbdd20aSAndroid Build Coastguard Worker target_config = "\n".join( 246*6dbdd20aSAndroid Build Coastguard Worker [f'{CONFIG_INDENT}target_cmdline: "{p}"' for p in matching_processes]) 247*6dbdd20aSAndroid Build Coastguard Worker 248*6dbdd20aSAndroid Build Coastguard Worker events = args.event or ['SW_CPU_CLOCK'] 249*6dbdd20aSAndroid Build Coastguard Worker for event in events: 250*6dbdd20aSAndroid Build Coastguard Worker CONFIG += (textwrap.dedent(''' 251*6dbdd20aSAndroid Build Coastguard Worker data_sources {{ 252*6dbdd20aSAndroid Build Coastguard Worker config {{ 253*6dbdd20aSAndroid Build Coastguard Worker name: "linux.perf" 254*6dbdd20aSAndroid Build Coastguard Worker target_buffer: 1 255*6dbdd20aSAndroid Build Coastguard Worker perf_event_config {{ 256*6dbdd20aSAndroid Build Coastguard Worker timebase {{ 257*6dbdd20aSAndroid Build Coastguard Worker counter: %s 258*6dbdd20aSAndroid Build Coastguard Worker frequency: {frequency} 259*6dbdd20aSAndroid Build Coastguard Worker timestamp_clock: PERF_CLOCK_MONOTONIC 260*6dbdd20aSAndroid Build Coastguard Worker }} 261*6dbdd20aSAndroid Build Coastguard Worker callstack_sampling {{ 262*6dbdd20aSAndroid Build Coastguard Worker scope {{ 263*6dbdd20aSAndroid Build Coastguard Worker {target_config} 264*6dbdd20aSAndroid Build Coastguard Worker }} 265*6dbdd20aSAndroid Build Coastguard Worker kernel_frames: {kernel_config} 266*6dbdd20aSAndroid Build Coastguard Worker }} 267*6dbdd20aSAndroid Build Coastguard Worker }} 268*6dbdd20aSAndroid Build Coastguard Worker }} 269*6dbdd20aSAndroid Build Coastguard Worker }} 270*6dbdd20aSAndroid Build Coastguard Worker ''') % (event)) 271*6dbdd20aSAndroid Build Coastguard Worker 272*6dbdd20aSAndroid Build Coastguard Worker if args.kernel_frames: 273*6dbdd20aSAndroid Build Coastguard Worker kernel_config = "true" 274*6dbdd20aSAndroid Build Coastguard Worker else: 275*6dbdd20aSAndroid Build Coastguard Worker kernel_config = "false" 276*6dbdd20aSAndroid Build Coastguard Worker 277*6dbdd20aSAndroid Build Coastguard Worker if not args.print_config: 278*6dbdd20aSAndroid Build Coastguard Worker print("Configured profiling for these processes:\n") 279*6dbdd20aSAndroid Build Coastguard Worker for matching_process in matching_processes: 280*6dbdd20aSAndroid Build Coastguard Worker print(matching_process) 281*6dbdd20aSAndroid Build Coastguard Worker print() 282*6dbdd20aSAndroid Build Coastguard Worker 283*6dbdd20aSAndroid Build Coastguard Worker config = CONFIG.format( 284*6dbdd20aSAndroid Build Coastguard Worker frequency=args.frequency, 285*6dbdd20aSAndroid Build Coastguard Worker duration=args.duration, 286*6dbdd20aSAndroid Build Coastguard Worker target_config=target_config, 287*6dbdd20aSAndroid Build Coastguard Worker kernel_config=kernel_config) 288*6dbdd20aSAndroid Build Coastguard Worker 289*6dbdd20aSAndroid Build Coastguard Worker return config 290*6dbdd20aSAndroid Build Coastguard Worker 291*6dbdd20aSAndroid Build Coastguard Worker 292*6dbdd20aSAndroid Build Coastguard Workerdef release_or_newer(release): 293*6dbdd20aSAndroid Build Coastguard Worker """Returns whether a new enough Android release is being used.""" 294*6dbdd20aSAndroid Build Coastguard Worker SDK = {'T': 33} 295*6dbdd20aSAndroid Build Coastguard Worker sdk = int( 296*6dbdd20aSAndroid Build Coastguard Worker adb_check_output( 297*6dbdd20aSAndroid Build Coastguard Worker ['adb', 'shell', 'getprop', 'ro.system.build.version.sdk']).strip()) 298*6dbdd20aSAndroid Build Coastguard Worker if sdk >= SDK[release]: 299*6dbdd20aSAndroid Build Coastguard Worker return True 300*6dbdd20aSAndroid Build Coastguard Worker 301*6dbdd20aSAndroid Build Coastguard Worker codename = adb_check_output( 302*6dbdd20aSAndroid Build Coastguard Worker ['adb', 'shell', 'getprop', 'ro.build.version.codename']).strip() 303*6dbdd20aSAndroid Build Coastguard Worker return codename == release 304*6dbdd20aSAndroid Build Coastguard Worker 305*6dbdd20aSAndroid Build Coastguard Worker 306*6dbdd20aSAndroid Build Coastguard Workerdef get_and_prepare_profile_target(args): 307*6dbdd20aSAndroid Build Coastguard Worker """Returns the target where the trace/profile will be output. Creates a 308*6dbdd20aSAndroid Build Coastguard Worker new directory if necessary. 309*6dbdd20aSAndroid Build Coastguard Worker 310*6dbdd20aSAndroid Build Coastguard Worker Args: 311*6dbdd20aSAndroid Build Coastguard Worker args: The command-line arguments provided to this script. 312*6dbdd20aSAndroid Build Coastguard Worker """ 313*6dbdd20aSAndroid Build Coastguard Worker profile_target = os.path.join(tempfile.gettempdir(), UUID) 314*6dbdd20aSAndroid Build Coastguard Worker if args.output is not None: 315*6dbdd20aSAndroid Build Coastguard Worker profile_target = args.output 316*6dbdd20aSAndroid Build Coastguard Worker else: 317*6dbdd20aSAndroid Build Coastguard Worker os.makedirs(profile_target, exist_ok=True) 318*6dbdd20aSAndroid Build Coastguard Worker if not os.path.isdir(profile_target): 319*6dbdd20aSAndroid Build Coastguard Worker sys.exit("Output directory {} not found.".format(profile_target)) 320*6dbdd20aSAndroid Build Coastguard Worker if os.listdir(profile_target): 321*6dbdd20aSAndroid Build Coastguard Worker sys.exit("Output directory {} not empty.".format(profile_target)) 322*6dbdd20aSAndroid Build Coastguard Worker 323*6dbdd20aSAndroid Build Coastguard Worker return profile_target 324*6dbdd20aSAndroid Build Coastguard Worker 325*6dbdd20aSAndroid Build Coastguard Worker 326*6dbdd20aSAndroid Build Coastguard Workerdef record_trace(config, profile_target): 327*6dbdd20aSAndroid Build Coastguard Worker """Runs Perfetto with the provided configuration to record a trace. 328*6dbdd20aSAndroid Build Coastguard Worker 329*6dbdd20aSAndroid Build Coastguard Worker Args: 330*6dbdd20aSAndroid Build Coastguard Worker config: The Perfetto config to be used for tracing/profiling. 331*6dbdd20aSAndroid Build Coastguard Worker profile_target: The directory where the recorded trace is output. 332*6dbdd20aSAndroid Build Coastguard Worker """ 333*6dbdd20aSAndroid Build Coastguard Worker NULL = open(os.devnull) 334*6dbdd20aSAndroid Build Coastguard Worker NO_OUT = { 335*6dbdd20aSAndroid Build Coastguard Worker 'stdout': NULL, 336*6dbdd20aSAndroid Build Coastguard Worker 'stderr': NULL, 337*6dbdd20aSAndroid Build Coastguard Worker } 338*6dbdd20aSAndroid Build Coastguard Worker if not release_or_newer('T'): 339*6dbdd20aSAndroid Build Coastguard Worker sys.exit("This tool requires Android T+ to run.") 340*6dbdd20aSAndroid Build Coastguard Worker 341*6dbdd20aSAndroid Build Coastguard Worker # Push configuration to the device. 342*6dbdd20aSAndroid Build Coastguard Worker tf = tempfile.NamedTemporaryFile() 343*6dbdd20aSAndroid Build Coastguard Worker tf.file.write(config.encode('utf-8')) 344*6dbdd20aSAndroid Build Coastguard Worker tf.file.flush() 345*6dbdd20aSAndroid Build Coastguard Worker profile_config_path = '/data/misc/perfetto-configs/config-' + UUID 346*6dbdd20aSAndroid Build Coastguard Worker adb_check_output(['adb', 'push', tf.name, profile_config_path]) 347*6dbdd20aSAndroid Build Coastguard Worker tf.close() 348*6dbdd20aSAndroid Build Coastguard Worker 349*6dbdd20aSAndroid Build Coastguard Worker 350*6dbdd20aSAndroid Build Coastguard Worker profile_device_path = '/data/misc/perfetto-traces/profile-' + UUID 351*6dbdd20aSAndroid Build Coastguard Worker perfetto_command = ('perfetto --txt -c {} -o {} -d') 352*6dbdd20aSAndroid Build Coastguard Worker try: 353*6dbdd20aSAndroid Build Coastguard Worker perfetto_pid = int( 354*6dbdd20aSAndroid Build Coastguard Worker adb_check_output([ 355*6dbdd20aSAndroid Build Coastguard Worker 'adb', 'exec-out', 356*6dbdd20aSAndroid Build Coastguard Worker perfetto_command.format(profile_config_path, profile_device_path) 357*6dbdd20aSAndroid Build Coastguard Worker ]).strip()) 358*6dbdd20aSAndroid Build Coastguard Worker except ValueError as error: 359*6dbdd20aSAndroid Build Coastguard Worker sys.exit("Unable to start profiling: {}".format(error)) 360*6dbdd20aSAndroid Build Coastguard Worker 361*6dbdd20aSAndroid Build Coastguard Worker print("Profiling active. Press Ctrl+C to terminate.") 362*6dbdd20aSAndroid Build Coastguard Worker 363*6dbdd20aSAndroid Build Coastguard Worker old_handler = signal.signal(signal.SIGINT, sigint_handler) 364*6dbdd20aSAndroid Build Coastguard Worker 365*6dbdd20aSAndroid Build Coastguard Worker perfetto_alive = True 366*6dbdd20aSAndroid Build Coastguard Worker while perfetto_alive and not IS_INTERRUPTED: 367*6dbdd20aSAndroid Build Coastguard Worker perfetto_alive = subprocess.call( 368*6dbdd20aSAndroid Build Coastguard Worker ['adb', 'shell', '[ -d /proc/{} ]'.format(perfetto_pid)], **NO_OUT) == 0 369*6dbdd20aSAndroid Build Coastguard Worker time.sleep(0.25) 370*6dbdd20aSAndroid Build Coastguard Worker 371*6dbdd20aSAndroid Build Coastguard Worker print("Finishing profiling and symbolization...") 372*6dbdd20aSAndroid Build Coastguard Worker 373*6dbdd20aSAndroid Build Coastguard Worker if IS_INTERRUPTED: 374*6dbdd20aSAndroid Build Coastguard Worker adb_check_output(['adb', 'shell', 'kill', '-INT', str(perfetto_pid)]) 375*6dbdd20aSAndroid Build Coastguard Worker 376*6dbdd20aSAndroid Build Coastguard Worker # Restore old handler. 377*6dbdd20aSAndroid Build Coastguard Worker signal.signal(signal.SIGINT, old_handler) 378*6dbdd20aSAndroid Build Coastguard Worker 379*6dbdd20aSAndroid Build Coastguard Worker while perfetto_alive: 380*6dbdd20aSAndroid Build Coastguard Worker perfetto_alive = subprocess.call( 381*6dbdd20aSAndroid Build Coastguard Worker ['adb', 'shell', '[ -d /proc/{} ]'.format(perfetto_pid)]) == 0 382*6dbdd20aSAndroid Build Coastguard Worker time.sleep(0.25) 383*6dbdd20aSAndroid Build Coastguard Worker 384*6dbdd20aSAndroid Build Coastguard Worker profile_host_path = os.path.join(profile_target, 'raw-trace') 385*6dbdd20aSAndroid Build Coastguard Worker adb_check_output(['adb', 'pull', profile_device_path, profile_host_path]) 386*6dbdd20aSAndroid Build Coastguard Worker adb_check_output(['adb', 'shell', 'rm', profile_config_path]) 387*6dbdd20aSAndroid Build Coastguard Worker adb_check_output(['adb', 'shell', 'rm', profile_device_path]) 388*6dbdd20aSAndroid Build Coastguard Worker 389*6dbdd20aSAndroid Build Coastguard Worker 390*6dbdd20aSAndroid Build Coastguard Workerdef get_traceconv(): 391*6dbdd20aSAndroid Build Coastguard Worker """Sets up and returns the path to `traceconv`.""" 392*6dbdd20aSAndroid Build Coastguard Worker try: 393*6dbdd20aSAndroid Build Coastguard Worker traceconv = get_perfetto_prebuilt(TRACECONV_MANIFEST, soft_fail=True) 394*6dbdd20aSAndroid Build Coastguard Worker except Exception as error: 395*6dbdd20aSAndroid Build Coastguard Worker exit_with_bug_report(error) 396*6dbdd20aSAndroid Build Coastguard Worker if traceconv is None: 397*6dbdd20aSAndroid Build Coastguard Worker exit_with_bug_report( 398*6dbdd20aSAndroid Build Coastguard Worker "Unable to download `traceconv` for symbolizing profiles.") 399*6dbdd20aSAndroid Build Coastguard Worker 400*6dbdd20aSAndroid Build Coastguard Worker return traceconv 401*6dbdd20aSAndroid Build Coastguard Worker 402*6dbdd20aSAndroid Build Coastguard Worker 403*6dbdd20aSAndroid Build Coastguard Workerdef concatenate_files(files_to_concatenate, output_file): 404*6dbdd20aSAndroid Build Coastguard Worker """Concatenates files. 405*6dbdd20aSAndroid Build Coastguard Worker 406*6dbdd20aSAndroid Build Coastguard Worker Args: 407*6dbdd20aSAndroid Build Coastguard Worker files_to_concatenate: Paths for input files to concatenate. 408*6dbdd20aSAndroid Build Coastguard Worker output_file: Path to the resultant output file. 409*6dbdd20aSAndroid Build Coastguard Worker """ 410*6dbdd20aSAndroid Build Coastguard Worker with open(output_file, 'wb') as output: 411*6dbdd20aSAndroid Build Coastguard Worker for file in files_to_concatenate: 412*6dbdd20aSAndroid Build Coastguard Worker with open(file, 'rb') as input: 413*6dbdd20aSAndroid Build Coastguard Worker shutil.copyfileobj(input, output) 414*6dbdd20aSAndroid Build Coastguard Worker 415*6dbdd20aSAndroid Build Coastguard Worker 416*6dbdd20aSAndroid Build Coastguard Workerdef symbolize_trace(traceconv, profile_target): 417*6dbdd20aSAndroid Build Coastguard Worker """Attempts symbolization of the recorded trace/profile, if symbols are 418*6dbdd20aSAndroid Build Coastguard Worker available. 419*6dbdd20aSAndroid Build Coastguard Worker 420*6dbdd20aSAndroid Build Coastguard Worker Args: 421*6dbdd20aSAndroid Build Coastguard Worker traceconv: The path to the `traceconv` binary used for symbolization. 422*6dbdd20aSAndroid Build Coastguard Worker profile_target: The directory where the recorded trace was output. 423*6dbdd20aSAndroid Build Coastguard Worker 424*6dbdd20aSAndroid Build Coastguard Worker Returns: 425*6dbdd20aSAndroid Build Coastguard Worker The path to the symbolized trace file if symbolization was completed, 426*6dbdd20aSAndroid Build Coastguard Worker and the original trace file, if it was not. 427*6dbdd20aSAndroid Build Coastguard Worker """ 428*6dbdd20aSAndroid Build Coastguard Worker binary_path = os.getenv('PERFETTO_BINARY_PATH') 429*6dbdd20aSAndroid Build Coastguard Worker trace_file = os.path.join(profile_target, 'raw-trace') 430*6dbdd20aSAndroid Build Coastguard Worker files_to_concatenate = [trace_file] 431*6dbdd20aSAndroid Build Coastguard Worker 432*6dbdd20aSAndroid Build Coastguard Worker if binary_path is not None: 433*6dbdd20aSAndroid Build Coastguard Worker try: 434*6dbdd20aSAndroid Build Coastguard Worker with open(os.path.join(profile_target, 'symbols'), 'w') as symbols_file: 435*6dbdd20aSAndroid Build Coastguard Worker return_code = subprocess.call([traceconv, 'symbolize', trace_file], 436*6dbdd20aSAndroid Build Coastguard Worker env=dict( 437*6dbdd20aSAndroid Build Coastguard Worker os.environ, 438*6dbdd20aSAndroid Build Coastguard Worker PERFETTO_BINARY_PATH=binary_path), 439*6dbdd20aSAndroid Build Coastguard Worker stdout=symbols_file) 440*6dbdd20aSAndroid Build Coastguard Worker except IOError as error: 441*6dbdd20aSAndroid Build Coastguard Worker sys.exit("Unable to write symbols to disk: {}".format(error)) 442*6dbdd20aSAndroid Build Coastguard Worker if return_code == 0: 443*6dbdd20aSAndroid Build Coastguard Worker files_to_concatenate.append(os.path.join(profile_target, 'symbols')) 444*6dbdd20aSAndroid Build Coastguard Worker else: 445*6dbdd20aSAndroid Build Coastguard Worker print("Failed to symbolize. Continuing without symbols.", file=sys.stderr) 446*6dbdd20aSAndroid Build Coastguard Worker 447*6dbdd20aSAndroid Build Coastguard Worker if len(files_to_concatenate) > 1: 448*6dbdd20aSAndroid Build Coastguard Worker trace_file = os.path.join(profile_target, 'symbolized-trace') 449*6dbdd20aSAndroid Build Coastguard Worker try: 450*6dbdd20aSAndroid Build Coastguard Worker concatenate_files(files_to_concatenate, trace_file) 451*6dbdd20aSAndroid Build Coastguard Worker except Exception as error: 452*6dbdd20aSAndroid Build Coastguard Worker sys.exit("Unable to write symbolized profile to disk: {}".format(error)) 453*6dbdd20aSAndroid Build Coastguard Worker 454*6dbdd20aSAndroid Build Coastguard Worker return trace_file 455*6dbdd20aSAndroid Build Coastguard Worker 456*6dbdd20aSAndroid Build Coastguard Worker 457*6dbdd20aSAndroid Build Coastguard Workerdef generate_pprof_profiles(traceconv, trace_file, args): 458*6dbdd20aSAndroid Build Coastguard Worker """Generates pprof profiles from the recorded trace. 459*6dbdd20aSAndroid Build Coastguard Worker 460*6dbdd20aSAndroid Build Coastguard Worker Args: 461*6dbdd20aSAndroid Build Coastguard Worker traceconv: The path to the `traceconv` binary used for generating profiles. 462*6dbdd20aSAndroid Build Coastguard Worker trace_file: The oath to the recorded and potentially symbolized trace file. 463*6dbdd20aSAndroid Build Coastguard Worker 464*6dbdd20aSAndroid Build Coastguard Worker Returns: 465*6dbdd20aSAndroid Build Coastguard Worker The directory where pprof profiles are output. 466*6dbdd20aSAndroid Build Coastguard Worker """ 467*6dbdd20aSAndroid Build Coastguard Worker try: 468*6dbdd20aSAndroid Build Coastguard Worker conversion_args = [traceconv, 'profile', '--perf'] + ( 469*6dbdd20aSAndroid Build Coastguard Worker ['--no-annotations'] if args.no_annotations else []) + [trace_file] 470*6dbdd20aSAndroid Build Coastguard Worker traceconv_output = subprocess.check_output(conversion_args) 471*6dbdd20aSAndroid Build Coastguard Worker except Exception as error: 472*6dbdd20aSAndroid Build Coastguard Worker exit_with_bug_report( 473*6dbdd20aSAndroid Build Coastguard Worker "Unable to extract profiles from trace: {}".format(error)) 474*6dbdd20aSAndroid Build Coastguard Worker 475*6dbdd20aSAndroid Build Coastguard Worker profiles_output_directory = None 476*6dbdd20aSAndroid Build Coastguard Worker for word in traceconv_output.decode('utf-8').split(): 477*6dbdd20aSAndroid Build Coastguard Worker if 'perf_profile-' in word: 478*6dbdd20aSAndroid Build Coastguard Worker profiles_output_directory = word 479*6dbdd20aSAndroid Build Coastguard Worker if profiles_output_directory is None: 480*6dbdd20aSAndroid Build Coastguard Worker exit_with_no_profile() 481*6dbdd20aSAndroid Build Coastguard Worker return profiles_output_directory 482*6dbdd20aSAndroid Build Coastguard Worker 483*6dbdd20aSAndroid Build Coastguard Worker 484*6dbdd20aSAndroid Build Coastguard Workerdef copy_profiles_to_destination(profile_target, profile_path): 485*6dbdd20aSAndroid Build Coastguard Worker """Copies recorded profiles to `profile_target` from `profile_path`.""" 486*6dbdd20aSAndroid Build Coastguard Worker profile_files = os.listdir(profile_path) 487*6dbdd20aSAndroid Build Coastguard Worker if not profile_files: 488*6dbdd20aSAndroid Build Coastguard Worker exit_with_no_profile() 489*6dbdd20aSAndroid Build Coastguard Worker 490*6dbdd20aSAndroid Build Coastguard Worker try: 491*6dbdd20aSAndroid Build Coastguard Worker for profile_file in profile_files: 492*6dbdd20aSAndroid Build Coastguard Worker shutil.copy(os.path.join(profile_path, profile_file), profile_target) 493*6dbdd20aSAndroid Build Coastguard Worker except Exception as error: 494*6dbdd20aSAndroid Build Coastguard Worker sys.exit("Unable to copy profiles to {}: {}".format(profile_target, error)) 495*6dbdd20aSAndroid Build Coastguard Worker 496*6dbdd20aSAndroid Build Coastguard Worker print("Wrote profiles to {}".format(profile_target)) 497*6dbdd20aSAndroid Build Coastguard Worker 498*6dbdd20aSAndroid Build Coastguard Worker 499*6dbdd20aSAndroid Build Coastguard Workerdef main(argv): 500*6dbdd20aSAndroid Build Coastguard Worker args = parse_and_validate_args() 501*6dbdd20aSAndroid Build Coastguard Worker profile_target = get_and_prepare_profile_target(args) 502*6dbdd20aSAndroid Build Coastguard Worker trace_config = get_perfetto_config(args) 503*6dbdd20aSAndroid Build Coastguard Worker if args.print_config: 504*6dbdd20aSAndroid Build Coastguard Worker print(trace_config) 505*6dbdd20aSAndroid Build Coastguard Worker return 0 506*6dbdd20aSAndroid Build Coastguard Worker record_trace(trace_config, profile_target) 507*6dbdd20aSAndroid Build Coastguard Worker traceconv = get_traceconv() 508*6dbdd20aSAndroid Build Coastguard Worker trace_file = symbolize_trace(traceconv, profile_target) 509*6dbdd20aSAndroid Build Coastguard Worker copy_profiles_to_destination( 510*6dbdd20aSAndroid Build Coastguard Worker profile_target, generate_pprof_profiles(traceconv, trace_file, args)) 511*6dbdd20aSAndroid Build Coastguard Worker return 0 512*6dbdd20aSAndroid Build Coastguard Worker 513*6dbdd20aSAndroid Build Coastguard Worker 514*6dbdd20aSAndroid Build Coastguard Workerif __name__ == '__main__': 515*6dbdd20aSAndroid Build Coastguard Worker sys.exit(main(sys.argv)) 516