140 lines
4.4 KiB
Python
140 lines
4.4 KiB
Python
|
#!/usr/bin/env python3
|
||
|
# -*- coding: utf-8 -*-
|
||
|
# Copyright 2020 The Chromium OS Authors. All rights reserved.
|
||
|
# Use of this source code is governed by a BSD-style license that can be
|
||
|
# found in the LICENSE file.
|
||
|
|
||
|
"""Fetches and submits the artifacts from Chrome OS toolchain's crash bucket.
|
||
|
"""
|
||
|
|
||
|
# pylint: disable=cros-logging-import
|
||
|
|
||
|
import argparse
|
||
|
import glob
|
||
|
import json
|
||
|
import logging
|
||
|
import os
|
||
|
import os.path
|
||
|
import shutil
|
||
|
import subprocess
|
||
|
import sys
|
||
|
|
||
|
import chroot
|
||
|
|
||
|
|
||
|
def get_artifacts(pattern):
|
||
|
results = subprocess.check_output(['gsutil.py', 'ls', pattern],
|
||
|
stderr=subprocess.STDOUT,
|
||
|
encoding='utf-8')
|
||
|
return sorted(l.strip() for l in results.splitlines())
|
||
|
|
||
|
|
||
|
def get_crash_reproducers(working_dir):
|
||
|
results = []
|
||
|
for src in [
|
||
|
f for f in glob.glob('%s/*.c*' % working_dir)
|
||
|
if f.split('.')[-1] in ['c', 'cc', 'cpp']
|
||
|
]:
|
||
|
script = '.'.join(src.split('.')[:-1]) + '.sh'
|
||
|
if not os.path.exists(script):
|
||
|
logging.warning('could not find the matching script of %s', src)
|
||
|
else:
|
||
|
results.append((src, script))
|
||
|
return results
|
||
|
|
||
|
|
||
|
def submit_crash_to_forcey(forcey: str, temporary_directory: str,
|
||
|
buildbucket_id: str, url: str) -> None:
|
||
|
dest_dir = os.path.join(temporary_directory, buildbucket_id)
|
||
|
dest_file = os.path.join(dest_dir, os.path.basename(url))
|
||
|
logging.info('Downloading and submitting %r...', url)
|
||
|
subprocess.check_output(['gsutil.py', 'cp', url, dest_file],
|
||
|
stderr=subprocess.STDOUT)
|
||
|
subprocess.check_output(['tar', '-xJf', dest_file], cwd=dest_dir)
|
||
|
for src, script in get_crash_reproducers(dest_dir):
|
||
|
subprocess.check_output([
|
||
|
forcey, 'reduce', '-wait=false', '-note',
|
||
|
'%s:%s' % (url, src), '-sh_file', script, '-src_file', src
|
||
|
])
|
||
|
|
||
|
|
||
|
def main(argv):
|
||
|
chroot.VerifyOutsideChroot()
|
||
|
logging.basicConfig(
|
||
|
format='%(asctime)s: %(levelname)s: %(filename)s:%(lineno)d: %(message)s',
|
||
|
level=logging.INFO,
|
||
|
)
|
||
|
cur_dir = os.path.dirname(os.path.abspath(__file__))
|
||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||
|
parser.add_argument(
|
||
|
'--4c', dest='forcey', required=True, help='Path to a 4c client binary')
|
||
|
parser.add_argument(
|
||
|
'--state_file',
|
||
|
default=os.path.join(cur_dir, 'chromeos-state.json'),
|
||
|
help='The path to the state file.')
|
||
|
parser.add_argument(
|
||
|
'--nocleanup',
|
||
|
action='store_false',
|
||
|
dest='cleanup',
|
||
|
help='Keep temporary files created after the script finishes.')
|
||
|
opts = parser.parse_args(argv)
|
||
|
|
||
|
state_file = os.path.abspath(opts.state_file)
|
||
|
os.makedirs(os.path.dirname(state_file), exist_ok=True)
|
||
|
temporary_directory = '/tmp/bisect_clang_crashes'
|
||
|
os.makedirs(temporary_directory, exist_ok=True)
|
||
|
urls = get_artifacts('gs://chromeos-toolchain-artifacts/clang-crash-diagnoses'
|
||
|
'/**/*clang_crash_diagnoses.tar.xz')
|
||
|
logging.info('%d crash URLs found', len(urls))
|
||
|
|
||
|
visited = {}
|
||
|
if os.path.exists(state_file):
|
||
|
buildbucket_ids = {url.split('/')[-2] for url in urls}
|
||
|
with open(state_file, encoding='utf-8') as f:
|
||
|
data = json.load(f)
|
||
|
visited = {k: v for k, v in data.items() if k in buildbucket_ids}
|
||
|
logging.info('Successfully loaded %d previously-submitted crashes',
|
||
|
len(visited))
|
||
|
|
||
|
try:
|
||
|
for url in urls:
|
||
|
splits = url.split('/')
|
||
|
buildbucket_id = splits[-2]
|
||
|
# Skip the builds that has been processed
|
||
|
if buildbucket_id in visited:
|
||
|
continue
|
||
|
submit_crash_to_forcey(
|
||
|
forcey=opts.forcey,
|
||
|
temporary_directory=temporary_directory,
|
||
|
buildbucket_id=buildbucket_id,
|
||
|
url=url,
|
||
|
)
|
||
|
visited[buildbucket_id] = url
|
||
|
|
||
|
exception_in_flight = False
|
||
|
except:
|
||
|
exception_in_flight = True
|
||
|
raise
|
||
|
finally:
|
||
|
if exception_in_flight:
|
||
|
# This is best-effort. If the machine powers off or similar, we'll just
|
||
|
# resubmit the same crashes, which is suboptimal, but otherwise
|
||
|
# acceptable.
|
||
|
logging.error('Something went wrong; attempting to save our work...')
|
||
|
else:
|
||
|
logging.info('Persisting state...')
|
||
|
|
||
|
tmp_state_file = state_file + '.tmp'
|
||
|
with open(tmp_state_file, 'w', encoding='utf-8') as f:
|
||
|
json.dump(visited, f, indent=2)
|
||
|
os.rename(tmp_state_file, state_file)
|
||
|
|
||
|
logging.info('State successfully persisted')
|
||
|
|
||
|
if opts.cleanup:
|
||
|
shutil.rmtree(temporary_directory)
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
sys.exit(main(sys.argv[1:]))
|