blob: f0de2098c84869e7420d81aa10e64fc58cb271c7 [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
Mike Frysinger0c0efa22014-02-09 23:32:23 -050014import hashlib
Mike Frysingera4fa1e82014-01-15 01:45:56 -050015import httplib
Don Garretta28be6d2016-06-16 18:09:35 -070016import itertools
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040017import os
Mike Frysingerfd355652014-01-23 02:57:48 -050018import socket
Mike Frysingerc5597f22014-11-27 15:39:15 -050019import sys
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040020import textwrap
21import tempfile
22import time
Mike Frysinger094a2172013-08-14 12:54:35 -040023import urllib2
Mike Frysingerd41938e2014-02-10 06:37:55 -050024import urlparse
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040025
Aviv Keshetb7519e12016-10-04 00:50:00 -070026from chromite.lib import constants
Mike Frysingerbbd1f112016-09-08 18:25:11 -040027
28# The isolateserver includes a bunch of third_party python packages that clash
29# with chromite's bundled third_party python packages (like oauth2client).
30# Since upload_symbols is not imported in to other parts of chromite, and there
31# are no deps in third_party we care about, purge the chromite copy. This way
32# we can use isolateserver for deduping.
33# TODO: If we ever sort out third_party/ handling and make it per-script opt-in,
34# we can purge this logic.
35third_party = os.path.join(constants.CHROMITE_DIR, 'third_party')
36while True:
37 try:
38 sys.path.remove(third_party)
39 except ValueError:
40 break
41sys.path.insert(0, os.path.join(third_party, 'swarming.client'))
42sys.path.insert(0, os.path.join(third_party, 'upload_symbols'))
43del third_party
44
45# Has to be after sys.path manipulation above.
46# And our sys.path muckery confuses pylint.
47import poster # pylint: disable=import-error
48
Mike Frysingerd41938e2014-02-10 06:37:55 -050049from chromite.lib import cache
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040050from chromite.lib import commandline
51from chromite.lib import cros_build_lib
Ralph Nathan5a582ff2015-03-20 18:18:30 -070052from chromite.lib import cros_logging as logging
Mike Frysingerd41938e2014-02-10 06:37:55 -050053from chromite.lib import gs
54from chromite.lib import osutils
Gilad Arnold83233ed2015-05-08 12:12:13 -070055from chromite.lib import path_util
Don Garrette1f47e92016-10-13 16:04:56 -070056from chromite.lib import retry_stats
Mike Frysinger0c0efa22014-02-09 23:32:23 -050057from chromite.lib import timeout_util
Mike Frysinger69cb41d2013-08-11 20:08:19 -040058from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040059
Mike Frysinger0c0efa22014-02-09 23:32:23 -050060# Needs to be after chromite imports.
Mike Frysingerc5597f22014-11-27 15:39:15 -050061# We don't want to import the general keyring module as that will implicitly
62# try to import & connect to a dbus server. That's a waste of time.
63sys.modules['keyring'] = None
Mike Frysingerbbd1f112016-09-08 18:25:11 -040064# And our sys.path muckery confuses pylint.
65import isolateserver # pylint: disable=import-error
Mike Frysinger0c0efa22014-02-09 23:32:23 -050066
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040067
Don Garretta28be6d2016-06-16 18:09:35 -070068# We need this to run once per process. Do it at module import time as that
69# will let us avoid doing it inline at function call time (see UploadSymbolFile)
70# as that func might be called by the multiprocessing module which means we'll
71# do the opener logic multiple times overall. Plus, if you're importing this
72# module, it's a pretty good chance that you're going to need this.
73poster.streaminghttp.register_openers()
74
75
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040076# URLs used for uploading symbols.
77OFFICIAL_UPLOAD_URL = 'http://clients2.google.com/cr/symbol'
78STAGING_UPLOAD_URL = 'http://clients2.google.com/cr/staging_symbol'
79
80
81# The crash server rejects files that are this big.
Ryo Hashimotof66d8ca2016-09-07 15:45:13 +090082CRASH_SERVER_FILE_LIMIT = 500 * 1024 * 1024
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040083# Give ourselves a little breathing room from what the server expects.
84DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
85
86
Mike Frysinger0c0efa22014-02-09 23:32:23 -050087# The batch limit when talking to the dedup server. We avoid sending one at a
88# time as the round trip overhead will dominate. Conversely, we avoid sending
89# all at once so we can start uploading symbols asap -- the symbol server is a
90# bit slow and will take longer than anything else.
Mike Frysinger0c0efa22014-02-09 23:32:23 -050091DEDUPE_LIMIT = 100
92
93# How long to wait for the server to respond with the results. Note that the
94# larger the limit above, the larger this will need to be. So we give it ~1
95# second per item max.
96DEDUPE_TIMEOUT = DEDUPE_LIMIT
97
Don Garretta28be6d2016-06-16 18:09:35 -070098# How long to wait for the notification to finish (in seconds).
99DEDUPE_NOTIFY_TIMEOUT = 240
Mike Frysinger4dd462e2014-04-30 16:21:51 -0400100
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500101# The unique namespace in the dedupe server that only we use. Helps avoid
102# collisions with all the hashed values and unrelated content.
Don Garrett747cc4b2015-10-07 14:48:48 -0700103OFFICIAL_DEDUPE_NAMESPACE_TMPL = '%s-upload-symbols'
104STAGING_DEDUPE_NAMESPACE_TMPL = '%s-staging' % OFFICIAL_DEDUPE_NAMESPACE_TMPL
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500105
106
Mike Frysinger71046662014-09-12 18:15:15 -0700107# The minimum average rate (in bytes per second) that we expect to maintain
108# when uploading symbols. This has to allow for symbols that are up to
109# CRASH_SERVER_FILE_LIMIT in size.
110UPLOAD_MIN_RATE = CRASH_SERVER_FILE_LIMIT / (30 * 60)
111
112# The lowest timeout (in seconds) we'll allow. If the server is overloaded,
113# then there might be a delay in setting up the connection, not just with the
114# transfer. So even a small file might need a larger value.
115UPLOAD_MIN_TIMEOUT = 2 * 60
Mike Frysingercd78a082013-06-26 17:13:04 -0400116
117
Don Garrett7a793092016-07-06 16:50:27 -0700118# Sleep for 500ms in between uploads to avoid DoS'ing symbol server.
119SLEEP_DELAY = 0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400120
121
122# Number of seconds to wait before retrying an upload. The delay will double
123# for each subsequent retry of the same symbol file.
124INITIAL_RETRY_DELAY = 1
125
126# Allow up to 7 attempts to upload a symbol file (total delay may be
127# 1+2+4+8+16+32=63 seconds).
128MAX_RETRIES = 6
129
Mike Frysingereb753bf2013-11-22 16:05:35 -0500130# Number of total errors, before uploads are no longer attempted.
131# This is used to avoid lots of errors causing unreasonable delays.
Mike Frysingereb753bf2013-11-22 16:05:35 -0500132MAX_TOTAL_ERRORS_FOR_RETRY = 30
133
Don Garrette1f47e92016-10-13 16:04:56 -0700134# Category to use for collection upload retry stats.
135UPLOAD_STATS = 'UPLOAD'
136
Don Garretta28be6d2016-06-16 18:09:35 -0700137
Don Garretta28be6d2016-06-16 18:09:35 -0700138def BatchGenerator(iterator, batch_size):
139 """Given an iterator, break into lists of size batch_size.
Fang Dengba680462015-08-16 20:34:11 -0700140
Don Garretta28be6d2016-06-16 18:09:35 -0700141 The result is a generator, that will only read in as many inputs as needed for
142 the current batch. The final result can be smaller than batch_size.
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700143 """
Don Garretta28be6d2016-06-16 18:09:35 -0700144 batch = []
145 for i in iterator:
146 batch.append(i)
147 if len(batch) >= batch_size:
148 yield batch
149 batch = []
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700150
Don Garretta28be6d2016-06-16 18:09:35 -0700151 if batch:
152 # if there was anything left in the final batch, yield it.
153 yield batch
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500154
155
Mike Frysingerd41938e2014-02-10 06:37:55 -0500156def IsTarball(path):
157 """Guess if this is a tarball based on the filename."""
158 parts = path.split('.')
159 if len(parts) <= 1:
160 return False
161
162 if parts[-1] == 'tar':
163 return True
164
165 if parts[-2] == 'tar':
166 return parts[-1] in ('bz2', 'gz', 'xz')
167
168 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
169
170
Don Garretta28be6d2016-06-16 18:09:35 -0700171class SymbolFile(object):
172 """This class represents the state of a symbol file during processing.
173
174 Properties:
175 display_path: Name of symbol file that should be consistent between builds.
176 file_name: Transient path of the symbol file.
177 header: ReadSymsHeader output. Dict with assorted meta-data.
178 status: INITIAL, DUPLICATE, or UPLOADED based on status of processing.
179 dedupe_item: None or instance of DedupeItem for this symbol file.
180 dedupe_push_state: Opaque value to return to dedupe code for file.
181 display_name: Read only friendly (short) file name for logging.
182 file_size: Read only size of the symbol file.
183 """
184 INITIAL = 'initial'
185 DUPLICATE = 'duplicate'
186 UPLOADED = 'uploaded'
Don Garrettdeb2e032016-07-06 16:44:14 -0700187 ERROR = 'error'
Don Garretta28be6d2016-06-16 18:09:35 -0700188
189 def __init__(self, display_path, file_name):
190 """An instance of this class represents a symbol file over time.
191
192 Args:
193 display_path: A unique/persistent between builds name to present to the
194 crash server. It is the file name, relative to where it
195 came from (tarball, breakpad dir, etc).
196 file_name: A the current location of the symbol file.
197 """
198 self.display_path = display_path
199 self.file_name = file_name
200 self.header = cros_generate_breakpad_symbols.ReadSymsHeader(file_name)
201 self.status = SymbolFile.INITIAL
202 self.dedupe_item = None
203 self.dedupe_push_state = None
204
205 @property
206 def display_name(self):
207 return os.path.basename(self.display_path)
208
209 def FileSize(self):
210 return os.path.getsize(self.file_name)
211
212
213class DedupeItem(isolateserver.BufferItem):
214 """Turn a SymbolFile into an isolateserver.Item"""
215
216 ALGO = hashlib.sha1
217
218 def __init__(self, symbol):
Mike Frysingerbbd1f112016-09-08 18:25:11 -0400219 isolateserver.BufferItem.__init__(self, str(symbol.header), self.ALGO)
Don Garretta28be6d2016-06-16 18:09:35 -0700220 self.symbol = symbol
221
222
223def FindSymbolFiles(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500224 """Locate symbol files in |paths|
225
Don Garretta28be6d2016-06-16 18:09:35 -0700226 This returns SymbolFile objects that contain file references which are valid
227 after this exits. Those files may exist externally, or be created in the
228 tempdir (say, when expanding tarballs). The caller must not consider
229 SymbolFile's valid after tempdir is cleaned up.
230
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500231 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700232 tempdir: Path to use for temporary files.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500233 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500234 Dirs are searched for files that end in ".sym". Urls are fetched and then
235 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500236
Don Garretta28be6d2016-06-16 18:09:35 -0700237 Yields:
238 A SymbolFile for every symbol file found in paths.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500239 """
Gilad Arnold83233ed2015-05-08 12:12:13 -0700240 cache_dir = path_util.GetCacheDir()
Mike Frysingere847efd2015-01-08 03:57:24 -0500241 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
242 tar_cache = cache.TarballCache(common_path)
243
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500244 for p in paths:
Don Garrett25f309a2014-03-19 14:02:12 -0700245 # Pylint is confused about members of ParseResult.
Don Garrettf8bf7842014-03-20 17:03:42 -0700246
Mike Frysingerd41938e2014-02-10 06:37:55 -0500247 o = urlparse.urlparse(p)
Don Garrettf8bf7842014-03-20 17:03:42 -0700248 if o.scheme: # pylint: disable=E1101
Mike Frysingerd41938e2014-02-10 06:37:55 -0500249 # Support globs of filenames.
250 ctx = gs.GSContext()
251 for p in ctx.LS(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700252 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500253 o = urlparse.urlparse(p)
Don Garrettf8bf7842014-03-20 17:03:42 -0700254 key = ('%s%s' % (o.netloc, o.path)).split('/') # pylint: disable=E1101
Mike Frysingerd41938e2014-02-10 06:37:55 -0500255 # The common cache will not be LRU, removing the need to hold a read
256 # lock on the cached gsutil.
257 ref = tar_cache.Lookup(key)
258 try:
259 ref.SetDefault(p)
260 except cros_build_lib.RunCommandError as e:
Ralph Nathan446aee92015-03-23 14:44:56 -0700261 logging.warning('ignoring %s\n%s', p, e)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500262 continue
Don Garretta28be6d2016-06-16 18:09:35 -0700263 for p in FindSymbolFiles(tempdir, [ref.path]):
Mike Frysingerd41938e2014-02-10 06:37:55 -0500264 yield p
265
266 elif os.path.isdir(p):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500267 for root, _, files in os.walk(p):
268 for f in files:
269 if f.endswith('.sym'):
Don Garretta28be6d2016-06-16 18:09:35 -0700270 # If p is '/tmp/foo' and filename is '/tmp/foo/bar/bar.sym',
271 # display_path = 'bar/bar.sym'
272 filename = os.path.join(root, f)
273 yield SymbolFile(display_path=filename[len(p):].lstrip('/'),
274 file_name=filename)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500275
276 elif IsTarball(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700277 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500278 tardir = tempfile.mkdtemp(dir=tempdir)
279 cache.Untar(os.path.realpath(p), tardir)
Don Garretta28be6d2016-06-16 18:09:35 -0700280 for p in FindSymbolFiles(tardir, [tardir]):
Mike Frysingerd41938e2014-02-10 06:37:55 -0500281 yield p
282
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500283 else:
Don Garretta28be6d2016-06-16 18:09:35 -0700284 yield SymbolFile(display_path=p, file_name=p)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500285
286
Don Garretta28be6d2016-06-16 18:09:35 -0700287def AdjustSymbolFileSize(symbol, tempdir, file_limit):
288 """Examine symbols files for size problems, and reduce if needed.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500289
Don Garretta28be6d2016-06-16 18:09:35 -0700290 If the symbols size is too big, strip out the call frame info. The CFI
291 is unnecessary for 32bit x86 targets where the frame pointer is used (as
292 all of ours have) and it accounts for over half the size of the symbols
293 uploaded.
294
295 Stripped files will be created inside tempdir, and will be the callers
296 responsibility to clean up.
297
298 We also warn, if a symbols file is still too large after stripping.
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500299
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500300 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700301 symbol: SymbolFile instance to be examined and modified as needed..
302 tempdir: A temporary directory we can create files in that the caller will
303 clean up.
304 file_limit: We only strip files which are larger than this limit.
305
306 Returns:
307 SymbolFile instance (original or modified as needed)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500308 """
Don Garretta28be6d2016-06-16 18:09:35 -0700309 file_size = symbol.FileSize()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500310
Don Garretta28be6d2016-06-16 18:09:35 -0700311 if file_limit and symbol.FileSize() > file_limit:
312 with tempfile.NamedTemporaryFile(
313 prefix='upload_symbols', bufsize=0,
314 dir=tempdir, delete=False) as temp_sym_file:
315
316 temp_sym_file.writelines(
317 [x for x in open(symbol.file_name, 'rb').readlines()
318 if not x.startswith('STACK CFI')]
319 )
320
321 original_file_size = file_size
322 symbol.file_name = temp_sym_file.name
323 file_size = symbol.FileSize()
324
325 logging.warning('stripped CFI for %s reducing size %s > %s',
326 symbol.display_name, original_file_size, file_size)
327
328 # Hopefully the crash server will let it through. But it probably won't.
329 # Not sure what the best answer is in this case.
330 if file_size >= CRASH_SERVER_FILE_LIMIT:
331 logging.PrintBuildbotStepWarnings()
332 logging.warning('upload file %s is awfully large, risking rejection by '
333 'the symbol server (%s > %s)', symbol.display_path,
334 file_size, CRASH_SERVER_FILE_LIMIT)
335
336 return symbol
337
338def OpenDeduplicateConnection(dedupe_namespace):
339 """Open a connection to the isolate server for Dedupe use.
340
341 Args:
342 dedupe_namespace: String id for the comparison namespace.
343
344 Returns:
345 Connection proxy, or None on failure.
346 """
347 try:
348 with timeout_util.Timeout(DEDUPE_TIMEOUT):
349 return isolateserver.get_storage_api(constants.ISOLATESERVER,
350 dedupe_namespace)
351 except Exception:
352 logging.warning('initializing isolate server connection failed',
353 exc_info=True)
354 return None
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500355
356
Don Garretta28be6d2016-06-16 18:09:35 -0700357def FindDuplicates(symbols, dedupe_namespace):
358 """Mark symbol files we've already uploaded as duplicates.
359
360 Using the swarming service, ask it to tell us which symbol files we've already
361 uploaded in previous runs and/or by other bots. If the query fails for any
362 reason, we'll just upload all symbols. This is fine as the symbol server will
363 do the right thing and this phase is purely an optimization.
364
365 Args:
366 symbols: An iterable of SymbolFiles to be uploaded.
367 dedupe_namespace: String id for the comparison namespace.
368
369 Yields:
370 All SymbolFiles from symbols, but duplicates have status updated to
371 DUPLICATE.
372 """
373 storage_query = OpenDeduplicateConnection(dedupe_namespace)
374
375 # We query isolate in batches, to reduce the number of network queries.
376 for batch in BatchGenerator(symbols, DEDUPE_LIMIT):
377 query_results = None
378
379 if storage_query:
380 # Convert SymbolFiles into DedupeItems.
381 items = [DedupeItem(x) for x in batch]
382 for item in items:
383 item.prepare(DedupeItem.ALGO)
384
385 # Look for duplicates.
386 try:
387 with timeout_util.Timeout(DEDUPE_TIMEOUT):
388 query_results = storage_query.contains(items)
389 except Exception:
390 logging.warning('talking to dedupe server failed', exc_info=True)
391 storage_query = None
392
393 if query_results is not None:
394 for b in batch:
395 b.status = SymbolFile.DUPLICATE
396
397 # Only the non-duplicates appear in the query_results.
398 for item, push_state in query_results.iteritems():
399 # Remember the dedupe state, so we can mark the symbol as uploaded
400 # later on.
401 item.symbol.status = SymbolFile.INITIAL
402 item.symbol.dedupe_item = item
403 item.symbol.dedupe_push_state = push_state
404
405 # Yield all symbols we haven't shown to be duplicates.
406 for b in batch:
407 if b.status == SymbolFile.DUPLICATE:
408 logging.debug('Found duplicate: %s', b.display_name)
409 yield b
410
411
412def PostForDeduplication(symbols, dedupe_namespace):
413 """Send a symbol file to the swarming service
414
415 Notify the isolate service of a successful upload. If the notification fails
416 for any reason, we ignore it. We don't care as it just means we'll upload it
417 again later on, and the symbol server will handle that graciously.
418
419 Args:
420 symbols: An iterable of SymbolFiles to be uploaded.
421 dedupe_namespace: String id for the comparison namespace.
422
423 Yields:
424 Each symbol from symbols, unmodified.
425 """
426 storage_query = OpenDeduplicateConnection(dedupe_namespace)
427
428 for s in symbols:
429 # If we can talk to isolate, and we uploaded this symbol, and we
430 # queried for it's presence before, upload to isolate now.
431
432 if storage_query and s.status == SymbolFile.UPLOADED and s.dedupe_item:
433 s.dedupe_item.prepare(DedupeItem.ALGO)
434 try:
435 with timeout_util.Timeout(DEDUPE_NOTIFY_TIMEOUT):
436 storage_query.push(s.dedupe_item, s.dedupe_push_state,
437 s.dedupe_item.content())
438 logging.info('sent %s', s.display_name)
439 except Exception:
440 logging.warning('posting %s to dedupe server failed',
441 os.path.basename(s.display_path), exc_info=True)
442 storage_query = None
443
444 yield s
445
446
447def GetUploadTimeout(symbol):
448 """How long to wait for a specific file to upload to the crash server.
449
450 This is a function largely to make unittesting easier.
451
452 Args:
453 symbol: A SymbolFile instance.
454
455 Returns:
456 Timeout length (in seconds)
457 """
458 # Scale the timeout based on the filesize.
459 return max(symbol.FileSize() / UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
460
461
462def UploadSymbolFile(upload_url, symbol, product_name):
463 """Upload a symbol file to the crash server.
464
465 The upload is a multipart/form-data POST with the following parameters:
466 code_file: the basename of the module, e.g. "app"
467 code_identifier: the module file's identifier
468 debug_file: the basename of the debugging file, e.g. "app"
469 debug_identifier: the debug file's identifier, usually consisting of
470 the guid and age embedded in the pdb, e.g.
471 "11111111BBBB3333DDDD555555555555F"
472 version: the file version of the module, e.g. "1.2.3.4"
473 product: HTTP-friendly product name
474 os: the operating system that the module was built for
475 cpu: the CPU that the module was built for
476 symbol_file: the contents of the breakpad-format symbol file
477
478 Args:
479 upload_url: The crash URL to POST the |sym_file| to
480 symbol: A SymbolFile instance.
481 product_name: A string for stats purposes. Usually 'ChromeOS' or 'Android'.
482 """
483 fields = (
484 ('code_file', symbol.header.name),
485 ('debug_file', symbol.header.name),
486 ('debug_identifier', symbol.header.id.replace('-', '')),
487 # The product/version fields are used by the server only for statistic
488 # purposes. They do not impact symbolization, so they're safe to set
489 # to any value all the time.
490 # In this case, we use it to help see the load our build system is
491 # placing on the server.
492 # Not sure what to set for the version. Maybe the git sha1 of this file.
493 # Note: the server restricts this to 30 chars.
494 #('version', None),
495 ('product', product_name),
496 ('os', symbol.header.os),
497 ('cpu', symbol.header.cpu),
498 poster.encode.MultipartParam.from_file('symbol_file', symbol.file_name),
499 )
500
501 data, headers = poster.encode.multipart_encode(fields)
502 request = urllib2.Request(upload_url, data, headers)
503 request.add_header('User-agent', 'chromite.upload_symbols')
504 urllib2.urlopen(request, timeout=GetUploadTimeout(symbol))
505
506
Don Garrettdeb2e032016-07-06 16:44:14 -0700507def PerformSymbolsFileUpload(symbols, upload_url, product_name='ChromeOS'):
Don Garretta28be6d2016-06-16 18:09:35 -0700508 """Upload the symbols to the crash server
509
510 Args:
Don Garrettdeb2e032016-07-06 16:44:14 -0700511 symbols: An iterable of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700512 upload_url: URL of crash server to upload too.
513 failures: Tracker for total upload failures.
514 product_name: A string for stats purposes. Usually 'ChromeOS' or 'Android'.
515
Don Garrettdeb2e032016-07-06 16:44:14 -0700516 Yields:
517 Each symbol from symbols, perhaps modified.
Don Garretta28be6d2016-06-16 18:09:35 -0700518 """
Don Garrettdeb2e032016-07-06 16:44:14 -0700519 failures = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700520
Don Garrettdeb2e032016-07-06 16:44:14 -0700521 for s in symbols:
522 if (failures < MAX_TOTAL_ERRORS_FOR_RETRY and
523 s.status in (SymbolFile.INITIAL, SymbolFile.ERROR)):
524 # Keeps us from DoS-ing the symbol server.
525 time.sleep(SLEEP_DELAY)
526 logging.info('Uploading symbol_file: %s', s.display_path)
527 try:
528 # This command retries the upload multiple times with growing delays. We
529 # only consider the upload a failure if these retries fail.
Don Garrette1f47e92016-10-13 16:04:56 -0700530 def ShouldRetryUpload(exception):
531 return isinstance(exception, (urllib2.HTTPError, urllib2.URLError))
532
Don Garrett440944e2016-10-03 16:33:31 -0700533 with cros_build_lib.TimedSection() as timer:
Don Garrette1f47e92016-10-13 16:04:56 -0700534 retry_stats.RetryWithStats(
535 UPLOAD_STATS, ShouldRetryUpload, MAX_RETRIES,
Don Garrett440944e2016-10-03 16:33:31 -0700536 UploadSymbolFile,
537 upload_url, s, product_name,
538 sleep=INITIAL_RETRY_DELAY)
539 logging.info('upload of %10i bytes took %s', s.FileSize(), timer.delta)
Don Garrettdeb2e032016-07-06 16:44:14 -0700540 s.status = SymbolFile.UPLOADED
541 except urllib2.HTTPError as e:
542 logging.warning('could not upload: %s: HTTP %s: %s',
543 s.display_name, e.code, e.reason)
544 s.status = SymbolFile.ERROR
545 failures += 1
546 except (urllib2.URLError, httplib.HTTPException, socket.error) as e:
547 logging.warning('could not upload: %s: %s', s.display_name, e)
548 s.status = SymbolFile.ERROR
549 failures += 1
550
551 # We pass the symbol along, on both success and failure.
552 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700553
554
Don Garrettdeb2e032016-07-06 16:44:14 -0700555def ReportResults(symbols, failed_list):
Don Garretta28be6d2016-06-16 18:09:35 -0700556 """Log a summary of the symbol uploading.
557
558 This has the side effect of fully consuming the symbols iterator.
559
560 Args:
561 symbols: An iterator of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700562 failed_list: A filename at which to write out a list of our failed uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700563
564 Returns:
565 The number of symbols not uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700566 """
567 upload_failures = []
568 result_counts = {
569 SymbolFile.INITIAL: 0,
570 SymbolFile.UPLOADED: 0,
571 SymbolFile.DUPLICATE: 0,
Don Garrettdeb2e032016-07-06 16:44:14 -0700572 SymbolFile.ERROR: 0,
Don Garretta28be6d2016-06-16 18:09:35 -0700573 }
574
575 for s in symbols:
576 result_counts[s.status] += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700577 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700578 upload_failures.append(s)
579
Don Garrette1f47e92016-10-13 16:04:56 -0700580 # Report retry numbers.
581 _, _, retries = retry_stats.CategoryStats(UPLOAD_STATS)
582 if retries:
583 logging.warning('%d upload retries performed.', retries)
584
Don Garretta28be6d2016-06-16 18:09:35 -0700585 logging.info('Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.',
586 result_counts)
587
Chris Ching91908032016-09-27 16:55:33 -0600588 if result_counts[SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700589 logging.PrintBuildbotStepWarnings()
Chris Ching91908032016-09-27 16:55:33 -0600590 logging.warning('%d non-recoverable upload errors',
591 result_counts[SymbolFile.ERROR])
592
593 if result_counts[SymbolFile.INITIAL]:
594 logging.PrintBuildbotStepWarnings()
595 logging.warning('%d upload(s) were skipped because of excessive errors',
Don Garrettdeb2e032016-07-06 16:44:14 -0700596 result_counts[SymbolFile.INITIAL])
Don Garretta28be6d2016-06-16 18:09:35 -0700597
598 if failed_list is not None:
599 with open(failed_list, 'w') as fl:
600 for s in upload_failures:
601 fl.write('%s\n' % s.display_path)
602
Don Garrettdeb2e032016-07-06 16:44:14 -0700603 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
604
Don Garretta28be6d2016-06-16 18:09:35 -0700605
606def UploadSymbols(sym_paths, upload_url, product_name, dedupe_namespace=None,
607 failed_list=None, upload_limit=None, strip_cfi=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400608 """Upload all the generated symbols for |board| to the crash server
609
610 Args:
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500611 sym_paths: Specific symbol files (or dirs of sym files) to upload,
612 otherwise search |breakpad_dir|
Don Garretta28be6d2016-06-16 18:09:35 -0700613 upload_url: URL of crash server to upload too.
614 product_name: A string for crash server stats purposes.
615 Usually 'ChromeOS' or 'Android'.
616 dedupe_namespace: None for no deduping, or string namespace in isolate.
617 failed_list: A filename at which to write out a list of our failed uploads.
618 upload_limit: Integer listing how many files to upload. None for no limit.
619 strip_cfi: File size at which we strip out CFI data. None for no limit.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500620
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400621 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400622 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400623 """
Don Garrette1f47e92016-10-13 16:04:56 -0700624 retry_stats.SetupStats()
625
Don Garretta28be6d2016-06-16 18:09:35 -0700626 # Note: This method looks like each step of processing is performed
627 # sequentially for all SymbolFiles, but instead each step is a generator that
628 # produces the next iteration only when it's read. This means that (except for
629 # some batching) each SymbolFile goes through all of these steps before the
630 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400631
Don Garretta28be6d2016-06-16 18:09:35 -0700632 # This is used to hold striped
633 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
634 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500635
Don Garrett1bc1e102016-07-06 17:06:10 -0700636 # Sort all of our symbols so the largest ones (probably the most important)
637 # are processed first.
638 symbols = list(symbols)
639 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
640
Don Garretta28be6d2016-06-16 18:09:35 -0700641 if upload_limit is not None:
642 # Restrict symbols processed to the limit.
643 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400644
Don Garretta28be6d2016-06-16 18:09:35 -0700645 # Strip CFI, if needed.
646 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500647
Don Garretta28be6d2016-06-16 18:09:35 -0700648 # Skip duplicates.
649 if dedupe_namespace:
650 symbols = FindDuplicates(symbols, dedupe_namespace)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500651
Don Garretta28be6d2016-06-16 18:09:35 -0700652 # Perform uploads
Don Garrettdeb2e032016-07-06 16:44:14 -0700653 symbols = PerformSymbolsFileUpload(symbols, upload_url, product_name)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400654
Don Garretta28be6d2016-06-16 18:09:35 -0700655 # Record for future deduping.
656 if dedupe_namespace:
657 symbols = PostForDeduplication(symbols, dedupe_namespace)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400658
Don Garretta28be6d2016-06-16 18:09:35 -0700659 # Log the final results, and consume the symbols generator fully.
Don Garrettdeb2e032016-07-06 16:44:14 -0700660 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500661
Don Garrettdeb2e032016-07-06 16:44:14 -0700662 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400663
664
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400665def main(argv):
666 parser = commandline.ArgumentParser(description=__doc__)
667
Don Garretta28be6d2016-06-16 18:09:35 -0700668 # TODO: Make sym_paths, breakpad_root, and root exclusive.
669
Mike Frysingerd41938e2014-02-10 06:37:55 -0500670 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
671 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400672 parser.add_argument('--board', default=None,
Don Garrett747cc4b2015-10-07 14:48:48 -0700673 help='Used to find default breakpad_root.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400674 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500675 help='full path to the breakpad symbol directory')
676 parser.add_argument('--root', type='path', default=None,
677 help='full path to the chroot dir')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400678 parser.add_argument('--official_build', action='store_true', default=False,
679 help='point to official symbol server')
Mike Frysinger38647542014-09-12 18:15:39 -0700680 parser.add_argument('--server', type=str, default=None,
681 help='URI for custom symbol server')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400682 parser.add_argument('--regenerate', action='store_true', default=False,
683 help='regenerate all symbols')
Don Garretta28be6d2016-06-16 18:09:35 -0700684 parser.add_argument('--upload-limit', type=int,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400685 help='only upload # number of symbols')
686 parser.add_argument('--strip_cfi', type=int,
Don Garretta28be6d2016-06-16 18:09:35 -0700687 default=DEFAULT_FILE_LIMIT,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400688 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500689 parser.add_argument('--failed-list', type='path',
690 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500691 parser.add_argument('--dedupe', action='store_true', default=False,
692 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400693 parser.add_argument('--yes', action='store_true', default=False,
694 help='answer yes to all prompts')
Don Garrett747cc4b2015-10-07 14:48:48 -0700695 parser.add_argument('--product_name', type=str, default='ChromeOS',
696 help='Produce Name for breakpad stats.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400697
698 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500699 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400700
Don Garretta28be6d2016-06-16 18:09:35 -0700701 # Figure out the symbol files/directories to upload.
702 if opts.sym_paths:
703 sym_paths = opts.sym_paths
704 elif opts.breakpad_root:
705 sym_paths = [opts.breakpad_root]
706 elif opts.root:
707 if not opts.board:
708 raise ValueError('--board must be set if --root is used.')
709 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(opts.board)
710 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip('/'))]
711 else:
712 raise ValueError('--sym_paths, --breakpad_root, or --root must be set.')
713
Don Garrett747cc4b2015-10-07 14:48:48 -0700714 if opts.sym_paths or opts.breakpad_root:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400715 if opts.regenerate:
Don Garrett747cc4b2015-10-07 14:48:48 -0700716 cros_build_lib.Die('--regenerate may not be used with specific files, '
717 'or breakpad_root')
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400718 else:
719 if opts.board is None:
720 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400721
Don Garretta28be6d2016-06-16 18:09:35 -0700722 # Figure out the dedupe namespace.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500723 dedupe_namespace = None
724 if opts.dedupe:
Don Garretta28be6d2016-06-16 18:09:35 -0700725 if opts.official_build:
Don Garrett747cc4b2015-10-07 14:48:48 -0700726 dedupe_namespace = OFFICIAL_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500727 else:
Don Garrett747cc4b2015-10-07 14:48:48 -0700728 dedupe_namespace = STAGING_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500729
Don Garretta28be6d2016-06-16 18:09:35 -0700730 # Figure out which crash server to upload too.
731 upload_url = opts.server
732 if not upload_url:
733 if opts.official_build:
734 upload_url = OFFICIAL_UPLOAD_URL
735 else:
736 logging.warning('unofficial builds upload to the staging server')
737 upload_url = STAGING_UPLOAD_URL
738
739 # Confirm we really want the long upload.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400740 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500741 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400742 Uploading symbols for an entire Chromium OS build is really only
743 necessary for release builds and in a few cases for developers
744 to debug problems. It will take considerable time to run. For
745 developer debugging purposes, consider instead passing specific
746 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500747 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400748 if not cros_build_lib.BooleanPrompt(
749 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500750 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400751 cros_build_lib.Die('better safe than sorry')
752
753 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700754
755 # Regenerate symbols from binaries.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400756 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400757 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
758 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400759
Don Garretta28be6d2016-06-16 18:09:35 -0700760 # Do the upload.
761 ret += UploadSymbols(
762 sym_paths=sym_paths,
763 upload_url=upload_url,
764 product_name=opts.product_name,
765 dedupe_namespace=dedupe_namespace,
766 failed_list=opts.failed_list,
767 upload_limit=opts.upload_limit,
768 strip_cfi=opts.strip_cfi)
769
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400770 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700771 logging.error('encountered %i problem(s)', ret)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400772 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
773 # return 0 in case we are a multiple of the mask.
Don Garretta28be6d2016-06-16 18:09:35 -0700774 return 1