Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / scripts / cros_list_buildbot_crashes.py
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.
4
5 """Script for listing top buildbot crashes."""
6
7 from __future__ import print_function
8
9 import collections
10 import contextlib
11 import datetime
12 import multiprocessing
13 import logging
14 import optparse
15 import os
16 import re
17 import sys
18
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
24
25
26 def ConvertGoogleStorageURLToHttpURL(url):
27   return url.replace('gs://', 'http://sandbox.google.com/storage/')
28
29
30 class CrashTriager(object):
31   """Helper class to manage crash triaging."""
32
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+')
36
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
45     self.jobs = jobs
46
47   def Run(self):
48     """Run the crash triager, printing the most common stack traces."""
49     with self._PrintStackTracesInBackground():
50       with self._DownloadCrashesInBackground():
51         with self._ProcessCrashListInBackground():
52           pass
53
54   def _GetGSPath(self, bot_id, build_config):
55     """Get the Google Storage path where crashes are stored for a given bot.
56
57     Args:
58       bot_id: Gather crashes from this bot id.
59       build_config: Configuration options for this bot.
60     """
61     if build_config['gs_path'] == cbuildbot_config.GS_PATH_DEFAULT:
62       gsutil_archive = 'gs://chromeos-image-archive/' + bot_id
63     else:
64       gsutil_archive = build_config['gs_path']
65     return gsutil_archive
66
67   def _ListCrashesForBot(self, bot_id, build_config):
68     """List all crashes for the specified bot.
69
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'
73     ]
74
75     Args:
76       bot_id: Gather crashes from this bot id.
77       build_config: Configuration options for this bot.
78     """
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],
83                                     error_code_ok=True,
84                                     redirect_stdout=True,
85                                     redirect_stderr=True,
86                                     print_cmd=False)
87     if out.returncode == 0:
88       return out.output.split('\n')
89     return []
90
91   def _ProcessCrashListForBot(self, bot_id, build_config):
92     """Process crashes for a given bot.
93
94     Args:
95       bot_id: Gather crashes from this bot id.
96       build_config: Configuration options for this bot.
97     """
98     for line in self._ListCrashesForBot(bot_id, build_config):
99       m = self.CRASH_PATTERN.search(line)
100       if m is None:
101         continue
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))
107
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))
116       yield
117
118   def _GetStackTrace(self, crash_report_url):
119     """Retrieve a stack trace using gsutil cat.
120
121     Args:
122       crash_report_url: The URL where the crash is stored.
123     """
124     out = cros_build_lib.RunCommand(['gsutil', 'cat', crash_report_url],
125                                     error_code_ok=True,
126                                     redirect_stdout=True,
127                                     redirect_stderr=True,
128                                     print_cmd=False)
129     return out
130
131   def _DownloadStackTrace(self, program, crash_date, url):
132     """Download a crash report, queuing up the stack trace info.
133
134     Args:
135       program: The program that crashed.
136       crash_date: The date of the crash.
137       url: The URL where the crash is stored.
138     """
139     out = self._GetStackTrace(url)
140     if out.returncode == 0:
141       self.stack_trace_queue.put((program, crash_date, url, out.output))
142
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):
149       yield
150
151   def _ProcessStackTrace(self, program, date, url, output):
152     """Process a stack trace that has been downloaded.
153
154     Args:
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.
159     """
160     signature = 'uncategorized'
161     m = self.STACK_TRACE_PATTERN.search(output)
162     functions = []
163     if m:
164       trace = m.group(1)
165       functions = self.FUNCTION_PATTERN.findall(trace)
166     last_function = None
167     for f in functions:
168       if not f.startswith('libc-'):
169         signature = f
170         if last_function:
171           signature += '[%s]' % last_function
172         break
173       last_function = f.partition('!')[2]
174     else:
175       if functions:
176         signature = functions[0]
177     stack_len = len(functions)
178     self.stack_traces[(program, signature)].append((date, stack_len, url))
179
180   def _PrintStackTraces(self):
181     """Print all stack traces."""
182
183     # Print header.
184     if self.list_all:
185       print('Crash count, program, function, date, URL')
186     else:
187       print('Crash count, program, function, first crash, last crash, URL')
188
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:
193       if self.list_all:
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=', ')
198       else:
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,
203                   crash_url)
204         print(*output, sep=', ')
205
206   @contextlib.contextmanager
207   def _PrintStackTracesInBackground(self):
208     with parallel.BackgroundTaskRunner(self._ProcessStackTrace,
209                                        queue=self.stack_trace_queue,
210                                        processes=1,
211                                        onexit=self._PrintStackTraces):
212       yield
213
214
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
220
221
222 def _CreateParser():
223   """Generate and return the parser with all the options."""
224   # Parse options
225   usage = 'usage: %prog [options]'
226   parser = optparse.OptionParser(usage=usage)
227
228   # Main options
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',
238                     default=False,
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.'))
242   return parser
243
244
245 def main(argv):
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
251   else:
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)
255     sys.exit(1)
256
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)
263   triager.Run()