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