1 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Script for listing top buildbot crashes."""
7 from __future__ import print_function
12 import multiprocessing
19 from chromite.cbuildbot import cbuildbot_config
20 from chromite.cbuildbot import constants
21 from chromite.cbuildbot import manifest_version
22 from chromite.lib import cros_build_lib
23 from chromite.lib import parallel
26 def ConvertGoogleStorageURLToHttpURL(url):
27 return url.replace('gs://', 'http://sandbox.google.com/storage/')
30 class CrashTriager(object):
31 """Helper class to manage crash triaging."""
33 CRASH_PATTERN = re.compile(r'/([^/.]*)\.(\d+)[^/]*\.dmp\.txt$')
34 STACK_TRACE_PATTERN = re.compile(r'Thread 0 ((?:[^\n]+\n)*)')
35 FUNCTION_PATTERN = re.compile(r'\S+!\S+')
37 def __init__(self, start_date, chrome_branch, all_programs, list_all, jobs):
38 self.start_date = start_date
39 self.chrome_branch = chrome_branch
40 self.crash_triage_queue = multiprocessing.Queue()
41 self.stack_trace_queue = multiprocessing.Queue()
42 self.stack_traces = collections.defaultdict(list)
43 self.all_programs = all_programs
44 self.list_all = list_all
48 """Run the crash triager, printing the most common stack traces."""
49 with self._PrintStackTracesInBackground():
50 with self._DownloadCrashesInBackground():
51 with self._ProcessCrashListInBackground():
54 def _GetGSPath(self, bot_id, build_config):
55 """Get the Google Storage path where crashes are stored for a given bot.
58 bot_id: Gather crashes from this bot id.
59 build_config: Configuration options for this bot.
61 if build_config['gs_path'] == cbuildbot_config.GS_PATH_DEFAULT:
62 gsutil_archive = 'gs://chromeos-image-archive/' + bot_id
64 gsutil_archive = build_config['gs_path']
67 def _ListCrashesForBot(self, bot_id, build_config):
68 """List all crashes for the specified bot.
70 Example output line: [
71 'gs://chromeos-image-archive/amd64-generic-full/R18-1414.0.0-a1-b537/' +
72 'chrome.20111207.181520.2533.dmp.txt'
76 bot_id: Gather crashes from this bot id.
77 build_config: Configuration options for this bot.
79 chrome_branch = self.chrome_branch
80 gsutil_archive = self._GetGSPath(bot_id, build_config)
81 pattern = '%s/R%s-**.dmp.txt' % (gsutil_archive, chrome_branch)
82 out = cros_build_lib.RunCommand(['gsutil', 'ls', pattern],
87 if out.returncode == 0:
88 return out.output.split('\n')
91 def _ProcessCrashListForBot(self, bot_id, build_config):
92 """Process crashes for a given bot.
95 bot_id: Gather crashes from this bot id.
96 build_config: Configuration options for this bot.
98 for line in self._ListCrashesForBot(bot_id, build_config):
99 m = self.CRASH_PATTERN.search(line)
102 program, crash_date = m.groups()
103 if self.all_programs or program == 'chrome':
104 crash_date_obj = datetime.datetime.strptime(crash_date, '%Y%m%d')
105 if self.start_date <= crash_date_obj:
106 self.crash_triage_queue.put((program, crash_date, line))
108 @contextlib.contextmanager
109 def _ProcessCrashListInBackground(self):
110 """Create a worker process for processing crash lists."""
111 with parallel.BackgroundTaskRunner(self._ProcessCrashListForBot,
112 processes=self.jobs) as queue:
113 for bot_id, build_config in cbuildbot_config.config.iteritems():
114 if build_config['vm_tests']:
115 queue.put((bot_id, build_config))
118 def _GetStackTrace(self, crash_report_url):
119 """Retrieve a stack trace using gsutil cat.
122 crash_report_url: The URL where the crash is stored.
124 out = cros_build_lib.RunCommand(['gsutil', 'cat', crash_report_url],
126 redirect_stdout=True,
127 redirect_stderr=True,
131 def _DownloadStackTrace(self, program, crash_date, url):
132 """Download a crash report, queuing up the stack trace info.
135 program: The program that crashed.
136 crash_date: The date of the crash.
137 url: The URL where the crash is stored.
139 out = self._GetStackTrace(url)
140 if out.returncode == 0:
141 self.stack_trace_queue.put((program, crash_date, url, out.output))
143 @contextlib.contextmanager
144 def _DownloadCrashesInBackground(self):
145 """Create a worker process for downloading stack traces."""
146 with parallel.BackgroundTaskRunner(self._DownloadStackTrace,
147 queue=self.crash_triage_queue,
148 processes=self.jobs):
151 def _ProcessStackTrace(self, program, date, url, output):
152 """Process a stack trace that has been downloaded.
155 program: The program that crashed.
156 date: The date of the crash.
157 url: The URL where the crash is stored.
158 output: The content of the stack trace.
160 signature = 'uncategorized'
161 m = self.STACK_TRACE_PATTERN.search(output)
165 functions = self.FUNCTION_PATTERN.findall(trace)
168 if not f.startswith('libc-'):
171 signature += '[%s]' % last_function
173 last_function = f.partition('!')[2]
176 signature = functions[0]
177 stack_len = len(functions)
178 self.stack_traces[(program, signature)].append((date, stack_len, url))
180 def _PrintStackTraces(self):
181 """Print all stack traces."""
185 print('Crash count, program, function, date, URL')
187 print('Crash count, program, function, first crash, last crash, URL')
189 # Print details about stack traces.
190 stack_traces = sorted(self.stack_traces.iteritems(),
191 key=lambda x: len(x[1]), reverse=True)
192 for (program, signature), crashes in stack_traces:
194 for crash in sorted(crashes, reverse=True):
195 crash_url = ConvertGoogleStorageURLToHttpURL(crash[2])
196 output = (str(len(crashes)), program, signature, crash[0], crash_url)
197 print(*output, sep=', ')
199 first_date = min(x[0] for x in crashes)
200 last_date = max(x[0] for x in crashes)
201 crash_url = ConvertGoogleStorageURLToHttpURL(max(crashes)[2])
202 output = (str(len(crashes)), program, signature, first_date, last_date,
204 print(*output, sep=', ')
206 @contextlib.contextmanager
207 def _PrintStackTracesInBackground(self):
208 with parallel.BackgroundTaskRunner(self._ProcessStackTrace,
209 queue=self.stack_trace_queue,
211 onexit=self._PrintStackTraces):
215 def _GetChromeBranch():
216 """Get the current Chrome branch."""
217 version_file = os.path.join(constants.SOURCE_ROOT, constants.VERSION_FILE)
218 version_info = manifest_version.VersionInfo(version_file=version_file)
219 return version_info.chrome_branch
223 """Generate and return the parser with all the options."""
225 usage = 'usage: %prog [options]'
226 parser = optparse.OptionParser(usage=usage)
229 parser.add_option('', '--days', dest='days', default=7, type='int',
230 help=('Number of days to look at for crash info.'))
231 parser.add_option('', '--chrome_branch', dest='chrome_branch',
232 default=_GetChromeBranch(),
233 help=('Chrome branch to look at for crash info.'))
234 parser.add_option('', '--all_programs', action='store_true',
235 dest='all_programs', default=False,
236 help=('Show crashes in programs other than Chrome.'))
237 parser.add_option('', '--list', action='store_true', dest='list_all',
239 help=('List all stack traces found (not just one).'))
240 parser.add_option('', '--jobs', dest='jobs', default=32, type='int',
241 help=('Number of processes to run in parallel.'))
246 # Setup boto config for gsutil.
247 boto_config = os.path.abspath(os.path.join(constants.SOURCE_ROOT,
248 'src/private-overlays/chromeos-overlay/googlestorage_account.boto'))
249 if os.path.isfile(boto_config):
250 os.environ['BOTO_CONFIG'] = boto_config
252 print('Cannot find %s' % boto_config, file=sys.stderr)
253 print('This function requires a private checkout.', file=sys.stderr)
254 print('See http://goto/chromeos-building', file=sys.stderr)
257 logging.disable(level=logging.INFO)
258 parser = _CreateParser()
259 (options, _) = parser.parse_args(argv)
260 since = datetime.datetime.today() - datetime.timedelta(days=options.days)
261 triager = CrashTriager(since, options.chrome_branch, options.all_programs,
262 options.list_all, options.jobs)