blob: 5817891ecf9c060e56a51d1511026456eb80371e [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 Frysinger094a2172013-08-14 12:54:35 -040018import poster
Mike Frysingerfd355652014-01-23 02:57:48 -050019import socket
Mike Frysingerc5597f22014-11-27 15:39:15 -050020import sys
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040021import textwrap
22import tempfile
23import time
Mike Frysinger094a2172013-08-14 12:54:35 -040024import urllib2
Mike Frysingerd41938e2014-02-10 06:37:55 -050025import urlparse
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040026
Don Garrett88b8d782014-05-13 17:30:55 -070027from chromite.cbuildbot import constants
Mike Frysingerd41938e2014-02-10 06:37:55 -050028from chromite.lib import cache
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040029from chromite.lib import commandline
30from chromite.lib import cros_build_lib
Ralph Nathan5a582ff2015-03-20 18:18:30 -070031from chromite.lib import cros_logging as logging
Mike Frysingerd41938e2014-02-10 06:37:55 -050032from chromite.lib import gs
33from chromite.lib import osutils
Gilad Arnold83233ed2015-05-08 12:12:13 -070034from chromite.lib import path_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080035from chromite.lib import retry_util
Mike Frysinger0c0efa22014-02-09 23:32:23 -050036from chromite.lib import timeout_util
Mike Frysinger69cb41d2013-08-11 20:08:19 -040037from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040038
Mike Frysinger0c0efa22014-02-09 23:32:23 -050039# Needs to be after chromite imports.
Mike Frysingerc5597f22014-11-27 15:39:15 -050040# We don't want to import the general keyring module as that will implicitly
41# try to import & connect to a dbus server. That's a waste of time.
42sys.modules['keyring'] = None
43import isolateserver
Mike Frysinger0c0efa22014-02-09 23:32:23 -050044
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040045
Don Garretta28be6d2016-06-16 18:09:35 -070046# We need this to run once per process. Do it at module import time as that
47# will let us avoid doing it inline at function call time (see UploadSymbolFile)
48# as that func might be called by the multiprocessing module which means we'll
49# do the opener logic multiple times overall. Plus, if you're importing this
50# module, it's a pretty good chance that you're going to need this.
51poster.streaminghttp.register_openers()
52
53
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040054# URLs used for uploading symbols.
55OFFICIAL_UPLOAD_URL = 'http://clients2.google.com/cr/symbol'
56STAGING_UPLOAD_URL = 'http://clients2.google.com/cr/staging_symbol'
57
58
59# The crash server rejects files that are this big.
60CRASH_SERVER_FILE_LIMIT = 350 * 1024 * 1024
61# Give ourselves a little breathing room from what the server expects.
62DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
63
64
Mike Frysinger0c0efa22014-02-09 23:32:23 -050065# The batch limit when talking to the dedup server. We avoid sending one at a
66# time as the round trip overhead will dominate. Conversely, we avoid sending
67# all at once so we can start uploading symbols asap -- the symbol server is a
68# bit slow and will take longer than anything else.
Mike Frysinger0c0efa22014-02-09 23:32:23 -050069DEDUPE_LIMIT = 100
70
71# How long to wait for the server to respond with the results. Note that the
72# larger the limit above, the larger this will need to be. So we give it ~1
73# second per item max.
74DEDUPE_TIMEOUT = DEDUPE_LIMIT
75
Don Garretta28be6d2016-06-16 18:09:35 -070076# How long to wait for the notification to finish (in seconds).
77DEDUPE_NOTIFY_TIMEOUT = 240
Mike Frysinger4dd462e2014-04-30 16:21:51 -040078
Mike Frysinger0c0efa22014-02-09 23:32:23 -050079# The unique namespace in the dedupe server that only we use. Helps avoid
80# collisions with all the hashed values and unrelated content.
Don Garrett747cc4b2015-10-07 14:48:48 -070081OFFICIAL_DEDUPE_NAMESPACE_TMPL = '%s-upload-symbols'
82STAGING_DEDUPE_NAMESPACE_TMPL = '%s-staging' % OFFICIAL_DEDUPE_NAMESPACE_TMPL
Mike Frysinger0c0efa22014-02-09 23:32:23 -050083
84
Mike Frysinger71046662014-09-12 18:15:15 -070085# The minimum average rate (in bytes per second) that we expect to maintain
86# when uploading symbols. This has to allow for symbols that are up to
87# CRASH_SERVER_FILE_LIMIT in size.
88UPLOAD_MIN_RATE = CRASH_SERVER_FILE_LIMIT / (30 * 60)
89
90# The lowest timeout (in seconds) we'll allow. If the server is overloaded,
91# then there might be a delay in setting up the connection, not just with the
92# transfer. So even a small file might need a larger value.
93UPLOAD_MIN_TIMEOUT = 2 * 60
Mike Frysingercd78a082013-06-26 17:13:04 -040094
95
Don Garrett7a793092016-07-06 16:50:27 -070096# Sleep for 500ms in between uploads to avoid DoS'ing symbol server.
97SLEEP_DELAY = 0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040098
99
100# Number of seconds to wait before retrying an upload. The delay will double
101# for each subsequent retry of the same symbol file.
102INITIAL_RETRY_DELAY = 1
103
104# Allow up to 7 attempts to upload a symbol file (total delay may be
105# 1+2+4+8+16+32=63 seconds).
106MAX_RETRIES = 6
107
Mike Frysingereb753bf2013-11-22 16:05:35 -0500108# Number of total errors, before uploads are no longer attempted.
109# This is used to avoid lots of errors causing unreasonable delays.
Mike Frysingereb753bf2013-11-22 16:05:35 -0500110MAX_TOTAL_ERRORS_FOR_RETRY = 30
111
Don Garretta28be6d2016-06-16 18:09:35 -0700112
113class Counter(object):
114 """Simple API for a counter. Easy to pass around by reference."""
115 def __init__(self):
116 self.value = 0
117
118 def increment(self):
119 self.value += 1
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400120
121
Don Garretta28be6d2016-06-16 18:09:35 -0700122def BatchGenerator(iterator, batch_size):
123 """Given an iterator, break into lists of size batch_size.
Fang Dengba680462015-08-16 20:34:11 -0700124
Don Garretta28be6d2016-06-16 18:09:35 -0700125 The result is a generator, that will only read in as many inputs as needed for
126 the current batch. The final result can be smaller than batch_size.
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700127 """
Don Garretta28be6d2016-06-16 18:09:35 -0700128 batch = []
129 for i in iterator:
130 batch.append(i)
131 if len(batch) >= batch_size:
132 yield batch
133 batch = []
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700134
Don Garretta28be6d2016-06-16 18:09:35 -0700135 if batch:
136 # if there was anything left in the final batch, yield it.
137 yield batch
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500138
139
Mike Frysingerd41938e2014-02-10 06:37:55 -0500140def IsTarball(path):
141 """Guess if this is a tarball based on the filename."""
142 parts = path.split('.')
143 if len(parts) <= 1:
144 return False
145
146 if parts[-1] == 'tar':
147 return True
148
149 if parts[-2] == 'tar':
150 return parts[-1] in ('bz2', 'gz', 'xz')
151
152 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
153
154
Don Garretta28be6d2016-06-16 18:09:35 -0700155class SymbolFile(object):
156 """This class represents the state of a symbol file during processing.
157
158 Properties:
159 display_path: Name of symbol file that should be consistent between builds.
160 file_name: Transient path of the symbol file.
161 header: ReadSymsHeader output. Dict with assorted meta-data.
162 status: INITIAL, DUPLICATE, or UPLOADED based on status of processing.
163 dedupe_item: None or instance of DedupeItem for this symbol file.
164 dedupe_push_state: Opaque value to return to dedupe code for file.
165 display_name: Read only friendly (short) file name for logging.
166 file_size: Read only size of the symbol file.
167 """
168 INITIAL = 'initial'
169 DUPLICATE = 'duplicate'
170 UPLOADED = 'uploaded'
171
172 def __init__(self, display_path, file_name):
173 """An instance of this class represents a symbol file over time.
174
175 Args:
176 display_path: A unique/persistent between builds name to present to the
177 crash server. It is the file name, relative to where it
178 came from (tarball, breakpad dir, etc).
179 file_name: A the current location of the symbol file.
180 """
181 self.display_path = display_path
182 self.file_name = file_name
183 self.header = cros_generate_breakpad_symbols.ReadSymsHeader(file_name)
184 self.status = SymbolFile.INITIAL
185 self.dedupe_item = None
186 self.dedupe_push_state = None
187
188 @property
189 def display_name(self):
190 return os.path.basename(self.display_path)
191
192 def FileSize(self):
193 return os.path.getsize(self.file_name)
194
195
196class DedupeItem(isolateserver.BufferItem):
197 """Turn a SymbolFile into an isolateserver.Item"""
198
199 ALGO = hashlib.sha1
200
201 def __init__(self, symbol):
202 super(DedupeItem, self).__init__(str(symbol.header), self.ALGO)
203 self.symbol = symbol
204
205
206def FindSymbolFiles(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500207 """Locate symbol files in |paths|
208
Don Garretta28be6d2016-06-16 18:09:35 -0700209 This returns SymbolFile objects that contain file references which are valid
210 after this exits. Those files may exist externally, or be created in the
211 tempdir (say, when expanding tarballs). The caller must not consider
212 SymbolFile's valid after tempdir is cleaned up.
213
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500214 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700215 tempdir: Path to use for temporary files.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500216 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500217 Dirs are searched for files that end in ".sym". Urls are fetched and then
218 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500219
Don Garretta28be6d2016-06-16 18:09:35 -0700220 Yields:
221 A SymbolFile for every symbol file found in paths.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500222 """
Gilad Arnold83233ed2015-05-08 12:12:13 -0700223 cache_dir = path_util.GetCacheDir()
Mike Frysingere847efd2015-01-08 03:57:24 -0500224 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
225 tar_cache = cache.TarballCache(common_path)
226
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500227 for p in paths:
Don Garrett25f309a2014-03-19 14:02:12 -0700228 # Pylint is confused about members of ParseResult.
Don Garrettf8bf7842014-03-20 17:03:42 -0700229
Mike Frysingerd41938e2014-02-10 06:37:55 -0500230 o = urlparse.urlparse(p)
Don Garrettf8bf7842014-03-20 17:03:42 -0700231 if o.scheme: # pylint: disable=E1101
Mike Frysingerd41938e2014-02-10 06:37:55 -0500232 # Support globs of filenames.
233 ctx = gs.GSContext()
234 for p in ctx.LS(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700235 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500236 o = urlparse.urlparse(p)
Don Garrettf8bf7842014-03-20 17:03:42 -0700237 key = ('%s%s' % (o.netloc, o.path)).split('/') # pylint: disable=E1101
Mike Frysingerd41938e2014-02-10 06:37:55 -0500238 # The common cache will not be LRU, removing the need to hold a read
239 # lock on the cached gsutil.
240 ref = tar_cache.Lookup(key)
241 try:
242 ref.SetDefault(p)
243 except cros_build_lib.RunCommandError as e:
Ralph Nathan446aee92015-03-23 14:44:56 -0700244 logging.warning('ignoring %s\n%s', p, e)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500245 continue
Don Garretta28be6d2016-06-16 18:09:35 -0700246 for p in FindSymbolFiles(tempdir, [ref.path]):
Mike Frysingerd41938e2014-02-10 06:37:55 -0500247 yield p
248
249 elif os.path.isdir(p):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500250 for root, _, files in os.walk(p):
251 for f in files:
252 if f.endswith('.sym'):
Don Garretta28be6d2016-06-16 18:09:35 -0700253 # If p is '/tmp/foo' and filename is '/tmp/foo/bar/bar.sym',
254 # display_path = 'bar/bar.sym'
255 filename = os.path.join(root, f)
256 yield SymbolFile(display_path=filename[len(p):].lstrip('/'),
257 file_name=filename)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500258
259 elif IsTarball(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700260 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500261 tardir = tempfile.mkdtemp(dir=tempdir)
262 cache.Untar(os.path.realpath(p), tardir)
Don Garretta28be6d2016-06-16 18:09:35 -0700263 for p in FindSymbolFiles(tardir, [tardir]):
Mike Frysingerd41938e2014-02-10 06:37:55 -0500264 yield p
265
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500266 else:
Don Garretta28be6d2016-06-16 18:09:35 -0700267 yield SymbolFile(display_path=p, file_name=p)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500268
269
Don Garretta28be6d2016-06-16 18:09:35 -0700270def AdjustSymbolFileSize(symbol, tempdir, file_limit):
271 """Examine symbols files for size problems, and reduce if needed.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500272
Don Garretta28be6d2016-06-16 18:09:35 -0700273 If the symbols size is too big, strip out the call frame info. The CFI
274 is unnecessary for 32bit x86 targets where the frame pointer is used (as
275 all of ours have) and it accounts for over half the size of the symbols
276 uploaded.
277
278 Stripped files will be created inside tempdir, and will be the callers
279 responsibility to clean up.
280
281 We also warn, if a symbols file is still too large after stripping.
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500282
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500283 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700284 symbol: SymbolFile instance to be examined and modified as needed..
285 tempdir: A temporary directory we can create files in that the caller will
286 clean up.
287 file_limit: We only strip files which are larger than this limit.
288
289 Returns:
290 SymbolFile instance (original or modified as needed)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500291 """
Don Garretta28be6d2016-06-16 18:09:35 -0700292 file_size = symbol.FileSize()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500293
Don Garretta28be6d2016-06-16 18:09:35 -0700294 if file_limit and symbol.FileSize() > file_limit:
295 with tempfile.NamedTemporaryFile(
296 prefix='upload_symbols', bufsize=0,
297 dir=tempdir, delete=False) as temp_sym_file:
298
299 temp_sym_file.writelines(
300 [x for x in open(symbol.file_name, 'rb').readlines()
301 if not x.startswith('STACK CFI')]
302 )
303
304 original_file_size = file_size
305 symbol.file_name = temp_sym_file.name
306 file_size = symbol.FileSize()
307
308 logging.warning('stripped CFI for %s reducing size %s > %s',
309 symbol.display_name, original_file_size, file_size)
310
311 # Hopefully the crash server will let it through. But it probably won't.
312 # Not sure what the best answer is in this case.
313 if file_size >= CRASH_SERVER_FILE_LIMIT:
314 logging.PrintBuildbotStepWarnings()
315 logging.warning('upload file %s is awfully large, risking rejection by '
316 'the symbol server (%s > %s)', symbol.display_path,
317 file_size, CRASH_SERVER_FILE_LIMIT)
318
319 return symbol
320
321def OpenDeduplicateConnection(dedupe_namespace):
322 """Open a connection to the isolate server for Dedupe use.
323
324 Args:
325 dedupe_namespace: String id for the comparison namespace.
326
327 Returns:
328 Connection proxy, or None on failure.
329 """
330 try:
331 with timeout_util.Timeout(DEDUPE_TIMEOUT):
332 return isolateserver.get_storage_api(constants.ISOLATESERVER,
333 dedupe_namespace)
334 except Exception:
335 logging.warning('initializing isolate server connection failed',
336 exc_info=True)
337 return None
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500338
339
Don Garretta28be6d2016-06-16 18:09:35 -0700340def FindDuplicates(symbols, dedupe_namespace):
341 """Mark symbol files we've already uploaded as duplicates.
342
343 Using the swarming service, ask it to tell us which symbol files we've already
344 uploaded in previous runs and/or by other bots. If the query fails for any
345 reason, we'll just upload all symbols. This is fine as the symbol server will
346 do the right thing and this phase is purely an optimization.
347
348 Args:
349 symbols: An iterable of SymbolFiles to be uploaded.
350 dedupe_namespace: String id for the comparison namespace.
351
352 Yields:
353 All SymbolFiles from symbols, but duplicates have status updated to
354 DUPLICATE.
355 """
356 storage_query = OpenDeduplicateConnection(dedupe_namespace)
357
358 # We query isolate in batches, to reduce the number of network queries.
359 for batch in BatchGenerator(symbols, DEDUPE_LIMIT):
360 query_results = None
361
362 if storage_query:
363 # Convert SymbolFiles into DedupeItems.
364 items = [DedupeItem(x) for x in batch]
365 for item in items:
366 item.prepare(DedupeItem.ALGO)
367
368 # Look for duplicates.
369 try:
370 with timeout_util.Timeout(DEDUPE_TIMEOUT):
371 query_results = storage_query.contains(items)
372 except Exception:
373 logging.warning('talking to dedupe server failed', exc_info=True)
374 storage_query = None
375
376 if query_results is not None:
377 for b in batch:
378 b.status = SymbolFile.DUPLICATE
379
380 # Only the non-duplicates appear in the query_results.
381 for item, push_state in query_results.iteritems():
382 # Remember the dedupe state, so we can mark the symbol as uploaded
383 # later on.
384 item.symbol.status = SymbolFile.INITIAL
385 item.symbol.dedupe_item = item
386 item.symbol.dedupe_push_state = push_state
387
388 # Yield all symbols we haven't shown to be duplicates.
389 for b in batch:
390 if b.status == SymbolFile.DUPLICATE:
391 logging.debug('Found duplicate: %s', b.display_name)
392 yield b
393
394
395def PostForDeduplication(symbols, dedupe_namespace):
396 """Send a symbol file to the swarming service
397
398 Notify the isolate service of a successful upload. If the notification fails
399 for any reason, we ignore it. We don't care as it just means we'll upload it
400 again later on, and the symbol server will handle that graciously.
401
402 Args:
403 symbols: An iterable of SymbolFiles to be uploaded.
404 dedupe_namespace: String id for the comparison namespace.
405
406 Yields:
407 Each symbol from symbols, unmodified.
408 """
409 storage_query = OpenDeduplicateConnection(dedupe_namespace)
410
411 for s in symbols:
412 # If we can talk to isolate, and we uploaded this symbol, and we
413 # queried for it's presence before, upload to isolate now.
414
415 if storage_query and s.status == SymbolFile.UPLOADED and s.dedupe_item:
416 s.dedupe_item.prepare(DedupeItem.ALGO)
417 try:
418 with timeout_util.Timeout(DEDUPE_NOTIFY_TIMEOUT):
419 storage_query.push(s.dedupe_item, s.dedupe_push_state,
420 s.dedupe_item.content())
421 logging.info('sent %s', s.display_name)
422 except Exception:
423 logging.warning('posting %s to dedupe server failed',
424 os.path.basename(s.display_path), exc_info=True)
425 storage_query = None
426
427 yield s
428
429
430def GetUploadTimeout(symbol):
431 """How long to wait for a specific file to upload to the crash server.
432
433 This is a function largely to make unittesting easier.
434
435 Args:
436 symbol: A SymbolFile instance.
437
438 Returns:
439 Timeout length (in seconds)
440 """
441 # Scale the timeout based on the filesize.
442 return max(symbol.FileSize() / UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
443
444
445def UploadSymbolFile(upload_url, symbol, product_name):
446 """Upload a symbol file to the crash server.
447
448 The upload is a multipart/form-data POST with the following parameters:
449 code_file: the basename of the module, e.g. "app"
450 code_identifier: the module file's identifier
451 debug_file: the basename of the debugging file, e.g. "app"
452 debug_identifier: the debug file's identifier, usually consisting of
453 the guid and age embedded in the pdb, e.g.
454 "11111111BBBB3333DDDD555555555555F"
455 version: the file version of the module, e.g. "1.2.3.4"
456 product: HTTP-friendly product name
457 os: the operating system that the module was built for
458 cpu: the CPU that the module was built for
459 symbol_file: the contents of the breakpad-format symbol file
460
461 Args:
462 upload_url: The crash URL to POST the |sym_file| to
463 symbol: A SymbolFile instance.
464 product_name: A string for stats purposes. Usually 'ChromeOS' or 'Android'.
465 """
466 fields = (
467 ('code_file', symbol.header.name),
468 ('debug_file', symbol.header.name),
469 ('debug_identifier', symbol.header.id.replace('-', '')),
470 # The product/version fields are used by the server only for statistic
471 # purposes. They do not impact symbolization, so they're safe to set
472 # to any value all the time.
473 # In this case, we use it to help see the load our build system is
474 # placing on the server.
475 # Not sure what to set for the version. Maybe the git sha1 of this file.
476 # Note: the server restricts this to 30 chars.
477 #('version', None),
478 ('product', product_name),
479 ('os', symbol.header.os),
480 ('cpu', symbol.header.cpu),
481 poster.encode.MultipartParam.from_file('symbol_file', symbol.file_name),
482 )
483
484 data, headers = poster.encode.multipart_encode(fields)
485 request = urllib2.Request(upload_url, data, headers)
486 request.add_header('User-agent', 'chromite.upload_symbols')
487 urllib2.urlopen(request, timeout=GetUploadTimeout(symbol))
488
489
490def PerformSymbolFileUpload(symbol, upload_url, failures,
491 product_name='ChromeOS'):
492 """Upload the symbols to the crash server
493
494 Args:
495 symbol: An instance of SymbolFile.
496 upload_url: URL of crash server to upload too.
497 failures: Tracker for total upload failures.
498 product_name: A string for stats purposes. Usually 'ChromeOS' or 'Android'.
499
500 Returns:
501 The same instance of SymbolFile (possibly with status UPLOADED).
502 """
503 if (failures.value < MAX_TOTAL_ERRORS_FOR_RETRY and
504 symbol.status == SymbolFile.INITIAL):
505 # Keeps us from DoS-ing the symbol server.
506 time.sleep(SLEEP_DELAY)
507 logging.info('Uploading symbol_file: %s', symbol.display_path)
508 try:
509 # This command retries the upload multiple times with growing delays. We
510 # only consider the upload a failure if these retries fail.
511 cros_build_lib.TimedCommand(
512 retry_util.RetryException,
513 (urllib2.HTTPError, urllib2.URLError), MAX_RETRIES,
514 UploadSymbolFile,
515 upload_url, symbol, product_name,
516 sleep=INITIAL_RETRY_DELAY,
517 timed_log_msg=('upload of %10i bytes took %%(delta)s' %
518 symbol.FileSize()))
519 symbol.status = SymbolFile.UPLOADED
520 except urllib2.HTTPError as e:
521 logging.warning('could not upload: %s: HTTP %s: %s',
522 symbol.display_name, e.code, e.reason)
523 failures.increment()
524 except (urllib2.URLError, httplib.HTTPException, socket.error) as e:
525 logging.warning('could not upload: %s: %s', symbol.display_name, e)
526 failures.increment()
527
528 # We pass the symbol along, on both success and failure.
529 return symbol
530
531
532def ReportResults(symbols, failures, failed_list):
533 """Log a summary of the symbol uploading.
534
535 This has the side effect of fully consuming the symbols iterator.
536
537 Args:
538 symbols: An iterator of SymbolFiles to be uploaded.
539 failures: Tracker for total upload failures.
540 failed_list: A filename at which to write out a list of our failed uploads.
541 """
542 upload_failures = []
543 result_counts = {
544 SymbolFile.INITIAL: 0,
545 SymbolFile.UPLOADED: 0,
546 SymbolFile.DUPLICATE: 0,
547 }
548
549 for s in symbols:
550 result_counts[s.status] += 1
551 if s.status == SymbolFile.INITIAL:
552 upload_failures.append(s)
553
554 logging.info('Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.',
555 result_counts)
556
557 if result_counts[SymbolFile.INITIAL]:
558 logging.PrintBuildbotStepWarnings()
559 logging.warning('%d non-recoverable upload errors caused %d skipped'
560 ' uploads.',
561 failures.value, result_counts[SymbolFile.INITIAL])
562
563 if failed_list is not None:
564 with open(failed_list, 'w') as fl:
565 for s in upload_failures:
566 fl.write('%s\n' % s.display_path)
567
568
569def UploadSymbols(sym_paths, upload_url, product_name, dedupe_namespace=None,
570 failed_list=None, upload_limit=None, strip_cfi=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400571 """Upload all the generated symbols for |board| to the crash server
572
573 Args:
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500574 sym_paths: Specific symbol files (or dirs of sym files) to upload,
575 otherwise search |breakpad_dir|
Don Garretta28be6d2016-06-16 18:09:35 -0700576 upload_url: URL of crash server to upload too.
577 product_name: A string for crash server stats purposes.
578 Usually 'ChromeOS' or 'Android'.
579 dedupe_namespace: None for no deduping, or string namespace in isolate.
580 failed_list: A filename at which to write out a list of our failed uploads.
581 upload_limit: Integer listing how many files to upload. None for no limit.
582 strip_cfi: File size at which we strip out CFI data. None for no limit.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500583
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400584 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400585 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400586 """
Don Garretta28be6d2016-06-16 18:09:35 -0700587 # Note: This method looks like each step of processing is performed
588 # sequentially for all SymbolFiles, but instead each step is a generator that
589 # produces the next iteration only when it's read. This means that (except for
590 # some batching) each SymbolFile goes through all of these steps before the
591 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400592
Don Garretta28be6d2016-06-16 18:09:35 -0700593 # Track unrecoverable errors, so we don't keep trying for too long.
594 failures = Counter()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400595
Don Garretta28be6d2016-06-16 18:09:35 -0700596 # This is used to hold striped
597 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
598 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500599
Don Garrett1bc1e102016-07-06 17:06:10 -0700600 # Sort all of our symbols so the largest ones (probably the most important)
601 # are processed first.
602 symbols = list(symbols)
603 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
604
Don Garretta28be6d2016-06-16 18:09:35 -0700605 if upload_limit is not None:
606 # Restrict symbols processed to the limit.
607 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400608
Don Garretta28be6d2016-06-16 18:09:35 -0700609 # Strip CFI, if needed.
610 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500611
Don Garretta28be6d2016-06-16 18:09:35 -0700612 # Skip duplicates.
613 if dedupe_namespace:
614 symbols = FindDuplicates(symbols, dedupe_namespace)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500615
Don Garretta28be6d2016-06-16 18:09:35 -0700616 # Perform uploads
617 symbols = (PerformSymbolFileUpload(s, upload_url, failures, product_name)
618 for s in symbols)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400619
Don Garretta28be6d2016-06-16 18:09:35 -0700620 # Record for future deduping.
621 if dedupe_namespace:
622 symbols = PostForDeduplication(symbols, dedupe_namespace)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400623
Don Garretta28be6d2016-06-16 18:09:35 -0700624 # Log the final results, and consume the symbols generator fully.
625 ReportResults(symbols, failures, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500626
Don Garretta28be6d2016-06-16 18:09:35 -0700627 return failures.value
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400628
629
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400630def main(argv):
631 parser = commandline.ArgumentParser(description=__doc__)
632
Don Garretta28be6d2016-06-16 18:09:35 -0700633 # TODO: Make sym_paths, breakpad_root, and root exclusive.
634
Mike Frysingerd41938e2014-02-10 06:37:55 -0500635 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
636 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400637 parser.add_argument('--board', default=None,
Don Garrett747cc4b2015-10-07 14:48:48 -0700638 help='Used to find default breakpad_root.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400639 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500640 help='full path to the breakpad symbol directory')
641 parser.add_argument('--root', type='path', default=None,
642 help='full path to the chroot dir')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400643 parser.add_argument('--official_build', action='store_true', default=False,
644 help='point to official symbol server')
Mike Frysinger38647542014-09-12 18:15:39 -0700645 parser.add_argument('--server', type=str, default=None,
646 help='URI for custom symbol server')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400647 parser.add_argument('--regenerate', action='store_true', default=False,
648 help='regenerate all symbols')
Don Garretta28be6d2016-06-16 18:09:35 -0700649 parser.add_argument('--upload-limit', type=int,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400650 help='only upload # number of symbols')
651 parser.add_argument('--strip_cfi', type=int,
Don Garretta28be6d2016-06-16 18:09:35 -0700652 default=DEFAULT_FILE_LIMIT,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400653 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500654 parser.add_argument('--failed-list', type='path',
655 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500656 parser.add_argument('--dedupe', action='store_true', default=False,
657 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400658 parser.add_argument('--yes', action='store_true', default=False,
659 help='answer yes to all prompts')
Don Garrett747cc4b2015-10-07 14:48:48 -0700660 parser.add_argument('--product_name', type=str, default='ChromeOS',
661 help='Produce Name for breakpad stats.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400662
663 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500664 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400665
Don Garretta28be6d2016-06-16 18:09:35 -0700666 # Figure out the symbol files/directories to upload.
667 if opts.sym_paths:
668 sym_paths = opts.sym_paths
669 elif opts.breakpad_root:
670 sym_paths = [opts.breakpad_root]
671 elif opts.root:
672 if not opts.board:
673 raise ValueError('--board must be set if --root is used.')
674 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(opts.board)
675 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip('/'))]
676 else:
677 raise ValueError('--sym_paths, --breakpad_root, or --root must be set.')
678
Don Garrett747cc4b2015-10-07 14:48:48 -0700679 if opts.sym_paths or opts.breakpad_root:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400680 if opts.regenerate:
Don Garrett747cc4b2015-10-07 14:48:48 -0700681 cros_build_lib.Die('--regenerate may not be used with specific files, '
682 'or breakpad_root')
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400683 else:
684 if opts.board is None:
685 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400686
Don Garretta28be6d2016-06-16 18:09:35 -0700687 # Figure out the dedupe namespace.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500688 dedupe_namespace = None
689 if opts.dedupe:
Don Garretta28be6d2016-06-16 18:09:35 -0700690 if opts.official_build:
Don Garrett747cc4b2015-10-07 14:48:48 -0700691 dedupe_namespace = OFFICIAL_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500692 else:
Don Garrett747cc4b2015-10-07 14:48:48 -0700693 dedupe_namespace = STAGING_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500694
Don Garretta28be6d2016-06-16 18:09:35 -0700695 # Figure out which crash server to upload too.
696 upload_url = opts.server
697 if not upload_url:
698 if opts.official_build:
699 upload_url = OFFICIAL_UPLOAD_URL
700 else:
701 logging.warning('unofficial builds upload to the staging server')
702 upload_url = STAGING_UPLOAD_URL
703
704 # Confirm we really want the long upload.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400705 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500706 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400707 Uploading symbols for an entire Chromium OS build is really only
708 necessary for release builds and in a few cases for developers
709 to debug problems. It will take considerable time to run. For
710 developer debugging purposes, consider instead passing specific
711 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500712 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400713 if not cros_build_lib.BooleanPrompt(
714 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500715 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400716 cros_build_lib.Die('better safe than sorry')
717
718 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700719
720 # Regenerate symbols from binaries.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400721 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400722 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
723 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400724
Don Garretta28be6d2016-06-16 18:09:35 -0700725 # Do the upload.
726 ret += UploadSymbols(
727 sym_paths=sym_paths,
728 upload_url=upload_url,
729 product_name=opts.product_name,
730 dedupe_namespace=dedupe_namespace,
731 failed_list=opts.failed_list,
732 upload_limit=opts.upload_limit,
733 strip_cfi=opts.strip_cfi)
734
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400735 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700736 logging.error('encountered %i problem(s)', ret)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400737 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
738 # return 0 in case we are a multiple of the mask.
Don Garretta28be6d2016-06-16 18:09:35 -0700739 return 1