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