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