1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright 2020 The Chromium OS Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7"""Fetches and submits the artifacts from Chrome OS toolchain's crash bucket. 8""" 9 10import argparse 11import glob 12import json 13import logging 14import os 15import os.path 16import shutil 17import subprocess 18import sys 19 20import chroot 21 22 23def get_artifacts(pattern): 24 results = subprocess.check_output(['gsutil.py', 'ls', pattern], 25 stderr=subprocess.STDOUT, 26 encoding='utf-8') 27 return sorted(l.strip() for l in results.splitlines()) 28 29 30def get_crash_reproducers(working_dir): 31 results = [] 32 for src in [ 33 f for f in glob.glob('%s/*.c*' % working_dir) 34 if f.split('.')[-1] in ['c', 'cc', 'cpp'] 35 ]: 36 script = '.'.join(src.split('.')[:-1]) + '.sh' 37 if not os.path.exists(script): 38 logging.warning('could not find the matching script of %s', src) 39 else: 40 results.append((src, script)) 41 return results 42 43 44def submit_crash_to_forcey(forcey: str, temporary_directory: str, 45 buildbucket_id: str, url: str) -> None: 46 dest_dir = os.path.join(temporary_directory, buildbucket_id) 47 dest_file = os.path.join(dest_dir, os.path.basename(url)) 48 logging.info('Downloading and submitting %r...', url) 49 subprocess.check_output(['gsutil.py', 'cp', url, dest_file], 50 stderr=subprocess.STDOUT) 51 subprocess.check_output(['tar', '-xJf', dest_file], cwd=dest_dir) 52 for src, script in get_crash_reproducers(dest_dir): 53 subprocess.check_output([ 54 forcey, 'reduce', '-wait=false', '-note', 55 '%s:%s' % (url, src), '-sh_file', script, '-src_file', src 56 ]) 57 58 59def main(argv): 60 chroot.VerifyOutsideChroot() 61 logging.basicConfig( 62 format='%(asctime)s: %(levelname)s: %(filename)s:%(lineno)d: %(message)s', 63 level=logging.INFO, 64 ) 65 cur_dir = os.path.dirname(os.path.abspath(__file__)) 66 parser = argparse.ArgumentParser(description=__doc__) 67 parser.add_argument( 68 '--4c', dest='forcey', required=True, help='Path to a 4c client binary') 69 parser.add_argument( 70 '--state_file', 71 default=os.path.join(cur_dir, 'chromeos-state.json'), 72 help='The path to the state file.') 73 parser.add_argument( 74 '--nocleanup', 75 action='store_false', 76 dest='cleanup', 77 help='Keep temporary files created after the script finishes.') 78 opts = parser.parse_args(argv) 79 80 state_file = os.path.abspath(opts.state_file) 81 os.makedirs(os.path.dirname(state_file), exist_ok=True) 82 temporary_directory = '/tmp/bisect_clang_crashes' 83 os.makedirs(temporary_directory, exist_ok=True) 84 urls = get_artifacts('gs://chromeos-toolchain-artifacts/clang-crash-diagnoses' 85 '/**/*clang_crash_diagnoses.tar.xz') 86 logging.info('%d crash URLs found', len(urls)) 87 88 visited = {} 89 if os.path.exists(state_file): 90 buildbucket_ids = {url.split('/')[-2] for url in urls} 91 with open(state_file, encoding='utf-8') as f: 92 data = json.load(f) 93 visited = {k: v for k, v in data.items() if k in buildbucket_ids} 94 logging.info('Successfully loaded %d previously-submitted crashes', 95 len(visited)) 96 97 try: 98 for url in urls: 99 splits = url.split('/') 100 buildbucket_id = splits[-2] 101 # Skip the builds that has been processed 102 if buildbucket_id in visited: 103 continue 104 submit_crash_to_forcey( 105 forcey=opts.forcey, 106 temporary_directory=temporary_directory, 107 buildbucket_id=buildbucket_id, 108 url=url, 109 ) 110 visited[buildbucket_id] = url 111 112 exception_in_flight = False 113 except: 114 exception_in_flight = True 115 raise 116 finally: 117 if exception_in_flight: 118 # This is best-effort. If the machine powers off or similar, we'll just 119 # resubmit the same crashes, which is suboptimal, but otherwise 120 # acceptable. 121 logging.error('Something went wrong; attempting to save our work...') 122 else: 123 logging.info('Persisting state...') 124 125 tmp_state_file = state_file + '.tmp' 126 with open(tmp_state_file, 'w', encoding='utf-8') as f: 127 json.dump(visited, f, indent=2) 128 os.rename(tmp_state_file, state_file) 129 130 logging.info('State successfully persisted') 131 132 if opts.cleanup: 133 shutil.rmtree(temporary_directory) 134 135 136if __name__ == '__main__': 137 sys.exit(main(sys.argv[1:])) 138