| #!/usr/bin/env python3 |
| # Copyright (C) 2017 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. |
| """Like llvm-symbolizer for UI JS/TS sources. |
| |
| This script is used to "symbolize" UI crashes. It takes a crash, typically |
| copied from a bug reports, of the form: |
| |
| (https://ui.perfetto.dev/v12.1.269/frontend_bundle.js:7639:61) at foo() |
| (https://ui.perfetto.dev/v12.1.269/frontend_bundle.js:9235:29) at bar() |
| |
| it fetches the corresponding source maps and emits in output a translated |
| crash report, of the form: |
| |
| https://android.googlesource.com/platform/external/perfetto/+/de4db33f/ui/src/foo.ts#61 at foo() |
| https://android.googlesource.com/platform/external/perfetto/+/de4db33f/ui/src/baz.ts#300 at bar() |
| """ |
| |
| import logging |
| import re |
| import sys |
| import tempfile |
| import urllib.request |
| import ssl |
| import os |
| |
| try: |
| import sourcemap |
| except: |
| print('Run `pip3 install sourcemap` and try again') |
| sys.exit(1) |
| |
| GERRIT_BASE_URL = 'https://android.googlesource.com/platform/external/perfetto/' |
| |
| |
| def fetch_url_cached(url): |
| normalized = re.sub('[^a-zA-Z0-9-._]', '_', url) |
| local_file = os.path.join(tempfile.gettempdir(), normalized) |
| if os.path.exists(local_file): |
| logging.debug('Using %s', local_file) |
| with open(local_file, 'r') as f: |
| return f.read() |
| context = ssl._create_unverified_context() |
| logging.info('Fetching %s', url) |
| resp = urllib.request.urlopen(url, context=context) |
| contents = resp.read().decode() |
| with open(local_file, 'w') as f: |
| f.write(contents) |
| return contents |
| |
| |
| def Main(): |
| if len(sys.argv) > 1: |
| with open(sys.argv[1], 'r') as f: |
| txt = f.read() |
| else: |
| if sys.stdin.isatty(): |
| print('Paste the crash log and press CTRL-D\n') |
| txt = sys.stdin.read() |
| |
| # Look for the GIT commitish appended in crash reports. This is not required |
| # for resolving the sourcemaps but helps generating better links. |
| matches = re.findall(r'https://ui.perfetto.dev/(.*-)([a-f0-9]{6,})\n', txt) |
| if not matches: |
| logging.fatal('Could not determine the version.' |
| 'The crash report should have a line like: ' |
| '"UI: https://ui.perfetto.dev/v12.3-abcdef"') |
| return 1 |
| |
| dir_name = matches[0][0] + matches[0][1] |
| git_rev = matches[0][1] |
| base_url = 'https://commondatastorage.googleapis.com/ui.perfetto.dev/' + dir_name + '/' |
| matches = re.findall(r'(\((.+[.]js):(\d+):(\d+)\))', txt) |
| maps_by_url = {} |
| sym_lines = '' |
| for entry in matches: |
| whole_token, script_url, line, col = entry |
| if '/' not in script_url: |
| script_url = base_url + script_url |
| map_url = script_url + '.map' |
| if map_url in maps_by_url: |
| srcmap = maps_by_url[map_url] |
| else: |
| map_file_contents = fetch_url_cached(map_url) |
| srcmap = sourcemap.loads(map_file_contents) |
| maps_by_url[map_url] = srcmap |
| sym = srcmap.lookup(int(line), int(col)) |
| src = sym.src.replace('../../', '') |
| sym_url = '%s#%s' % (src, sym.src_line) |
| if src.startswith('../out/ui/'): |
| src = src.replace('../out/ui/', 'ui/') |
| sym_url = GERRIT_BASE_URL + '/+/%s/%s#%d' % (git_rev, src, sym.src_line) |
| sym_lines += sym_url + '\n' |
| txt = txt.replace(whole_token, sym_url) |
| |
| print(txt) |
| print('\nResolved symbols:\n' + sym_lines) |
| |
| |
| if __name__ == '__main__': |
| logging.basicConfig(level=logging.INFO) |
| sys.exit(Main()) |