1#!/usr/bin/env python3 2# Copyright (C) 2021 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 17# DO NOT EDIT. Auto-generated by tools/gen_amalgamated_python_tools 18# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 19 20import atexit 21import argparse 22import datetime 23import hashlib 24import http.server 25import os 26import re 27import shutil 28import signal 29import socketserver 30import subprocess 31import sys 32import time 33import webbrowser 34 35 36# ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/tracebox.py 37# This file has been generated by: tools/roll-prebuilts v48.1 38TRACEBOX_MANIFEST = [{ 39 'arch': 40 'mac-amd64', 41 'file_name': 42 'tracebox', 43 'file_size': 44 1613864, 45 'url': 46 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-amd64/tracebox', 47 'sha256': 48 'dfb1a3affe905d2e7d1f82bc4dda46b1fda6db054d60ae87c3215dd529b77fee', 49 'platform': 50 'darwin', 51 'machine': ['x86_64'] 52}, { 53 'arch': 54 'mac-arm64', 55 'file_name': 56 'tracebox', 57 'file_size': 58 1492184, 59 'url': 60 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-arm64/tracebox', 61 'sha256': 62 '4a492a629dd1f13f3146c4b8267c0b163afba8cef1d49e0c00c48bb727496066', 63 'platform': 64 'darwin', 65 'machine': ['arm64'] 66}, { 67 'arch': 68 'linux-amd64', 69 'file_name': 70 'tracebox', 71 'file_size': 72 2380040, 73 'url': 74 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-amd64/tracebox', 75 'sha256': 76 'd70b284e8c28858fd539ae61ca59764d7f9fd6232073c304926e892fe75e692a', 77 'platform': 78 'linux', 79 'machine': ['x86_64'] 80}, { 81 'arch': 82 'linux-arm', 83 'file_name': 84 'tracebox', 85 'file_size': 86 1450708, 87 'url': 88 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm/tracebox', 89 'sha256': 90 '178fa6a1a9bc80f72d81938d40fe201c25c595ffaff7e030d59c2af09dfcc06c', 91 'platform': 92 'linux', 93 'machine': ['armv6l', 'armv7l', 'armv8l'] 94}, { 95 'arch': 96 'linux-arm64', 97 'file_name': 98 'tracebox', 99 'file_size': 100 2269816, 101 'url': 102 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm64/tracebox', 103 'sha256': 104 '42c64f9807756aaa08a2bfa13e9e4828c193a6b90ba1329408873c3ebf5adf3f', 105 'platform': 106 'linux', 107 'machine': ['aarch64'] 108}, { 109 'arch': 110 'android-arm', 111 'file_name': 112 'tracebox', 113 'file_size': 114 1333336, 115 'url': 116 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm/tracebox', 117 'sha256': 118 '93a78d2c42e3c00f117e2f155326383f69c891281ed693a39d87b8cb54ca4e19' 119}, { 120 'arch': 121 'android-arm64', 122 'file_name': 123 'tracebox', 124 'file_size': 125 2115984, 126 'url': 127 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm64/tracebox', 128 'sha256': 129 '508248a9e47ab605fd742efb700391d7267b68b586199a93e13e6ca14b72fe3d' 130}, { 131 'arch': 132 'android-x86', 133 'file_name': 134 'tracebox', 135 'file_size': 136 2302960, 137 'url': 138 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x86/tracebox', 139 'sha256': 140 '63d20a69c4e0c291329d7917e640fa0d4f146c344e79988e87393b1431d594b1' 141}, { 142 'arch': 143 'android-x64', 144 'file_name': 145 'tracebox', 146 'file_size': 147 2147880, 148 'url': 149 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x64/tracebox', 150 'sha256': 151 'c0ea1d5fd6d020e4c2b45d4d45cdd0c44ae63cd755d69260a3e5d2bacd3cbd6a' 152}] 153 154# ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py 155 156# ----- Amalgamator: begin of python/perfetto/prebuilts/perfetto_prebuilts.py 157# Copyright (C) 2021 The Android Open Source Project 158# 159# Licensed under the Apache License, Version 2.0 (the "License"); 160# you may not use this file except in compliance with the License. 161# You may obtain a copy of the License at 162# 163# http://www.apache.org/licenses/LICENSE-2.0 164# 165# Unless required by applicable law or agreed to in writing, software 166# distributed under the License is distributed on an "AS IS" BASIS, 167# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 168# See the License for the specific language governing permissions and 169# limitations under the License. 170""" 171Functions to fetch pre-pinned Perfetto prebuilts. 172 173This function is used in different places: 174- Into the //tools/{trace_processor, traceconv} scripts, which are just plain 175 wrappers around executables. 176- Into the //tools/{heap_profiler, record_android_trace} scripts, which contain 177 some other hand-written python code. 178 179The manifest argument looks as follows: 180TRACECONV_MANIFEST = [ 181 { 182 'arch': 'mac-amd64', 183 'file_name': 'traceconv', 184 'file_size': 7087080, 185 'url': https://commondatastorage.googleapis.com/.../trace_to_text', 186 'sha256': 7d957c005b0dc130f5bd855d6cec27e060d38841b320d04840afc569f9087490', 187 'platform': 'darwin', 188 'machine': 'x86_64' 189 }, 190 ... 191] 192 193The intended usage is: 194 195 from perfetto.prebuilts.manifests.traceconv import TRACECONV_MANIFEST 196 bin_path = get_perfetto_prebuilt(TRACECONV_MANIFEST) 197 subprocess.call(bin_path, ...) 198""" 199 200import hashlib 201import os 202import platform 203import random 204import subprocess 205import sys 206 207 208def download_or_get_cached(file_name, url, sha256): 209 """ Downloads a prebuilt or returns a cached version 210 211 The first time this is invoked, it downloads the |url| and caches it into 212 ~/.local/share/perfetto/prebuilts/$tool_name. On subsequent invocations it 213 just runs the cached version. 214 """ 215 dir = os.path.join( 216 os.path.expanduser('~'), '.local', 'share', 'perfetto', 'prebuilts') 217 os.makedirs(dir, exist_ok=True) 218 bin_path = os.path.join(dir, file_name) 219 sha256_path = os.path.join(dir, file_name + '.sha256') 220 needs_download = True 221 222 # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last 223 # download is cached into file_name.sha256, just check if that matches. 224 if os.path.exists(bin_path) and os.path.exists(sha256_path): 225 with open(sha256_path, 'rb') as f: 226 digest = f.read().decode() 227 if digest == sha256: 228 needs_download = False 229 230 if needs_download: # The file doesn't exist or the SHA256 doesn't match. 231 # Use a unique random file to guard against concurrent executions. 232 # See https://github.com/google/perfetto/issues/786 . 233 tmp_path = '%s.%d.tmp' % (bin_path, random.randint(0, 100000)) 234 print('Downloading ' + url) 235 subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url]) 236 with open(tmp_path, 'rb') as fd: 237 actual_sha256 = hashlib.sha256(fd.read()).hexdigest() 238 if actual_sha256 != sha256: 239 raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' % 240 (url, actual_sha256, sha256)) 241 os.chmod(tmp_path, 0o755) 242 os.replace(tmp_path, bin_path) 243 with open(tmp_path, 'w') as f: 244 f.write(sha256) 245 os.replace(tmp_path, sha256_path) 246 return bin_path 247 248 249def get_perfetto_prebuilt(manifest, soft_fail=False, arch=None): 250 """ Downloads the prebuilt, if necessary, and returns its path on disk. """ 251 plat = sys.platform.lower() 252 machine = platform.machine().lower() 253 manifest_entry = None 254 for entry in manifest: 255 # If the caller overrides the arch, just match that (for Android prebuilts). 256 if arch: 257 if entry.get('arch') == arch: 258 manifest_entry = entry 259 break 260 continue 261 # Otherwise guess the local machine arch. 262 if entry.get('platform') == plat and machine in entry.get('machine', []): 263 manifest_entry = entry 264 break 265 if manifest_entry is None: 266 if soft_fail: 267 return None 268 raise Exception( 269 ('No prebuilts available for %s-%s\n' % (plat, machine)) + 270 'See https://perfetto.dev/docs/contributing/build-instructions') 271 272 return download_or_get_cached( 273 file_name=manifest_entry['file_name'], 274 url=manifest_entry['url'], 275 sha256=manifest_entry['sha256']) 276 277 278def run_perfetto_prebuilt(manifest): 279 bin_path = get_perfetto_prebuilt(manifest) 280 if sys.platform.lower() == 'win32': 281 sys.exit(subprocess.check_call([bin_path, *sys.argv[1:]])) 282 os.execv(bin_path, [bin_path] + sys.argv[1:]) 283 284# ----- Amalgamator: end of python/perfetto/prebuilts/perfetto_prebuilts.py 285 286# ----- Amalgamator: begin of python/perfetto/common/repo_utils.py 287# Copyright (C) 2021 The Android Open Source Project 288# 289# Licensed under the Apache License, Version 2.0 (the "License"); 290# you may not use this file except in compliance with the License. 291# You may obtain a copy of the License at 292# 293# http://www.apache.org/licenses/LICENSE-2.0 294# 295# Unless required by applicable law or agreed to in writing, software 296# distributed under the License is distributed on an "AS IS" BASIS, 297# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 298# See the License for the specific language governing permissions and 299# limitations under the License. 300 301import os 302 303 304def repo_root(): 305 """ Finds the repo root by traversing up the hierarchy 306 307 This is for use in scripts that get amalgamated, where _file_ can be either 308 python/perfetto/... or tools/amalgamated_tool. 309 """ 310 path = os.path.dirname(os.path.abspath(__file__)) # amalgamator:nocheck 311 last_dir = '' 312 while path and path != last_dir: 313 if os.path.exists(os.path.join(path, 'perfetto.rc')): 314 return path 315 last_dir = path 316 path = os.path.dirname(path) 317 return None 318 319 320def repo_dir(rel_path): 321 return os.path.join(repo_root() or '', rel_path) 322 323# ----- Amalgamator: end of python/perfetto/common/repo_utils.py 324 325# This is not required. It's only used as a fallback if no adb is found on the 326# PATH. It's fine if it doesn't exist so this script can be copied elsewhere. 327HERMETIC_ADB_PATH = repo_dir('buildtools/android_sdk/platform-tools/adb') 328 329# Translates the Android ro.product.cpu.abi into the GN's target_cpu. 330ABI_TO_ARCH = { 331 'armeabi-v7a': 'arm', 332 'arm64-v8a': 'arm64', 333 'x86': 'x86', 334 'x86_64': 'x64', 335} 336 337MAX_ADB_FAILURES = 15 # 2 seconds between retries, 30 seconds total. 338 339devnull = open(os.devnull, 'rb') 340adb_path = None 341procs = [] 342 343 344class ANSI: 345 END = '\033[0m' 346 BOLD = '\033[1m' 347 RED = '\033[91m' 348 BLACK = '\033[30m' 349 BLUE = '\033[94m' 350 BG_YELLOW = '\033[43m' 351 BG_BLUE = '\033[44m' 352 353 354# HTTP Server used to open the trace in the browser. 355class HttpHandler(http.server.SimpleHTTPRequestHandler): 356 357 def end_headers(self): 358 self.send_header('Access-Control-Allow-Origin', self.server.allow_origin) 359 self.send_header('Cache-Control', 'no-cache') 360 super().end_headers() 361 362 def do_GET(self): 363 if self.path != '/' + self.server.expected_fname: 364 self.send_error(404, "File not found") 365 return 366 367 self.server.fname_get_completed = True 368 super().do_GET() 369 370 def do_POST(self): 371 self.send_error(404, "File not found") 372 373 374def setup_arguments(): 375 atexit.register(kill_all_subprocs_on_exit) 376 default_out_dir_str = '~/traces/' 377 default_out_dir = os.path.expanduser(default_out_dir_str) 378 379 examples = '\n'.join([ 380 ANSI.BOLD + 'Examples' + ANSI.END, ' -t 10s -b 32mb sched gfx wm -a*', 381 ' -t 5s sched/sched_switch raw_syscalls/sys_enter raw_syscalls/sys_exit', 382 ' -c /path/to/full-textual-trace.config', '', 383 ANSI.BOLD + 'Long traces' + ANSI.END, 384 'If you want to record a hours long trace and stream it into a file ', 385 'you need to pass a full trace config and set write_into_file = true.', 386 'See https://perfetto.dev/docs/concepts/config#long-traces .' 387 ]) 388 parser = argparse.ArgumentParser( 389 epilog=examples, formatter_class=argparse.RawTextHelpFormatter) 390 391 help = 'Output file or directory (default: %s)' % default_out_dir_str 392 parser.add_argument('-o', '--out', default=default_out_dir, help=help) 393 394 help = 'Don\'t open or serve the trace' 395 parser.add_argument('-n', '--no-open', action='store_true', help=help) 396 397 help = 'Don\'t open in browser, but still serve trace (good for remote use)' 398 parser.add_argument('--no-open-browser', action='store_true', help=help) 399 400 help = 'The web address used to open trace files' 401 parser.add_argument('--origin', default='https://ui.perfetto.dev', help=help) 402 403 help = 'Force the use of the sideloaded binaries rather than system daemons' 404 parser.add_argument('--sideload', action='store_true', help=help) 405 406 help = ('Sideload the given binary rather than downloading it. ' + 407 'Implies --sideload') 408 parser.add_argument('--sideload-path', default=None, help=help) 409 410 help = 'Ignores any tracing guardrails which might be used' 411 parser.add_argument('--no-guardrails', action='store_true', help=help) 412 413 help = 'Don\'t run `adb root` run as user (only when sideloading)' 414 parser.add_argument('-u', '--user', action='store_true', help=help) 415 416 help = 'Specify the ADB device serial' 417 parser.add_argument('--serial', '-s', default=None, help=help) 418 419 grp = parser.add_argument_group( 420 'Short options: (only when not using -c/--config)') 421 422 help = 'Trace duration N[s,m,h] (default: trace until stopped)' 423 grp.add_argument('-t', '--time', default='0s', help=help) 424 425 help = 'Ring buffer size N[mb,gb] (default: 32mb)' 426 grp.add_argument('-b', '--buffer', default='32mb', help=help) 427 428 help = ('Android (atrace) app names. Can be specified multiple times.\n-a*' + 429 'for all apps (without space between a and * or bash will expand it)') 430 grp.add_argument( 431 '-a', 432 '--app', 433 metavar='com.myapp', 434 action='append', 435 default=[], 436 help=help) 437 438 help = 'sched, gfx, am, wm (see --list)' 439 grp.add_argument('events', metavar='Atrace events', nargs='*', help=help) 440 441 help = 'sched/sched_switch kmem/kmem (see --list-ftrace)' 442 grp.add_argument('_', metavar='Ftrace events', nargs='*', help=help) 443 444 help = 'Lists all the categories available' 445 grp.add_argument('--list', action='store_true', help=help) 446 447 help = 'Lists all the ftrace events available' 448 grp.add_argument('--list-ftrace', action='store_true', help=help) 449 450 section = ('Full trace config (only when not using short options)') 451 grp = parser.add_argument_group(section) 452 453 help = 'Can be generated with https://ui.perfetto.dev/#!/record' 454 grp.add_argument('-c', '--config', default=None, help=help) 455 456 help = 'Parse input from --config as binary proto (default: parse as text)' 457 grp.add_argument('--bin', action='store_true', help=help) 458 459 help = ('Pass the trace through the trace reporter API. Only works when ' 460 'using the full trace config (-c) with the reporter package name ' 461 "'android.perfetto.cts.reporter' and the reporter class name " 462 "'android.perfetto.cts.reporter.PerfettoReportService' with the " 463 'reporter installed on the device (see ' 464 'tools/install_test_reporter_app.py).') 465 grp.add_argument('--reporter-api', action='store_true', help=help) 466 467 args = parser.parse_args() 468 args.sideload = args.sideload or args.sideload_path is not None 469 470 if args.serial: 471 os.environ["ANDROID_SERIAL"] = args.serial 472 473 find_adb() 474 475 if args.list: 476 adb('shell', 'atrace', '--list_categories').wait() 477 sys.exit(0) 478 479 if args.list_ftrace: 480 adb('shell', 'cat /d/tracing/available_events | tr : /').wait() 481 sys.exit(0) 482 483 if args.config is not None and not os.path.exists(args.config): 484 prt('Config file not found: %s' % args.config, ANSI.RED) 485 sys.exit(1) 486 487 if len(args.events) == 0 and args.config is None: 488 prt('Must either pass short options (e.g. -t 10s sched) or a --config file', 489 ANSI.RED) 490 parser.print_help() 491 sys.exit(1) 492 493 if args.config is None and args.events and os.path.exists(args.events[0]): 494 prt(('The passed event name "%s" is a local file. ' % args.events[0] + 495 'Did you mean to pass -c / --config ?'), ANSI.RED) 496 sys.exit(1) 497 498 if args.reporter_api and not args.config: 499 prt('Must pass --config when using --reporter-api', ANSI.RED) 500 parser.print_help() 501 sys.exit(1) 502 503 return args 504 505 506class SignalException(Exception): 507 pass 508 509 510def signal_handler(sig, frame): 511 raise SignalException('Received signal ' + str(sig)) 512 513 514signal.signal(signal.SIGINT, signal_handler) 515signal.signal(signal.SIGTERM, signal_handler) 516 517 518def start_trace(args, print_log=True): 519 perfetto_cmd = 'perfetto' 520 device_dir = '/data/misc/perfetto-traces/' 521 522 # Check the version of android. If too old (< Q) sideload tracebox. Also use 523 # use /data/local/tmp as /data/misc/perfetto-traces was introduced only later. 524 probe_cmd = 'getprop ro.build.version.sdk; getprop ro.product.cpu.abi; whoami' 525 probe = adb('shell', probe_cmd, stdout=subprocess.PIPE) 526 lines = probe.communicate()[0].decode().strip().split('\n') 527 lines = [x.strip() for x in lines] # To strip \r(s) on Windows. 528 if probe.returncode != 0: 529 prt('ADB connection failed', ANSI.RED) 530 sys.exit(1) 531 api_level = int(lines[0]) 532 abi = lines[1] 533 arch = ABI_TO_ARCH.get(abi) 534 if arch is None: 535 prt('Unsupported ABI: ' + abi) 536 sys.exit(1) 537 shell_user = lines[2] 538 if api_level < 29 or args.sideload: # 29: Android Q. 539 tracebox_bin = args.sideload_path 540 if tracebox_bin is None: 541 tracebox_bin = get_perfetto_prebuilt( 542 TRACEBOX_MANIFEST, arch='android-' + arch) 543 perfetto_cmd = '/data/local/tmp/tracebox' 544 exit_code = adb('push', '--sync', tracebox_bin, perfetto_cmd).wait() 545 exit_code |= adb('shell', 'chmod 755 ' + perfetto_cmd).wait() 546 if exit_code != 0: 547 prt('ADB push failed', ANSI.RED) 548 sys.exit(1) 549 device_dir = '/data/local/tmp/' 550 if shell_user != 'root' and not args.user: 551 # Run as root if possible as that will give access to more tracing 552 # capabilities. Non-root still works, but some ftrace events might not be 553 # available. 554 adb('root').wait() 555 556 tstamp = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M') 557 fname = '%s-%s.pftrace' % (tstamp, os.urandom(3).hex()) 558 device_file = device_dir + fname 559 560 cmd = [perfetto_cmd, '--background'] 561 if not args.bin: 562 cmd.append('--txt') 563 564 if args.no_guardrails: 565 cmd.append('--no-guardrails') 566 567 if args.reporter_api: 568 # Remove all old reporter files to avoid polluting the file we will extract 569 # later. 570 adb('shell', 571 'rm /sdcard/Android/data/android.perfetto.cts.reporter/files/*').wait() 572 cmd.append('--upload') 573 else: 574 cmd.extend(['-o', device_file]) 575 576 on_device_config = None 577 on_host_config = None 578 if args.config is not None: 579 cmd += ['-c', '-'] 580 if api_level < 24: 581 # adb shell does not redirect stdin. Push the config on a temporary file 582 # on the device. 583 mktmp = adb( 584 'shell', 585 'mktemp', 586 '--tmpdir', 587 '/data/local/tmp', 588 stdout=subprocess.PIPE) 589 on_device_config = mktmp.communicate()[0].decode().strip().strip() 590 if mktmp.returncode != 0: 591 prt('Failed to create config on device', ANSI.RED) 592 sys.exit(1) 593 exit_code = adb('push', '--sync', args.config, on_device_config).wait() 594 if exit_code != 0: 595 prt('Failed to push config on device', ANSI.RED) 596 sys.exit(1) 597 cmd = ['cat', on_device_config, '|'] + cmd 598 else: 599 on_host_config = args.config 600 else: 601 cmd += ['-t', args.time, '-b', args.buffer] 602 for app in args.app: 603 cmd += ['--app', '\'' + app + '\''] 604 cmd += args.events 605 606 # Work out the output file or directory. 607 if args.out.endswith('/') or os.path.isdir(args.out): 608 host_dir = args.out 609 host_file = os.path.join(args.out, fname) 610 else: 611 host_file = args.out 612 host_dir = os.path.dirname(host_file) 613 if host_dir == '': 614 host_dir = '.' 615 host_file = './' + host_file 616 if not os.path.exists(host_dir): 617 shutil.os.makedirs(host_dir) 618 619 with open(on_host_config or os.devnull, 'rb') as f: 620 if print_log: 621 print('Running ' + ' '.join(cmd)) 622 proc = adb('shell', *cmd, stdin=f, stdout=subprocess.PIPE) 623 proc_out = proc.communicate()[0].decode().strip() 624 if on_device_config is not None: 625 adb('shell', 'rm', on_device_config).wait() 626 # On older versions of Android (x86_64 emulator running API 22) the output 627 # looks like: 628 # WARNING: linker: /data/local/tmp/tracebox: unused DT entry: ... 629 # WARNING: ... (other 2 WARNING: linker: lines) 630 # 1234 <-- The actual pid we want. 631 match = re.search(r'^(\d+)$', proc_out, re.M) 632 if match is None: 633 prt('Failed to read the pid from perfetto --background', ANSI.RED) 634 prt(proc_out) 635 sys.exit(1) 636 bg_pid = match.group(1) 637 exit_code = proc.wait() 638 639 if exit_code != 0: 640 prt('Perfetto invocation failed', ANSI.RED) 641 sys.exit(1) 642 643 prt('Trace started. Press CTRL+C to stop', ANSI.BLACK + ANSI.BG_BLUE) 644 log_level = "-v" 645 if not print_log: 646 log_level = "-e" 647 logcat = adb('logcat', log_level, 'brief', '-s', 'perfetto', '-b', 'main', 648 '-T', '1') 649 650 ctrl_c_count = 0 651 adb_failure_count = 0 652 while ctrl_c_count < 2: 653 try: 654 # On older Android devices adbd doesn't propagate the exit code. Hence 655 # the RUN/TERM parts. 656 poll = adb( 657 'shell', 658 'test -d /proc/%s && echo RUN || echo TERM' % bg_pid, 659 stdout=subprocess.PIPE) 660 poll_res = poll.communicate()[0].decode().strip() 661 if poll_res == 'TERM': 662 break # Process terminated 663 if poll_res == 'RUN': 664 # The 'perfetto' cmdline client is still running. If previously we had 665 # an ADB error, tell the user now it's all right again. 666 if adb_failure_count > 0: 667 adb_failure_count = 0 668 prt('ADB connection re-established, the trace is still ongoing', 669 ANSI.BLUE) 670 time.sleep(0.5) 671 continue 672 # Some ADB error happened. This can happen when tracing soon after boot, 673 # before logging in, when adb gets restarted. 674 adb_failure_count += 1 675 if adb_failure_count >= MAX_ADB_FAILURES: 676 prt('Too many unrecoverable ADB failures, bailing out', ANSI.RED) 677 sys.exit(1) 678 time.sleep(2) 679 except (KeyboardInterrupt, SignalException): 680 sig = 'TERM' if ctrl_c_count == 0 else 'KILL' 681 ctrl_c_count += 1 682 if print_log: 683 prt('Stopping the trace (SIG%s)' % sig, ANSI.BLACK + ANSI.BG_YELLOW) 684 adb('shell', 'kill -%s %s' % (sig, bg_pid)).wait() 685 686 logcat.kill() 687 logcat.wait() 688 689 if args.reporter_api: 690 if print_log: 691 prt('Waiting a few seconds to allow reporter to copy trace') 692 time.sleep(5) 693 694 ret = adb( 695 'shell', 696 'cp /sdcard/Android/data/android.perfetto.cts.reporter/files/* ' + 697 device_file).wait() 698 if ret != 0: 699 prt('Failed to extract reporter trace', ANSI.RED) 700 sys.exit(1) 701 702 if print_log: 703 prt('\n') 704 prt('Pulling into %s' % host_file, ANSI.BOLD) 705 adb('pull', device_file, host_file).wait() 706 adb('shell', 'rm -f ' + device_file).wait() 707 708 if not args.no_open: 709 if print_log: 710 prt('\n') 711 prt('Opening the trace (%s) in the browser' % host_file) 712 open_browser = not args.no_open_browser 713 open_trace_in_browser(host_file, open_browser, args.origin) 714 715 return host_file 716 717 718def main(): 719 args = setup_arguments() 720 start_trace(args) 721 722 723def prt(msg, colors=ANSI.END): 724 print(colors + msg + ANSI.END) 725 726 727def find_adb(): 728 """ Locate the "right" adb path 729 730 If adb is in the PATH use that (likely what the user wants) otherwise use the 731 hermetic one in our SDK copy. 732 """ 733 global adb_path 734 for path in ['adb', HERMETIC_ADB_PATH]: 735 try: 736 subprocess.call([path, '--version'], stdout=devnull, stderr=devnull) 737 adb_path = path 738 break 739 except OSError: 740 continue 741 if adb_path is None: 742 sdk_url = 'https://developer.android.com/studio/releases/platform-tools' 743 prt('Could not find a suitable adb binary in the PATH. ', ANSI.RED) 744 prt('You can download adb from %s' % sdk_url, ANSI.RED) 745 sys.exit(1) 746 747 748def open_trace_in_browser(path, open_browser, origin): 749 # We reuse the HTTP+RPC port because it's the only one allowed by the CSP. 750 PORT = 9001 751 path = os.path.abspath(path) 752 os.chdir(os.path.dirname(path)) 753 fname = os.path.basename(path) 754 socketserver.TCPServer.allow_reuse_address = True 755 with socketserver.TCPServer(('127.0.0.1', PORT), HttpHandler) as httpd: 756 address = f'{origin}/#!/?url=http://127.0.0.1:{PORT}/{fname}&referrer=record_android_trace' 757 if open_browser: 758 webbrowser.open_new_tab(address) 759 else: 760 print(f'Open URL in browser: {address}') 761 762 httpd.expected_fname = fname 763 httpd.fname_get_completed = None 764 httpd.allow_origin = origin 765 while httpd.fname_get_completed is None: 766 httpd.handle_request() 767 768 769def adb(*args, stdin=devnull, stdout=None, stderr=None): 770 cmd = [adb_path, *args] 771 setpgrp = None 772 if os.name != 'nt': 773 # On Linux/Mac, start a new process group so all child processes are killed 774 # on exit. Unsupported on Windows. 775 setpgrp = lambda: os.setpgrp() 776 proc = subprocess.Popen( 777 cmd, stdin=stdin, stdout=stdout, stderr=stderr, preexec_fn=setpgrp) 778 procs.append(proc) 779 return proc 780 781 782def kill_all_subprocs_on_exit(): 783 for p in [p for p in procs if p.poll() is None]: 784 p.kill() 785 786 787def check_hash(file_name, sha_value): 788 with open(file_name, 'rb') as fd: 789 file_hash = hashlib.sha1(fd.read()).hexdigest() 790 return file_hash == sha_value 791 792 793if __name__ == '__main__': 794 sys.exit(main()) 795