• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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