roll_prebuilt: update scripts

This change runs tools/roll_prebuilt and also introduces
some matching changes required to make use of it in
tools/{heap_profile, record_android_trace}

Bug: 177349647
Change-Id: Ia5b569086d66896ff09f23680235547ed2ee181a
diff --git a/tools/get_perfetto_prebuilt.py b/tools/get_perfetto_prebuilt.py
index 28e9806..99f3459 100644
--- a/tools/get_perfetto_prebuilt.py
+++ b/tools/get_perfetto_prebuilt.py
@@ -106,7 +106,7 @@
       break
     # Otherwise guess the local machine arch.
     if entry.get('tool') == tool_name and entry.get(
-        'platform') == plat and entry.get('machine') == machine:
+        'platform') == plat and machine in entry.get('machine', []):
       manifest_entry = entry
       break
   if manifest_entry is None:
diff --git a/tools/heap_profile b/tools/heap_profile
index 6a931cf..09bd96c 100755
--- a/tools/heap_profile
+++ b/tools/heap_profile
@@ -29,15 +29,6 @@
 import tempfile
 import time
 import uuid
-import platform
-
-
-TRACE_TO_TEXT_SHAS = {
-    'linux': '150d8e65b8173c317642abfc44f3861832fbcd4e',
-    'mac': '818143f3283559a89c311e7e506d0099d1f6408c',
-}
-TRACE_TO_TEXT_PATH = tempfile.gettempdir()
-TRACE_TO_TEXT_BASE_URL = ('https://storage.googleapis.com/perfetto/')
 
 NULL = open(os.devnull)
 NOOUT = {
@@ -47,37 +38,6 @@
 
 UUID = str(uuid.uuid4())[-6:]
 
-def check_hash(file_name, sha_value):
-  file_hash = hashlib.sha1()
-  with open(file_name, 'rb') as fd:
-    while True:
-      chunk = fd.read(4096)
-      if not chunk:
-        break
-      file_hash.update(chunk)
-    return file_hash.hexdigest() == sha_value
-
-
-def load_trace_to_text(os_name):
-  sha_value = TRACE_TO_TEXT_SHAS[os_name]
-  file_name = 'trace_to_text-' + os_name + '-' + sha_value
-  local_file = os.path.join(TRACE_TO_TEXT_PATH, file_name)
-
-  if os.path.exists(local_file):
-    if not check_hash(local_file, sha_value):
-      os.remove(local_file)
-    else:
-      return local_file
-
-  url = TRACE_TO_TEXT_BASE_URL + file_name
-  subprocess.check_call(['curl', '-L', '-#', '-o', local_file, url])
-  if not check_hash(local_file, sha_value):
-    os.remove(local_file)
-    raise ValueError("Invalid signature.")
-  os.chmod(local_file, 0o755)
-  return local_file
-
-
 PACKAGES_LIST_CFG = '''data_sources {
   config {
     name: "android.packages_list"
@@ -120,6 +80,7 @@
 
 IS_INTERRUPTED = False
 
+
 def sigint_handler(sig, frame):
   global IS_INTERRUPTED
   IS_INTERRUPTED = True
@@ -128,44 +89,52 @@
 def print_no_profile_error():
   print("No profiles generated", file=sys.stderr)
   print(
-    "If this is unexpected, check "
-    "https://perfetto.dev/docs/data-sources/native-heap-profiler#troubleshooting.",
-    file=sys.stderr)
+      "If this is unexpected, check "
+      "https://perfetto.dev/docs/data-sources/native-heap-profiler#troubleshooting.",
+      file=sys.stderr)
+
 
 def known_issues_url(number):
   return ('https://perfetto.dev/docs/data-sources/native-heap-profiler'
           '#known-issues-android{}'.format(number))
 
+
 KNOWN_ISSUES = {
-  '10': known_issues_url(10),
-  'Q': known_issues_url(10),
-  '11': known_issues_url(11),
-  'R': known_issues_url(11),
+    '10': known_issues_url(10),
+    'Q': known_issues_url(10),
+    '11': known_issues_url(11),
+    'R': known_issues_url(11),
 }
 
+
 def maybe_known_issues():
   release_or_codename = subprocess.check_output(
-    ['adb', 'shell', 'getprop', 'ro.build.version.release_or_codename']
-  ).decode('utf-8').strip()
+      ['adb', 'shell', 'getprop',
+       'ro.build.version.release_or_codename']).decode('utf-8').strip()
   return KNOWN_ISSUES.get(release_or_codename, None)
 
+
 SDK = {
     'R': 30,
 }
 
+
 def release_or_newer(release):
-  sdk = int(subprocess.check_output(
-    ['adb', 'shell', 'getprop', 'ro.system.build.version.sdk']
-  ).decode('utf-8').strip())
+  sdk = int(
+      subprocess.check_output(
+          ['adb', 'shell', 'getprop',
+           'ro.system.build.version.sdk']).decode('utf-8').strip())
   if sdk >= SDK[release]:
     return True
   codename = subprocess.check_output(
-    ['adb', 'shell', 'getprop', 'ro.build.version.codename']
-  ).decode('utf-8').strip()
+      ['adb', 'shell', 'getprop',
+       'ro.build.version.codename']).decode('utf-8').strip()
   return codename == release
 
+
 ORDER = ['-n', '-p', '-i', '-o']
 
+
 def arg_order(action):
   result = len(ORDER)
   for opt in action.option_strings:
@@ -173,16 +142,18 @@
       result = min(ORDER.index(opt), result)
   return result, action.option_strings[0].strip('-')
 
+
 def print_options(parser):
   for action in sorted(parser._actions, key=arg_order):
     if action.help is argparse.SUPPRESS:
       continue
     opts = ', '.join('`' + x + '`' for x in action.option_strings)
-    metavar = '' if action.metavar is None else ' _'     + action.metavar + '_'
+    metavar = '' if action.metavar is None else ' _' + action.metavar + '_'
     print('{}{}'.format(opts, metavar))
     print(':    {}'.format(action.help))
     print()
 
+
 def main(argv):
   parser = argparse.ArgumentParser()
   parser.add_argument(
@@ -229,14 +200,12 @@
   parser.add_argument(
       "--all-heaps",
       action="store_true",
-      help="Collect allocations from all heaps registered by target."
-  )
+      help="Collect allocations from all heaps registered by target.")
   parser.add_argument(
       "--no-android-tree-symbolization",
       action="store_true",
       help="Do not symbolize using currently lunched target in the "
-      "Android tree."
-  )
+      "Android tree.")
   parser.add_argument(
       "--disable-selinux",
       action="store_true",
@@ -314,10 +283,7 @@
       metavar="DIRECTORY",
       default=None)
   parser.add_argument(
-      "--print-options",
-      action="store_true",
-      help=argparse.SUPPRESS
-  )
+      "--print-options", action="store_true", help=argparse.SUPPRESS)
 
   args = parser.parse_args()
   if args.print_options:
@@ -352,8 +318,8 @@
     target_cfg += CFG_INDENT + "block_client: true\n"
   if args.block_client_timeout:
     target_cfg += (
-      CFG_INDENT + "block_client_timeout_us: %s\n" % args.block_client_timeout
-    )
+        CFG_INDENT +
+        "block_client_timeout_us: %s\n" % args.block_client_timeout)
   if args.no_startup:
     target_cfg += CFG_INDENT + "no_startup: true\n"
   if args.no_running:
@@ -401,25 +367,9 @@
 
   # Do this AFTER print_config so we do not download trace_to_text only to
   # print out the config.
-  has_trace_to_text = True
   if trace_to_text_binary is None:
-    os_name = None
-    if sys.platform.startswith('linux'):
-      os_name = 'linux'
-    elif sys.platform.startswith('darwin'):
-      os_name = 'mac'
-    elif sys.platform.startswith('win32'):
-      has_trace_to_text = False
-    else:
-      print("Invalid platform: {}".format(sys.platform), file=sys.stderr)
-      return 1
-
-    arch = platform.machine()
-    if arch not in ['x86_64', 'amd64']:
-      has_trace_to_text = False
-
-    if has_trace_to_text:
-      trace_to_text_binary = load_trace_to_text(os_name)
+    trace_to_text_binary = get_perfetto_prebuilt(
+        'trace_to_text', soft_fail=True)
 
   known_issues = maybe_known_issues()
   if known_issues:
@@ -432,8 +382,8 @@
   if uuid_trace:
     profile_device_path = '/data/misc/perfetto-traces/profile-' + UUID
   else:
-    user = subprocess.check_output(
-      ['adb', 'shell', 'whoami']).decode('utf-8').strip()
+    user = subprocess.check_output(['adb', 'shell',
+                                    'whoami']).decode('utf-8').strip()
     profile_device_path = '/data/misc/perfetto-traces/profile-' + user
 
   perfetto_cmd = ('CFG=\'{cfg}\'; echo ${{CFG}} | '
@@ -461,18 +411,17 @@
     os.mkdir(profile_target)
 
   if not os.path.isdir(profile_target):
-    print("Output directory {} not found".format(profile_target),
-            file=sys.stderr)
+    print(
+        "Output directory {} not found".format(profile_target), file=sys.stderr)
     return 1
 
   if os.listdir(profile_target):
-    print("Output directory {} not empty".format(profile_target),
-            file=sys.stderr)
+    print(
+        "Output directory {} not empty".format(profile_target), file=sys.stderr)
     return 1
 
   perfetto_pid = subprocess.check_output(
-      ['adb', 'exec-out',
-       perfetto_cmd.format(cfg=cfg)]).strip()
+      ['adb', 'exec-out', perfetto_cmd.format(cfg=cfg)]).strip()
   try:
     perfetto_pid = int(perfetto_pid.strip())
   except ValueError:
@@ -503,8 +452,8 @@
       time.sleep(1)
     subprocess.check_call(
         ['adb', 'pull', '/data/local/tmp/heapprofd_profile', profile_target])
-    print(
-      "Pulled simpleperf profile to " + profile_target + "/heapprofd_profile")
+    print("Pulled simpleperf profile to " + profile_target +
+          "/heapprofd_profile")
 
   # Wait for perfetto cmd to return.
   while exists:
@@ -513,15 +462,17 @@
     time.sleep(1)
 
   profile_host_path = os.path.join(profile_target, 'raw-trace')
-  subprocess.check_call(
-    ['adb', 'pull', profile_device_path, profile_host_path], stdout=NULL)
+  subprocess.check_call(['adb', 'pull', profile_device_path, profile_host_path],
+                        stdout=NULL)
   if uuid_trace:
-    subprocess.check_call(
-          ['adb', 'shell', 'rm', profile_device_path], stdout=NULL)
+    subprocess.check_call(['adb', 'shell', 'rm', profile_device_path],
+                          stdout=NULL)
 
-  if not has_trace_to_text:
+  if trace_to_text_binary is None:
     print('Wrote profile to {}'.format(profile_host_path))
-    print('This file can be opened using the Perfetto UI, https://ui.perfetto.dev')
+    print(
+        'This file can be opened using the Perfetto UI, https://ui.perfetto.dev'
+    )
     return 0
 
   binary_path = os.getenv('PERFETTO_BINARY_PATH')
@@ -544,29 +495,32 @@
     with open(os.path.join(profile_target, 'symbols'), 'w') as fd:
       ret = subprocess.call([
           trace_to_text_binary, 'symbolize',
-          os.path.join(profile_target, 'raw-trace')],
-          env=dict(os.environ, PERFETTO_BINARY_PATH=binary_path),
-          stdout=fd)
+          os.path.join(profile_target, 'raw-trace')
+      ],
+                            env=dict(
+                                os.environ, PERFETTO_BINARY_PATH=binary_path),
+                            stdout=fd)
     if ret == 0:
       concat_files.append(os.path.join(profile_target, 'symbols'))
     else:
-      print("Failed to symbolize. Continuing without symbols.",
-      file=sys.stderr)
+      print("Failed to symbolize. Continuing without symbols.", file=sys.stderr)
 
   proguard_map = os.getenv('PERFETTO_PROGUARD_MAP')
   if proguard_map is not None:
     with open(os.path.join(profile_target, 'deobfuscation-packets'), 'w') as fd:
       ret = subprocess.call([
           trace_to_text_binary, 'deobfuscate',
-          os.path.join(profile_target, 'raw-trace')],
-          env=dict(os.environ, PERFETTO_PROGUARD_MAP=proguard_map),
-          stdout=fd)
+          os.path.join(profile_target, 'raw-trace')
+      ],
+                            env=dict(
+                                os.environ, PERFETTO_PROGUARD_MAP=proguard_map),
+                            stdout=fd)
     if ret == 0:
-      concat_files.append(
-        os.path.join(profile_target, 'deobfuscation-packets'))
+      concat_files.append(os.path.join(profile_target, 'deobfuscation-packets'))
     else:
-      print("Failed to deobfuscate. Continuing without deobfuscated.",
-      file=sys.stderr)
+      print(
+          "Failed to deobfuscate. Continuing without deobfuscated.",
+          file=sys.stderr)
 
   if len(concat_files) > 1:
     with open(os.path.join(profile_target, 'symbolized-trace'), 'wb') as out:
@@ -598,20 +552,19 @@
     shutil.copy(os.path.join(profile_path, profile_file), profile_target)
 
   subprocess.check_call(
-      ['gzip'] +
-      [os.path.join(profile_target, x) for x in profile_files])
+      ['gzip'] + [os.path.join(profile_target, x) for x in profile_files])
 
   symlink_path = None
   if args.output is None:
     symlink_path = os.path.join(
-      os.path.dirname(profile_target), "heap_profile-latest")
+        os.path.dirname(profile_target), "heap_profile-latest")
     if os.path.lexists(symlink_path):
       os.unlink(symlink_path)
     os.symlink(profile_target, symlink_path)
 
   if symlink_path is not None:
-    print("Wrote profiles to {} (symlink {})".format(
-        profile_target, symlink_path))
+    print("Wrote profiles to {} (symlink {})".format(profile_target,
+                                                     symlink_path))
   else:
     print("Wrote profiles to {}".format(profile_target))
 
@@ -619,5 +572,131 @@
         "upload them.")
 
 
+# BEGIN_SECTION_GENERATED_BY(roll-prebuilts)
+# Revision: 45571639857aa59be41f77a2d774f6577d03c86c
+PERFETTO_PREBUILT_MANIFEST = [{
+    'tool':
+        'trace_to_text',
+    'arch':
+        'mac-amd64',
+    'file_name':
+        'trace_to_text',
+    'file_size':
+        7087056,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/mac-amd64/trace_to_text',
+    'sha256':
+        'beaf8151ed0994f970e4e6d4da10b6681ac48a74f29133f4bea6f3d3294c1009',
+    'platform':
+        'darwin',
+    'machine': ['x86_64']
+}, {
+    'tool':
+        'trace_to_text',
+    'arch':
+        'windows-amd64',
+    'file_name':
+        'trace_to_text.exe',
+    'file_size':
+        6702080,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/windows-amd64/trace_to_text.exe',
+    'sha256':
+        '58a1b1e1bb69465002ccf9611d13a029943257d41311a6603d45ba1ebfcfa077',
+    'platform':
+        'win32',
+    'machine': ['amd64']
+}, {
+    'tool':
+        'trace_to_text',
+    'arch':
+        'linux-amd64',
+    'file_name':
+        'trace_to_text',
+    'file_size':
+        7435576,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/linux-amd64/trace_to_text',
+    'sha256':
+        '6c19500bfa983cfbf49fe0ef55284285ba674b43d1d2273f19249fa5b3dd9f84',
+    'platform':
+        'linux',
+    'machine': ['x86_64']
+}]
+
+
+# DO NOT EDIT. If you wish to make edits to this code, you need to change only
+# //tools/get_perfetto_prebuilt.py and run /tools/roll-prebuilts to regenerate
+# all the others scripts this is embedded into.
+def get_perfetto_prebuilt(tool_name, soft_fail=False, arch=None):
+  """ Downloads the prebuilt, if necessary, and returns its path on disk. """
+
+  # The first time this is invoked, it downloads the |url| and caches it into
+  # ~/.perfetto/prebuilts/$tool_name. On subsequent invocations it just runs the
+  # cached version.
+  def download_or_get_cached(file_name, url, sha256):
+    import os, hashlib, subprocess
+    dir = os.path.join(
+        os.path.expanduser('~'), '.local', 'share', 'perfetto', 'prebuilts')
+    os.makedirs(dir, exist_ok=True)
+    bin_path = os.path.join(dir, file_name)
+    sha256_path = os.path.join(dir, file_name + '.sha256')
+    needs_download = True
+
+    # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
+    # download is cached into file_name.sha256, just check if that matches.
+    if os.path.exists(bin_path) and os.path.exists(sha256_path):
+      with open(sha256_path, 'rb') as f:
+        digest = f.read().decode()
+        if digest == sha256:
+          needs_download = False
+
+    if needs_download:
+      # Either the filed doesn't exist or the SHA256 doesn't match.
+      tmp_path = bin_path + '.tmp'
+      print('Downloading ' + url)
+      subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
+      with open(tmp_path, 'rb') as fd:
+        actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
+      if actual_sha256 != sha256:
+        raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
+                        (url, actual_sha256, sha256))
+      os.chmod(tmp_path, 0o755)
+      os.rename(tmp_path, bin_path)
+      with open(sha256_path, 'w') as f:
+        f.write(sha256)
+    return bin_path
+    # --- end of download_or_get_cached() ---
+
+  # --- get_perfetto_prebuilt() function starts here. ---
+  import os, platform, sys
+  plat = sys.platform.lower()
+  machine = platform.machine().lower()
+  manifest_entry = None
+  for entry in PERFETTO_PREBUILT_MANIFEST:
+    # If the caller overrides the arch, just match that (for Android prebuilts).
+    if arch and entry.get('arch') == arch:
+      manifest_entry = entry
+      break
+    # Otherwise guess the local machine arch.
+    if entry.get('tool') == tool_name and entry.get(
+        'platform') == plat and machine in entry.get('machine', []):
+      manifest_entry = entry
+      break
+  if manifest_entry is None:
+    if soft_fail:
+      return None
+    raise Exception(
+        ('No prebuilts available for %s-%s\n' % (plat, machine)) +
+        'See https://perfetto.dev/docs/contributing/build-instructions')
+
+  return download_or_get_cached(
+      file_name=manifest_entry['file_name'],
+      url=manifest_entry['url'],
+      sha256=manifest_entry['sha256'])
+
+
+# END_SECTION_GENERATED_BY(roll-prebuilts)
+
 if __name__ == '__main__':
   sys.exit(main(sys.argv))
diff --git a/tools/record_android_trace b/tools/record_android_trace
index b59b8bb..c079c1a 100755
--- a/tools/record_android_trace
+++ b/tools/record_android_trace
@@ -33,16 +33,6 @@
 # PATH. It's fine if it doesn't exist so this script can be copied elsewhere.
 HERMETIC_ADB_PATH = ROOT_DIR + '/buildtools/android_sdk/platform-tools/adb'
 
-# For downloading and sideloading tracebox on older devices, or when the
-# --sideload argument is passed.
-TRACEBOX_BASE_URL = 'https://storage.googleapis.com/perfetto/'
-TRACEBOX_SHA1S = {
-    'android-arm': '6e9dfee326468fc6858c6d95e00be110efd187a3',  # v15.0.248
-    'android-arm64': 'ca2d4a02511f73dac32a2ae49964f3e5cd59d252',  # v15.0.248
-    'android-x86': '3fdbc9246412e460d0a373140c114cff858b9b7c',  # v15.0.248
-    'android-x64': 'be85f6f4a2d014d425246b85941c137c28d158cc',  # v15.0.248
-}
-
 # Translates the Android ro.product.cpu.abi into the GN's target_cpu.
 ABI_TO_ARCH = {
     'armeabi-v7a': 'arm',
@@ -207,7 +197,9 @@
     sys.exit(1)
   shell_user = lines[2]
   if api_level < 29 or args.sideload:  # 29: Android Q.
-    tracebox_bin = args.sideload_path or download_tracebox_if_needed(arch)
+    tracebox_bin = args.sideload_path
+    if tracebox_bin is None:
+      tracebox_bin = get_perfetto_prebuilt('tracebox', arch='android-' + arch)
     perfetto_cmd = '/data/local/tmp/tracebox'
     exit_code = adb('push', '--sync', tracebox_bin, perfetto_cmd).wait()
     exit_code |= adb('shell', 'chmod 755 ' + perfetto_cmd).wait()
@@ -370,26 +362,135 @@
     return file_hash == sha_value
 
 
-def download_tracebox_if_needed(arch):
-  sha_value = TRACEBOX_SHA1S.get('android-' + arch)
-  if sha_value is None:
-    prt('Unsupported architecture ' + arch)
-    sys.exit(1)
-  file_name = 'tracebox-android-' + arch + '-' + sha_value
-  local_file = os.path.join(tempfile.gettempdir(), file_name)
-  if os.path.exists(local_file):
-    if not check_hash(local_file, sha_value):
-      os.remove(local_file)
-    else:
-      return local_file
-  url = TRACEBOX_BASE_URL + file_name
-  prt('Downloading %s' % url)
-  subprocess.check_call(['curl', '-L', '-#', '-o', local_file, url])
-  if not check_hash(local_file, sha_value):
-    os.remove(local_file)
-    raise ValueError('Invalid SHA1 for %s' % local_file)
-  return local_file
+# BEGIN_SECTION_GENERATED_BY(roll-prebuilts)
+# Revision: 45571639857aa59be41f77a2d774f6577d03c86c
+PERFETTO_PREBUILT_MANIFEST = [{
+    'tool':
+        'tracebox',
+    'arch':
+        'android-arm',
+    'file_name':
+        'tracebox',
+    'file_size':
+        1021824,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/android-arm/tracebox',
+    'sha256':
+        'b6b05fdc623462b010ac7720557d7fb0c3313d620cdbe9830a41f2e6d22042a0'
+}, {
+    'tool':
+        'tracebox',
+    'arch':
+        'android-arm64',
+    'file_name':
+        'tracebox',
+    'file_size':
+        1559112,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/android-arm64/tracebox',
+    'sha256':
+        '62cb645e979510c59a599af2d5feb265572197ddd6740aa329e3345d6ed4f813'
+}, {
+    'tool':
+        'tracebox',
+    'arch':
+        'android-x86',
+    'file_name':
+        'tracebox',
+    'file_size':
+        1587028,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/android-x86/tracebox',
+    'sha256':
+        '01d7111c0c9f4da18f5308358e24227b1b8e732fc5787969cd07543916d5ac00'
+}, {
+    'tool':
+        'tracebox',
+    'arch':
+        'android-x64',
+    'file_name':
+        'tracebox',
+    'file_size':
+        1829448,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/android-x64/tracebox',
+    'sha256':
+        '2eea577e7b001ab6193265dfd2f0a7aeab46bb22ed21a7da0d0c5f80639bd7af'
+}]
 
 
+# DO NOT EDIT. If you wish to make edits to this code, you need to change only
+# //tools/get_perfetto_prebuilt.py and run /tools/roll-prebuilts to regenerate
+# all the others scripts this is embedded into.
+def get_perfetto_prebuilt(tool_name, soft_fail=False, arch=None):
+  """ Downloads the prebuilt, if necessary, and returns its path on disk. """
+
+  # The first time this is invoked, it downloads the |url| and caches it into
+  # ~/.perfetto/prebuilts/$tool_name. On subsequent invocations it just runs the
+  # cached version.
+  def download_or_get_cached(file_name, url, sha256):
+    import os, hashlib, subprocess
+    dir = os.path.join(
+        os.path.expanduser('~'), '.local', 'share', 'perfetto', 'prebuilts')
+    os.makedirs(dir, exist_ok=True)
+    bin_path = os.path.join(dir, file_name)
+    sha256_path = os.path.join(dir, file_name + '.sha256')
+    needs_download = True
+
+    # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
+    # download is cached into file_name.sha256, just check if that matches.
+    if os.path.exists(bin_path) and os.path.exists(sha256_path):
+      with open(sha256_path, 'rb') as f:
+        digest = f.read().decode()
+        if digest == sha256:
+          needs_download = False
+
+    if needs_download:
+      # Either the filed doesn't exist or the SHA256 doesn't match.
+      tmp_path = bin_path + '.tmp'
+      print('Downloading ' + url)
+      subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
+      with open(tmp_path, 'rb') as fd:
+        actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
+      if actual_sha256 != sha256:
+        raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
+                        (url, actual_sha256, sha256))
+      os.chmod(tmp_path, 0o755)
+      os.rename(tmp_path, bin_path)
+      with open(sha256_path, 'w') as f:
+        f.write(sha256)
+    return bin_path
+    # --- end of download_or_get_cached() ---
+
+  # --- get_perfetto_prebuilt() function starts here. ---
+  import os, platform, sys
+  plat = sys.platform.lower()
+  machine = platform.machine().lower()
+  manifest_entry = None
+  for entry in PERFETTO_PREBUILT_MANIFEST:
+    # If the caller overrides the arch, just match that (for Android prebuilts).
+    if arch and entry.get('arch') == arch:
+      manifest_entry = entry
+      break
+    # Otherwise guess the local machine arch.
+    if entry.get('tool') == tool_name and entry.get(
+        'platform') == plat and machine in entry.get('machine', []):
+      manifest_entry = entry
+      break
+  if manifest_entry is None:
+    if soft_fail:
+      return None
+    raise Exception(
+        ('No prebuilts available for %s-%s\n' % (plat, machine)) +
+        'See https://perfetto.dev/docs/contributing/build-instructions')
+
+  return download_or_get_cached(
+      file_name=manifest_entry['file_name'],
+      url=manifest_entry['url'],
+      sha256=manifest_entry['sha256'])
+
+
+# END_SECTION_GENERATED_BY(roll-prebuilts)
+
 if __name__ == '__main__':
   sys.exit(main())
diff --git a/tools/roll-prebuilts b/tools/roll-prebuilts
index 5cd3aee..4a68508 100755
--- a/tools/roll-prebuilts
+++ b/tools/roll-prebuilts
@@ -41,9 +41,14 @@
 
 SCRIPTS_TO_UPDATE = [
     {
-        'script': 'trace_processor',
-        'tool': 'trace_processor_shell',
-        'archs': ['mac-amd64', 'linux-amd64', 'windows-amd64']
+        'script':
+            'trace_processor',
+        'tool':
+            'trace_processor_shell',
+        'archs': [
+            'mac-amd64', 'windows-amd64', 'linux-amd64', 'linux-arm',
+            'linux-arm64'
+        ]
     },
     {
         'script': 'traceconv',
@@ -51,14 +56,19 @@
         'archs': ['mac-amd64', 'linux-amd64', 'windows-amd64']
     },
     {
-        'script': 'tracebox',
-        'tool': 'tracebox',
-        'archs': ['mac-amd64', 'linux-amd64']
+        'script':
+            'tracebox',
+        'tool':
+            'tracebox',
+        'archs': [
+            'mac-amd64', 'linux-amd64', 'linux-amd64', 'linux-arm',
+            'linux-arm64'
+        ]
     },
     {
         'script': 'heap_profile',
         'tool': 'trace_to_text',
-        'archs': ['mac-amd64', 'linux-amd64', 'windows-amd64']
+        'archs': ['mac-amd64', 'windows-amd64', 'linux-amd64']
     },
     {
         'script': 'record_android_trace',
@@ -67,20 +77,29 @@
     },
 ]
 
-# Maps a 'os-arch' string into corresponding tuples that match against
-# python's platform / machine API (see get_perfetto_prebuilt.py).
+# Maps a 'os-arch' string (were arch follows LUCI conventions) into
+# corresponding tuples that match against python's platform / machine API
+# (see get_perfetto_prebuilt.py for usage).
 ARCH_TO_PYTHON = {
     'mac-amd64': {
         'platform': 'darwin',
-        'machine': 'x86_64'
-    },
-    'linux-amd64': {
-        'platform': 'linux',
-        'machine': 'x86_64'
+        'machine': ['x86_64'],
     },
     'windows-amd64': {
         'platform': 'win32',
-        'machine': 'amd64'
+        'machine': ['amd64'],
+    },
+    'linux-amd64': {
+        'platform': 'linux',
+        'machine': ['x86_64'],
+    },
+    'linux-arm': {
+        'platform': 'linux',
+        'machine': ['armv6l', 'armv7l', 'armv8l'],
+    },
+    'linux-arm64': {
+        'platform': 'linux',
+        'machine': ['aarch64'],
     },
 }
 
diff --git a/tools/trace_processor b/tools/trace_processor
index f680cff..0a3a00e 100755
--- a/tools/trace_processor
+++ b/tools/trace_processor
@@ -24,73 +24,169 @@
 exec python3 - "$@" <<'#'EOF
 #"""
 
-import hashlib
-import os
-import sys
-import tempfile
-import subprocess
-import platform
+TOOL_NAME = 'trace_processor_shell'
 
-TRACE_PROCESSOR_SHELL_SHAS = {
-    'linux': '6dcb207dabcf5dc0d88aee8b3ae6abe2380e3e8d',
-    'mac': '8196240f76ee9472e39e538afa1bc76bdfce292a',
-}
-TRACE_PROCESSOR_SHELL_PATH = tempfile.gettempdir()
-TRACE_PROCESSOR_SHELL_BASE_URL = ('https://storage.googleapis.com/perfetto/')
+# BEGIN_SECTION_GENERATED_BY(roll-prebuilts)
+# Revision: 45571639857aa59be41f77a2d774f6577d03c86c
+PERFETTO_PREBUILT_MANIFEST = [{
+    'tool':
+        'trace_processor_shell',
+    'arch':
+        'mac-amd64',
+    'file_name':
+        'trace_processor_shell',
+    'file_size':
+        7022312,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/mac-amd64/trace_processor_shell',
+    'sha256':
+        '1981f80067ea95d1da9431b816de592b6b64eac5bd73519e0f5f6e263935078b',
+    'platform':
+        'darwin',
+    'machine': ['x86_64']
+}, {
+    'tool':
+        'trace_processor_shell',
+    'arch':
+        'windows-amd64',
+    'file_name':
+        'trace_processor_shell.exe',
+    'file_size':
+        6663680,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/windows-amd64/trace_processor_shell.exe',
+    'sha256':
+        '5e03b5c3cbdc5fc58690bfd0cce310ee9f6fe786eca9edced23ee7b37d2dffff',
+    'platform':
+        'win32',
+    'machine': ['amd64']
+}, {
+    'tool':
+        'trace_processor_shell',
+    'arch':
+        'linux-amd64',
+    'file_name':
+        'trace_processor_shell',
+    'file_size':
+        7378592,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/linux-amd64/trace_processor_shell',
+    'sha256':
+        'bfb8f8258be397edf0a9757847fe3c0ccfc0985fa379f694cdc26b885960f34b',
+    'platform':
+        'linux',
+    'machine': ['x86_64']
+}, {
+    'tool':
+        'trace_processor_shell',
+    'arch':
+        'linux-arm',
+    'file_name':
+        'trace_processor_shell',
+    'file_size':
+        4635352,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/linux-arm/trace_processor_shell',
+    'sha256':
+        '55b5cf7d9363ee3996fc991830b4d253a20c015b9bddbd9c929557721e16b16b',
+    'platform':
+        'linux',
+    'machine': ['armv6l', 'armv7l', 'armv8l']
+}, {
+    'tool':
+        'trace_processor_shell',
+    'arch':
+        'linux-arm64',
+    'file_name':
+        'trace_processor_shell',
+    'file_size':
+        6165664,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/linux-arm64/trace_processor_shell',
+    'sha256':
+        '6e1fc84dc5d238d48563d2f5f58509ce2a0e61ad0ac4f75af26e541eff1cd648',
+    'platform':
+        'linux',
+    'machine': ['aarch64']
+}]
 
 
-def DownloadURL(url, out_file):
-  subprocess.check_call(['curl', '-L', '-#', '-o', out_file, url])
+# DO NOT EDIT. If you wish to make edits to this code, you need to change only
+# //tools/get_perfetto_prebuilt.py and run /tools/roll-prebuilts to regenerate
+# all the others scripts this is embedded into.
+def get_perfetto_prebuilt(tool_name, soft_fail=False, arch=None):
+  """ Downloads the prebuilt, if necessary, and returns its path on disk. """
+
+  # The first time this is invoked, it downloads the |url| and caches it into
+  # ~/.perfetto/prebuilts/$tool_name. On subsequent invocations it just runs the
+  # cached version.
+  def download_or_get_cached(file_name, url, sha256):
+    import os, hashlib, subprocess
+    dir = os.path.join(
+        os.path.expanduser('~'), '.local', 'share', 'perfetto', 'prebuilts')
+    os.makedirs(dir, exist_ok=True)
+    bin_path = os.path.join(dir, file_name)
+    sha256_path = os.path.join(dir, file_name + '.sha256')
+    needs_download = True
+
+    # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
+    # download is cached into file_name.sha256, just check if that matches.
+    if os.path.exists(bin_path) and os.path.exists(sha256_path):
+      with open(sha256_path, 'rb') as f:
+        digest = f.read().decode()
+        if digest == sha256:
+          needs_download = False
+
+    if needs_download:
+      # Either the filed doesn't exist or the SHA256 doesn't match.
+      tmp_path = bin_path + '.tmp'
+      print('Downloading ' + url)
+      subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
+      with open(tmp_path, 'rb') as fd:
+        actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
+      if actual_sha256 != sha256:
+        raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
+                        (url, actual_sha256, sha256))
+      os.chmod(tmp_path, 0o755)
+      os.rename(tmp_path, bin_path)
+      with open(sha256_path, 'w') as f:
+        f.write(sha256)
+    return bin_path
+    # --- end of download_or_get_cached() ---
+
+  # --- get_perfetto_prebuilt() function starts here. ---
+  import os, platform, sys
+  plat = sys.platform.lower()
+  machine = platform.machine().lower()
+  manifest_entry = None
+  for entry in PERFETTO_PREBUILT_MANIFEST:
+    # If the caller overrides the arch, just match that (for Android prebuilts).
+    if arch and entry.get('arch') == arch:
+      manifest_entry = entry
+      break
+    # Otherwise guess the local machine arch.
+    if entry.get('tool') == tool_name and entry.get(
+        'platform') == plat and machine in entry.get('machine', []):
+      manifest_entry = entry
+      break
+  if manifest_entry is None:
+    if soft_fail:
+      return None
+    raise Exception(
+        ('No prebuilts available for %s-%s\n' % (plat, machine)) +
+        'See https://perfetto.dev/docs/contributing/build-instructions')
+
+  return download_or_get_cached(
+      file_name=manifest_entry['file_name'],
+      url=manifest_entry['url'],
+      sha256=manifest_entry['sha256'])
 
 
-def check_hash(file_name, sha_value):
-  with open(file_name, 'rb') as fd:
-    file_hash = hashlib.sha1(fd.read()).hexdigest()
-    return file_hash == sha_value
-
-
-def load_trace_processor_shell(platform):
-  sha_value = TRACE_PROCESSOR_SHELL_SHAS[platform]
-  file_name = 'trace_processor_shell-' + platform + '-' + sha_value
-  local_file = os.path.join(TRACE_PROCESSOR_SHELL_PATH, file_name)
-
-  if os.path.exists(local_file):
-    if not check_hash(local_file, sha_value):
-      os.remove(local_file)
-    else:
-      return local_file
-
-  url = TRACE_PROCESSOR_SHELL_BASE_URL + file_name
-  DownloadURL(url, local_file)
-  if not check_hash(local_file, sha_value):
-    os.remove(local_file)
-    raise ValueError("Invalid signature.")
-  os.chmod(local_file, 0o755)
-  return local_file
-
-
-def main(argv):
-  os_name = None
-  if sys.platform.startswith('linux'):
-    os_name = 'linux'
-  elif sys.platform.startswith('darwin'):
-    os_name = 'mac'
-  else:
-    print("Invalid platform: {}".format(sys.platform))
-    return 1
-
-  arch = platform.machine()
-  if arch not in ['x86_64', 'amd64']:
-    print("Prebuilts are only available for x86_64 (found '{}' instead).".format(arch))
-    print("Follow https://perfetto.dev/docs/contributing/build-instructions to build locally.")
-    return 1
-
-  trace_processor_shell_binary = load_trace_processor_shell(os_name)
-  os.execv(trace_processor_shell_binary,
-           [trace_processor_shell_binary] + argv[1:])
-
+# END_SECTION_GENERATED_BY(roll-prebuilts)
 
 if __name__ == '__main__':
-  sys.exit(main(sys.argv))
+  import sys, os
+  bin_path = get_perfetto_prebuilt(TOOL_NAME)
+  os.execv(bin_path, [bin_path] + sys.argv[1:])
 
 #EOF
diff --git a/tools/tracebox b/tools/tracebox
new file mode 100755
index 0000000..5183f2c
--- /dev/null
+++ b/tools/tracebox
@@ -0,0 +1,192 @@
+#!/usr/bin/env python3
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This file should do the same thing when being invoked in any of these ways:
+# ./tracebox
+# python tracebox
+# bash tracebox
+# cat ./tracebox | bash
+# cat ./tracebox | python -
+
+BASH_FALLBACK = """ "
+exec python3 - "$@" <<'#'EOF
+#"""
+
+TOOL_NAME = 'tracebox'
+
+# BEGIN_SECTION_GENERATED_BY(roll-prebuilts)
+# Revision: 45571639857aa59be41f77a2d774f6577d03c86c
+PERFETTO_PREBUILT_MANIFEST = [{
+    'tool':
+        'tracebox',
+    'arch':
+        'mac-amd64',
+    'file_name':
+        'tracebox',
+    'file_size':
+        1315792,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/mac-amd64/tracebox',
+    'sha256':
+        '5ccbdf4548ab6d80f728b028ad88e7e9c95c02a06691212a396356449036487a',
+    'platform':
+        'darwin',
+    'machine': ['x86_64']
+}, {
+    'tool':
+        'tracebox',
+    'arch':
+        'linux-amd64',
+    'file_name':
+        'tracebox',
+    'file_size':
+        1750688,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/linux-amd64/tracebox',
+    'sha256':
+        '9e304cebb0bb09b47ff1e1c42b0d8ad6c9e499a12565de6b66961f9431109a02',
+    'platform':
+        'linux',
+    'machine': ['x86_64']
+}, {
+    'tool':
+        'tracebox',
+    'arch':
+        'linux-amd64',
+    'file_name':
+        'tracebox',
+    'file_size':
+        1750688,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/linux-amd64/tracebox',
+    'sha256':
+        '9e304cebb0bb09b47ff1e1c42b0d8ad6c9e499a12565de6b66961f9431109a02',
+    'platform':
+        'linux',
+    'machine': ['x86_64']
+}, {
+    'tool':
+        'tracebox',
+    'arch':
+        'linux-arm',
+    'file_name':
+        'tracebox',
+    'file_size':
+        990096,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/linux-arm/tracebox',
+    'sha256':
+        '259d676d90d3a3cd37af61ded9a7956f1dfe8370bff1c9f62bfced202731c231',
+    'platform':
+        'linux',
+    'machine': ['armv6l', 'armv7l', 'armv8l']
+}, {
+    'tool':
+        'tracebox',
+    'arch':
+        'linux-arm64',
+    'file_name':
+        'tracebox',
+    'file_size':
+        1493456,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/linux-arm64/tracebox',
+    'sha256':
+        '23c9d3f732c678f4002577f4f415703e221b092a90fc83e2bea3d69dd4061257',
+    'platform':
+        'linux',
+    'machine': ['aarch64']
+}]
+
+
+# DO NOT EDIT. If you wish to make edits to this code, you need to change only
+# //tools/get_perfetto_prebuilt.py and run /tools/roll-prebuilts to regenerate
+# all the others scripts this is embedded into.
+def get_perfetto_prebuilt(tool_name, soft_fail=False, arch=None):
+  """ Downloads the prebuilt, if necessary, and returns its path on disk. """
+
+  # The first time this is invoked, it downloads the |url| and caches it into
+  # ~/.perfetto/prebuilts/$tool_name. On subsequent invocations it just runs the
+  # cached version.
+  def download_or_get_cached(file_name, url, sha256):
+    import os, hashlib, subprocess
+    dir = os.path.join(
+        os.path.expanduser('~'), '.local', 'share', 'perfetto', 'prebuilts')
+    os.makedirs(dir, exist_ok=True)
+    bin_path = os.path.join(dir, file_name)
+    sha256_path = os.path.join(dir, file_name + '.sha256')
+    needs_download = True
+
+    # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
+    # download is cached into file_name.sha256, just check if that matches.
+    if os.path.exists(bin_path) and os.path.exists(sha256_path):
+      with open(sha256_path, 'rb') as f:
+        digest = f.read().decode()
+        if digest == sha256:
+          needs_download = False
+
+    if needs_download:
+      # Either the filed doesn't exist or the SHA256 doesn't match.
+      tmp_path = bin_path + '.tmp'
+      print('Downloading ' + url)
+      subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
+      with open(tmp_path, 'rb') as fd:
+        actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
+      if actual_sha256 != sha256:
+        raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
+                        (url, actual_sha256, sha256))
+      os.chmod(tmp_path, 0o755)
+      os.rename(tmp_path, bin_path)
+      with open(sha256_path, 'w') as f:
+        f.write(sha256)
+    return bin_path
+    # --- end of download_or_get_cached() ---
+
+  # --- get_perfetto_prebuilt() function starts here. ---
+  import os, platform, sys
+  plat = sys.platform.lower()
+  machine = platform.machine().lower()
+  manifest_entry = None
+  for entry in PERFETTO_PREBUILT_MANIFEST:
+    # If the caller overrides the arch, just match that (for Android prebuilts).
+    if arch and entry.get('arch') == arch:
+      manifest_entry = entry
+      break
+    # Otherwise guess the local machine arch.
+    if entry.get('tool') == tool_name and entry.get(
+        'platform') == plat and machine in entry.get('machine', []):
+      manifest_entry = entry
+      break
+  if manifest_entry is None:
+    if soft_fail:
+      return None
+    raise Exception(
+        ('No prebuilts available for %s-%s\n' % (plat, machine)) +
+        'See https://perfetto.dev/docs/contributing/build-instructions')
+
+  return download_or_get_cached(
+      file_name=manifest_entry['file_name'],
+      url=manifest_entry['url'],
+      sha256=manifest_entry['sha256'])
+
+
+# END_SECTION_GENERATED_BY(roll-prebuilts)
+
+if __name__ == '__main__':
+  import sys, os
+  bin_path = get_perfetto_prebuilt(TOOL_NAME)
+  os.execv(bin_path, [bin_path] + sys.argv[1:])
+
+#EOF
diff --git a/tools/traceconv b/tools/traceconv
index f0648aa..19d7194 100755
--- a/tools/traceconv
+++ b/tools/traceconv
@@ -1,5 +1,5 @@
 #!/usr/bin/env python3
-# Copyright (C) 2019 The Android Open Source Project
+# Copyright (C) 2021 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -24,73 +24,137 @@
 exec python3 - "$@" <<'#'EOF
 #"""
 
-import hashlib
-import os
-import sys
-import subprocess
-import tempfile
-import platform
+TOOL_NAME = 'trace_to_text'
+
+# BEGIN_SECTION_GENERATED_BY(roll-prebuilts)
+# Revision: 45571639857aa59be41f77a2d774f6577d03c86c
+PERFETTO_PREBUILT_MANIFEST = [{
+    'tool':
+        'trace_to_text',
+    'arch':
+        'mac-amd64',
+    'file_name':
+        'trace_to_text',
+    'file_size':
+        7087056,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/mac-amd64/trace_to_text',
+    'sha256':
+        'beaf8151ed0994f970e4e6d4da10b6681ac48a74f29133f4bea6f3d3294c1009',
+    'platform':
+        'darwin',
+    'machine': ['x86_64']
+}, {
+    'tool':
+        'trace_to_text',
+    'arch':
+        'linux-amd64',
+    'file_name':
+        'trace_to_text',
+    'file_size':
+        7435576,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/linux-amd64/trace_to_text',
+    'sha256':
+        '6c19500bfa983cfbf49fe0ef55284285ba674b43d1d2273f19249fa5b3dd9f84',
+    'platform':
+        'linux',
+    'machine': ['x86_64']
+}, {
+    'tool':
+        'trace_to_text',
+    'arch':
+        'windows-amd64',
+    'file_name':
+        'trace_to_text.exe',
+    'file_size':
+        6702080,
+    'url':
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/45571639857aa59be41f77a2d774f6577d03c86c/windows-amd64/trace_to_text.exe',
+    'sha256':
+        '58a1b1e1bb69465002ccf9611d13a029943257d41311a6603d45ba1ebfcfa077',
+    'platform':
+        'win32',
+    'machine': ['amd64']
+}]
 
 
-TRACE_TO_TEXT_SHAS = {
-    'linux': '150d8e65b8173c317642abfc44f3861832fbcd4e',
-    'mac': '818143f3283559a89c311e7e506d0099d1f6408c',
-}
-TRACE_TO_TEXT_PATH = tempfile.gettempdir()
-TRACE_TO_TEXT_BASE_URL = ('https://storage.googleapis.com/perfetto/')
+# DO NOT EDIT. If you wish to make edits to this code, you need to change only
+# //tools/get_perfetto_prebuilt.py and run /tools/roll-prebuilts to regenerate
+# all the others scripts this is embedded into.
+def get_perfetto_prebuilt(tool_name, soft_fail=False, arch=None):
+  """ Downloads the prebuilt, if necessary, and returns its path on disk. """
+
+  # The first time this is invoked, it downloads the |url| and caches it into
+  # ~/.perfetto/prebuilts/$tool_name. On subsequent invocations it just runs the
+  # cached version.
+  def download_or_get_cached(file_name, url, sha256):
+    import os, hashlib, subprocess
+    dir = os.path.join(
+        os.path.expanduser('~'), '.local', 'share', 'perfetto', 'prebuilts')
+    os.makedirs(dir, exist_ok=True)
+    bin_path = os.path.join(dir, file_name)
+    sha256_path = os.path.join(dir, file_name + '.sha256')
+    needs_download = True
+
+    # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
+    # download is cached into file_name.sha256, just check if that matches.
+    if os.path.exists(bin_path) and os.path.exists(sha256_path):
+      with open(sha256_path, 'rb') as f:
+        digest = f.read().decode()
+        if digest == sha256:
+          needs_download = False
+
+    if needs_download:
+      # Either the filed doesn't exist or the SHA256 doesn't match.
+      tmp_path = bin_path + '.tmp'
+      print('Downloading ' + url)
+      subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
+      with open(tmp_path, 'rb') as fd:
+        actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
+      if actual_sha256 != sha256:
+        raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
+                        (url, actual_sha256, sha256))
+      os.chmod(tmp_path, 0o755)
+      os.rename(tmp_path, bin_path)
+      with open(sha256_path, 'w') as f:
+        f.write(sha256)
+    return bin_path
+    # --- end of download_or_get_cached() ---
+
+  # --- get_perfetto_prebuilt() function starts here. ---
+  import os, platform, sys
+  plat = sys.platform.lower()
+  machine = platform.machine().lower()
+  manifest_entry = None
+  for entry in PERFETTO_PREBUILT_MANIFEST:
+    # If the caller overrides the arch, just match that (for Android prebuilts).
+    if arch and entry.get('arch') == arch:
+      manifest_entry = entry
+      break
+    # Otherwise guess the local machine arch.
+    if entry.get('tool') == tool_name and entry.get(
+        'platform') == plat and machine in entry.get('machine', []):
+      manifest_entry = entry
+      break
+  if manifest_entry is None:
+    if soft_fail:
+      return None
+    raise Exception(
+        ('No prebuilts available for %s-%s\n' % (plat, machine)) +
+        'See https://perfetto.dev/docs/contributing/build-instructions')
+
+  return download_or_get_cached(
+      file_name=manifest_entry['file_name'],
+      url=manifest_entry['url'],
+      sha256=manifest_entry['sha256'])
 
 
-def DownloadURL(url, out_file):
-  subprocess.check_call(['curl', '-L', '-#', '-o', out_file, url])
-
-
-def check_hash(file_name, sha_value):
-  with open(file_name, 'rb') as fd:
-    file_hash = hashlib.sha1(fd.read()).hexdigest()
-    return file_hash == sha_value
-
-
-def load_trace_to_text(platform):
-  sha_value = TRACE_TO_TEXT_SHAS[platform]
-  file_name = 'trace_to_text-' + platform + '-' + sha_value
-  local_file = os.path.join(TRACE_TO_TEXT_PATH, file_name)
-
-  if os.path.exists(local_file):
-    if not check_hash(local_file, sha_value):
-      os.remove(local_file)
-    else:
-      return local_file
-
-  url = TRACE_TO_TEXT_BASE_URL + file_name
-  DownloadURL(url, local_file)
-  if not check_hash(local_file, sha_value):
-    os.remove(local_file)
-    raise ValueError("Invalid signature.")
-  os.chmod(local_file, 0o755)
-  return local_file
-
-
-def main(argv):
-  os_name = None
-  if sys.platform.startswith('linux'):
-    os_name = 'linux'
-  elif sys.platform.startswith('darwin'):
-    os_name = 'mac'
-  else:
-    print("Invalid platform: {}".format(sys.platform))
-    return 1
-
-  arch = platform.machine()
-  if arch not in ['x86_64', 'amd64']:
-    print("Prebuilts are only available for x86_64 (found '{}' instead).".format(arch))
-    print("Follow https://perfetto.dev/docs/contributing/build-instructions to build locally.")
-    return 1
-
-  trace_to_text_binary = load_trace_to_text(os_name)
-  os.execv(trace_to_text_binary, [trace_to_text_binary] + argv[1:])
-
+# END_SECTION_GENERATED_BY(roll-prebuilts)
 
 if __name__ == '__main__':
-  sys.exit(main(sys.argv))
+  import sys, os
+  bin_path = get_perfetto_prebuilt(TOOL_NAME)
+  os.execv(bin_path, [bin_path] + sys.argv[1:])
 
 #EOF