blob: 8e94e8064086242a098f93ceca7fcf906e1aabd2 [file] [log] [blame]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -04001# Copyright (c) 2013 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"""Upload all debug symbols required for crash reporting purposes.
6
7This script need only be used to upload release builds symbols or to debug
8crashes on non-release builds (in which case try to only upload the symbols
Mike Frysinger02e1e072013-11-10 22:11:34 -05009for those executables involved).
10"""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040011
Mike Frysingera4fa1e82014-01-15 01:45:56 -050012from __future__ import print_function
13
Fang Dengba680462015-08-16 20:34:11 -070014import collections
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040015import ctypes
Mike Frysinger8ec8c502014-02-10 00:19:13 -050016import datetime
Mike Frysingerdad2a032014-05-11 23:05:11 -040017import errno
Mike Frysinger02e92402013-11-22 16:22:02 -050018import functools
Mike Frysinger0c0efa22014-02-09 23:32:23 -050019import hashlib
Mike Frysingera4fa1e82014-01-15 01:45:56 -050020import httplib
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040021import multiprocessing
22import os
Mike Frysinger094a2172013-08-14 12:54:35 -040023import poster
Mike Frysinger66e51e92014-05-03 16:52:00 -040024try:
25 import Queue
26except ImportError:
27 # Python-3 renamed to "queue". We still use Queue to avoid collisions
28 # with naming variables as "queue". Maybe we'll transition at some point.
29 # pylint: disable=F0401
30 import queue as Queue
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040031import random
Mike Frysinger66e51e92014-05-03 16:52:00 -040032import signal
Mike Frysingerfd355652014-01-23 02:57:48 -050033import socket
Mike Frysingerc5597f22014-11-27 15:39:15 -050034import sys
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040035import textwrap
36import tempfile
37import time
Mike Frysinger094a2172013-08-14 12:54:35 -040038import urllib2
Mike Frysingerd41938e2014-02-10 06:37:55 -050039import urlparse
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040040
Don Garrett88b8d782014-05-13 17:30:55 -070041from chromite.cbuildbot import constants
Mike Frysingerd41938e2014-02-10 06:37:55 -050042from chromite.lib import cache
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040043from chromite.lib import commandline
44from chromite.lib import cros_build_lib
Ralph Nathan5a582ff2015-03-20 18:18:30 -070045from chromite.lib import cros_logging as logging
Mike Frysingerd41938e2014-02-10 06:37:55 -050046from chromite.lib import gs
47from chromite.lib import osutils
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040048from chromite.lib import parallel
Gilad Arnold83233ed2015-05-08 12:12:13 -070049from chromite.lib import path_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080050from chromite.lib import retry_util
Mike Frysinger66e51e92014-05-03 16:52:00 -040051from chromite.lib import signals
Mike Frysinger0c0efa22014-02-09 23:32:23 -050052from chromite.lib import timeout_util
Mike Frysinger69cb41d2013-08-11 20:08:19 -040053from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040054
Mike Frysinger0c0efa22014-02-09 23:32:23 -050055# Needs to be after chromite imports.
Mike Frysingerc5597f22014-11-27 15:39:15 -050056# We don't want to import the general keyring module as that will implicitly
57# try to import & connect to a dbus server. That's a waste of time.
58sys.modules['keyring'] = None
59import isolateserver
Mike Frysinger0c0efa22014-02-09 23:32:23 -050060
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040061
62# URLs used for uploading symbols.
63OFFICIAL_UPLOAD_URL = 'http://clients2.google.com/cr/symbol'
64STAGING_UPLOAD_URL = 'http://clients2.google.com/cr/staging_symbol'
65
66
67# The crash server rejects files that are this big.
68CRASH_SERVER_FILE_LIMIT = 350 * 1024 * 1024
69# Give ourselves a little breathing room from what the server expects.
70DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
71
72
Mike Frysinger0c0efa22014-02-09 23:32:23 -050073# The batch limit when talking to the dedup server. We avoid sending one at a
74# time as the round trip overhead will dominate. Conversely, we avoid sending
75# all at once so we can start uploading symbols asap -- the symbol server is a
76# bit slow and will take longer than anything else.
77# TODO: A better algorithm would be adaptive. If we have more than one symbol
78# in the upload queue waiting, we could send more symbols to the dedupe server
79# at a time.
80DEDUPE_LIMIT = 100
81
82# How long to wait for the server to respond with the results. Note that the
83# larger the limit above, the larger this will need to be. So we give it ~1
84# second per item max.
85DEDUPE_TIMEOUT = DEDUPE_LIMIT
86
Mike Frysinger4dd462e2014-04-30 16:21:51 -040087# How long to wait for the notification to finish (in minutes). If it takes
88# longer than this, we'll stop notifiying, but that's not a big deal as we
89# will be able to recover in later runs.
90DEDUPE_NOTIFY_TIMEOUT = 20
91
Mike Frysinger0c0efa22014-02-09 23:32:23 -050092# The unique namespace in the dedupe server that only we use. Helps avoid
93# collisions with all the hashed values and unrelated content.
Don Garrett747cc4b2015-10-07 14:48:48 -070094OFFICIAL_DEDUPE_NAMESPACE_TMPL = '%s-upload-symbols'
95STAGING_DEDUPE_NAMESPACE_TMPL = '%s-staging' % OFFICIAL_DEDUPE_NAMESPACE_TMPL
Mike Frysinger0c0efa22014-02-09 23:32:23 -050096
97
Mike Frysinger71046662014-09-12 18:15:15 -070098# The minimum average rate (in bytes per second) that we expect to maintain
99# when uploading symbols. This has to allow for symbols that are up to
100# CRASH_SERVER_FILE_LIMIT in size.
101UPLOAD_MIN_RATE = CRASH_SERVER_FILE_LIMIT / (30 * 60)
102
103# The lowest timeout (in seconds) we'll allow. If the server is overloaded,
104# then there might be a delay in setting up the connection, not just with the
105# transfer. So even a small file might need a larger value.
106UPLOAD_MIN_TIMEOUT = 2 * 60
Mike Frysingercd78a082013-06-26 17:13:04 -0400107
108
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400109# Sleep for 200ms in between uploads to avoid DoS'ing symbol server.
110DEFAULT_SLEEP_DELAY = 0.2
111
112
113# Number of seconds to wait before retrying an upload. The delay will double
114# for each subsequent retry of the same symbol file.
115INITIAL_RETRY_DELAY = 1
116
117# Allow up to 7 attempts to upload a symbol file (total delay may be
118# 1+2+4+8+16+32=63 seconds).
119MAX_RETRIES = 6
120
Mike Frysingereb753bf2013-11-22 16:05:35 -0500121# Number of total errors, before uploads are no longer attempted.
122# This is used to avoid lots of errors causing unreasonable delays.
123# See the related, but independent, error values below.
124MAX_TOTAL_ERRORS_FOR_RETRY = 30
125
126# A watermark of transient errors which we allow recovery from. If we hit
127# errors infrequently, overall we're probably doing fine. For example, if
128# we have one failure every 100 passes, then we probably don't want to fail
129# right away. But if we hit a string of failures in a row, we want to abort.
130#
131# The watermark starts at 0 (and can never go below that). When this error
132# level is exceeded, we stop uploading. When a failure happens, we add the
133# fail adjustment, and when an upload succeeds, we add the pass adjustment.
134# We want to penalize failures more so that we ramp up when there is a string
135# of them, but then slowly back off as things start working.
136#
137# A quick example:
138# 0.0: Starting point.
139# 0.0: Upload works, so add -0.5, and then clamp to 0.
140# 1.0: Upload fails, so add 1.0.
141# 2.0: Upload fails, so add 1.0.
142# 1.5: Upload works, so add -0.5.
143# 1.0: Upload works, so add -0.5.
144ERROR_WATERMARK = 3.0
145ERROR_ADJUST_FAIL = 1.0
146ERROR_ADJUST_PASS = -0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400147
148
Fang Dengba680462015-08-16 20:34:11 -0700149# A named tuple which hold a SymbolItem object and
150# a isolateserver._IsolateServerPushState item.
151SymbolElement = collections.namedtuple(
152 'SymbolElement', ('symbol_item', 'opaque_push_state'))
153
154
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700155def GetUploadTimeout(path):
156 """How long to wait for a specific file to upload to the crash server.
157
158 This is a function largely to make unittesting easier.
159
160 Args:
161 path: The path to the file to calculate the timeout for
162
163 Returns:
164 Timeout length (in seconds)
165 """
166 # Scale the timeout based on the filesize.
167 return max(os.path.getsize(path) / UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
168
169
Don Garrettcea3eea2015-09-29 17:20:46 -0700170def SymUpload(upload_url, sym_item, product_name):
Mike Frysinger094a2172013-08-14 12:54:35 -0400171 """Upload a symbol file to a HTTP server
172
173 The upload is a multipart/form-data POST with the following parameters:
174 code_file: the basename of the module, e.g. "app"
175 code_identifier: the module file's identifier
176 debug_file: the basename of the debugging file, e.g. "app"
177 debug_identifier: the debug file's identifier, usually consisting of
178 the guid and age embedded in the pdb, e.g.
179 "11111111BBBB3333DDDD555555555555F"
180 version: the file version of the module, e.g. "1.2.3.4"
181 product: HTTP-friendly product name
182 os: the operating system that the module was built for
183 cpu: the CPU that the module was built for
184 symbol_file: the contents of the breakpad-format symbol file
185
186 Args:
Mike Frysinger094a2172013-08-14 12:54:35 -0400187 upload_url: The crash URL to POST the |sym_file| to
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500188 sym_item: A SymbolItem containing the path to the breakpad symbol to upload
Don Garrettcea3eea2015-09-29 17:20:46 -0700189 product_name: A string for stats purposes. Usually 'ChromeOS' or 'Android'.
Mike Frysinger094a2172013-08-14 12:54:35 -0400190 """
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500191 sym_header = sym_item.sym_header
192 sym_file = sym_item.sym_file
Mike Frysinger094a2172013-08-14 12:54:35 -0400193
194 fields = (
195 ('code_file', sym_header.name),
196 ('debug_file', sym_header.name),
197 ('debug_identifier', sym_header.id.replace('-', '')),
Mike Frysingerb8a966b2014-03-19 17:36:18 -0400198 # The product/version fields are used by the server only for statistic
199 # purposes. They do not impact symbolization, so they're safe to set
200 # to any value all the time.
201 # In this case, we use it to help see the load our build system is
202 # placing on the server.
203 # Not sure what to set for the version. Maybe the git sha1 of this file.
204 # Note: the server restricts this to 30 chars.
Mike Frysinger094a2172013-08-14 12:54:35 -0400205 #('version', None),
Don Garrettcea3eea2015-09-29 17:20:46 -0700206 ('product', product_name),
Mike Frysinger094a2172013-08-14 12:54:35 -0400207 ('os', sym_header.os),
208 ('cpu', sym_header.cpu),
209 poster.encode.MultipartParam.from_file('symbol_file', sym_file),
210 )
211
212 data, headers = poster.encode.multipart_encode(fields)
213 request = urllib2.Request(upload_url, data, headers)
214 request.add_header('User-agent', 'chromite.upload_symbols')
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700215 urllib2.urlopen(request, timeout=GetUploadTimeout(sym_file))
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400216
217
Don Garrettcea3eea2015-09-29 17:20:46 -0700218def TestingSymUpload(upload_url, sym_item, _product_name):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400219 """A stub version of SymUpload for --testing usage"""
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500220 cmd = ['sym_upload', sym_item.sym_file, upload_url]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400221 # Randomly fail 80% of the time (the retry logic makes this 80%/3 per file).
222 returncode = random.randint(1, 100) <= 80
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700223 logging.debug('would run (and return %i): %s', returncode,
224 cros_build_lib.CmdToStr(cmd))
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400225 if returncode:
226 output = 'Failed to send the symbol file.'
227 else:
228 output = 'Successfully sent the symbol file.'
229 result = cros_build_lib.CommandResult(cmd=cmd, error=None, output=output,
230 returncode=returncode)
231 if returncode:
Mike Frysingera4fa1e82014-01-15 01:45:56 -0500232 exceptions = (
Mike Frysingerfd355652014-01-23 02:57:48 -0500233 socket.error('[socket.error] forced test fail'),
Mike Frysingera4fa1e82014-01-15 01:45:56 -0500234 httplib.BadStatusLine('[BadStatusLine] forced test fail'),
235 urllib2.HTTPError(upload_url, 400, '[HTTPError] forced test fail',
236 {}, None),
237 urllib2.URLError('[URLError] forced test fail'),
238 )
239 raise random.choice(exceptions)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400240 else:
241 return result
242
243
Mike Frysingereb753bf2013-11-22 16:05:35 -0500244def ErrorLimitHit(num_errors, watermark_errors):
245 """See if our error limit has been hit
246
247 Args:
248 num_errors: A multiprocessing.Value of the raw number of failures.
249 watermark_errors: A multiprocessing.Value of the current rate of failures.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500250
Mike Frysingereb753bf2013-11-22 16:05:35 -0500251 Returns:
252 True if our error limits have been exceeded.
253 """
254 return ((num_errors is not None and
255 num_errors.value > MAX_TOTAL_ERRORS_FOR_RETRY) or
256 (watermark_errors is not None and
257 watermark_errors.value > ERROR_WATERMARK))
258
259
260def _UpdateCounter(counter, adj):
261 """Update |counter| by |adj|
262
263 Handle atomic updates of |counter|. Also make sure it does not
264 fall below 0.
265
266 Args:
267 counter: A multiprocessing.Value to update
268 adj: The value to add to |counter|
269 """
270 def _Update():
271 clamp = 0 if type(adj) is int else 0.0
272 counter.value = max(clamp, counter.value + adj)
273
274 if hasattr(counter, 'get_lock'):
275 with counter.get_lock():
276 _Update()
277 elif counter is not None:
278 _Update()
279
280
Don Garrettcea3eea2015-09-29 17:20:46 -0700281def UploadSymbol(upload_url, symbol_element, product_name,
282 file_limit=DEFAULT_FILE_LIMIT, sleep=0, num_errors=None,
283 watermark_errors=None, failed_queue=None, passed_queue=None):
Fang Dengba680462015-08-16 20:34:11 -0700284 """Upload |sym_element.symbol_item| to |upload_url|
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400285
286 Args:
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400287 upload_url: The crash server to upload things to
Fang Dengba680462015-08-16 20:34:11 -0700288 symbol_element: A SymbolElement tuple. symbol_element.symbol_item is a
289 SymbolItem object containing the path to the breakpad symbol
290 to upload. symbol_element.opaque_push_state is an object of
291 _IsolateServerPushState or None if the item doesn't have
292 a push state.
Don Garrettcea3eea2015-09-29 17:20:46 -0700293 product_name: A string for stats purposes. Usually 'ChromeOS' or 'Android'.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400294 file_limit: The max file size of a symbol file before we try to strip it
295 sleep: Number of seconds to sleep before running
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400296 num_errors: An object to update with the error count (needs a .value member)
Mike Frysingereb753bf2013-11-22 16:05:35 -0500297 watermark_errors: An object to track current error behavior (needs a .value)
Mike Frysinger02e92402013-11-22 16:22:02 -0500298 failed_queue: When a symbol fails, add it to this queue
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500299 passed_queue: When a symbol passes, add it to this queue
Mike Frysinger1a736a82013-12-12 01:50:59 -0500300
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400301 Returns:
302 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400303 """
Fang Dengba680462015-08-16 20:34:11 -0700304 sym_file = symbol_element.symbol_item.sym_file
305 upload_item = symbol_element.symbol_item
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500306
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400307 if num_errors is None:
308 num_errors = ctypes.c_int()
Mike Frysingereb753bf2013-11-22 16:05:35 -0500309 if ErrorLimitHit(num_errors, watermark_errors):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400310 # Abandon ship! It's on fire! NOoooooooooooOOOoooooo.
Mike Frysinger7f9be142014-01-15 02:16:42 -0500311 if failed_queue:
312 failed_queue.put(sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400313 return 0
314
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400315 if sleep:
316 # Keeps us from DoS-ing the symbol server.
317 time.sleep(sleep)
318
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700319 logging.debug('uploading %s' % sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400320
321 # Ideally there'd be a tempfile.SpooledNamedTemporaryFile that we could use.
322 with tempfile.NamedTemporaryFile(prefix='upload_symbols',
323 bufsize=0) as temp_sym_file:
324 if file_limit:
325 # If the symbols size is too big, strip out the call frame info. The CFI
326 # is unnecessary for 32bit x86 targets where the frame pointer is used (as
327 # all of ours have) and it accounts for over half the size of the symbols
328 # uploaded.
329 file_size = os.path.getsize(sym_file)
330 if file_size > file_limit:
Ralph Nathan446aee92015-03-23 14:44:56 -0700331 logging.warning('stripping CFI from %s due to size %s > %s', sym_file,
332 file_size, file_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400333 temp_sym_file.writelines([x for x in open(sym_file, 'rb').readlines()
334 if not x.startswith('STACK CFI')])
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500335
336 upload_item = FakeItem(sym_file=temp_sym_file.name,
Fang Dengba680462015-08-16 20:34:11 -0700337 sym_header=symbol_element.symbol_item.sym_header)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400338
339 # Hopefully the crash server will let it through. But it probably won't.
340 # Not sure what the best answer is in this case.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500341 file_size = os.path.getsize(upload_item.sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400342 if file_size > CRASH_SERVER_FILE_LIMIT:
Prathmesh Prabhu17f07422015-07-17 11:40:40 -0700343 logging.PrintBuildbotStepWarnings()
Ralph Nathan446aee92015-03-23 14:44:56 -0700344 logging.warning('upload file %s is awfully large, risking rejection by '
345 'the symbol server (%s > %s)', sym_file, file_size,
346 CRASH_SERVER_FILE_LIMIT)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400347
348 # Upload the symbol file.
Mike Frysingereb753bf2013-11-22 16:05:35 -0500349 success = False
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400350 try:
Mike Frysinger9adcfd22013-10-24 12:01:40 -0400351 cros_build_lib.TimedCommand(
David Jamesc93e6a4d2014-01-13 11:37:36 -0800352 retry_util.RetryException,
Mike Frysinger9adcfd22013-10-24 12:01:40 -0400353 (urllib2.HTTPError, urllib2.URLError), MAX_RETRIES, SymUpload,
Don Garrettcea3eea2015-09-29 17:20:46 -0700354 upload_url, upload_item, product_name, sleep=INITIAL_RETRY_DELAY,
Mike Frysingerf50187c2014-11-27 22:01:29 -0500355 timed_log_msg=('upload of %10i bytes took %%(delta)s: %s' %
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500356 (file_size, os.path.basename(sym_file))))
Mike Frysingereb753bf2013-11-22 16:05:35 -0500357 success = True
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500358
359 if passed_queue:
Fang Dengba680462015-08-16 20:34:11 -0700360 passed_queue.put(symbol_element)
Mike Frysinger094a2172013-08-14 12:54:35 -0400361 except urllib2.HTTPError as e:
Ralph Nathan446aee92015-03-23 14:44:56 -0700362 logging.warning('could not upload: %s: HTTP %s: %s',
363 os.path.basename(sym_file), e.code, e.reason)
Mike Frysingerfd355652014-01-23 02:57:48 -0500364 except (urllib2.URLError, httplib.HTTPException, socket.error) as e:
Ralph Nathan446aee92015-03-23 14:44:56 -0700365 logging.warning('could not upload: %s: %s', os.path.basename(sym_file), e)
Mike Frysingereb753bf2013-11-22 16:05:35 -0500366 finally:
367 if success:
368 _UpdateCounter(watermark_errors, ERROR_ADJUST_PASS)
369 else:
370 _UpdateCounter(num_errors, 1)
371 _UpdateCounter(watermark_errors, ERROR_ADJUST_FAIL)
Mike Frysinger02e92402013-11-22 16:22:02 -0500372 if failed_queue:
373 failed_queue.put(sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400374
375 return num_errors.value
376
377
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500378# A dummy class that allows for stubbing in tests and SymUpload.
379FakeItem = cros_build_lib.Collection(
380 'FakeItem', sym_file=None, sym_header=None, content=lambda x: '')
381
382
Mike Frysingerc5597f22014-11-27 15:39:15 -0500383class SymbolItem(isolateserver.BufferItem):
384 """Turn a sym_file into an isolateserver.Item"""
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500385
Mike Frysingerc5597f22014-11-27 15:39:15 -0500386 ALGO = hashlib.sha1
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500387
Mike Frysingerc5597f22014-11-27 15:39:15 -0500388 def __init__(self, sym_file):
389 sym_header = cros_generate_breakpad_symbols.ReadSymsHeader(sym_file)
390 super(SymbolItem, self).__init__(str(sym_header), self.ALGO)
391 self.sym_header = sym_header
392 self.sym_file = sym_file
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500393
394
395def SymbolDeduplicatorNotify(dedupe_namespace, dedupe_queue):
396 """Send a symbol file to the swarming service
397
398 Notify the swarming service of a successful upload. If the notification fails
399 for any reason, we ignore it. We don't care as it just means we'll upload it
400 again later on, and the symbol server will handle that graciously.
401
402 This func runs in a different process from the main one, so we cannot share
403 the storage object. Instead, we create our own. This func stays alive for
404 the life of the process, so we only create one here overall.
405
406 Args:
407 dedupe_namespace: The isolateserver namespace to dedupe uploaded symbols.
Fang Dengba680462015-08-16 20:34:11 -0700408 dedupe_queue: The queue to read SymbolElements from
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500409 """
410 if dedupe_queue is None:
411 return
412
Fang Dengba680462015-08-16 20:34:11 -0700413 sym_file = ''
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500414 try:
Mike Frysinger650e6722014-04-28 18:29:15 -0400415 with timeout_util.Timeout(DEDUPE_TIMEOUT):
416 storage = isolateserver.get_storage_api(constants.ISOLATESERVER,
417 dedupe_namespace)
Fang Dengba680462015-08-16 20:34:11 -0700418 for symbol_element in iter(dedupe_queue.get, None):
419 if not symbol_element or not symbol_element.symbol_item:
420 continue
421 symbol_item = symbol_element.symbol_item
422 push_state = symbol_element.opaque_push_state
423 sym_file = symbol_item.sym_file if symbol_item.sym_file else ''
424 if push_state is not None:
425 with timeout_util.Timeout(DEDUPE_TIMEOUT):
426 logging.debug('sending %s to dedupe server', sym_file)
427 symbol_item.prepare(SymbolItem.ALGO)
428 storage.push(symbol_item, push_state, symbol_item.content())
429 logging.debug('sent %s', sym_file)
Ralph Nathan03047282015-03-23 11:09:32 -0700430 logging.info('dedupe notification finished; exiting')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500431 except Exception:
Ralph Nathan446aee92015-03-23 14:44:56 -0700432 logging.warning('posting %s to dedupe server failed',
433 os.path.basename(sym_file), exc_info=True)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500434
Mike Frysinger58312e92014-03-18 04:18:36 -0400435 # Keep draining the queue though so it doesn't fill up.
436 while dedupe_queue.get() is not None:
437 continue
438
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500439
440def SymbolDeduplicator(storage, sym_paths):
441 """Filter out symbol files that we've already uploaded
442
443 Using the swarming service, ask it to tell us which symbol files we've already
444 uploaded in previous runs and/or by other bots. If the query fails for any
445 reason, we'll just upload all symbols. This is fine as the symbol server will
446 do the right thing and this phase is purely an optimization.
447
448 This code runs in the main thread which is why we can re-use the existing
449 storage object. Saves us from having to recreate one all the time.
450
451 Args:
452 storage: An isolateserver.StorageApi object
453 sym_paths: List of symbol files to check against the dedupe server
454
455 Returns:
Fang Dengba680462015-08-16 20:34:11 -0700456 List of SymbolElement objects that have not been uploaded before
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500457 """
458 if not sym_paths:
459 return sym_paths
460
461 items = [SymbolItem(x) for x in sym_paths]
Fang Dengba680462015-08-16 20:34:11 -0700462 for item in items:
463 item.prepare(SymbolItem.ALGO)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500464 if storage:
465 try:
466 with timeout_util.Timeout(DEDUPE_TIMEOUT):
467 items = storage.contains(items)
Fang Dengba680462015-08-16 20:34:11 -0700468 return [SymbolElement(symbol_item=item, opaque_push_state=push_state)
469 for (item, push_state) in items.iteritems()]
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500470 except Exception:
Ralph Nathan446aee92015-03-23 14:44:56 -0700471 logging.warning('talking to dedupe server failed', exc_info=True)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500472
Fang Dengba680462015-08-16 20:34:11 -0700473 return [SymbolElement(symbol_item=item, opaque_push_state=None)
474 for item in items]
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500475
476
Mike Frysingerd41938e2014-02-10 06:37:55 -0500477def IsTarball(path):
478 """Guess if this is a tarball based on the filename."""
479 parts = path.split('.')
480 if len(parts) <= 1:
481 return False
482
483 if parts[-1] == 'tar':
484 return True
485
486 if parts[-2] == 'tar':
487 return parts[-1] in ('bz2', 'gz', 'xz')
488
489 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
490
491
492def SymbolFinder(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500493 """Locate symbol files in |paths|
494
495 Args:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500496 tempdir: Path to use for temporary files (caller will clean up).
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500497 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500498 Dirs are searched for files that end in ".sym". Urls are fetched and then
499 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500500
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500501 Returns:
502 Yield every viable sym file.
503 """
Gilad Arnold83233ed2015-05-08 12:12:13 -0700504 cache_dir = path_util.GetCacheDir()
Mike Frysingere847efd2015-01-08 03:57:24 -0500505 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
506 tar_cache = cache.TarballCache(common_path)
507
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500508 for p in paths:
Don Garrett25f309a2014-03-19 14:02:12 -0700509 # Pylint is confused about members of ParseResult.
Don Garrettf8bf7842014-03-20 17:03:42 -0700510
Mike Frysingerd41938e2014-02-10 06:37:55 -0500511 o = urlparse.urlparse(p)
Don Garrettf8bf7842014-03-20 17:03:42 -0700512 if o.scheme: # pylint: disable=E1101
Mike Frysingerd41938e2014-02-10 06:37:55 -0500513 # Support globs of filenames.
514 ctx = gs.GSContext()
515 for p in ctx.LS(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700516 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500517 o = urlparse.urlparse(p)
Don Garrettf8bf7842014-03-20 17:03:42 -0700518 key = ('%s%s' % (o.netloc, o.path)).split('/') # pylint: disable=E1101
Mike Frysingerd41938e2014-02-10 06:37:55 -0500519 # The common cache will not be LRU, removing the need to hold a read
520 # lock on the cached gsutil.
521 ref = tar_cache.Lookup(key)
522 try:
523 ref.SetDefault(p)
524 except cros_build_lib.RunCommandError as e:
Ralph Nathan446aee92015-03-23 14:44:56 -0700525 logging.warning('ignoring %s\n%s', p, e)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500526 continue
527 for p in SymbolFinder(tempdir, [ref.path]):
528 yield p
529
530 elif os.path.isdir(p):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500531 for root, _, files in os.walk(p):
532 for f in files:
533 if f.endswith('.sym'):
534 yield os.path.join(root, f)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500535
536 elif IsTarball(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700537 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500538 tardir = tempfile.mkdtemp(dir=tempdir)
539 cache.Untar(os.path.realpath(p), tardir)
540 for p in SymbolFinder(tardir, [tardir]):
541 yield p
542
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500543 else:
544 yield p
545
546
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500547def WriteQueueToFile(listing, queue, relpath=None):
548 """Write all the items in |queue| to the |listing|.
549
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500550 Note: The queue must have a sentinel None appended to the end.
551
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500552 Args:
553 listing: Where to write out the list of files.
554 queue: The queue of paths to drain.
555 relpath: If set, write out paths relative to this one.
556 """
557 if not listing:
Mike Frysingera0ddac62014-03-14 10:30:25 -0400558 # Still drain the queue so we make sure the producer has finished
559 # before we return. Otherwise, the queue might get destroyed too
560 # quickly which will trigger a traceback in the producer.
561 while queue.get() is not None:
562 continue
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500563 return
564
565 with cros_build_lib.Open(listing, 'wb+') as f:
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500566 while True:
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500567 path = queue.get()
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500568 if path is None:
569 return
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500570 if relpath:
571 path = os.path.relpath(path, relpath)
572 f.write('%s\n' % path)
573
574
Mike Frysinger38647542014-09-12 18:15:39 -0700575def UploadSymbols(board=None, official=False, server=None, breakpad_dir=None,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400576 file_limit=DEFAULT_FILE_LIMIT, sleep=DEFAULT_SLEEP_DELAY,
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500577 upload_limit=None, sym_paths=None, failed_list=None,
Don Garrettcea3eea2015-09-29 17:20:46 -0700578 root=None, retry=True, dedupe_namespace=None,
579 product_name='ChromeOS'):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400580 """Upload all the generated symbols for |board| to the crash server
581
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400582 You can use in a few ways:
583 * pass |board| to locate all of its symbols
584 * pass |breakpad_dir| to upload all the symbols in there
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500585 * pass |sym_paths| to upload specific symbols (or dirs of symbols)
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400586
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400587 Args:
588 board: The board whose symbols we wish to upload
589 official: Use the official symbol server rather than the staging one
Mike Frysinger38647542014-09-12 18:15:39 -0700590 server: Explicit server to post symbols to
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400591 breakpad_dir: The full path to the breakpad directory where symbols live
592 file_limit: The max file size of a symbol file before we try to strip it
593 sleep: How long to sleep in between uploads
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500594 upload_limit: If set, only upload this many symbols (meant for testing)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500595 sym_paths: Specific symbol files (or dirs of sym files) to upload,
596 otherwise search |breakpad_dir|
Mike Frysinger7f9be142014-01-15 02:16:42 -0500597 failed_list: Write the names of all sym files we did not upload; can be a
598 filename or file-like object.
Mike Frysinger118d2502013-08-19 03:36:56 -0400599 root: The tree to prefix to |breakpad_dir| (if |breakpad_dir| is not set)
Mike Frysinger02e92402013-11-22 16:22:02 -0500600 retry: Whether we should retry failures.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500601 dedupe_namespace: The isolateserver namespace to dedupe uploaded symbols.
Don Garrettcea3eea2015-09-29 17:20:46 -0700602 product_name: A string for stats purposes. Usually 'ChromeOS' or 'Android'.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500603
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400604 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400605 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400606 """
Mike Frysinger38647542014-09-12 18:15:39 -0700607 if server is None:
608 if official:
609 upload_url = OFFICIAL_UPLOAD_URL
610 else:
Ralph Nathan446aee92015-03-23 14:44:56 -0700611 logging.warning('unofficial builds upload to the staging server')
Mike Frysinger38647542014-09-12 18:15:39 -0700612 upload_url = STAGING_UPLOAD_URL
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400613 else:
Mike Frysinger38647542014-09-12 18:15:39 -0700614 upload_url = server
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400615
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500616 if sym_paths:
Ralph Nathan03047282015-03-23 11:09:32 -0700617 logging.info('uploading specified symbols to %s', upload_url)
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400618 else:
619 if breakpad_dir is None:
Mike Frysingerc5597f22014-11-27 15:39:15 -0500620 if root is None:
621 raise ValueError('breakpad_dir requires root to be set')
Mike Frysinger118d2502013-08-19 03:36:56 -0400622 breakpad_dir = os.path.join(
623 root,
624 cros_generate_breakpad_symbols.FindBreakpadDir(board).lstrip('/'))
Ralph Nathan03047282015-03-23 11:09:32 -0700625 logging.info('uploading all symbols to %s from %s', upload_url,
626 breakpad_dir)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500627 sym_paths = [breakpad_dir]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400628
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500629 # We use storage_query to ask the server about existing symbols. The
630 # storage_notify_proc process is used to post updates to the server. We
631 # cannot safely share the storage object between threads/processes, but
632 # we also want to minimize creating new ones as each object has to init
633 # new state (like server connections).
Mike Frysinger650e6722014-04-28 18:29:15 -0400634 storage_query = None
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500635 if dedupe_namespace:
636 dedupe_limit = DEDUPE_LIMIT
637 dedupe_queue = multiprocessing.Queue()
Mike Frysinger650e6722014-04-28 18:29:15 -0400638 try:
639 with timeout_util.Timeout(DEDUPE_TIMEOUT):
640 storage_query = isolateserver.get_storage_api(constants.ISOLATESERVER,
641 dedupe_namespace)
642 except Exception:
Ralph Nathan446aee92015-03-23 14:44:56 -0700643 logging.warning('initializing dedupe server connection failed',
644 exc_info=True)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500645 else:
646 dedupe_limit = 1
Mike Frysinger650e6722014-04-28 18:29:15 -0400647 dedupe_queue = None
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500648 # Can't use parallel.BackgroundTaskRunner because that'll create multiple
649 # processes and we want only one the whole time (see comment above).
650 storage_notify_proc = multiprocessing.Process(
651 target=SymbolDeduplicatorNotify, args=(dedupe_namespace, dedupe_queue))
652
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400653 bg_errors = multiprocessing.Value('i')
Mike Frysingereb753bf2013-11-22 16:05:35 -0500654 watermark_errors = multiprocessing.Value('f')
Mike Frysinger02e92402013-11-22 16:22:02 -0500655 failed_queue = multiprocessing.Queue()
656 uploader = functools.partial(
Don Garrettcea3eea2015-09-29 17:20:46 -0700657 UploadSymbol, upload_url, product_name=product_name,
658 file_limit=file_limit, sleep=sleep, num_errors=bg_errors,
659 watermark_errors=watermark_errors, failed_queue=failed_queue,
660 passed_queue=dedupe_queue)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400661
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500662 start_time = datetime.datetime.now()
663 Counters = cros_build_lib.Collection(
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500664 'Counters', upload_limit=upload_limit, uploaded_count=0, deduped_count=0)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500665 counters = Counters()
666
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500667 def _Upload(queue, counters, files):
668 if not files:
669 return
670
671 missing_count = 0
672 for item in SymbolDeduplicator(storage_query, files):
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500673 missing_count += 1
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400674
675 if counters.upload_limit == 0:
676 continue
677
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500678 queue.put((item,))
679 counters.uploaded_count += 1
680 if counters.upload_limit is not None:
681 counters.upload_limit -= 1
682
683 counters.deduped_count += (len(files) - missing_count)
684
Mike Frysinger13870082014-03-14 10:41:20 -0400685 try:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500686 storage_notify_proc.start()
Mike Frysinger02e92402013-11-22 16:22:02 -0500687
Mike Frysinger13870082014-03-14 10:41:20 -0400688 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
689 # For the first run, we collect the symbols that failed. If the
690 # overall failure rate was low, we'll retry them on the second run.
691 for retry in (retry, False):
692 # We need to limit ourselves to one upload at a time to avoid the server
693 # kicking in DoS protection. See these bugs for more details:
694 # http://crbug.com/209442
695 # http://crbug.com/212496
696 with parallel.BackgroundTaskRunner(uploader, processes=1) as queue:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500697 dedupe_list = []
Mike Frysinger13870082014-03-14 10:41:20 -0400698 for sym_file in SymbolFinder(tempdir, sym_paths):
699 dedupe_list.append(sym_file)
700 dedupe_len = len(dedupe_list)
701 if dedupe_len < dedupe_limit:
702 if (counters.upload_limit is None or
703 dedupe_len < counters.upload_limit):
704 continue
Mike Frysinger02e92402013-11-22 16:22:02 -0500705
Mike Frysinger1010a892014-03-14 11:24:17 -0400706 # We check the counter before _Upload so that we don't keep talking
707 # to the dedupe server. Otherwise, we end up sending one symbol at
708 # a time to it and that slows things down a lot.
709 if counters.upload_limit == 0:
710 break
711
Mike Frysinger13870082014-03-14 10:41:20 -0400712 _Upload(queue, counters, dedupe_list)
713 dedupe_list = []
714 _Upload(queue, counters, dedupe_list)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500715
Mike Frysinger13870082014-03-14 10:41:20 -0400716 # See if we need to retry, and if we haven't failed too many times yet.
717 if not retry or ErrorLimitHit(bg_errors, watermark_errors):
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500718 break
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500719
Mike Frysinger13870082014-03-14 10:41:20 -0400720 sym_paths = []
721 failed_queue.put(None)
722 while True:
723 sym_path = failed_queue.get()
724 if sym_path is None:
725 break
726 sym_paths.append(sym_path)
Mike Frysinger02e92402013-11-22 16:22:02 -0500727
Mike Frysinger13870082014-03-14 10:41:20 -0400728 if sym_paths:
Ralph Nathan446aee92015-03-23 14:44:56 -0700729 logging.warning('retrying %i symbols', len(sym_paths))
Mike Frysinger13870082014-03-14 10:41:20 -0400730 if counters.upload_limit is not None:
731 counters.upload_limit += len(sym_paths)
732 # Decrement the error count in case we recover in the second pass.
733 assert bg_errors.value >= len(sym_paths), \
734 'more failed files than errors?'
735 bg_errors.value -= len(sym_paths)
736 else:
737 # No failed symbols, so just return now.
738 break
Mike Frysinger7f9be142014-01-15 02:16:42 -0500739
Mike Frysinger13870082014-03-14 10:41:20 -0400740 # If the user has requested it, save all the symbol files that we failed to
741 # upload to a listing file. This should help with recovery efforts later.
742 failed_queue.put(None)
743 WriteQueueToFile(failed_list, failed_queue, breakpad_dir)
744
745 finally:
Ralph Nathan03047282015-03-23 11:09:32 -0700746 logging.info('finished uploading; joining background process')
Mike Frysinger13870082014-03-14 10:41:20 -0400747 if dedupe_queue:
748 dedupe_queue.put(None)
Mike Frysinger4dd462e2014-04-30 16:21:51 -0400749
750 # The notification might be slow going, so give it some time to finish.
751 # We have to poll here as the process monitor is watching for output and
752 # will kill us if we go silent for too long.
753 wait_minutes = DEDUPE_NOTIFY_TIMEOUT
754 while storage_notify_proc.is_alive() and wait_minutes > 0:
Aviv Keshetd1f04632014-05-09 11:33:46 -0700755 if dedupe_queue:
756 qsize = str(dedupe_queue.qsize())
757 else:
758 qsize = '[None]'
Ralph Nathan03047282015-03-23 11:09:32 -0700759 logging.info('waiting up to %i minutes for ~%s notifications',
760 wait_minutes, qsize)
Mike Frysinger4dd462e2014-04-30 16:21:51 -0400761 storage_notify_proc.join(60)
762 wait_minutes -= 1
763
764 # The process is taking too long, so kill it and complain.
765 if storage_notify_proc.is_alive():
Ralph Nathan446aee92015-03-23 14:44:56 -0700766 logging.warning('notification process took too long')
Prathmesh Prabhu17f07422015-07-17 11:40:40 -0700767 logging.PrintBuildbotStepWarnings()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500768
Mike Frysinger66e51e92014-05-03 16:52:00 -0400769 # Kill it gracefully first (traceback) before tacking it down harder.
770 pid = storage_notify_proc.pid
771 for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGKILL):
Ralph Nathan446aee92015-03-23 14:44:56 -0700772 logging.warning('sending %s to %i', signals.StrSignal(sig), pid)
Mike Frysingerdad2a032014-05-11 23:05:11 -0400773 # The process might have exited between the last check and the
774 # actual kill below, so ignore ESRCH errors.
775 try:
776 os.kill(pid, sig)
777 except OSError as e:
778 if e.errno == errno.ESRCH:
779 break
780 else:
781 raise
Mike Frysinger66e51e92014-05-03 16:52:00 -0400782 time.sleep(5)
Mike Frysingerdad2a032014-05-11 23:05:11 -0400783 if not storage_notify_proc.is_alive():
Mike Frysinger66e51e92014-05-03 16:52:00 -0400784 break
785
786 # Drain the queue so we don't hang when we finish.
787 try:
Ralph Nathan446aee92015-03-23 14:44:56 -0700788 logging.warning('draining the notify queue manually')
Mike Frysinger4f5ea832014-05-12 00:54:28 -0400789 with timeout_util.Timeout(60):
790 try:
791 while dedupe_queue.get_nowait():
792 pass
793 except Queue.Empty:
794 pass
795 except timeout_util.TimeoutError:
Ralph Nathan446aee92015-03-23 14:44:56 -0700796 logging.warning('draining the notify queue failed; trashing it')
Mike Frysinger4f5ea832014-05-12 00:54:28 -0400797 dedupe_queue.cancel_join_thread()
Mike Frysinger66e51e92014-05-03 16:52:00 -0400798
Ralph Nathan03047282015-03-23 11:09:32 -0700799 logging.info('uploaded %i symbols (%i were deduped) which took: %s',
800 counters.uploaded_count, counters.deduped_count,
801 datetime.datetime.now() - start_time)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500802
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500803 return bg_errors.value
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400804
805
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400806def main(argv):
807 parser = commandline.ArgumentParser(description=__doc__)
808
Mike Frysingerd41938e2014-02-10 06:37:55 -0500809 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
810 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400811 parser.add_argument('--board', default=None,
Don Garrett747cc4b2015-10-07 14:48:48 -0700812 help='Used to find default breakpad_root.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400813 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500814 help='full path to the breakpad symbol directory')
815 parser.add_argument('--root', type='path', default=None,
816 help='full path to the chroot dir')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400817 parser.add_argument('--official_build', action='store_true', default=False,
818 help='point to official symbol server')
Mike Frysinger38647542014-09-12 18:15:39 -0700819 parser.add_argument('--server', type=str, default=None,
820 help='URI for custom symbol server')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400821 parser.add_argument('--regenerate', action='store_true', default=False,
822 help='regenerate all symbols')
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500823 parser.add_argument('--upload-limit', type=int, default=None,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400824 help='only upload # number of symbols')
825 parser.add_argument('--strip_cfi', type=int,
826 default=CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024),
827 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500828 parser.add_argument('--failed-list', type='path',
829 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500830 parser.add_argument('--dedupe', action='store_true', default=False,
831 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400832 parser.add_argument('--testing', action='store_true', default=False,
833 help='run in testing mode')
834 parser.add_argument('--yes', action='store_true', default=False,
835 help='answer yes to all prompts')
Don Garrett747cc4b2015-10-07 14:48:48 -0700836 parser.add_argument('--product_name', type=str, default='ChromeOS',
837 help='Produce Name for breakpad stats.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400838
839 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500840 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400841
Don Garrett747cc4b2015-10-07 14:48:48 -0700842 if opts.sym_paths or opts.breakpad_root:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400843 if opts.regenerate:
Don Garrett747cc4b2015-10-07 14:48:48 -0700844 cros_build_lib.Die('--regenerate may not be used with specific files, '
845 'or breakpad_root')
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400846 else:
847 if opts.board is None:
848 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400849
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400850 if opts.testing:
851 # TODO(build): Kill off --testing mode once unittests are up-to-snuff.
Ralph Nathan03047282015-03-23 11:09:32 -0700852 logging.info('running in testing mode')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400853 # pylint: disable=W0601,W0603
854 global INITIAL_RETRY_DELAY, SymUpload, DEFAULT_SLEEP_DELAY
855 INITIAL_RETRY_DELAY = DEFAULT_SLEEP_DELAY = 0
856 SymUpload = TestingSymUpload
857
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500858 dedupe_namespace = None
859 if opts.dedupe:
860 if opts.official_build and not opts.testing:
Don Garrett747cc4b2015-10-07 14:48:48 -0700861 dedupe_namespace = OFFICIAL_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500862 else:
Don Garrett747cc4b2015-10-07 14:48:48 -0700863 dedupe_namespace = STAGING_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500864
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400865 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500866 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400867 Uploading symbols for an entire Chromium OS build is really only
868 necessary for release builds and in a few cases for developers
869 to debug problems. It will take considerable time to run. For
870 developer debugging purposes, consider instead passing specific
871 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500872 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400873 if not cros_build_lib.BooleanPrompt(
874 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500875 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400876 cros_build_lib.Die('better safe than sorry')
877
878 ret = 0
879 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400880 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
881 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400882
883 ret += UploadSymbols(opts.board, official=opts.official_build,
Mike Frysinger38647542014-09-12 18:15:39 -0700884 server=opts.server, breakpad_dir=opts.breakpad_root,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400885 file_limit=opts.strip_cfi, sleep=DEFAULT_SLEEP_DELAY,
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500886 upload_limit=opts.upload_limit, sym_paths=opts.sym_paths,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500887 failed_list=opts.failed_list, root=opts.root,
Don Garrett747cc4b2015-10-07 14:48:48 -0700888 dedupe_namespace=dedupe_namespace,
889 product_name=opts.product_name)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400890 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700891 logging.error('encountered %i problem(s)', ret)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400892 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
893 # return 0 in case we are a multiple of the mask.
894 ret = 1
895
896 return ret
Mike Frysinger094a2172013-08-14 12:54:35 -0400897
898
899# We need this to run once per process. Do it at module import time as that
900# will let us avoid doing it inline at function call time (see SymUpload) as
901# that func might be called by the multiprocessing module which means we'll
902# do the opener logic multiple times overall. Plus, if you're importing this
903# module, it's a pretty good chance that you're going to need this.
904poster.streaminghttp.register_openers()