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