xref: /aosp_15_r20/external/perfetto/tools/symbolize-ui-crash (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1#!/usr/bin/env python3
2# Copyright (C) 2017 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"""Like llvm-symbolizer for UI JS/TS sources.
16
17This script is used to "symbolize" UI crashes. It takes a crash, typically
18copied from a bug reports, of the form:
19
20(https://ui.perfetto.dev/v12.1.269/frontend_bundle.js:7639:61) at foo()
21(https://ui.perfetto.dev/v12.1.269/frontend_bundle.js:9235:29) at bar()
22
23it fetches the corresponding source maps and emits in output a translated
24crash report, of the form:
25
26https://android.googlesource.com/platform/external/perfetto/+/de4db33f/ui/src/foo.ts#61 at foo()
27https://android.googlesource.com/platform/external/perfetto/+/de4db33f/ui/src/baz.ts#300 at bar()
28"""
29
30import logging
31import re
32import sys
33import tempfile
34import urllib.request
35import ssl
36import os
37
38try:
39  import sourcemap
40except:
41  print('Run `pip3 install sourcemap` and try again')
42  sys.exit(1)
43
44GERRIT_BASE_URL = 'https://android.googlesource.com/platform/external/perfetto/'
45
46
47def fetch_url_cached(url):
48  normalized = re.sub('[^a-zA-Z0-9-._]', '_', url)
49  local_file = os.path.join(tempfile.gettempdir(), normalized)
50  if os.path.exists(local_file):
51    logging.debug('Using %s', local_file)
52    with open(local_file, 'r') as f:
53      return f.read()
54  context = ssl._create_unverified_context()
55  logging.info('Fetching %s', url)
56  resp = urllib.request.urlopen(url, context=context)
57  contents = resp.read().decode()
58  with open(local_file, 'w') as f:
59    f.write(contents)
60  return contents
61
62
63def Main():
64  if len(sys.argv) > 1:
65    with open(sys.argv[1], 'r') as f:
66      txt = f.read()
67  else:
68    if sys.stdin.isatty():
69      print('Paste the crash log and press CTRL-D\n')
70    txt = sys.stdin.read()
71
72  # Look for the GIT commitish appended in crash reports. This is not required
73  # for resolving the sourcemaps but helps generating better links.
74  matches = re.findall(r'https://ui.perfetto.dev/(.*-)([a-f0-9]{6,})\n', txt)
75  if not matches:
76    logging.fatal('Could not determine the version.'
77                  'The crash report should have a line like: '
78                  '"UI: https://ui.perfetto.dev/v12.3-abcdef"')
79    return 1
80
81  dir_name = matches[0][0] + matches[0][1]
82  git_rev = matches[0][1]
83  base_url = 'https://commondatastorage.googleapis.com/ui.perfetto.dev/' + dir_name + '/'
84  matches = re.findall(r'(\((.+[.]js):(\d+):(\d+)\))', txt)
85  maps_by_url = {}
86  sym_lines = ''
87  for entry in matches:
88    whole_token, script_url, line, col = entry
89    if '/' not in script_url:
90      script_url = base_url + script_url
91    map_url = script_url + '.map'
92    if map_url in maps_by_url:
93      srcmap = maps_by_url[map_url]
94    else:
95      map_file_contents = fetch_url_cached(map_url)
96      srcmap = sourcemap.loads(map_file_contents)
97      maps_by_url[map_url] = srcmap
98    sym = srcmap.lookup(int(line), int(col))
99    src = sym.src.replace('../../', '')
100    sym_url = '%s#%s' % (src, sym.src_line)
101    if src.startswith('../out/ui/'):
102      src = src.replace('../out/ui/', 'ui/')
103      sym_url = GERRIT_BASE_URL + '/+/%s/%s#%d' % (git_rev, src, sym.src_line)
104    sym_lines += sym_url + '\n'
105    txt = txt.replace(whole_token, sym_url)
106
107  print(txt)
108  print('\nResolved symbols:\n' + sym_lines)
109
110
111if __name__ == '__main__':
112  logging.basicConfig(level=logging.INFO)
113  sys.exit(Main())
114