blob: 5e3bc2cea3c633832b8b581989bda73d048f433b [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.
Ryo Hashimotof66d8ca2016-09-07 15:45:13 +090060CRASH_SERVER_FILE_LIMIT = 500 * 1024 * 1024
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040061# 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
Don Garretta28be6d2016-06-16 18:09:35 -0700113def BatchGenerator(iterator, batch_size):
114 """Given an iterator, break into lists of size batch_size.
Fang Dengba680462015-08-16 20:34:11 -0700115
Don Garretta28be6d2016-06-16 18:09:35 -0700116 The result is a generator, that will only read in as many inputs as needed for
117 the current batch. The final result can be smaller than batch_size.
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700118 """
Don Garretta28be6d2016-06-16 18:09:35 -0700119 batch = []
120 for i in iterator:
121 batch.append(i)
122 if len(batch) >= batch_size:
123 yield batch
124 batch = []
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700125
Don Garretta28be6d2016-06-16 18:09:35 -0700126 if batch:
127 # if there was anything left in the final batch, yield it.
128 yield batch
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500129
130
Mike Frysingerd41938e2014-02-10 06:37:55 -0500131def IsTarball(path):
132 """Guess if this is a tarball based on the filename."""
133 parts = path.split('.')
134 if len(parts) <= 1:
135 return False
136
137 if parts[-1] == 'tar':
138 return True
139
140 if parts[-2] == 'tar':
141 return parts[-1] in ('bz2', 'gz', 'xz')
142
143 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
144
145
Don Garretta28be6d2016-06-16 18:09:35 -0700146class SymbolFile(object):
147 """This class represents the state of a symbol file during processing.
148
149 Properties:
150 display_path: Name of symbol file that should be consistent between builds.
151 file_name: Transient path of the symbol file.
152 header: ReadSymsHeader output. Dict with assorted meta-data.
153 status: INITIAL, DUPLICATE, or UPLOADED based on status of processing.
154 dedupe_item: None or instance of DedupeItem for this symbol file.
155 dedupe_push_state: Opaque value to return to dedupe code for file.
156 display_name: Read only friendly (short) file name for logging.
157 file_size: Read only size of the symbol file.
158 """
159 INITIAL = 'initial'
160 DUPLICATE = 'duplicate'
161 UPLOADED = 'uploaded'
Don Garrettdeb2e032016-07-06 16:44:14 -0700162 ERROR = 'error'
Don Garretta28be6d2016-06-16 18:09:35 -0700163
164 def __init__(self, display_path, file_name):
165 """An instance of this class represents a symbol file over time.
166
167 Args:
168 display_path: A unique/persistent between builds name to present to the
169 crash server. It is the file name, relative to where it
170 came from (tarball, breakpad dir, etc).
171 file_name: A the current location of the symbol file.
172 """
173 self.display_path = display_path
174 self.file_name = file_name
175 self.header = cros_generate_breakpad_symbols.ReadSymsHeader(file_name)
176 self.status = SymbolFile.INITIAL
177 self.dedupe_item = None
178 self.dedupe_push_state = None
179
180 @property
181 def display_name(self):
182 return os.path.basename(self.display_path)
183
184 def FileSize(self):
185 return os.path.getsize(self.file_name)
186
187
188class DedupeItem(isolateserver.BufferItem):
189 """Turn a SymbolFile into an isolateserver.Item"""
190
191 ALGO = hashlib.sha1
192
193 def __init__(self, symbol):
194 super(DedupeItem, self).__init__(str(symbol.header), self.ALGO)
195 self.symbol = symbol
196
197
198def FindSymbolFiles(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500199 """Locate symbol files in |paths|
200
Don Garretta28be6d2016-06-16 18:09:35 -0700201 This returns SymbolFile objects that contain file references which are valid
202 after this exits. Those files may exist externally, or be created in the
203 tempdir (say, when expanding tarballs). The caller must not consider
204 SymbolFile's valid after tempdir is cleaned up.
205
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500206 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700207 tempdir: Path to use for temporary files.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500208 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500209 Dirs are searched for files that end in ".sym". Urls are fetched and then
210 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500211
Don Garretta28be6d2016-06-16 18:09:35 -0700212 Yields:
213 A SymbolFile for every symbol file found in paths.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500214 """
Gilad Arnold83233ed2015-05-08 12:12:13 -0700215 cache_dir = path_util.GetCacheDir()
Mike Frysingere847efd2015-01-08 03:57:24 -0500216 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
217 tar_cache = cache.TarballCache(common_path)
218
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500219 for p in paths:
Don Garrett25f309a2014-03-19 14:02:12 -0700220 # Pylint is confused about members of ParseResult.
Don Garrettf8bf7842014-03-20 17:03:42 -0700221
Mike Frysingerd41938e2014-02-10 06:37:55 -0500222 o = urlparse.urlparse(p)
Don Garrettf8bf7842014-03-20 17:03:42 -0700223 if o.scheme: # pylint: disable=E1101
Mike Frysingerd41938e2014-02-10 06:37:55 -0500224 # Support globs of filenames.
225 ctx = gs.GSContext()
226 for p in ctx.LS(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700227 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500228 o = urlparse.urlparse(p)
Don Garrettf8bf7842014-03-20 17:03:42 -0700229 key = ('%s%s' % (o.netloc, o.path)).split('/') # pylint: disable=E1101
Mike Frysingerd41938e2014-02-10 06:37:55 -0500230 # The common cache will not be LRU, removing the need to hold a read
231 # lock on the cached gsutil.
232 ref = tar_cache.Lookup(key)
233 try:
234 ref.SetDefault(p)
235 except cros_build_lib.RunCommandError as e:
Ralph Nathan446aee92015-03-23 14:44:56 -0700236 logging.warning('ignoring %s\n%s', p, e)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500237 continue
Don Garretta28be6d2016-06-16 18:09:35 -0700238 for p in FindSymbolFiles(tempdir, [ref.path]):
Mike Frysingerd41938e2014-02-10 06:37:55 -0500239 yield p
240
241 elif os.path.isdir(p):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500242 for root, _, files in os.walk(p):
243 for f in files:
244 if f.endswith('.sym'):
Don Garretta28be6d2016-06-16 18:09:35 -0700245 # If p is '/tmp/foo' and filename is '/tmp/foo/bar/bar.sym',
246 # display_path = 'bar/bar.sym'
247 filename = os.path.join(root, f)
248 yield SymbolFile(display_path=filename[len(p):].lstrip('/'),
249 file_name=filename)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500250
251 elif IsTarball(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700252 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500253 tardir = tempfile.mkdtemp(dir=tempdir)
254 cache.Untar(os.path.realpath(p), tardir)
Don Garretta28be6d2016-06-16 18:09:35 -0700255 for p in FindSymbolFiles(tardir, [tardir]):
Mike Frysingerd41938e2014-02-10 06:37:55 -0500256 yield p
257
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500258 else:
Don Garretta28be6d2016-06-16 18:09:35 -0700259 yield SymbolFile(display_path=p, file_name=p)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500260
261
Don Garretta28be6d2016-06-16 18:09:35 -0700262def AdjustSymbolFileSize(symbol, tempdir, file_limit):
263 """Examine symbols files for size problems, and reduce if needed.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500264
Don Garretta28be6d2016-06-16 18:09:35 -0700265 If the symbols size is too big, strip out the call frame info. The CFI
266 is unnecessary for 32bit x86 targets where the frame pointer is used (as
267 all of ours have) and it accounts for over half the size of the symbols
268 uploaded.
269
270 Stripped files will be created inside tempdir, and will be the callers
271 responsibility to clean up.
272
273 We also warn, if a symbols file is still too large after stripping.
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500274
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500275 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700276 symbol: SymbolFile instance to be examined and modified as needed..
277 tempdir: A temporary directory we can create files in that the caller will
278 clean up.
279 file_limit: We only strip files which are larger than this limit.
280
281 Returns:
282 SymbolFile instance (original or modified as needed)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500283 """
Don Garretta28be6d2016-06-16 18:09:35 -0700284 file_size = symbol.FileSize()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500285
Don Garretta28be6d2016-06-16 18:09:35 -0700286 if file_limit and symbol.FileSize() > file_limit:
287 with tempfile.NamedTemporaryFile(
288 prefix='upload_symbols', bufsize=0,
289 dir=tempdir, delete=False) as temp_sym_file:
290
291 temp_sym_file.writelines(
292 [x for x in open(symbol.file_name, 'rb').readlines()
293 if not x.startswith('STACK CFI')]
294 )
295
296 original_file_size = file_size
297 symbol.file_name = temp_sym_file.name
298 file_size = symbol.FileSize()
299
300 logging.warning('stripped CFI for %s reducing size %s > %s',
301 symbol.display_name, original_file_size, file_size)
302
303 # Hopefully the crash server will let it through. But it probably won't.
304 # Not sure what the best answer is in this case.
305 if file_size >= CRASH_SERVER_FILE_LIMIT:
306 logging.PrintBuildbotStepWarnings()
307 logging.warning('upload file %s is awfully large, risking rejection by '
308 'the symbol server (%s > %s)', symbol.display_path,
309 file_size, CRASH_SERVER_FILE_LIMIT)
310
311 return symbol
312
313def OpenDeduplicateConnection(dedupe_namespace):
314 """Open a connection to the isolate server for Dedupe use.
315
316 Args:
317 dedupe_namespace: String id for the comparison namespace.
318
319 Returns:
320 Connection proxy, or None on failure.
321 """
322 try:
323 with timeout_util.Timeout(DEDUPE_TIMEOUT):
324 return isolateserver.get_storage_api(constants.ISOLATESERVER,
325 dedupe_namespace)
326 except Exception:
327 logging.warning('initializing isolate server connection failed',
328 exc_info=True)
329 return None
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500330
331
Don Garretta28be6d2016-06-16 18:09:35 -0700332def FindDuplicates(symbols, dedupe_namespace):
333 """Mark symbol files we've already uploaded as duplicates.
334
335 Using the swarming service, ask it to tell us which symbol files we've already
336 uploaded in previous runs and/or by other bots. If the query fails for any
337 reason, we'll just upload all symbols. This is fine as the symbol server will
338 do the right thing and this phase is purely an optimization.
339
340 Args:
341 symbols: An iterable of SymbolFiles to be uploaded.
342 dedupe_namespace: String id for the comparison namespace.
343
344 Yields:
345 All SymbolFiles from symbols, but duplicates have status updated to
346 DUPLICATE.
347 """
348 storage_query = OpenDeduplicateConnection(dedupe_namespace)
349
350 # We query isolate in batches, to reduce the number of network queries.
351 for batch in BatchGenerator(symbols, DEDUPE_LIMIT):
352 query_results = None
353
354 if storage_query:
355 # Convert SymbolFiles into DedupeItems.
356 items = [DedupeItem(x) for x in batch]
357 for item in items:
358 item.prepare(DedupeItem.ALGO)
359
360 # Look for duplicates.
361 try:
362 with timeout_util.Timeout(DEDUPE_TIMEOUT):
363 query_results = storage_query.contains(items)
364 except Exception:
365 logging.warning('talking to dedupe server failed', exc_info=True)
366 storage_query = None
367
368 if query_results is not None:
369 for b in batch:
370 b.status = SymbolFile.DUPLICATE
371
372 # Only the non-duplicates appear in the query_results.
373 for item, push_state in query_results.iteritems():
374 # Remember the dedupe state, so we can mark the symbol as uploaded
375 # later on.
376 item.symbol.status = SymbolFile.INITIAL
377 item.symbol.dedupe_item = item
378 item.symbol.dedupe_push_state = push_state
379
380 # Yield all symbols we haven't shown to be duplicates.
381 for b in batch:
382 if b.status == SymbolFile.DUPLICATE:
383 logging.debug('Found duplicate: %s', b.display_name)
384 yield b
385
386
387def PostForDeduplication(symbols, dedupe_namespace):
388 """Send a symbol file to the swarming service
389
390 Notify the isolate service of a successful upload. If the notification fails
391 for any reason, we ignore it. We don't care as it just means we'll upload it
392 again later on, and the symbol server will handle that graciously.
393
394 Args:
395 symbols: An iterable of SymbolFiles to be uploaded.
396 dedupe_namespace: String id for the comparison namespace.
397
398 Yields:
399 Each symbol from symbols, unmodified.
400 """
401 storage_query = OpenDeduplicateConnection(dedupe_namespace)
402
403 for s in symbols:
404 # If we can talk to isolate, and we uploaded this symbol, and we
405 # queried for it's presence before, upload to isolate now.
406
407 if storage_query and s.status == SymbolFile.UPLOADED and s.dedupe_item:
408 s.dedupe_item.prepare(DedupeItem.ALGO)
409 try:
410 with timeout_util.Timeout(DEDUPE_NOTIFY_TIMEOUT):
411 storage_query.push(s.dedupe_item, s.dedupe_push_state,
412 s.dedupe_item.content())
413 logging.info('sent %s', s.display_name)
414 except Exception:
415 logging.warning('posting %s to dedupe server failed',
416 os.path.basename(s.display_path), exc_info=True)
417 storage_query = None
418
419 yield s
420
421
422def GetUploadTimeout(symbol):
423 """How long to wait for a specific file to upload to the crash server.
424
425 This is a function largely to make unittesting easier.
426
427 Args:
428 symbol: A SymbolFile instance.
429
430 Returns:
431 Timeout length (in seconds)
432 """
433 # Scale the timeout based on the filesize.
434 return max(symbol.FileSize() / UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
435
436
437def UploadSymbolFile(upload_url, symbol, product_name):
438 """Upload a symbol file to the crash server.
439
440 The upload is a multipart/form-data POST with the following parameters:
441 code_file: the basename of the module, e.g. "app"
442 code_identifier: the module file's identifier
443 debug_file: the basename of the debugging file, e.g. "app"
444 debug_identifier: the debug file's identifier, usually consisting of
445 the guid and age embedded in the pdb, e.g.
446 "11111111BBBB3333DDDD555555555555F"
447 version: the file version of the module, e.g. "1.2.3.4"
448 product: HTTP-friendly product name
449 os: the operating system that the module was built for
450 cpu: the CPU that the module was built for
451 symbol_file: the contents of the breakpad-format symbol file
452
453 Args:
454 upload_url: The crash URL to POST the |sym_file| to
455 symbol: A SymbolFile instance.
456 product_name: A string for stats purposes. Usually 'ChromeOS' or 'Android'.
457 """
458 fields = (
459 ('code_file', symbol.header.name),
460 ('debug_file', symbol.header.name),
461 ('debug_identifier', symbol.header.id.replace('-', '')),
462 # The product/version fields are used by the server only for statistic
463 # purposes. They do not impact symbolization, so they're safe to set
464 # to any value all the time.
465 # In this case, we use it to help see the load our build system is
466 # placing on the server.
467 # Not sure what to set for the version. Maybe the git sha1 of this file.
468 # Note: the server restricts this to 30 chars.
469 #('version', None),
470 ('product', product_name),
471 ('os', symbol.header.os),
472 ('cpu', symbol.header.cpu),
473 poster.encode.MultipartParam.from_file('symbol_file', symbol.file_name),
474 )
475
476 data, headers = poster.encode.multipart_encode(fields)
477 request = urllib2.Request(upload_url, data, headers)
478 request.add_header('User-agent', 'chromite.upload_symbols')
479 urllib2.urlopen(request, timeout=GetUploadTimeout(symbol))
480
481
Don Garrettdeb2e032016-07-06 16:44:14 -0700482def PerformSymbolsFileUpload(symbols, upload_url, product_name='ChromeOS'):
Don Garretta28be6d2016-06-16 18:09:35 -0700483 """Upload the symbols to the crash server
484
485 Args:
Don Garrettdeb2e032016-07-06 16:44:14 -0700486 symbols: An iterable of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700487 upload_url: URL of crash server to upload too.
488 failures: Tracker for total upload failures.
489 product_name: A string for stats purposes. Usually 'ChromeOS' or 'Android'.
490
Don Garrettdeb2e032016-07-06 16:44:14 -0700491 Yields:
492 Each symbol from symbols, perhaps modified.
Don Garretta28be6d2016-06-16 18:09:35 -0700493 """
Don Garrettdeb2e032016-07-06 16:44:14 -0700494 failures = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700495
Don Garrettdeb2e032016-07-06 16:44:14 -0700496 for s in symbols:
497 if (failures < MAX_TOTAL_ERRORS_FOR_RETRY and
498 s.status in (SymbolFile.INITIAL, SymbolFile.ERROR)):
499 # Keeps us from DoS-ing the symbol server.
500 time.sleep(SLEEP_DELAY)
501 logging.info('Uploading symbol_file: %s', s.display_path)
502 try:
503 # This command retries the upload multiple times with growing delays. We
504 # only consider the upload a failure if these retries fail.
505 cros_build_lib.TimedCommand(
506 retry_util.RetryException,
507 (urllib2.HTTPError, urllib2.URLError), MAX_RETRIES,
508 UploadSymbolFile,
509 upload_url, s, product_name,
510 sleep=INITIAL_RETRY_DELAY,
511 timed_log_msg=('upload of %10i bytes took %%(delta)s' %
512 s.FileSize()))
513 s.status = SymbolFile.UPLOADED
514 except urllib2.HTTPError as e:
515 logging.warning('could not upload: %s: HTTP %s: %s',
516 s.display_name, e.code, e.reason)
517 s.status = SymbolFile.ERROR
518 failures += 1
519 except (urllib2.URLError, httplib.HTTPException, socket.error) as e:
520 logging.warning('could not upload: %s: %s', s.display_name, e)
521 s.status = SymbolFile.ERROR
522 failures += 1
523
524 # We pass the symbol along, on both success and failure.
525 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700526
527
Don Garrettdeb2e032016-07-06 16:44:14 -0700528def ReportResults(symbols, failed_list):
Don Garretta28be6d2016-06-16 18:09:35 -0700529 """Log a summary of the symbol uploading.
530
531 This has the side effect of fully consuming the symbols iterator.
532
533 Args:
534 symbols: An iterator of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700535 failed_list: A filename at which to write out a list of our failed uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700536
537 Returns:
538 The number of symbols not uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700539 """
540 upload_failures = []
541 result_counts = {
542 SymbolFile.INITIAL: 0,
543 SymbolFile.UPLOADED: 0,
544 SymbolFile.DUPLICATE: 0,
Don Garrettdeb2e032016-07-06 16:44:14 -0700545 SymbolFile.ERROR: 0,
Don Garretta28be6d2016-06-16 18:09:35 -0700546 }
547
548 for s in symbols:
549 result_counts[s.status] += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700550 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700551 upload_failures.append(s)
552
553 logging.info('Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.',
554 result_counts)
555
Don Garrettdeb2e032016-07-06 16:44:14 -0700556 if result_counts[SymbolFile.INITIAL] or result_counts[SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700557 logging.PrintBuildbotStepWarnings()
558 logging.warning('%d non-recoverable upload errors caused %d skipped'
559 ' uploads.',
Don Garrettdeb2e032016-07-06 16:44:14 -0700560 result_counts[SymbolFile.ERROR],
561 result_counts[SymbolFile.INITIAL])
Don Garretta28be6d2016-06-16 18:09:35 -0700562
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
Don Garrettdeb2e032016-07-06 16:44:14 -0700568 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
569
Don Garretta28be6d2016-06-16 18:09:35 -0700570
571def UploadSymbols(sym_paths, upload_url, product_name, dedupe_namespace=None,
572 failed_list=None, upload_limit=None, strip_cfi=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400573 """Upload all the generated symbols for |board| to the crash server
574
575 Args:
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500576 sym_paths: Specific symbol files (or dirs of sym files) to upload,
577 otherwise search |breakpad_dir|
Don Garretta28be6d2016-06-16 18:09:35 -0700578 upload_url: URL of crash server to upload too.
579 product_name: A string for crash server stats purposes.
580 Usually 'ChromeOS' or 'Android'.
581 dedupe_namespace: None for no deduping, or string namespace in isolate.
582 failed_list: A filename at which to write out a list of our failed uploads.
583 upload_limit: Integer listing how many files to upload. None for no limit.
584 strip_cfi: File size at which we strip out CFI data. None for no limit.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500585
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400586 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400587 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400588 """
Don Garretta28be6d2016-06-16 18:09:35 -0700589 # Note: This method looks like each step of processing is performed
590 # sequentially for all SymbolFiles, but instead each step is a generator that
591 # produces the next iteration only when it's read. This means that (except for
592 # some batching) each SymbolFile goes through all of these steps before the
593 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400594
Don Garretta28be6d2016-06-16 18:09:35 -0700595 # This is used to hold striped
596 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
597 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500598
Don Garrett1bc1e102016-07-06 17:06:10 -0700599 # Sort all of our symbols so the largest ones (probably the most important)
600 # are processed first.
601 symbols = list(symbols)
602 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
603
Don Garretta28be6d2016-06-16 18:09:35 -0700604 if upload_limit is not None:
605 # Restrict symbols processed to the limit.
606 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400607
Don Garretta28be6d2016-06-16 18:09:35 -0700608 # Strip CFI, if needed.
609 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500610
Don Garretta28be6d2016-06-16 18:09:35 -0700611 # Skip duplicates.
612 if dedupe_namespace:
613 symbols = FindDuplicates(symbols, dedupe_namespace)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500614
Don Garretta28be6d2016-06-16 18:09:35 -0700615 # Perform uploads
Don Garrettdeb2e032016-07-06 16:44:14 -0700616 symbols = PerformSymbolsFileUpload(symbols, upload_url, product_name)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400617
Don Garretta28be6d2016-06-16 18:09:35 -0700618 # Record for future deduping.
619 if dedupe_namespace:
620 symbols = PostForDeduplication(symbols, dedupe_namespace)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400621
Don Garretta28be6d2016-06-16 18:09:35 -0700622 # Log the final results, and consume the symbols generator fully.
Don Garrettdeb2e032016-07-06 16:44:14 -0700623 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500624
Don Garrettdeb2e032016-07-06 16:44:14 -0700625 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400626
627
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400628def main(argv):
629 parser = commandline.ArgumentParser(description=__doc__)
630
Don Garretta28be6d2016-06-16 18:09:35 -0700631 # TODO: Make sym_paths, breakpad_root, and root exclusive.
632
Mike Frysingerd41938e2014-02-10 06:37:55 -0500633 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
634 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400635 parser.add_argument('--board', default=None,
Don Garrett747cc4b2015-10-07 14:48:48 -0700636 help='Used to find default breakpad_root.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400637 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500638 help='full path to the breakpad symbol directory')
639 parser.add_argument('--root', type='path', default=None,
640 help='full path to the chroot dir')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400641 parser.add_argument('--official_build', action='store_true', default=False,
642 help='point to official symbol server')
Mike Frysinger38647542014-09-12 18:15:39 -0700643 parser.add_argument('--server', type=str, default=None,
644 help='URI for custom symbol server')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400645 parser.add_argument('--regenerate', action='store_true', default=False,
646 help='regenerate all symbols')
Don Garretta28be6d2016-06-16 18:09:35 -0700647 parser.add_argument('--upload-limit', type=int,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400648 help='only upload # number of symbols')
649 parser.add_argument('--strip_cfi', type=int,
Don Garretta28be6d2016-06-16 18:09:35 -0700650 default=DEFAULT_FILE_LIMIT,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400651 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500652 parser.add_argument('--failed-list', type='path',
653 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500654 parser.add_argument('--dedupe', action='store_true', default=False,
655 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400656 parser.add_argument('--yes', action='store_true', default=False,
657 help='answer yes to all prompts')
Don Garrett747cc4b2015-10-07 14:48:48 -0700658 parser.add_argument('--product_name', type=str, default='ChromeOS',
659 help='Produce Name for breakpad stats.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400660
661 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500662 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400663
Don Garretta28be6d2016-06-16 18:09:35 -0700664 # Figure out the symbol files/directories to upload.
665 if opts.sym_paths:
666 sym_paths = opts.sym_paths
667 elif opts.breakpad_root:
668 sym_paths = [opts.breakpad_root]
669 elif opts.root:
670 if not opts.board:
671 raise ValueError('--board must be set if --root is used.')
672 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(opts.board)
673 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip('/'))]
674 else:
675 raise ValueError('--sym_paths, --breakpad_root, or --root must be set.')
676
Don Garrett747cc4b2015-10-07 14:48:48 -0700677 if opts.sym_paths or opts.breakpad_root:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400678 if opts.regenerate:
Don Garrett747cc4b2015-10-07 14:48:48 -0700679 cros_build_lib.Die('--regenerate may not be used with specific files, '
680 'or breakpad_root')
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400681 else:
682 if opts.board is None:
683 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400684
Don Garretta28be6d2016-06-16 18:09:35 -0700685 # Figure out the dedupe namespace.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500686 dedupe_namespace = None
687 if opts.dedupe:
Don Garretta28be6d2016-06-16 18:09:35 -0700688 if opts.official_build:
Don Garrett747cc4b2015-10-07 14:48:48 -0700689 dedupe_namespace = OFFICIAL_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500690 else:
Don Garrett747cc4b2015-10-07 14:48:48 -0700691 dedupe_namespace = STAGING_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500692
Don Garretta28be6d2016-06-16 18:09:35 -0700693 # Figure out which crash server to upload too.
694 upload_url = opts.server
695 if not upload_url:
696 if opts.official_build:
697 upload_url = OFFICIAL_UPLOAD_URL
698 else:
699 logging.warning('unofficial builds upload to the staging server')
700 upload_url = STAGING_UPLOAD_URL
701
702 # Confirm we really want the long upload.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400703 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500704 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400705 Uploading symbols for an entire Chromium OS build is really only
706 necessary for release builds and in a few cases for developers
707 to debug problems. It will take considerable time to run. For
708 developer debugging purposes, consider instead passing specific
709 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500710 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400711 if not cros_build_lib.BooleanPrompt(
712 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500713 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400714 cros_build_lib.Die('better safe than sorry')
715
716 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700717
718 # Regenerate symbols from binaries.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400719 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400720 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
721 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400722
Don Garretta28be6d2016-06-16 18:09:35 -0700723 # Do the upload.
724 ret += UploadSymbols(
725 sym_paths=sym_paths,
726 upload_url=upload_url,
727 product_name=opts.product_name,
728 dedupe_namespace=dedupe_namespace,
729 failed_list=opts.failed_list,
730 upload_limit=opts.upload_limit,
731 strip_cfi=opts.strip_cfi)
732
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400733 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700734 logging.error('encountered %i problem(s)', ret)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400735 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
736 # return 0 in case we are a multiple of the mask.
Don Garretta28be6d2016-06-16 18:09:35 -0700737 return 1