1#!/usr/bin/env python3 2# Copyright (C) 2022 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"""Runs tracing with CPU profiling enabled, and symbolizes traces if requested. 16 17# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 18# DO NOT EDIT. Auto-generated by tools/gen_amalgamated_python_tools 19# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 20 21For usage instructions, please see: 22https://perfetto.dev/docs/quickstart/callstack-sampling 23 24Adapted in large part from `heap_profile`. 25""" 26 27import argparse 28import os 29import shutil 30import signal 31import subprocess 32import sys 33import tempfile 34import textwrap 35import time 36import uuid 37 38 39# ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py 40# This file has been generated by: /Users/hjd/src/perfetto/tools/roll-prebuilts v34.0 41TRACECONV_MANIFEST = [{ 42 'arch': 43 'mac-amd64', 44 'file_name': 45 'traceconv', 46 'file_size': 47 7904536, 48 'url': 49 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-amd64/traceconv', 50 'sha256': 51 '037f84ac943f3f4d75447c668cc49c966fe3d85eca3a455c958b24fc6a9e314a', 52 'platform': 53 'darwin', 54 'machine': ['x86_64'] 55}, { 56 'arch': 57 'mac-arm64', 58 'file_name': 59 'traceconv', 60 'file_size': 61 6554600, 62 'url': 63 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-arm64/traceconv', 64 'sha256': 65 'eda545ef4fa37fdfa1b47ced7cbbe0aa3c0df9bd161cacd7c78e6c55aef98d20', 66 'platform': 67 'darwin', 68 'machine': ['arm64'] 69}, { 70 'arch': 71 'linux-amd64', 72 'file_name': 73 'traceconv', 74 'file_size': 75 7664384, 76 'url': 77 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-amd64/traceconv', 78 'sha256': 79 '24285e6e0e873d393fa5a993bac18ec8e1ab5fae6f4e3453214e095ef36e4c45', 80 'platform': 81 'linux', 82 'machine': ['x86_64'] 83}, { 84 'arch': 85 'linux-arm', 86 'file_name': 87 'traceconv', 88 'file_size': 89 5657944, 90 'url': 91 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm/traceconv', 92 'sha256': 93 'c9af3d976f849fc75e96c2c552cb14fcc9eacce6fe7c45c4a8289080b0f66706', 94 'platform': 95 'linux', 96 'machine': ['armv6l', 'armv7l', 'armv8l'] 97}, { 98 'arch': 99 'linux-arm64', 100 'file_name': 101 'traceconv', 102 'file_size': 103 7184224, 104 'url': 105 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm64/traceconv', 106 'sha256': 107 'c6dc936492d58a40cd8e0b58abc46bd479e0c1c387cd1ba29198a6c9b2000d7a', 108 'platform': 109 'linux', 110 'machine': ['aarch64'] 111}, { 112 'arch': 113 'android-arm', 114 'file_name': 115 'traceconv', 116 'file_size': 117 5325260, 118 'url': 119 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm/traceconv', 120 'sha256': 121 '963267dcb58cdde9f61a952e5cb7f3557833209d3251e7fdcefc3b52db54f77b' 122}, { 123 'arch': 124 'android-arm64', 125 'file_name': 126 'traceconv', 127 'file_size': 128 6572688, 129 'url': 130 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm64/traceconv', 131 'sha256': 132 '87373c351fe5e947826cd957438cab8a37a352bf83b1cbbb15fe276eee9d873a' 133}, { 134 'arch': 135 'android-x86', 136 'file_name': 137 'traceconv', 138 'file_size': 139 7303588, 140 'url': 141 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x86/traceconv', 142 'sha256': 143 'dfc4e714963b5ed662d29d6028ffa69e67f8cd2f9a28223f715437a260fd456f' 144}, { 145 'arch': 146 'android-x64', 147 'file_name': 148 'traceconv', 149 'file_size': 150 7482056, 151 'url': 152 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x64/traceconv', 153 'sha256': 154 '79c666c629fcffd810635270b45e58b40ed253d22650f41550057e5d8f8c49a7' 155}, { 156 'arch': 157 'windows-amd64', 158 'file_name': 159 'traceconv.exe', 160 'file_size': 161 7072768, 162 'url': 163 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/windows-amd64/traceconv.exe', 164 'sha256': 165 '40fac80fdeae443a924e160650c94629e6463c1fb5a4f04f4ef6e9e5e72a3965', 166 'platform': 167 'win32', 168 'machine': ['amd64'] 169}] 170 171# ----- Amalgamator: end of python/perfetto/prebuilts/manifests/traceconv.py 172 173# ----- Amalgamator: begin of python/perfetto/prebuilts/perfetto_prebuilts.py 174# Copyright (C) 2021 The Android Open Source Project 175# 176# Licensed under the Apache License, Version 2.0 (the "License"); 177# you may not use this file except in compliance with the License. 178# You may obtain a copy of the License at 179# 180# http://www.apache.org/licenses/LICENSE-2.0 181# 182# Unless required by applicable law or agreed to in writing, software 183# distributed under the License is distributed on an "AS IS" BASIS, 184# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 185# See the License for the specific language governing permissions and 186# limitations under the License. 187""" 188Functions to fetch pre-pinned Perfetto prebuilts. 189 190This function is used in different places: 191- Into the //tools/{trace_processor, traceconv} scripts, which are just plain 192 wrappers around executables. 193- Into the //tools/{heap_profiler, record_android_trace} scripts, which contain 194 some other hand-written python code. 195 196The manifest argument looks as follows: 197TRACECONV_MANIFEST = [ 198 { 199 'arch': 'mac-amd64', 200 'file_name': 'traceconv', 201 'file_size': 7087080, 202 'url': https://commondatastorage.googleapis.com/.../trace_to_text', 203 'sha256': 7d957c005b0dc130f5bd855d6cec27e060d38841b320d04840afc569f9087490', 204 'platform': 'darwin', 205 'machine': 'x86_64' 206 }, 207 ... 208] 209 210The intended usage is: 211 212 from perfetto.prebuilts.manifests.traceconv import TRACECONV_MANIFEST 213 bin_path = get_perfetto_prebuilt(TRACECONV_MANIFEST) 214 subprocess.call(bin_path, ...) 215""" 216 217import hashlib 218import os 219import platform 220import subprocess 221import sys 222 223 224def download_or_get_cached(file_name, url, sha256): 225 """ Downloads a prebuilt or returns a cached version 226 227 The first time this is invoked, it downloads the |url| and caches it into 228 ~/.local/share/perfetto/prebuilts/$tool_name. On subsequent invocations it 229 just runs the cached version. 230 """ 231 dir = os.path.join( 232 os.path.expanduser('~'), '.local', 'share', 'perfetto', 'prebuilts') 233 os.makedirs(dir, exist_ok=True) 234 bin_path = os.path.join(dir, file_name) 235 sha256_path = os.path.join(dir, file_name + '.sha256') 236 needs_download = True 237 238 # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last 239 # download is cached into file_name.sha256, just check if that matches. 240 if os.path.exists(bin_path) and os.path.exists(sha256_path): 241 with open(sha256_path, 'rb') as f: 242 digest = f.read().decode() 243 if digest == sha256: 244 needs_download = False 245 246 if needs_download: 247 # Either the filed doesn't exist or the SHA256 doesn't match. 248 tmp_path = bin_path + '.tmp' 249 print('Downloading ' + url) 250 subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url]) 251 with open(tmp_path, 'rb') as fd: 252 actual_sha256 = hashlib.sha256(fd.read()).hexdigest() 253 if actual_sha256 != sha256: 254 raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' % 255 (url, actual_sha256, sha256)) 256 os.chmod(tmp_path, 0o755) 257 os.replace(tmp_path, bin_path) 258 with open(sha256_path, 'w') as f: 259 f.write(sha256) 260 return bin_path 261 262 263def get_perfetto_prebuilt(manifest, soft_fail=False, arch=None): 264 """ Downloads the prebuilt, if necessary, and returns its path on disk. """ 265 plat = sys.platform.lower() 266 machine = platform.machine().lower() 267 manifest_entry = None 268 for entry in manifest: 269 # If the caller overrides the arch, just match that (for Android prebuilts). 270 if arch: 271 if entry.get('arch') == arch: 272 manifest_entry = entry 273 break 274 continue 275 # Otherwise guess the local machine arch. 276 if entry.get('platform') == plat and machine in entry.get('machine', []): 277 manifest_entry = entry 278 break 279 if manifest_entry is None: 280 if soft_fail: 281 return None 282 raise Exception( 283 ('No prebuilts available for %s-%s\n' % (plat, machine)) + 284 'See https://perfetto.dev/docs/contributing/build-instructions') 285 286 return download_or_get_cached( 287 file_name=manifest_entry['file_name'], 288 url=manifest_entry['url'], 289 sha256=manifest_entry['sha256']) 290 291 292def run_perfetto_prebuilt(manifest): 293 bin_path = get_perfetto_prebuilt(manifest) 294 if sys.platform.lower() == 'win32': 295 sys.exit(subprocess.check_call([bin_path, *sys.argv[1:]])) 296 os.execv(bin_path, [bin_path] + sys.argv[1:]) 297 298# ----- Amalgamator: end of python/perfetto/prebuilts/perfetto_prebuilts.py 299 300# Used for creating directories, etc. 301UUID = str(uuid.uuid4())[-6:] 302 303# See `sigint_handler` below. 304IS_INTERRUPTED = False 305 306 307def sigint_handler(signal, frame): 308 """Useful for cleanly interrupting tracing.""" 309 global IS_INTERRUPTED 310 IS_INTERRUPTED = True 311 312 313def exit_with_no_profile(): 314 sys.exit("No profiles generated.") 315 316 317def exit_with_bug_report(error): 318 sys.exit( 319 "{}\n\n If this is unexpected, please consider filing a bug at: \n" 320 "https://perfetto.dev/docs/contributing/getting-started#bugs.".format( 321 error)) 322 323 324def adb_check_output(command): 325 """Runs an `adb` command and returns its output.""" 326 try: 327 return subprocess.check_output(command).decode('utf-8') 328 except FileNotFoundError: 329 sys.exit("`adb` not found: Is it installed or on PATH?") 330 except subprocess.CalledProcessError as error: 331 sys.exit("`adb` error: Are any (or multiple) devices connected?\n" 332 "If multiple devices are connected, please select one by " 333 "setting `ANDROID_SERIAL=device_id`.\n" 334 "{}".format(error)) 335 except Exception as error: 336 exit_with_bug_report(error) 337 338 339def parse_and_validate_args(): 340 """Parses, validates, and returns command-line arguments for this script.""" 341 DESCRIPTION = """Runs tracing with CPU profiling enabled, and symbolizes 342 traces if requested. 343 344 For usage instructions, please see: 345 https://perfetto.dev/docs/quickstart/callstack-sampling 346 """ 347 parser = argparse.ArgumentParser(description=DESCRIPTION) 348 parser.add_argument( 349 "-f", 350 "--frequency", 351 help="Sampling frequency (Hz). " 352 "Default: 100 Hz.", 353 metavar="FREQUENCY", 354 type=int, 355 default=100) 356 parser.add_argument( 357 "-d", 358 "--duration", 359 help="Duration of profile (ms). 0 to run until interrupted. " 360 "Default: until interrupted by user.", 361 metavar="DURATION", 362 type=int, 363 default=0) 364 # Profiling using hardware counters. 365 parser.add_argument( 366 "-e", 367 "--event", 368 help="Use the specified hardware counter event for sampling.", 369 metavar="EVENT", 370 action="append", 371 # See: '//perfetto/protos/perfetto/trace/perfetto_trace.proto'. 372 choices=['HW_CPU_CYCLES', 'HW_INSTRUCTIONS', 'HW_CACHE_REFERENCES', 373 'HW_CACHE_MISSES', 'HW_BRANCH_INSTRUCTIONS', 'HW_BRANCH_MISSES', 374 'HW_BUS_CYCLES', 'HW_STALLED_CYCLES_FRONTEND', 375 'HW_STALLED_CYCLES_BACKEND'], 376 default=[]) 377 parser.add_argument( 378 "-k", 379 "--kernel-frames", 380 help="Collect kernel frames. Default: false.", 381 action="store_true", 382 default=False) 383 parser.add_argument( 384 "-n", 385 "--name", 386 help="Comma-separated list of names of processes to be profiled.", 387 metavar="NAMES", 388 default=None) 389 parser.add_argument( 390 "-p", 391 "--partial-matching", 392 help="If set, enables \"partial matching\" on the strings in --names/-n." 393 "Processes that are already running when profiling is started, and whose " 394 "names include any of the values in --names/-n as substrings will be " 395 "profiled.", 396 action="store_true") 397 parser.add_argument( 398 "-c", 399 "--config", 400 help="A custom configuration file, if any, to be used for profiling. " 401 "If provided, --frequency/-f, --duration/-d, and --name/-n are not used.", 402 metavar="CONFIG", 403 default=None) 404 parser.add_argument( 405 "--no-annotations", 406 help="Do not suffix the pprof function names with Android ART mode " 407 "annotations such as [jit].", 408 action="store_true") 409 parser.add_argument( 410 "--print-config", 411 action="store_true", 412 help="Print config instead of running. For debugging.") 413 parser.add_argument( 414 "-o", 415 "--output", 416 help="Output directory for recorded trace.", 417 metavar="DIRECTORY", 418 default=None) 419 420 args = parser.parse_args() 421 if args.config is not None: 422 if args.name is not None: 423 sys.exit("--name/-n should not be specified with --config/-c.") 424 elif args.event: 425 sys.exit("-e/--event should not be specified with --config/-c.") 426 elif args.config is None and args.name is None: 427 sys.exit("One of --names/-n or --config/-c is required.") 428 429 return args 430 431 432def get_matching_processes(args, names_to_match): 433 """Returns a list of currently-running processes whose names match 434 `names_to_match`. 435 436 Args: 437 args: The command-line arguments provided to this script. 438 names_to_match: The list of process names provided by the user. 439 """ 440 # Returns names as they are. 441 if not args.partial_matching: 442 return names_to_match 443 444 # Attempt to match names to names of currently running processes. 445 PS_PROCESS_OFFSET = 8 446 matching_processes = [] 447 for line in adb_check_output(['adb', 'shell', 'ps', '-A']).splitlines(): 448 line_split = line.split() 449 if len(line_split) <= PS_PROCESS_OFFSET: 450 continue 451 process = line_split[PS_PROCESS_OFFSET] 452 for name in names_to_match: 453 if name in process: 454 matching_processes.append(process) 455 break 456 457 return matching_processes 458 459 460def get_perfetto_config(args): 461 """Returns a Perfetto config with CPU profiling enabled for the selected 462 processes. 463 464 Args: 465 args: The command-line arguments provided to this script. 466 """ 467 if args.config is not None: 468 try: 469 with open(args.config, 'r') as config_file: 470 return config_file.read() 471 except IOError as error: 472 sys.exit("Unable to read config file: {}".format(error)) 473 474 CONFIG_INDENT = ' ' 475 CONFIG = textwrap.dedent('''\ 476 buffers {{ 477 size_kb: 2048 478 }} 479 480 buffers {{ 481 size_kb: 63488 482 }} 483 484 data_sources {{ 485 config {{ 486 name: "linux.process_stats" 487 target_buffer: 0 488 process_stats_config {{ 489 proc_stats_poll_ms: 100 490 }} 491 }} 492 }} 493 494 duration_ms: {duration} 495 write_into_file: true 496 flush_timeout_ms: 30000 497 flush_period_ms: 604800000 498 ''') 499 500 matching_processes = [] 501 if args.name is not None: 502 names_to_match = [name.strip() for name in args.name.split(',')] 503 matching_processes = get_matching_processes(args, names_to_match) 504 505 if not matching_processes: 506 sys.exit("No running processes matched for profiling.") 507 508 target_config = "\n".join( 509 [f'{CONFIG_INDENT}target_cmdline: "{p}"' for p in matching_processes]) 510 511 events = args.event or ['SW_CPU_CLOCK'] 512 for event in events: 513 CONFIG += (textwrap.dedent(''' 514 data_sources {{ 515 config {{ 516 name: "linux.perf" 517 target_buffer: 1 518 perf_event_config {{ 519 timebase {{ 520 counter: %s 521 frequency: {frequency} 522 timestamp_clock: PERF_CLOCK_MONOTONIC 523 }} 524 callstack_sampling {{ 525 scope {{ 526 {target_config} 527 }} 528 kernel_frames: {kernel_config} 529 }} 530 }} 531 }} 532 }} 533 ''') % (event)) 534 535 if args.kernel_frames: 536 kernel_config = "true" 537 else: 538 kernel_config = "false" 539 540 if not args.print_config: 541 print("Configured profiling for these processes:\n") 542 for matching_process in matching_processes: 543 print(matching_process) 544 print() 545 546 config = CONFIG.format( 547 frequency=args.frequency, 548 duration=args.duration, 549 target_config=target_config, 550 kernel_config=kernel_config) 551 552 return config 553 554 555def release_or_newer(release): 556 """Returns whether a new enough Android release is being used.""" 557 SDK = {'R': 30} 558 sdk = int( 559 adb_check_output( 560 ['adb', 'shell', 'getprop', 'ro.system.build.version.sdk']).strip()) 561 if sdk >= SDK[release]: 562 return True 563 564 codename = adb_check_output( 565 ['adb', 'shell', 'getprop', 'ro.build.version.codename']).strip() 566 return codename == release 567 568 569def get_and_prepare_profile_target(args): 570 """Returns the target where the trace/profile will be output. Creates a 571 new directory if necessary. 572 573 Args: 574 args: The command-line arguments provided to this script. 575 """ 576 profile_target = os.path.join(tempfile.gettempdir(), UUID) 577 if args.output is not None: 578 profile_target = args.output 579 else: 580 os.makedirs(profile_target, exist_ok=True) 581 if not os.path.isdir(profile_target): 582 sys.exit("Output directory {} not found.".format(profile_target)) 583 if os.listdir(profile_target): 584 sys.exit("Output directory {} not empty.".format(profile_target)) 585 586 return profile_target 587 588 589def record_trace(config, profile_target): 590 """Runs Perfetto with the provided configuration to record a trace. 591 592 Args: 593 config: The Perfetto config to be used for tracing/profiling. 594 profile_target: The directory where the recorded trace is output. 595 """ 596 NULL = open(os.devnull) 597 NO_OUT = { 598 'stdout': NULL, 599 'stderr': NULL, 600 } 601 if not release_or_newer('R'): 602 sys.exit("This tool requires Android R+ to run.") 603 604 # Push configuration to the device. 605 tf = tempfile.NamedTemporaryFile() 606 tf.file.write(config.encode('utf-8')) 607 tf.file.flush() 608 profile_config_path = '/data/misc/perfetto-configs/config-' + UUID 609 adb_check_output(['adb', 'push', tf.name, profile_config_path]) 610 tf.close() 611 612 613 profile_device_path = '/data/misc/perfetto-traces/profile-' + UUID 614 perfetto_command = ('perfetto --txt -c {} -o {} -d') 615 try: 616 perfetto_pid = int( 617 adb_check_output([ 618 'adb', 'exec-out', 619 perfetto_command.format(profile_config_path, profile_device_path) 620 ]).strip()) 621 except ValueError as error: 622 sys.exit("Unable to start profiling: {}".format(error)) 623 624 print("Profiling active. Press Ctrl+C to terminate.") 625 626 old_handler = signal.signal(signal.SIGINT, sigint_handler) 627 628 perfetto_alive = True 629 while perfetto_alive and not IS_INTERRUPTED: 630 perfetto_alive = subprocess.call( 631 ['adb', 'shell', '[ -d /proc/{} ]'.format(perfetto_pid)], **NO_OUT) == 0 632 time.sleep(0.25) 633 634 print("Finishing profiling and symbolization...") 635 636 if IS_INTERRUPTED: 637 adb_check_output(['adb', 'shell', 'kill', '-INT', str(perfetto_pid)]) 638 639 # Restore old handler. 640 signal.signal(signal.SIGINT, old_handler) 641 642 while perfetto_alive: 643 perfetto_alive = subprocess.call( 644 ['adb', 'shell', '[ -d /proc/{} ]'.format(perfetto_pid)]) == 0 645 time.sleep(0.25) 646 647 profile_host_path = os.path.join(profile_target, 'raw-trace') 648 adb_check_output(['adb', 'pull', profile_device_path, profile_host_path]) 649 adb_check_output(['adb', 'shell', 'rm', profile_config_path]) 650 adb_check_output(['adb', 'shell', 'rm', profile_device_path]) 651 652 653def get_traceconv(): 654 """Sets up and returns the path to `traceconv`.""" 655 try: 656 traceconv = get_perfetto_prebuilt(TRACECONV_MANIFEST, soft_fail=True) 657 except Exception as error: 658 exit_with_bug_report(error) 659 if traceconv is None: 660 exit_with_bug_report( 661 "Unable to download `traceconv` for symbolizing profiles.") 662 663 return traceconv 664 665 666def concatenate_files(files_to_concatenate, output_file): 667 """Concatenates files. 668 669 Args: 670 files_to_concatenate: Paths for input files to concatenate. 671 output_file: Path to the resultant output file. 672 """ 673 with open(output_file, 'wb') as output: 674 for file in files_to_concatenate: 675 with open(file, 'rb') as input: 676 shutil.copyfileobj(input, output) 677 678 679def symbolize_trace(traceconv, profile_target): 680 """Attempts symbolization of the recorded trace/profile, if symbols are 681 available. 682 683 Args: 684 traceconv: The path to the `traceconv` binary used for symbolization. 685 profile_target: The directory where the recorded trace was output. 686 687 Returns: 688 The path to the symbolized trace file if symbolization was completed, 689 and the original trace file, if it was not. 690 """ 691 binary_path = os.getenv('PERFETTO_BINARY_PATH') 692 trace_file = os.path.join(profile_target, 'raw-trace') 693 files_to_concatenate = [trace_file] 694 695 if binary_path is not None: 696 try: 697 with open(os.path.join(profile_target, 'symbols'), 'w') as symbols_file: 698 return_code = subprocess.call([traceconv, 'symbolize', trace_file], 699 env=dict( 700 os.environ, 701 PERFETTO_BINARY_PATH=binary_path), 702 stdout=symbols_file) 703 except IOError as error: 704 sys.exit("Unable to write symbols to disk: {}".format(error)) 705 if return_code == 0: 706 files_to_concatenate.append(os.path.join(profile_target, 'symbols')) 707 else: 708 print("Failed to symbolize. Continuing without symbols.", file=sys.stderr) 709 710 if len(files_to_concatenate) > 1: 711 trace_file = os.path.join(profile_target, 'symbolized-trace') 712 try: 713 concatenate_files(files_to_concatenate, trace_file) 714 except Exception as error: 715 sys.exit("Unable to write symbolized profile to disk: {}".format(error)) 716 717 return trace_file 718 719 720def generate_pprof_profiles(traceconv, trace_file, args): 721 """Generates pprof profiles from the recorded trace. 722 723 Args: 724 traceconv: The path to the `traceconv` binary used for generating profiles. 725 trace_file: The oath to the recorded and potentially symbolized trace file. 726 727 Returns: 728 The directory where pprof profiles are output. 729 """ 730 try: 731 conversion_args = [traceconv, 'profile', '--perf'] + ( 732 ['--no-annotations'] if args.no_annotations else []) + [trace_file] 733 traceconv_output = subprocess.check_output(conversion_args) 734 except Exception as error: 735 exit_with_bug_report( 736 "Unable to extract profiles from trace: {}".format(error)) 737 738 profiles_output_directory = None 739 for word in traceconv_output.decode('utf-8').split(): 740 if 'perf_profile-' in word: 741 profiles_output_directory = word 742 if profiles_output_directory is None: 743 exit_with_no_profile() 744 return profiles_output_directory 745 746 747def copy_profiles_to_destination(profile_target, profile_path): 748 """Copies recorded profiles to `profile_target` from `profile_path`.""" 749 profile_files = os.listdir(profile_path) 750 if not profile_files: 751 exit_with_no_profile() 752 753 try: 754 for profile_file in profile_files: 755 shutil.copy(os.path.join(profile_path, profile_file), profile_target) 756 except Exception as error: 757 sys.exit("Unable to copy profiles to {}: {}".format(profile_target, error)) 758 759 print("Wrote profiles to {}".format(profile_target)) 760 761 762def main(argv): 763 args = parse_and_validate_args() 764 profile_target = get_and_prepare_profile_target(args) 765 trace_config = get_perfetto_config(args) 766 if args.print_config: 767 print(trace_config) 768 return 0 769 record_trace(trace_config, profile_target) 770 traceconv = get_traceconv() 771 trace_file = symbolize_trace(traceconv, profile_target) 772 copy_profiles_to_destination( 773 profile_target, generate_pprof_profiles(traceconv, trace_file, args)) 774 return 0 775 776 777if __name__ == '__main__': 778 sys.exit(main(sys.argv)) 779