blob: 339c22f7a74483ac63e00001c750ae0df25393a3 [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):
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,
Luigi Semenzato5104f3c2016-10-12 12:37:42 -0700538 sleep=INITIAL_RETRY_DELAY,
539 log_all_retries=True)
Don Garrett440944e2016-10-03 16:33:31 -0700540 logging.info('upload of %10i bytes took %s', s.FileSize(), timer.delta)
Don Garrettdeb2e032016-07-06 16:44:14 -0700541 s.status = SymbolFile.UPLOADED
542 except urllib2.HTTPError as e:
543 logging.warning('could not upload: %s: HTTP %s: %s',
544 s.display_name, e.code, e.reason)
545 s.status = SymbolFile.ERROR
546 failures += 1
547 except (urllib2.URLError, httplib.HTTPException, socket.error) as e:
Ryo Hashimoto45273f02017-03-16 17:45:56 +0900548 logging.warning('could not upload: %s: %s %s', s.display_name,
549 type(e).__name__, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700550 s.status = SymbolFile.ERROR
551 failures += 1
552
553 # We pass the symbol along, on both success and failure.
554 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700555
556
Don Garrettdeb2e032016-07-06 16:44:14 -0700557def ReportResults(symbols, failed_list):
Don Garretta28be6d2016-06-16 18:09:35 -0700558 """Log a summary of the symbol uploading.
559
560 This has the side effect of fully consuming the symbols iterator.
561
562 Args:
563 symbols: An iterator of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700564 failed_list: A filename at which to write out a list of our failed uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700565
566 Returns:
567 The number of symbols not uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700568 """
569 upload_failures = []
570 result_counts = {
571 SymbolFile.INITIAL: 0,
572 SymbolFile.UPLOADED: 0,
573 SymbolFile.DUPLICATE: 0,
Don Garrettdeb2e032016-07-06 16:44:14 -0700574 SymbolFile.ERROR: 0,
Don Garretta28be6d2016-06-16 18:09:35 -0700575 }
576
577 for s in symbols:
578 result_counts[s.status] += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700579 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700580 upload_failures.append(s)
581
Don Garrette1f47e92016-10-13 16:04:56 -0700582 # Report retry numbers.
583 _, _, retries = retry_stats.CategoryStats(UPLOAD_STATS)
584 if retries:
585 logging.warning('%d upload retries performed.', retries)
586
Don Garretta28be6d2016-06-16 18:09:35 -0700587 logging.info('Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.',
588 result_counts)
589
Chris Ching91908032016-09-27 16:55:33 -0600590 if result_counts[SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700591 logging.PrintBuildbotStepWarnings()
Chris Ching91908032016-09-27 16:55:33 -0600592 logging.warning('%d non-recoverable upload errors',
593 result_counts[SymbolFile.ERROR])
594
595 if result_counts[SymbolFile.INITIAL]:
596 logging.PrintBuildbotStepWarnings()
597 logging.warning('%d upload(s) were skipped because of excessive errors',
Don Garrettdeb2e032016-07-06 16:44:14 -0700598 result_counts[SymbolFile.INITIAL])
Don Garretta28be6d2016-06-16 18:09:35 -0700599
600 if failed_list is not None:
601 with open(failed_list, 'w') as fl:
602 for s in upload_failures:
603 fl.write('%s\n' % s.display_path)
604
Don Garrettdeb2e032016-07-06 16:44:14 -0700605 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
606
Don Garretta28be6d2016-06-16 18:09:35 -0700607
608def UploadSymbols(sym_paths, upload_url, product_name, dedupe_namespace=None,
609 failed_list=None, upload_limit=None, strip_cfi=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400610 """Upload all the generated symbols for |board| to the crash server
611
612 Args:
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500613 sym_paths: Specific symbol files (or dirs of sym files) to upload,
614 otherwise search |breakpad_dir|
Don Garretta28be6d2016-06-16 18:09:35 -0700615 upload_url: URL of crash server to upload too.
616 product_name: A string for crash server stats purposes.
617 Usually 'ChromeOS' or 'Android'.
618 dedupe_namespace: None for no deduping, or string namespace in isolate.
619 failed_list: A filename at which to write out a list of our failed uploads.
620 upload_limit: Integer listing how many files to upload. None for no limit.
621 strip_cfi: File size at which we strip out CFI data. None for no limit.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500622
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400623 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400624 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400625 """
Don Garrette1f47e92016-10-13 16:04:56 -0700626 retry_stats.SetupStats()
627
Don Garretta28be6d2016-06-16 18:09:35 -0700628 # Note: This method looks like each step of processing is performed
629 # sequentially for all SymbolFiles, but instead each step is a generator that
630 # produces the next iteration only when it's read. This means that (except for
631 # some batching) each SymbolFile goes through all of these steps before the
632 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400633
Don Garretta28be6d2016-06-16 18:09:35 -0700634 # This is used to hold striped
635 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
636 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500637
Don Garrett1bc1e102016-07-06 17:06:10 -0700638 # Sort all of our symbols so the largest ones (probably the most important)
639 # are processed first.
640 symbols = list(symbols)
641 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
642
Don Garretta28be6d2016-06-16 18:09:35 -0700643 if upload_limit is not None:
644 # Restrict symbols processed to the limit.
645 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400646
Don Garretta28be6d2016-06-16 18:09:35 -0700647 # Strip CFI, if needed.
648 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500649
Don Garretta28be6d2016-06-16 18:09:35 -0700650 # Skip duplicates.
651 if dedupe_namespace:
652 symbols = FindDuplicates(symbols, dedupe_namespace)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500653
Don Garretta28be6d2016-06-16 18:09:35 -0700654 # Perform uploads
Don Garrettdeb2e032016-07-06 16:44:14 -0700655 symbols = PerformSymbolsFileUpload(symbols, upload_url, product_name)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400656
Don Garretta28be6d2016-06-16 18:09:35 -0700657 # Record for future deduping.
658 if dedupe_namespace:
659 symbols = PostForDeduplication(symbols, dedupe_namespace)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400660
Don Garretta28be6d2016-06-16 18:09:35 -0700661 # Log the final results, and consume the symbols generator fully.
Don Garrettdeb2e032016-07-06 16:44:14 -0700662 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500663
Don Garrettdeb2e032016-07-06 16:44:14 -0700664 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400665
666
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400667def main(argv):
668 parser = commandline.ArgumentParser(description=__doc__)
669
Don Garretta28be6d2016-06-16 18:09:35 -0700670 # TODO: Make sym_paths, breakpad_root, and root exclusive.
671
Mike Frysingerd41938e2014-02-10 06:37:55 -0500672 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
673 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400674 parser.add_argument('--board', default=None,
Don Garrett747cc4b2015-10-07 14:48:48 -0700675 help='Used to find default breakpad_root.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400676 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500677 help='full path to the breakpad symbol directory')
678 parser.add_argument('--root', type='path', default=None,
679 help='full path to the chroot dir')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400680 parser.add_argument('--official_build', action='store_true', default=False,
681 help='point to official symbol server')
Mike Frysinger38647542014-09-12 18:15:39 -0700682 parser.add_argument('--server', type=str, default=None,
683 help='URI for custom symbol server')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400684 parser.add_argument('--regenerate', action='store_true', default=False,
685 help='regenerate all symbols')
Don Garretta28be6d2016-06-16 18:09:35 -0700686 parser.add_argument('--upload-limit', type=int,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400687 help='only upload # number of symbols')
688 parser.add_argument('--strip_cfi', type=int,
Don Garretta28be6d2016-06-16 18:09:35 -0700689 default=DEFAULT_FILE_LIMIT,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400690 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500691 parser.add_argument('--failed-list', type='path',
692 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500693 parser.add_argument('--dedupe', action='store_true', default=False,
694 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400695 parser.add_argument('--yes', action='store_true', default=False,
696 help='answer yes to all prompts')
Don Garrett747cc4b2015-10-07 14:48:48 -0700697 parser.add_argument('--product_name', type=str, default='ChromeOS',
698 help='Produce Name for breakpad stats.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400699
700 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500701 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400702
Don Garretta28be6d2016-06-16 18:09:35 -0700703 # Figure out the symbol files/directories to upload.
704 if opts.sym_paths:
705 sym_paths = opts.sym_paths
706 elif opts.breakpad_root:
707 sym_paths = [opts.breakpad_root]
708 elif opts.root:
709 if not opts.board:
710 raise ValueError('--board must be set if --root is used.')
711 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(opts.board)
712 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip('/'))]
713 else:
714 raise ValueError('--sym_paths, --breakpad_root, or --root must be set.')
715
Don Garrett747cc4b2015-10-07 14:48:48 -0700716 if opts.sym_paths or opts.breakpad_root:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400717 if opts.regenerate:
Don Garrett747cc4b2015-10-07 14:48:48 -0700718 cros_build_lib.Die('--regenerate may not be used with specific files, '
719 'or breakpad_root')
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400720 else:
721 if opts.board is None:
722 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400723
Don Garretta28be6d2016-06-16 18:09:35 -0700724 # Figure out the dedupe namespace.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500725 dedupe_namespace = None
726 if opts.dedupe:
Don Garretta28be6d2016-06-16 18:09:35 -0700727 if opts.official_build:
Don Garrett747cc4b2015-10-07 14:48:48 -0700728 dedupe_namespace = OFFICIAL_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500729 else:
Don Garrett747cc4b2015-10-07 14:48:48 -0700730 dedupe_namespace = STAGING_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500731
Don Garretta28be6d2016-06-16 18:09:35 -0700732 # Figure out which crash server to upload too.
733 upload_url = opts.server
734 if not upload_url:
735 if opts.official_build:
736 upload_url = OFFICIAL_UPLOAD_URL
737 else:
738 logging.warning('unofficial builds upload to the staging server')
739 upload_url = STAGING_UPLOAD_URL
740
741 # Confirm we really want the long upload.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400742 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500743 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400744 Uploading symbols for an entire Chromium OS build is really only
745 necessary for release builds and in a few cases for developers
746 to debug problems. It will take considerable time to run. For
747 developer debugging purposes, consider instead passing specific
748 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500749 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400750 if not cros_build_lib.BooleanPrompt(
751 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500752 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400753 cros_build_lib.Die('better safe than sorry')
754
755 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700756
757 # Regenerate symbols from binaries.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400758 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400759 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
760 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400761
Don Garretta28be6d2016-06-16 18:09:35 -0700762 # Do the upload.
763 ret += UploadSymbols(
764 sym_paths=sym_paths,
765 upload_url=upload_url,
766 product_name=opts.product_name,
767 dedupe_namespace=dedupe_namespace,
768 failed_list=opts.failed_list,
769 upload_limit=opts.upload_limit,
770 strip_cfi=opts.strip_cfi)
771
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400772 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700773 logging.error('encountered %i problem(s)', ret)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400774 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
775 # return 0 in case we are a multiple of the mask.
Don Garretta28be6d2016-06-16 18:09:35 -0700776 return 1