• 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: /usr/local/google/home/lalitm/perfetto/tools/roll-prebuilts v30.0
37TRACEBOX_MANIFEST = [{
38    'arch':
39        'mac-amd64',
40    'file_name':
41        'tracebox',
42    'file_size':
43        1415504,
44    'url':
45        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v30.0/mac-amd64/tracebox',
46    'sha256':
47        'a3c7576144c77c20e278cd9cf530a33bd07bbbe5b4fab568da073287cf9f10ba',
48    'platform':
49        'darwin',
50    'machine': ['x86_64']
51}, {
52    'arch':
53        'mac-arm64',
54    'file_name':
55        'tracebox',
56    'file_size':
57        1309048,
58    'url':
59        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v30.0/mac-arm64/tracebox',
60    'sha256':
61        '28036a713bb5711783584207dc972eb864c665d22fae5d04c5269bc562401f2b',
62    'platform':
63        'darwin',
64    'machine': ['arm64']
65}, {
66    'arch':
67        'linux-amd64',
68    'file_name':
69        'tracebox',
70    'file_size':
71        2309752,
72    'url':
73        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v30.0/linux-amd64/tracebox',
74    'sha256':
75        '1596849e3a44a3066cfea59e8795b9ad808e975c5a12a01c9839c447495042e7',
76    'platform':
77        'linux',
78    'machine': ['x86_64']
79}, {
80    'arch':
81        'linux-arm',
82    'file_name':
83        'tracebox',
84    'file_size':
85        1361728,
86    'url':
87        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v30.0/linux-arm/tracebox',
88    'sha256':
89        'cb2887dbf2f2c04980bc55f49b8abde3930e7e786061bb740f525f4a6983f819',
90    'platform':
91        'linux',
92    'machine': ['armv6l', 'armv7l', 'armv8l']
93}, {
94    'arch':
95        'linux-arm64',
96    'file_name':
97        'tracebox',
98    'file_size':
99        2255864,
100    'url':
101        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v30.0/linux-arm64/tracebox',
102    'sha256':
103        '42e4c81031bb489cc0ff8c38abb992adf21f1826beadaba036e2ad7dd44f8110',
104    'platform':
105        'linux',
106    'machine': ['aarch64']
107}, {
108    'arch':
109        'android-arm',
110    'file_name':
111        'tracebox',
112    'file_size':
113        1148884,
114    'url':
115        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v30.0/android-arm/tracebox',
116    'sha256':
117        '0fa492d3a91e8967b786654d1f990ce38921c993039f055f622336b8619463de'
118}, {
119    'arch':
120        'android-arm64',
121    'file_name':
122        'tracebox',
123    'file_size':
124        1751720,
125    'url':
126        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v30.0/android-arm64/tracebox',
127    'sha256':
128        '7ec7e9c077c5c0f6b8513411990ab0dd74c6a745f345c9ab35f58829ad367518'
129}, {
130    'arch':
131        'android-x86',
132    'file_name':
133        'tracebox',
134    'file_size':
135        1742764,
136    'url':
137        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v30.0/android-x86/tracebox',
138    'sha256':
139        'e513c920b17bbf1379f4ca9cf3445151e28da8668b05db9ae2d84ec436ab6b20'
140}, {
141    'arch':
142        'android-x64',
143    'file_name':
144        'tracebox',
145    'file_size':
146        2017960,
147    'url':
148        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v30.0/android-x64/tracebox',
149    'sha256':
150        'd6e4793ff198b9ae8c8f2bc0568f6f25273a1ebb5bd44c6ed319e8e8eecb9478'
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      adb('shell', 'rm -f ' + device_file).wait()
573      sys.exit(1)
574    bg_pid = match.group(1)
575    exit_code = proc.wait()
576
577  if exit_code != 0:
578    prt('Perfetto invocation failed', ANSI.RED)
579    adb('shell', 'rm -f ' + device_file).wait()
580    sys.exit(1)
581
582  prt('Trace started. Press CTRL+C to stop', ANSI.BLACK + ANSI.BG_BLUE)
583  logcat = adb('logcat', '-v', 'brief', '-s', 'perfetto', '-b', 'main', '-T',
584               '1')
585
586  ctrl_c_count = 0
587  adb_failure_count = 0
588  while ctrl_c_count < 2:
589    try:
590      # On older Android devices adbd doesn't propagate the exit code. Hence
591      # the RUN/TERM parts.
592      poll = adb(
593          'shell',
594          'test -d /proc/%s && echo RUN || echo TERM' % bg_pid,
595          stdout=subprocess.PIPE)
596      poll_res = poll.communicate()[0].decode().strip()
597      if poll_res == 'TERM':
598        break  # Process terminated
599      if poll_res == 'RUN':
600        # The 'perfetto' cmdline client is still running. If previously we had
601        # an ADB error, tell the user now it's all right again.
602        if adb_failure_count > 0:
603          adb_failure_count = 0
604          prt('ADB connection re-established, the trace is still ongoing',
605              ANSI.BLUE)
606        time.sleep(0.5)
607        continue
608      # Some ADB error happened. This can happen when tracing soon after boot,
609      # before logging in, when adb gets restarted.
610      adb_failure_count += 1
611      if adb_failure_count >= MAX_ADB_FAILURES:
612        prt('Too many unrecoverable ADB failures, bailing out', ANSI.RED)
613        adb('shell', 'rm -f ' + device_file).wait()
614        sys.exit(1)
615      time.sleep(2)
616    except KeyboardInterrupt:
617      sig = 'TERM' if ctrl_c_count == 0 else 'KILL'
618      ctrl_c_count += 1
619      prt('Stopping the trace (SIG%s)' % sig, ANSI.BLACK + ANSI.BG_YELLOW)
620      adb('shell', 'kill -%s %s' % (sig, bg_pid)).wait()
621
622  logcat.kill()
623  logcat.wait()
624
625  prt('\n')
626  prt('Pulling into %s' % host_file, ANSI.BOLD)
627  adb('pull', device_file, host_file).wait()
628  adb('shell', 'rm -f ' + device_file).wait()
629
630  if not args.no_open:
631    prt('\n')
632    prt('Opening the trace (%s) in the browser' % host_file)
633    open_trace_in_browser(host_file)
634
635
636def prt(msg, colors=ANSI.END):
637  print(colors + msg + ANSI.END)
638
639
640def find_adb():
641  """ Locate the "right" adb path
642
643  If adb is in the PATH use that (likely what the user wants) otherwise use the
644  hermetic one in our SDK copy.
645  """
646  global adb_path
647  for path in ['adb', HERMETIC_ADB_PATH]:
648    try:
649      subprocess.call([path, '--version'], stdout=devnull, stderr=devnull)
650      adb_path = path
651      break
652    except OSError:
653      continue
654  if adb_path is None:
655    sdk_url = 'https://developer.android.com/studio/releases/platform-tools'
656    prt('Could not find a suitable adb binary in the PATH. ', ANSI.RED)
657    prt('You can download adb from %s' % sdk_url, ANSI.RED)
658    sys.exit(1)
659
660
661def open_trace_in_browser(path):
662  # We reuse the HTTP+RPC port because it's the only one allowed by the CSP.
663  PORT = 9001
664  os.chdir(os.path.dirname(path))
665  fname = os.path.basename(path)
666  socketserver.TCPServer.allow_reuse_address = True
667  with socketserver.TCPServer(('127.0.0.1', PORT), HttpHandler) as httpd:
668    webbrowser.open_new_tab(
669        'https://ui.perfetto.dev/#!/?url=http://127.0.0.1:%d/%s' %
670        (PORT, fname))
671    while httpd.__dict__.get('last_request') != '/' + fname:
672      httpd.handle_request()
673
674
675def adb(*args, stdin=devnull, stdout=None):
676  cmd = [adb_path, *args]
677  setpgrp = None
678  if os.name != 'nt':
679    # On Linux/Mac, start a new process group so all child processes are killed
680    # on exit. Unsupported on Windows.
681    setpgrp = lambda: os.setpgrp()
682  proc = subprocess.Popen(cmd, stdin=stdin, stdout=stdout, preexec_fn=setpgrp)
683  procs.append(proc)
684  return proc
685
686
687def kill_all_subprocs_on_exit():
688  for p in [p for p in procs if p.poll() is None]:
689    p.kill()
690
691
692def check_hash(file_name, sha_value):
693  with open(file_name, 'rb') as fd:
694    file_hash = hashlib.sha1(fd.read()).hexdigest()
695    return file_hash == sha_value
696
697
698if __name__ == '__main__':
699  sys.exit(main())
700