• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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