blob: 08f90bd3a7521319035cde75d1a65b3e462f68f7 [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 Hashimotof864c0e2017-02-14 12:46:08 +090082CRASH_SERVER_FILE_LIMIT = 700 * 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.
Ryo Hashimotoc0049372017-02-16 18:50:00 +0900115UPLOAD_MIN_TIMEOUT = 5 * 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):
Ryo Hashimoto4ddf3f02017-03-22 18:25:27 +0900531 return isinstance(exception, (urllib2.HTTPError, urllib2.URLError,
532 httplib.HTTPException, socket.error))
Don Garrette1f47e92016-10-13 16:04:56 -0700533
Don Garrett440944e2016-10-03 16:33:31 -0700534 with cros_build_lib.TimedSection() as timer:
Don Garrette1f47e92016-10-13 16:04:56 -0700535 retry_stats.RetryWithStats(
536 UPLOAD_STATS, ShouldRetryUpload, MAX_RETRIES,
Don Garrett440944e2016-10-03 16:33:31 -0700537 UploadSymbolFile,
538 upload_url, s, product_name,
Luigi Semenzato5104f3c2016-10-12 12:37:42 -0700539 sleep=INITIAL_RETRY_DELAY,
540 log_all_retries=True)
Don Garrett440944e2016-10-03 16:33:31 -0700541 logging.info('upload of %10i bytes took %s', s.FileSize(), timer.delta)
Don Garrettdeb2e032016-07-06 16:44:14 -0700542 s.status = SymbolFile.UPLOADED
543 except urllib2.HTTPError as e:
544 logging.warning('could not upload: %s: HTTP %s: %s',
545 s.display_name, e.code, e.reason)
546 s.status = SymbolFile.ERROR
547 failures += 1
548 except (urllib2.URLError, httplib.HTTPException, socket.error) as e:
Ryo Hashimoto45273f02017-03-16 17:45:56 +0900549 logging.warning('could not upload: %s: %s %s', s.display_name,
550 type(e).__name__, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700551 s.status = SymbolFile.ERROR
552 failures += 1
553
554 # We pass the symbol along, on both success and failure.
555 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700556
557
Don Garrettdeb2e032016-07-06 16:44:14 -0700558def ReportResults(symbols, failed_list):
Don Garretta28be6d2016-06-16 18:09:35 -0700559 """Log a summary of the symbol uploading.
560
561 This has the side effect of fully consuming the symbols iterator.
562
563 Args:
564 symbols: An iterator of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700565 failed_list: A filename at which to write out a list of our failed uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700566
567 Returns:
568 The number of symbols not uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700569 """
570 upload_failures = []
571 result_counts = {
572 SymbolFile.INITIAL: 0,
573 SymbolFile.UPLOADED: 0,
574 SymbolFile.DUPLICATE: 0,
Don Garrettdeb2e032016-07-06 16:44:14 -0700575 SymbolFile.ERROR: 0,
Don Garretta28be6d2016-06-16 18:09:35 -0700576 }
577
578 for s in symbols:
579 result_counts[s.status] += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700580 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700581 upload_failures.append(s)
582
Don Garrette1f47e92016-10-13 16:04:56 -0700583 # Report retry numbers.
584 _, _, retries = retry_stats.CategoryStats(UPLOAD_STATS)
585 if retries:
586 logging.warning('%d upload retries performed.', retries)
587
Don Garretta28be6d2016-06-16 18:09:35 -0700588 logging.info('Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.',
589 result_counts)
590
Chris Ching91908032016-09-27 16:55:33 -0600591 if result_counts[SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700592 logging.PrintBuildbotStepWarnings()
Chris Ching91908032016-09-27 16:55:33 -0600593 logging.warning('%d non-recoverable upload errors',
594 result_counts[SymbolFile.ERROR])
595
596 if result_counts[SymbolFile.INITIAL]:
597 logging.PrintBuildbotStepWarnings()
598 logging.warning('%d upload(s) were skipped because of excessive errors',
Don Garrettdeb2e032016-07-06 16:44:14 -0700599 result_counts[SymbolFile.INITIAL])
Don Garretta28be6d2016-06-16 18:09:35 -0700600
601 if failed_list is not None:
602 with open(failed_list, 'w') as fl:
603 for s in upload_failures:
604 fl.write('%s\n' % s.display_path)
605
Don Garrettdeb2e032016-07-06 16:44:14 -0700606 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
607
Don Garretta28be6d2016-06-16 18:09:35 -0700608
609def UploadSymbols(sym_paths, upload_url, product_name, dedupe_namespace=None,
610 failed_list=None, upload_limit=None, strip_cfi=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400611 """Upload all the generated symbols for |board| to the crash server
612
613 Args:
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500614 sym_paths: Specific symbol files (or dirs of sym files) to upload,
615 otherwise search |breakpad_dir|
Don Garretta28be6d2016-06-16 18:09:35 -0700616 upload_url: URL of crash server to upload too.
617 product_name: A string for crash server stats purposes.
618 Usually 'ChromeOS' or 'Android'.
619 dedupe_namespace: None for no deduping, or string namespace in isolate.
620 failed_list: A filename at which to write out a list of our failed uploads.
621 upload_limit: Integer listing how many files to upload. None for no limit.
622 strip_cfi: File size at which we strip out CFI data. None for no limit.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500623
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400624 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400625 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400626 """
Don Garrette1f47e92016-10-13 16:04:56 -0700627 retry_stats.SetupStats()
628
Don Garretta28be6d2016-06-16 18:09:35 -0700629 # Note: This method looks like each step of processing is performed
630 # sequentially for all SymbolFiles, but instead each step is a generator that
631 # produces the next iteration only when it's read. This means that (except for
632 # some batching) each SymbolFile goes through all of these steps before the
633 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400634
Don Garretta28be6d2016-06-16 18:09:35 -0700635 # This is used to hold striped
636 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
637 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500638
Don Garrett1bc1e102016-07-06 17:06:10 -0700639 # Sort all of our symbols so the largest ones (probably the most important)
640 # are processed first.
641 symbols = list(symbols)
642 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
643
Don Garretta28be6d2016-06-16 18:09:35 -0700644 if upload_limit is not None:
645 # Restrict symbols processed to the limit.
646 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400647
Don Garretta28be6d2016-06-16 18:09:35 -0700648 # Strip CFI, if needed.
649 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500650
Don Garretta28be6d2016-06-16 18:09:35 -0700651 # Skip duplicates.
652 if dedupe_namespace:
653 symbols = FindDuplicates(symbols, dedupe_namespace)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500654
Don Garretta28be6d2016-06-16 18:09:35 -0700655 # Perform uploads
Don Garrettdeb2e032016-07-06 16:44:14 -0700656 symbols = PerformSymbolsFileUpload(symbols, upload_url, product_name)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400657
Don Garretta28be6d2016-06-16 18:09:35 -0700658 # Record for future deduping.
659 if dedupe_namespace:
660 symbols = PostForDeduplication(symbols, dedupe_namespace)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400661
Don Garretta28be6d2016-06-16 18:09:35 -0700662 # Log the final results, and consume the symbols generator fully.
Don Garrettdeb2e032016-07-06 16:44:14 -0700663 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500664
Don Garrettdeb2e032016-07-06 16:44:14 -0700665 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400666
667
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400668def main(argv):
669 parser = commandline.ArgumentParser(description=__doc__)
670
Don Garretta28be6d2016-06-16 18:09:35 -0700671 # TODO: Make sym_paths, breakpad_root, and root exclusive.
672
Mike Frysingerd41938e2014-02-10 06:37:55 -0500673 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
674 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400675 parser.add_argument('--board', default=None,
Don Garrett747cc4b2015-10-07 14:48:48 -0700676 help='Used to find default breakpad_root.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400677 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500678 help='full path to the breakpad symbol directory')
679 parser.add_argument('--root', type='path', default=None,
680 help='full path to the chroot dir')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400681 parser.add_argument('--official_build', action='store_true', default=False,
682 help='point to official symbol server')
Mike Frysinger38647542014-09-12 18:15:39 -0700683 parser.add_argument('--server', type=str, default=None,
684 help='URI for custom symbol server')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400685 parser.add_argument('--regenerate', action='store_true', default=False,
686 help='regenerate all symbols')
Don Garretta28be6d2016-06-16 18:09:35 -0700687 parser.add_argument('--upload-limit', type=int,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400688 help='only upload # number of symbols')
689 parser.add_argument('--strip_cfi', type=int,
Don Garretta28be6d2016-06-16 18:09:35 -0700690 default=DEFAULT_FILE_LIMIT,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400691 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500692 parser.add_argument('--failed-list', type='path',
693 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500694 parser.add_argument('--dedupe', action='store_true', default=False,
695 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400696 parser.add_argument('--yes', action='store_true', default=False,
697 help='answer yes to all prompts')
Don Garrett747cc4b2015-10-07 14:48:48 -0700698 parser.add_argument('--product_name', type=str, default='ChromeOS',
699 help='Produce Name for breakpad stats.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400700
701 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500702 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400703
Don Garretta28be6d2016-06-16 18:09:35 -0700704 # Figure out the symbol files/directories to upload.
705 if opts.sym_paths:
706 sym_paths = opts.sym_paths
707 elif opts.breakpad_root:
708 sym_paths = [opts.breakpad_root]
709 elif opts.root:
710 if not opts.board:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400711 cros_build_lib.Die('--board must be set if --root is used.')
Don Garretta28be6d2016-06-16 18:09:35 -0700712 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(opts.board)
713 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip('/'))]
714 else:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400715 cros_build_lib.Die('--sym_paths, --breakpad_root, or --root must be set.')
Don Garretta28be6d2016-06-16 18:09:35 -0700716
Don Garrett747cc4b2015-10-07 14:48:48 -0700717 if opts.sym_paths or opts.breakpad_root:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400718 if opts.regenerate:
Don Garrett747cc4b2015-10-07 14:48:48 -0700719 cros_build_lib.Die('--regenerate may not be used with specific files, '
720 'or breakpad_root')
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400721 else:
722 if opts.board is None:
723 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400724
Don Garretta28be6d2016-06-16 18:09:35 -0700725 # Figure out the dedupe namespace.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500726 dedupe_namespace = None
727 if opts.dedupe:
Don Garretta28be6d2016-06-16 18:09:35 -0700728 if opts.official_build:
Don Garrett747cc4b2015-10-07 14:48:48 -0700729 dedupe_namespace = OFFICIAL_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500730 else:
Don Garrett747cc4b2015-10-07 14:48:48 -0700731 dedupe_namespace = STAGING_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500732
Don Garretta28be6d2016-06-16 18:09:35 -0700733 # Figure out which crash server to upload too.
734 upload_url = opts.server
735 if not upload_url:
736 if opts.official_build:
737 upload_url = OFFICIAL_UPLOAD_URL
738 else:
739 logging.warning('unofficial builds upload to the staging server')
740 upload_url = STAGING_UPLOAD_URL
741
742 # Confirm we really want the long upload.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400743 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500744 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400745 Uploading symbols for an entire Chromium OS build is really only
746 necessary for release builds and in a few cases for developers
747 to debug problems. It will take considerable time to run. For
748 developer debugging purposes, consider instead passing specific
749 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500750 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400751 if not cros_build_lib.BooleanPrompt(
752 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500753 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400754 cros_build_lib.Die('better safe than sorry')
755
756 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700757
758 # Regenerate symbols from binaries.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400759 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400760 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
761 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400762
Don Garretta28be6d2016-06-16 18:09:35 -0700763 # Do the upload.
764 ret += UploadSymbols(
765 sym_paths=sym_paths,
766 upload_url=upload_url,
767 product_name=opts.product_name,
768 dedupe_namespace=dedupe_namespace,
769 failed_list=opts.failed_list,
770 upload_limit=opts.upload_limit,
771 strip_cfi=opts.strip_cfi)
772
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400773 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700774 logging.error('encountered %i problem(s)', ret)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400775 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
776 # return 0 in case we are a multiple of the mask.
Don Garretta28be6d2016-06-16 18:09:35 -0700777 return 1