| #!/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 | |
| try: | |
| sym = srcmap.lookup(int(line), int(col)) | |
| except IndexError: | |
| sym = sourcemap.objects.Token(src='<Could not resolve symbol>') | |
| 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()) |