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: /Users/hjd/src/perfetto/tools/roll-prebuilts v34.0 37TRACEBOX_MANIFEST = [{ 38 'arch': 39 'mac-amd64', 40 'file_name': 41 'tracebox', 42 'file_size': 43 1432064, 44 'url': 45 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-amd64/tracebox', 46 'sha256': 47 '4ceb7646cd99303224ab5e7ff0a9f84c04f3c5466fff65a55dab65171ae9d482', 48 'platform': 49 'darwin', 50 'machine': ['x86_64'] 51}, { 52 'arch': 53 'mac-arm64', 54 'file_name': 55 'tracebox', 56 'file_size': 57 1325704, 58 'url': 59 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-arm64/tracebox', 60 'sha256': 61 '2c560fcce5e19eb692e50487af134e2078347cdb79decba0c572917860528388', 62 'platform': 63 'darwin', 64 'machine': ['arm64'] 65}, { 66 'arch': 67 'linux-amd64', 68 'file_name': 69 'tracebox', 70 'file_size': 71 2155496, 72 'url': 73 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-amd64/tracebox', 74 'sha256': 75 '10b92180bb461a7e21be3f8b3d4640430a98d0547238ce095709213b378217d2', 76 'platform': 77 'linux', 78 'machine': ['x86_64'] 79}, { 80 'arch': 81 'linux-arm', 82 'file_name': 83 'tracebox', 84 'file_size': 85 1288764, 86 'url': 87 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm/tracebox', 88 'sha256': 89 'fa28950ce2b7a9345fbb9272f2dd04d3d4eb2a87f021df25e1e649840eae60b5', 90 'platform': 91 'linux', 92 'machine': ['armv6l', 'armv7l', 'armv8l'] 93}, { 94 'arch': 95 'linux-arm64', 96 'file_name': 97 'tracebox', 98 'file_size': 99 2082704, 100 'url': 101 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm64/tracebox', 102 'sha256': 103 '85c371d79b8e23d22a293c29e6399dc311d891a6bd85d7eeaf2cb0179c69eb27', 104 'platform': 105 'linux', 106 'machine': ['aarch64'] 107}, { 108 'arch': 109 'android-arm', 110 'file_name': 111 'tracebox', 112 'file_size': 113 1169364, 114 'url': 115 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm/tracebox', 116 'sha256': 117 '40a3f31600f02dea10e290134d5c862e0e717f4f039756889a4e72c60f1591b6' 118}, { 119 'arch': 120 'android-arm64', 121 'file_name': 122 'tracebox', 123 'file_size': 124 1776296, 125 'url': 126 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm64/tracebox', 127 'sha256': 128 '562505fca18b34a97687dc002aeebcbf20acef68c8a8e48bed6d618c20e07c92' 129}, { 130 'arch': 131 'android-x86', 132 'file_name': 133 'tracebox', 134 'file_size': 135 1767340, 136 'url': 137 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x86/tracebox', 138 'sha256': 139 'eb47eb43ba93403557dd15a61196799e945ec324d96109db2f155fb131f9996a' 140}, { 141 'arch': 142 'android-x64', 143 'file_name': 144 'tracebox', 145 'file_size': 146 2054824, 147 'url': 148 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x64/tracebox', 149 'sha256': 150 'a3ae6d108e041ba368a9770f952772f111865d4eff7c8e4e4e2f653f45017948' 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 sys.exit(1) 573 bg_pid = match.group(1) 574 exit_code = proc.wait() 575 576 if exit_code != 0: 577 prt('Perfetto invocation failed', ANSI.RED) 578 sys.exit(1) 579 580 prt('Trace started. Press CTRL+C to stop', ANSI.BLACK + ANSI.BG_BLUE) 581 logcat = adb('logcat', '-v', 'brief', '-s', 'perfetto', '-b', 'main', '-T', 582 '1') 583 584 ctrl_c_count = 0 585 adb_failure_count = 0 586 while ctrl_c_count < 2: 587 try: 588 # On older Android devices adbd doesn't propagate the exit code. Hence 589 # the RUN/TERM parts. 590 poll = adb( 591 'shell', 592 'test -d /proc/%s && echo RUN || echo TERM' % bg_pid, 593 stdout=subprocess.PIPE) 594 poll_res = poll.communicate()[0].decode().strip() 595 if poll_res == 'TERM': 596 break # Process terminated 597 if poll_res == 'RUN': 598 # The 'perfetto' cmdline client is still running. If previously we had 599 # an ADB error, tell the user now it's all right again. 600 if adb_failure_count > 0: 601 adb_failure_count = 0 602 prt('ADB connection re-established, the trace is still ongoing', 603 ANSI.BLUE) 604 time.sleep(0.5) 605 continue 606 # Some ADB error happened. This can happen when tracing soon after boot, 607 # before logging in, when adb gets restarted. 608 adb_failure_count += 1 609 if adb_failure_count >= MAX_ADB_FAILURES: 610 prt('Too many unrecoverable ADB failures, bailing out', ANSI.RED) 611 sys.exit(1) 612 time.sleep(2) 613 except KeyboardInterrupt: 614 sig = 'TERM' if ctrl_c_count == 0 else 'KILL' 615 ctrl_c_count += 1 616 prt('Stopping the trace (SIG%s)' % sig, ANSI.BLACK + ANSI.BG_YELLOW) 617 adb('shell', 'kill -%s %s' % (sig, bg_pid)).wait() 618 619 logcat.kill() 620 logcat.wait() 621 622 prt('\n') 623 prt('Pulling into %s' % host_file, ANSI.BOLD) 624 adb('pull', device_file, host_file).wait() 625 adb('shell', 'rm -f ' + device_file).wait() 626 627 if not args.no_open: 628 prt('\n') 629 prt('Opening the trace (%s) in the browser' % host_file) 630 open_trace_in_browser(host_file) 631 632 633def prt(msg, colors=ANSI.END): 634 print(colors + msg + ANSI.END) 635 636 637def find_adb(): 638 """ Locate the "right" adb path 639 640 If adb is in the PATH use that (likely what the user wants) otherwise use the 641 hermetic one in our SDK copy. 642 """ 643 global adb_path 644 for path in ['adb', HERMETIC_ADB_PATH]: 645 try: 646 subprocess.call([path, '--version'], stdout=devnull, stderr=devnull) 647 adb_path = path 648 break 649 except OSError: 650 continue 651 if adb_path is None: 652 sdk_url = 'https://developer.android.com/studio/releases/platform-tools' 653 prt('Could not find a suitable adb binary in the PATH. ', ANSI.RED) 654 prt('You can download adb from %s' % sdk_url, ANSI.RED) 655 sys.exit(1) 656 657 658def open_trace_in_browser(path): 659 # We reuse the HTTP+RPC port because it's the only one allowed by the CSP. 660 PORT = 9001 661 os.chdir(os.path.dirname(path)) 662 fname = os.path.basename(path) 663 socketserver.TCPServer.allow_reuse_address = True 664 with socketserver.TCPServer(('127.0.0.1', PORT), HttpHandler) as httpd: 665 webbrowser.open_new_tab( 666 'https://ui.perfetto.dev/#!/?url=http://127.0.0.1:%d/%s' % 667 (PORT, fname)) 668 while httpd.__dict__.get('last_request') != '/' + fname: 669 httpd.handle_request() 670 671 672def adb(*args, stdin=devnull, stdout=None): 673 cmd = [adb_path, *args] 674 setpgrp = None 675 if os.name != 'nt': 676 # On Linux/Mac, start a new process group so all child processes are killed 677 # on exit. Unsupported on Windows. 678 setpgrp = lambda: os.setpgrp() 679 proc = subprocess.Popen(cmd, stdin=stdin, stdout=stdout, preexec_fn=setpgrp) 680 procs.append(proc) 681 return proc 682 683 684def kill_all_subprocs_on_exit(): 685 for p in [p for p in procs if p.poll() is None]: 686 p.kill() 687 688 689def check_hash(file_name, sha_value): 690 with open(file_name, 'rb') as fd: 691 file_hash = hashlib.sha1(fd.read()).hexdigest() 692 return file_hash == sha_value 693 694 695if __name__ == '__main__': 696 sys.exit(main()) 697