blob: 232c55fcf198d49d3628f9be4ab451954a444f09 [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
David Jamesc93e6a4d2014-01-13 11:37:36 -080056from chromite.lib import retry_util
Mike Frysinger0c0efa22014-02-09 23:32:23 -050057from chromite.lib import timeout_util
Mike Frysinger69cb41d2013-08-11 20:08:19 -040058from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040059
Mike Frysinger0c0efa22014-02-09 23:32:23 -050060# Needs to be after chromite imports.
Mike Frysingerc5597f22014-11-27 15:39:15 -050061# We don't want to import the general keyring module as that will implicitly
62# try to import & connect to a dbus server. That's a waste of time.
63sys.modules['keyring'] = None
Mike Frysingerbbd1f112016-09-08 18:25:11 -040064# And our sys.path muckery confuses pylint.
65import isolateserver # pylint: disable=import-error
Mike Frysinger0c0efa22014-02-09 23:32:23 -050066
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040067
Don Garretta28be6d2016-06-16 18:09:35 -070068# We need this to run once per process. Do it at module import time as that
69# will let us avoid doing it inline at function call time (see UploadSymbolFile)
70# as that func might be called by the multiprocessing module which means we'll
71# do the opener logic multiple times overall. Plus, if you're importing this
72# module, it's a pretty good chance that you're going to need this.
73poster.streaminghttp.register_openers()
74
75
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040076# URLs used for uploading symbols.
77OFFICIAL_UPLOAD_URL = 'http://clients2.google.com/cr/symbol'
78STAGING_UPLOAD_URL = 'http://clients2.google.com/cr/staging_symbol'
79
80
81# The crash server rejects files that are this big.
Ryo Hashimotof66d8ca2016-09-07 15:45:13 +090082CRASH_SERVER_FILE_LIMIT = 500 * 1024 * 1024
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040083# Give ourselves a little breathing room from what the server expects.
84DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
85
86
Mike Frysinger0c0efa22014-02-09 23:32:23 -050087# The batch limit when talking to the dedup server. We avoid sending one at a
88# time as the round trip overhead will dominate. Conversely, we avoid sending
89# all at once so we can start uploading symbols asap -- the symbol server is a
90# bit slow and will take longer than anything else.
Mike Frysinger0c0efa22014-02-09 23:32:23 -050091DEDUPE_LIMIT = 100
92
93# How long to wait for the server to respond with the results. Note that the
94# larger the limit above, the larger this will need to be. So we give it ~1
95# second per item max.
96DEDUPE_TIMEOUT = DEDUPE_LIMIT
97
Don Garretta28be6d2016-06-16 18:09:35 -070098# How long to wait for the notification to finish (in seconds).
99DEDUPE_NOTIFY_TIMEOUT = 240
Mike Frysinger4dd462e2014-04-30 16:21:51 -0400100
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500101# The unique namespace in the dedupe server that only we use. Helps avoid
102# collisions with all the hashed values and unrelated content.
Don Garrett747cc4b2015-10-07 14:48:48 -0700103OFFICIAL_DEDUPE_NAMESPACE_TMPL = '%s-upload-symbols'
104STAGING_DEDUPE_NAMESPACE_TMPL = '%s-staging' % OFFICIAL_DEDUPE_NAMESPACE_TMPL
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500105
106
Mike Frysinger71046662014-09-12 18:15:15 -0700107# The minimum average rate (in bytes per second) that we expect to maintain
108# when uploading symbols. This has to allow for symbols that are up to
109# CRASH_SERVER_FILE_LIMIT in size.
110UPLOAD_MIN_RATE = CRASH_SERVER_FILE_LIMIT / (30 * 60)
111
112# The lowest timeout (in seconds) we'll allow. If the server is overloaded,
113# then there might be a delay in setting up the connection, not just with the
114# transfer. So even a small file might need a larger value.
115UPLOAD_MIN_TIMEOUT = 2 * 60
Mike Frysingercd78a082013-06-26 17:13:04 -0400116
117
Don Garrett7a793092016-07-06 16:50:27 -0700118# Sleep for 500ms in between uploads to avoid DoS'ing symbol server.
119SLEEP_DELAY = 0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400120
121
122# Number of seconds to wait before retrying an upload. The delay will double
123# for each subsequent retry of the same symbol file.
124INITIAL_RETRY_DELAY = 1
125
126# Allow up to 7 attempts to upload a symbol file (total delay may be
127# 1+2+4+8+16+32=63 seconds).
128MAX_RETRIES = 6
129
Mike Frysingereb753bf2013-11-22 16:05:35 -0500130# Number of total errors, before uploads are no longer attempted.
131# This is used to avoid lots of errors causing unreasonable delays.
Mike Frysingereb753bf2013-11-22 16:05:35 -0500132MAX_TOTAL_ERRORS_FOR_RETRY = 30
133
Don Garretta28be6d2016-06-16 18:09:35 -0700134
Don Garretta28be6d2016-06-16 18:09:35 -0700135def BatchGenerator(iterator, batch_size):
136 """Given an iterator, break into lists of size batch_size.
Fang Dengba680462015-08-16 20:34:11 -0700137
Don Garretta28be6d2016-06-16 18:09:35 -0700138 The result is a generator, that will only read in as many inputs as needed for
139 the current batch. The final result can be smaller than batch_size.
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700140 """
Don Garretta28be6d2016-06-16 18:09:35 -0700141 batch = []
142 for i in iterator:
143 batch.append(i)
144 if len(batch) >= batch_size:
145 yield batch
146 batch = []
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700147
Don Garretta28be6d2016-06-16 18:09:35 -0700148 if batch:
149 # if there was anything left in the final batch, yield it.
150 yield batch
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500151
152
Mike Frysingerd41938e2014-02-10 06:37:55 -0500153def IsTarball(path):
154 """Guess if this is a tarball based on the filename."""
155 parts = path.split('.')
156 if len(parts) <= 1:
157 return False
158
159 if parts[-1] == 'tar':
160 return True
161
162 if parts[-2] == 'tar':
163 return parts[-1] in ('bz2', 'gz', 'xz')
164
165 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
166
167
Don Garretta28be6d2016-06-16 18:09:35 -0700168class SymbolFile(object):
169 """This class represents the state of a symbol file during processing.
170
171 Properties:
172 display_path: Name of symbol file that should be consistent between builds.
173 file_name: Transient path of the symbol file.
174 header: ReadSymsHeader output. Dict with assorted meta-data.
175 status: INITIAL, DUPLICATE, or UPLOADED based on status of processing.
176 dedupe_item: None or instance of DedupeItem for this symbol file.
177 dedupe_push_state: Opaque value to return to dedupe code for file.
178 display_name: Read only friendly (short) file name for logging.
179 file_size: Read only size of the symbol file.
180 """
181 INITIAL = 'initial'
182 DUPLICATE = 'duplicate'
183 UPLOADED = 'uploaded'
Don Garrettdeb2e032016-07-06 16:44:14 -0700184 ERROR = 'error'
Don Garretta28be6d2016-06-16 18:09:35 -0700185
186 def __init__(self, display_path, file_name):
187 """An instance of this class represents a symbol file over time.
188
189 Args:
190 display_path: A unique/persistent between builds name to present to the
191 crash server. It is the file name, relative to where it
192 came from (tarball, breakpad dir, etc).
193 file_name: A the current location of the symbol file.
194 """
195 self.display_path = display_path
196 self.file_name = file_name
197 self.header = cros_generate_breakpad_symbols.ReadSymsHeader(file_name)
198 self.status = SymbolFile.INITIAL
199 self.dedupe_item = None
200 self.dedupe_push_state = None
201
202 @property
203 def display_name(self):
204 return os.path.basename(self.display_path)
205
206 def FileSize(self):
207 return os.path.getsize(self.file_name)
208
209
210class DedupeItem(isolateserver.BufferItem):
211 """Turn a SymbolFile into an isolateserver.Item"""
212
213 ALGO = hashlib.sha1
214
215 def __init__(self, symbol):
Mike Frysingerbbd1f112016-09-08 18:25:11 -0400216 isolateserver.BufferItem.__init__(self, str(symbol.header), self.ALGO)
Don Garretta28be6d2016-06-16 18:09:35 -0700217 self.symbol = symbol
218
219
220def FindSymbolFiles(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500221 """Locate symbol files in |paths|
222
Don Garretta28be6d2016-06-16 18:09:35 -0700223 This returns SymbolFile objects that contain file references which are valid
224 after this exits. Those files may exist externally, or be created in the
225 tempdir (say, when expanding tarballs). The caller must not consider
226 SymbolFile's valid after tempdir is cleaned up.
227
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500228 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700229 tempdir: Path to use for temporary files.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500230 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500231 Dirs are searched for files that end in ".sym". Urls are fetched and then
232 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500233
Don Garretta28be6d2016-06-16 18:09:35 -0700234 Yields:
235 A SymbolFile for every symbol file found in paths.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500236 """
Gilad Arnold83233ed2015-05-08 12:12:13 -0700237 cache_dir = path_util.GetCacheDir()
Mike Frysingere847efd2015-01-08 03:57:24 -0500238 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
239 tar_cache = cache.TarballCache(common_path)
240
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500241 for p in paths:
Don Garrett25f309a2014-03-19 14:02:12 -0700242 # Pylint is confused about members of ParseResult.
Don Garrettf8bf7842014-03-20 17:03:42 -0700243
Mike Frysingerd41938e2014-02-10 06:37:55 -0500244 o = urlparse.urlparse(p)
Don Garrettf8bf7842014-03-20 17:03:42 -0700245 if o.scheme: # pylint: disable=E1101
Mike Frysingerd41938e2014-02-10 06:37:55 -0500246 # Support globs of filenames.
247 ctx = gs.GSContext()
248 for p in ctx.LS(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700249 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500250 o = urlparse.urlparse(p)
Don Garrettf8bf7842014-03-20 17:03:42 -0700251 key = ('%s%s' % (o.netloc, o.path)).split('/') # pylint: disable=E1101
Mike Frysingerd41938e2014-02-10 06:37:55 -0500252 # The common cache will not be LRU, removing the need to hold a read
253 # lock on the cached gsutil.
254 ref = tar_cache.Lookup(key)
255 try:
256 ref.SetDefault(p)
257 except cros_build_lib.RunCommandError as e:
Ralph Nathan446aee92015-03-23 14:44:56 -0700258 logging.warning('ignoring %s\n%s', p, e)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500259 continue
Don Garretta28be6d2016-06-16 18:09:35 -0700260 for p in FindSymbolFiles(tempdir, [ref.path]):
Mike Frysingerd41938e2014-02-10 06:37:55 -0500261 yield p
262
263 elif os.path.isdir(p):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500264 for root, _, files in os.walk(p):
265 for f in files:
266 if f.endswith('.sym'):
Don Garretta28be6d2016-06-16 18:09:35 -0700267 # If p is '/tmp/foo' and filename is '/tmp/foo/bar/bar.sym',
268 # display_path = 'bar/bar.sym'
269 filename = os.path.join(root, f)
270 yield SymbolFile(display_path=filename[len(p):].lstrip('/'),
271 file_name=filename)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500272
273 elif IsTarball(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700274 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500275 tardir = tempfile.mkdtemp(dir=tempdir)
276 cache.Untar(os.path.realpath(p), tardir)
Don Garretta28be6d2016-06-16 18:09:35 -0700277 for p in FindSymbolFiles(tardir, [tardir]):
Mike Frysingerd41938e2014-02-10 06:37:55 -0500278 yield p
279
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500280 else:
Don Garretta28be6d2016-06-16 18:09:35 -0700281 yield SymbolFile(display_path=p, file_name=p)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500282
283
Don Garretta28be6d2016-06-16 18:09:35 -0700284def AdjustSymbolFileSize(symbol, tempdir, file_limit):
285 """Examine symbols files for size problems, and reduce if needed.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500286
Don Garretta28be6d2016-06-16 18:09:35 -0700287 If the symbols size is too big, strip out the call frame info. The CFI
288 is unnecessary for 32bit x86 targets where the frame pointer is used (as
289 all of ours have) and it accounts for over half the size of the symbols
290 uploaded.
291
292 Stripped files will be created inside tempdir, and will be the callers
293 responsibility to clean up.
294
295 We also warn, if a symbols file is still too large after stripping.
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500296
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500297 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700298 symbol: SymbolFile instance to be examined and modified as needed..
299 tempdir: A temporary directory we can create files in that the caller will
300 clean up.
301 file_limit: We only strip files which are larger than this limit.
302
303 Returns:
304 SymbolFile instance (original or modified as needed)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500305 """
Don Garretta28be6d2016-06-16 18:09:35 -0700306 file_size = symbol.FileSize()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500307
Don Garretta28be6d2016-06-16 18:09:35 -0700308 if file_limit and symbol.FileSize() > file_limit:
309 with tempfile.NamedTemporaryFile(
310 prefix='upload_symbols', bufsize=0,
311 dir=tempdir, delete=False) as temp_sym_file:
312
313 temp_sym_file.writelines(
314 [x for x in open(symbol.file_name, 'rb').readlines()
315 if not x.startswith('STACK CFI')]
316 )
317
318 original_file_size = file_size
319 symbol.file_name = temp_sym_file.name
320 file_size = symbol.FileSize()
321
322 logging.warning('stripped CFI for %s reducing size %s > %s',
323 symbol.display_name, original_file_size, file_size)
324
325 # Hopefully the crash server will let it through. But it probably won't.
326 # Not sure what the best answer is in this case.
327 if file_size >= CRASH_SERVER_FILE_LIMIT:
328 logging.PrintBuildbotStepWarnings()
329 logging.warning('upload file %s is awfully large, risking rejection by '
330 'the symbol server (%s > %s)', symbol.display_path,
331 file_size, CRASH_SERVER_FILE_LIMIT)
332
333 return symbol
334
335def OpenDeduplicateConnection(dedupe_namespace):
336 """Open a connection to the isolate server for Dedupe use.
337
338 Args:
339 dedupe_namespace: String id for the comparison namespace.
340
341 Returns:
342 Connection proxy, or None on failure.
343 """
344 try:
345 with timeout_util.Timeout(DEDUPE_TIMEOUT):
346 return isolateserver.get_storage_api(constants.ISOLATESERVER,
347 dedupe_namespace)
348 except Exception:
349 logging.warning('initializing isolate server connection failed',
350 exc_info=True)
351 return None
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500352
353
Don Garretta28be6d2016-06-16 18:09:35 -0700354def FindDuplicates(symbols, dedupe_namespace):
355 """Mark symbol files we've already uploaded as duplicates.
356
357 Using the swarming service, ask it to tell us which symbol files we've already
358 uploaded in previous runs and/or by other bots. If the query fails for any
359 reason, we'll just upload all symbols. This is fine as the symbol server will
360 do the right thing and this phase is purely an optimization.
361
362 Args:
363 symbols: An iterable of SymbolFiles to be uploaded.
364 dedupe_namespace: String id for the comparison namespace.
365
366 Yields:
367 All SymbolFiles from symbols, but duplicates have status updated to
368 DUPLICATE.
369 """
370 storage_query = OpenDeduplicateConnection(dedupe_namespace)
371
372 # We query isolate in batches, to reduce the number of network queries.
373 for batch in BatchGenerator(symbols, DEDUPE_LIMIT):
374 query_results = None
375
376 if storage_query:
377 # Convert SymbolFiles into DedupeItems.
378 items = [DedupeItem(x) for x in batch]
379 for item in items:
380 item.prepare(DedupeItem.ALGO)
381
382 # Look for duplicates.
383 try:
384 with timeout_util.Timeout(DEDUPE_TIMEOUT):
385 query_results = storage_query.contains(items)
386 except Exception:
387 logging.warning('talking to dedupe server failed', exc_info=True)
388 storage_query = None
389
390 if query_results is not None:
391 for b in batch:
392 b.status = SymbolFile.DUPLICATE
393
394 # Only the non-duplicates appear in the query_results.
395 for item, push_state in query_results.iteritems():
396 # Remember the dedupe state, so we can mark the symbol as uploaded
397 # later on.
398 item.symbol.status = SymbolFile.INITIAL
399 item.symbol.dedupe_item = item
400 item.symbol.dedupe_push_state = push_state
401
402 # Yield all symbols we haven't shown to be duplicates.
403 for b in batch:
404 if b.status == SymbolFile.DUPLICATE:
405 logging.debug('Found duplicate: %s', b.display_name)
406 yield b
407
408
409def PostForDeduplication(symbols, dedupe_namespace):
410 """Send a symbol file to the swarming service
411
412 Notify the isolate service of a successful upload. If the notification fails
413 for any reason, we ignore it. We don't care as it just means we'll upload it
414 again later on, and the symbol server will handle that graciously.
415
416 Args:
417 symbols: An iterable of SymbolFiles to be uploaded.
418 dedupe_namespace: String id for the comparison namespace.
419
420 Yields:
421 Each symbol from symbols, unmodified.
422 """
423 storage_query = OpenDeduplicateConnection(dedupe_namespace)
424
425 for s in symbols:
426 # If we can talk to isolate, and we uploaded this symbol, and we
427 # queried for it's presence before, upload to isolate now.
428
429 if storage_query and s.status == SymbolFile.UPLOADED and s.dedupe_item:
430 s.dedupe_item.prepare(DedupeItem.ALGO)
431 try:
432 with timeout_util.Timeout(DEDUPE_NOTIFY_TIMEOUT):
433 storage_query.push(s.dedupe_item, s.dedupe_push_state,
434 s.dedupe_item.content())
435 logging.info('sent %s', s.display_name)
436 except Exception:
437 logging.warning('posting %s to dedupe server failed',
438 os.path.basename(s.display_path), exc_info=True)
439 storage_query = None
440
441 yield s
442
443
444def GetUploadTimeout(symbol):
445 """How long to wait for a specific file to upload to the crash server.
446
447 This is a function largely to make unittesting easier.
448
449 Args:
450 symbol: A SymbolFile instance.
451
452 Returns:
453 Timeout length (in seconds)
454 """
455 # Scale the timeout based on the filesize.
456 return max(symbol.FileSize() / UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
457
458
459def UploadSymbolFile(upload_url, symbol, product_name):
460 """Upload a symbol file to the crash server.
461
462 The upload is a multipart/form-data POST with the following parameters:
463 code_file: the basename of the module, e.g. "app"
464 code_identifier: the module file's identifier
465 debug_file: the basename of the debugging file, e.g. "app"
466 debug_identifier: the debug file's identifier, usually consisting of
467 the guid and age embedded in the pdb, e.g.
468 "11111111BBBB3333DDDD555555555555F"
469 version: the file version of the module, e.g. "1.2.3.4"
470 product: HTTP-friendly product name
471 os: the operating system that the module was built for
472 cpu: the CPU that the module was built for
473 symbol_file: the contents of the breakpad-format symbol file
474
475 Args:
476 upload_url: The crash URL to POST the |sym_file| to
477 symbol: A SymbolFile instance.
478 product_name: A string for stats purposes. Usually 'ChromeOS' or 'Android'.
479 """
480 fields = (
481 ('code_file', symbol.header.name),
482 ('debug_file', symbol.header.name),
483 ('debug_identifier', symbol.header.id.replace('-', '')),
484 # The product/version fields are used by the server only for statistic
485 # purposes. They do not impact symbolization, so they're safe to set
486 # to any value all the time.
487 # In this case, we use it to help see the load our build system is
488 # placing on the server.
489 # Not sure what to set for the version. Maybe the git sha1 of this file.
490 # Note: the server restricts this to 30 chars.
491 #('version', None),
492 ('product', product_name),
493 ('os', symbol.header.os),
494 ('cpu', symbol.header.cpu),
495 poster.encode.MultipartParam.from_file('symbol_file', symbol.file_name),
496 )
497
498 data, headers = poster.encode.multipart_encode(fields)
499 request = urllib2.Request(upload_url, data, headers)
500 request.add_header('User-agent', 'chromite.upload_symbols')
501 urllib2.urlopen(request, timeout=GetUploadTimeout(symbol))
502
503
Don Garrettdeb2e032016-07-06 16:44:14 -0700504def PerformSymbolsFileUpload(symbols, upload_url, product_name='ChromeOS'):
Don Garretta28be6d2016-06-16 18:09:35 -0700505 """Upload the symbols to the crash server
506
507 Args:
Don Garrettdeb2e032016-07-06 16:44:14 -0700508 symbols: An iterable of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700509 upload_url: URL of crash server to upload too.
510 failures: Tracker for total upload failures.
511 product_name: A string for stats purposes. Usually 'ChromeOS' or 'Android'.
512
Don Garrettdeb2e032016-07-06 16:44:14 -0700513 Yields:
514 Each symbol from symbols, perhaps modified.
Don Garretta28be6d2016-06-16 18:09:35 -0700515 """
Don Garrettdeb2e032016-07-06 16:44:14 -0700516 failures = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700517
Don Garrettdeb2e032016-07-06 16:44:14 -0700518 for s in symbols:
519 if (failures < MAX_TOTAL_ERRORS_FOR_RETRY and
520 s.status in (SymbolFile.INITIAL, SymbolFile.ERROR)):
521 # Keeps us from DoS-ing the symbol server.
522 time.sleep(SLEEP_DELAY)
523 logging.info('Uploading symbol_file: %s', s.display_path)
524 try:
525 # This command retries the upload multiple times with growing delays. We
526 # only consider the upload a failure if these retries fail.
Don Garrett440944e2016-10-03 16:33:31 -0700527 with cros_build_lib.TimedSection() as timer:
528 retry_util.RetryException(
529 (urllib2.HTTPError, urllib2.URLError), MAX_RETRIES,
530 UploadSymbolFile,
531 upload_url, s, product_name,
532 sleep=INITIAL_RETRY_DELAY)
533 logging.info('upload of %10i bytes took %s', s.FileSize(), timer.delta)
Don Garrettdeb2e032016-07-06 16:44:14 -0700534 s.status = SymbolFile.UPLOADED
535 except urllib2.HTTPError as e:
536 logging.warning('could not upload: %s: HTTP %s: %s',
537 s.display_name, e.code, e.reason)
538 s.status = SymbolFile.ERROR
539 failures += 1
540 except (urllib2.URLError, httplib.HTTPException, socket.error) as e:
541 logging.warning('could not upload: %s: %s', s.display_name, e)
542 s.status = SymbolFile.ERROR
543 failures += 1
544
545 # We pass the symbol along, on both success and failure.
546 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700547
548
Don Garrettdeb2e032016-07-06 16:44:14 -0700549def ReportResults(symbols, failed_list):
Don Garretta28be6d2016-06-16 18:09:35 -0700550 """Log a summary of the symbol uploading.
551
552 This has the side effect of fully consuming the symbols iterator.
553
554 Args:
555 symbols: An iterator of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700556 failed_list: A filename at which to write out a list of our failed uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700557
558 Returns:
559 The number of symbols not uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700560 """
561 upload_failures = []
562 result_counts = {
563 SymbolFile.INITIAL: 0,
564 SymbolFile.UPLOADED: 0,
565 SymbolFile.DUPLICATE: 0,
Don Garrettdeb2e032016-07-06 16:44:14 -0700566 SymbolFile.ERROR: 0,
Don Garretta28be6d2016-06-16 18:09:35 -0700567 }
568
569 for s in symbols:
570 result_counts[s.status] += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700571 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700572 upload_failures.append(s)
573
574 logging.info('Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.',
575 result_counts)
576
Chris Ching91908032016-09-27 16:55:33 -0600577 if result_counts[SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700578 logging.PrintBuildbotStepWarnings()
Chris Ching91908032016-09-27 16:55:33 -0600579 logging.warning('%d non-recoverable upload errors',
580 result_counts[SymbolFile.ERROR])
581
582 if result_counts[SymbolFile.INITIAL]:
583 logging.PrintBuildbotStepWarnings()
584 logging.warning('%d upload(s) were skipped because of excessive errors',
Don Garrettdeb2e032016-07-06 16:44:14 -0700585 result_counts[SymbolFile.INITIAL])
Don Garretta28be6d2016-06-16 18:09:35 -0700586
587 if failed_list is not None:
588 with open(failed_list, 'w') as fl:
589 for s in upload_failures:
590 fl.write('%s\n' % s.display_path)
591
Don Garrettdeb2e032016-07-06 16:44:14 -0700592 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
593
Don Garretta28be6d2016-06-16 18:09:35 -0700594
595def UploadSymbols(sym_paths, upload_url, product_name, dedupe_namespace=None,
596 failed_list=None, upload_limit=None, strip_cfi=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400597 """Upload all the generated symbols for |board| to the crash server
598
599 Args:
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500600 sym_paths: Specific symbol files (or dirs of sym files) to upload,
601 otherwise search |breakpad_dir|
Don Garretta28be6d2016-06-16 18:09:35 -0700602 upload_url: URL of crash server to upload too.
603 product_name: A string for crash server stats purposes.
604 Usually 'ChromeOS' or 'Android'.
605 dedupe_namespace: None for no deduping, or string namespace in isolate.
606 failed_list: A filename at which to write out a list of our failed uploads.
607 upload_limit: Integer listing how many files to upload. None for no limit.
608 strip_cfi: File size at which we strip out CFI data. None for no limit.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500609
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400610 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400611 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400612 """
Don Garretta28be6d2016-06-16 18:09:35 -0700613 # Note: This method looks like each step of processing is performed
614 # sequentially for all SymbolFiles, but instead each step is a generator that
615 # produces the next iteration only when it's read. This means that (except for
616 # some batching) each SymbolFile goes through all of these steps before the
617 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400618
Don Garretta28be6d2016-06-16 18:09:35 -0700619 # This is used to hold striped
620 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
621 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500622
Don Garrett1bc1e102016-07-06 17:06:10 -0700623 # Sort all of our symbols so the largest ones (probably the most important)
624 # are processed first.
625 symbols = list(symbols)
626 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
627
Don Garretta28be6d2016-06-16 18:09:35 -0700628 if upload_limit is not None:
629 # Restrict symbols processed to the limit.
630 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400631
Don Garretta28be6d2016-06-16 18:09:35 -0700632 # Strip CFI, if needed.
633 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500634
Don Garretta28be6d2016-06-16 18:09:35 -0700635 # Skip duplicates.
636 if dedupe_namespace:
637 symbols = FindDuplicates(symbols, dedupe_namespace)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500638
Don Garretta28be6d2016-06-16 18:09:35 -0700639 # Perform uploads
Don Garrettdeb2e032016-07-06 16:44:14 -0700640 symbols = PerformSymbolsFileUpload(symbols, upload_url, product_name)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400641
Don Garretta28be6d2016-06-16 18:09:35 -0700642 # Record for future deduping.
643 if dedupe_namespace:
644 symbols = PostForDeduplication(symbols, dedupe_namespace)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400645
Don Garretta28be6d2016-06-16 18:09:35 -0700646 # Log the final results, and consume the symbols generator fully.
Don Garrettdeb2e032016-07-06 16:44:14 -0700647 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500648
Don Garrettdeb2e032016-07-06 16:44:14 -0700649 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400650
651
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400652def main(argv):
653 parser = commandline.ArgumentParser(description=__doc__)
654
Don Garretta28be6d2016-06-16 18:09:35 -0700655 # TODO: Make sym_paths, breakpad_root, and root exclusive.
656
Mike Frysingerd41938e2014-02-10 06:37:55 -0500657 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
658 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400659 parser.add_argument('--board', default=None,
Don Garrett747cc4b2015-10-07 14:48:48 -0700660 help='Used to find default breakpad_root.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400661 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500662 help='full path to the breakpad symbol directory')
663 parser.add_argument('--root', type='path', default=None,
664 help='full path to the chroot dir')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400665 parser.add_argument('--official_build', action='store_true', default=False,
666 help='point to official symbol server')
Mike Frysinger38647542014-09-12 18:15:39 -0700667 parser.add_argument('--server', type=str, default=None,
668 help='URI for custom symbol server')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400669 parser.add_argument('--regenerate', action='store_true', default=False,
670 help='regenerate all symbols')
Don Garretta28be6d2016-06-16 18:09:35 -0700671 parser.add_argument('--upload-limit', type=int,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400672 help='only upload # number of symbols')
673 parser.add_argument('--strip_cfi', type=int,
Don Garretta28be6d2016-06-16 18:09:35 -0700674 default=DEFAULT_FILE_LIMIT,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400675 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500676 parser.add_argument('--failed-list', type='path',
677 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500678 parser.add_argument('--dedupe', action='store_true', default=False,
679 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400680 parser.add_argument('--yes', action='store_true', default=False,
681 help='answer yes to all prompts')
Don Garrett747cc4b2015-10-07 14:48:48 -0700682 parser.add_argument('--product_name', type=str, default='ChromeOS',
683 help='Produce Name for breakpad stats.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400684
685 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500686 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400687
Don Garretta28be6d2016-06-16 18:09:35 -0700688 # Figure out the symbol files/directories to upload.
689 if opts.sym_paths:
690 sym_paths = opts.sym_paths
691 elif opts.breakpad_root:
692 sym_paths = [opts.breakpad_root]
693 elif opts.root:
694 if not opts.board:
695 raise ValueError('--board must be set if --root is used.')
696 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(opts.board)
697 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip('/'))]
698 else:
699 raise ValueError('--sym_paths, --breakpad_root, or --root must be set.')
700
Don Garrett747cc4b2015-10-07 14:48:48 -0700701 if opts.sym_paths or opts.breakpad_root:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400702 if opts.regenerate:
Don Garrett747cc4b2015-10-07 14:48:48 -0700703 cros_build_lib.Die('--regenerate may not be used with specific files, '
704 'or breakpad_root')
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400705 else:
706 if opts.board is None:
707 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400708
Don Garretta28be6d2016-06-16 18:09:35 -0700709 # Figure out the dedupe namespace.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500710 dedupe_namespace = None
711 if opts.dedupe:
Don Garretta28be6d2016-06-16 18:09:35 -0700712 if opts.official_build:
Don Garrett747cc4b2015-10-07 14:48:48 -0700713 dedupe_namespace = OFFICIAL_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500714 else:
Don Garrett747cc4b2015-10-07 14:48:48 -0700715 dedupe_namespace = STAGING_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500716
Don Garretta28be6d2016-06-16 18:09:35 -0700717 # Figure out which crash server to upload too.
718 upload_url = opts.server
719 if not upload_url:
720 if opts.official_build:
721 upload_url = OFFICIAL_UPLOAD_URL
722 else:
723 logging.warning('unofficial builds upload to the staging server')
724 upload_url = STAGING_UPLOAD_URL
725
726 # Confirm we really want the long upload.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400727 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500728 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400729 Uploading symbols for an entire Chromium OS build is really only
730 necessary for release builds and in a few cases for developers
731 to debug problems. It will take considerable time to run. For
732 developer debugging purposes, consider instead passing specific
733 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500734 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400735 if not cros_build_lib.BooleanPrompt(
736 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500737 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400738 cros_build_lib.Die('better safe than sorry')
739
740 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700741
742 # Regenerate symbols from binaries.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400743 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400744 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
745 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400746
Don Garretta28be6d2016-06-16 18:09:35 -0700747 # Do the upload.
748 ret += UploadSymbols(
749 sym_paths=sym_paths,
750 upload_url=upload_url,
751 product_name=opts.product_name,
752 dedupe_namespace=dedupe_namespace,
753 failed_list=opts.failed_list,
754 upload_limit=opts.upload_limit,
755 strip_cfi=opts.strip_cfi)
756
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400757 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700758 logging.error('encountered %i problem(s)', ret)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400759 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
760 # return 0 in case we are a multiple of the mask.
Don Garretta28be6d2016-06-16 18:09:35 -0700761 return 1