| #!/usr/bin/env vpython3 |
| # Copyright 2019 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Looks for crash reports in tools/clang/crashreports and uploads them to GCS. |
| """ |
| |
| from __future__ import print_function |
| |
| import argparse |
| import datetime |
| import getpass |
| import glob |
| import os |
| import shutil |
| import subprocess |
| import sys |
| import tarfile |
| import tempfile |
| |
| |
| GCS_BUCKET = 'chrome-clang-crash-reports' |
| THIS_DIR = os.path.dirname(__file__) |
| CRASHREPORTS_DIR = os.path.join(THIS_DIR, '..', 'crashreports') |
| GSUTIL = os.path.join( |
| THIS_DIR, '..', '..', '..', 'third_party', 'depot_tools', 'gsutil.py') |
| |
| |
| def ProcessCrashreport(base, source): |
| """Zip up all files belonging to a crash base name and upload them to GCS.""" |
| sys.stdout.write('processing %s... ' % base) |
| sys.stdout.flush() |
| |
| # Note that this will include the .sh and other files: |
| files = glob.glob(os.path.join(CRASHREPORTS_DIR, base + '.*')) |
| |
| # Path design. |
| # - For each crash, it should be easy to see which platform it was on, |
| # and which configuration it happened for. |
| # - Crash prefixes should be regular so that a second bot could download |
| # crash reports and auto-triage them. |
| # - Ideally the assert reason would be easily visible too, but clang doesn't |
| # write that to disk. |
| # Prepend with '/v1' so that we can move to other schemes in the future if |
| # needed. |
| # /v1/yyyy-mm-dd/botname-basename.tgz |
| now = datetime.datetime.now() |
| dest = 'gs://%s/v1/%04d/%02d/%02d/%s-%s.tgz' % ( |
| GCS_BUCKET, now.year, now.month, now.day, source, base) |
| |
| # zipfile.ZipFile() defaults to Z_DEFAULT_COMPRESSION (6) and that can't |
| # be overridden until Python 3.7. tarfile always uses compression level 9, |
| # so use tarfile. |
| tmp_name = None |
| try: |
| with tempfile.NamedTemporaryFile(delete=False, suffix='.tgz') as tmp: |
| tmp_name = tmp.name |
| sys.stdout.write('compressing... ') |
| sys.stdout.flush() |
| with tarfile.open(mode='w:gz', fileobj=tmp) as tgz: |
| for f in files: |
| tgz.add(f, os.path.basename(f)) |
| sys.stdout.write('uploading... ') |
| sys.stdout.flush() |
| subprocess.check_call([sys.executable, GSUTIL, '-q', 'cp', tmp_name, dest]) |
| print('done') |
| print(' %s' % dest) |
| except subprocess.CalledProcessError as e: |
| print('upload failed; if it was due to missing permissions, try running') |
| print('download_from_google_storage --config') |
| print('and then try again') |
| finally: |
| if tmp_name: |
| os.remove(tmp_name) |
| |
| |
| def DeleteCrashFiles(): |
| for root, dirs, files in os.walk(CRASHREPORTS_DIR, topdown=True): |
| for d in dirs: |
| print('removing dir', d) |
| shutil.rmtree(os.path.join(root, d)) |
| for f in files: |
| if f != '.gitignore': |
| print('removing', f) |
| os.remove(os.path.join(root, f)) |
| del dirs[:] # Abort os.walk() after one level. |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument('--delete', dest='delete', action='store_true', |
| help='Delete all crashreports after processing them ' |
| '(default)') |
| parser.add_argument('--no-delete', dest='delete', action='store_false', |
| help='Do not delete crashreports after processing them') |
| parser.set_defaults(delete=True) |
| parser.add_argument('--source', default='user-' + getpass.getuser(), |
| help='Source of the crash -- usually a bot name. ' |
| 'Leave empty to use your username.') |
| args = parser.parse_args() |
| # When clang notices that it crashes, it tries to write a .sh file containing |
| # the command used to invoke clang, a source file containing the whole |
| # input source code with an extension matching the input file (.c, .cpp, ...), |
| # and potentially other temp files and directories. |
| # If generating the unified input source file fails, the .sh file won't |
| # be written. (see Driver::generateCompilationDiagnostics()). |
| # As a heuristic, find all .sh files in the crashreports directory, then |
| # zip each up along with all other files that have the same basename with |
| # different extensions. |
| clang_reproducers = glob.glob(os.path.join(CRASHREPORTS_DIR, '*.sh')) |
| # lld reproducers just leave a .tar |
| lld_reproducers = glob.glob( |
| os.path.join(CRASHREPORTS_DIR, 'linker-crash*.tar')) |
| for reproducer in clang_reproducers + lld_reproducers: |
| base = os.path.splitext(os.path.basename(reproducer))[0] |
| ProcessCrashreport(base, args.source) |
| |
| if args.delete: |
| DeleteCrashFiles() |
| |
| |
| if __name__ == '__main__': |
| try: |
| main() |
| except Exception as e: |
| print('got exception:', e) |