blob: 0488a834a2b776cd205ee019da4b60271c9b70fd [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -04002# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Upload all debug symbols required for crash reporting purposes.
7
8This script need only be used to upload release builds symbols or to debug
9crashes on non-release builds (in which case try to only upload the symbols
Mike Frysinger02e1e072013-11-10 22:11:34 -050010for those executables involved).
11"""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040012
Mike Frysingera4fa1e82014-01-15 01:45:56 -050013from __future__ import print_function
14
Mike Frysinger0c0efa22014-02-09 23:32:23 -050015import hashlib
Mike Frysingera4fa1e82014-01-15 01:45:56 -050016import httplib
Don Garretta28be6d2016-06-16 18:09:35 -070017import itertools
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040018import os
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
Aviv Keshetb7519e12016-10-04 00:50:00 -070027from chromite.lib import constants
Mike Frysingerbbd1f112016-09-08 18:25:11 -040028
29# The isolateserver includes a bunch of third_party python packages that clash
30# with chromite's bundled third_party python packages (like oauth2client).
31# Since upload_symbols is not imported in to other parts of chromite, and there
32# are no deps in third_party we care about, purge the chromite copy. This way
33# we can use isolateserver for deduping.
34# TODO: If we ever sort out third_party/ handling and make it per-script opt-in,
35# we can purge this logic.
36third_party = os.path.join(constants.CHROMITE_DIR, 'third_party')
37while True:
38 try:
39 sys.path.remove(third_party)
40 except ValueError:
41 break
42sys.path.insert(0, os.path.join(third_party, 'swarming.client'))
43sys.path.insert(0, os.path.join(third_party, 'upload_symbols'))
44del third_party
45
46# Has to be after sys.path manipulation above.
47# And our sys.path muckery confuses pylint.
48import poster # pylint: disable=import-error
49
Mike Frysingerd41938e2014-02-10 06:37:55 -050050from chromite.lib import cache
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040051from chromite.lib import commandline
52from chromite.lib import cros_build_lib
Ralph Nathan5a582ff2015-03-20 18:18:30 -070053from chromite.lib import cros_logging as logging
Mike Frysingerd41938e2014-02-10 06:37:55 -050054from chromite.lib import gs
55from chromite.lib import osutils
Gilad Arnold83233ed2015-05-08 12:12:13 -070056from chromite.lib import path_util
Don Garrette1f47e92016-10-13 16:04:56 -070057from chromite.lib import retry_stats
Mike Frysinger0c0efa22014-02-09 23:32:23 -050058from chromite.lib import timeout_util
Mike Frysinger69cb41d2013-08-11 20:08:19 -040059from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040060
Mike Frysinger0c0efa22014-02-09 23:32:23 -050061# Needs to be after chromite imports.
Mike Frysingerc5597f22014-11-27 15:39:15 -050062# We don't want to import the general keyring module as that will implicitly
63# try to import & connect to a dbus server. That's a waste of time.
64sys.modules['keyring'] = None
Mike Frysingerbbd1f112016-09-08 18:25:11 -040065# And our sys.path muckery confuses pylint.
Xixuan Wu3e840592018-05-30 15:27:03 -070066import isolate_storage # pylint: disable=import-error
Mike Frysingerbbd1f112016-09-08 18:25:11 -040067import isolateserver # pylint: disable=import-error
Mike Frysinger0c0efa22014-02-09 23:32:23 -050068
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040069
Don Garretta28be6d2016-06-16 18:09:35 -070070# We need this to run once per process. Do it at module import time as that
71# will let us avoid doing it inline at function call time (see UploadSymbolFile)
72# as that func might be called by the multiprocessing module which means we'll
73# do the opener logic multiple times overall. Plus, if you're importing this
74# module, it's a pretty good chance that you're going to need this.
75poster.streaminghttp.register_openers()
76
77
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040078# URLs used for uploading symbols.
79OFFICIAL_UPLOAD_URL = 'http://clients2.google.com/cr/symbol'
80STAGING_UPLOAD_URL = 'http://clients2.google.com/cr/staging_symbol'
81
82
83# The crash server rejects files that are this big.
Ryo Hashimotof864c0e2017-02-14 12:46:08 +090084CRASH_SERVER_FILE_LIMIT = 700 * 1024 * 1024
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040085# Give ourselves a little breathing room from what the server expects.
86DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
87
88
Mike Frysinger0c0efa22014-02-09 23:32:23 -050089# The batch limit when talking to the dedup server. We avoid sending one at a
90# time as the round trip overhead will dominate. Conversely, we avoid sending
91# all at once so we can start uploading symbols asap -- the symbol server is a
92# bit slow and will take longer than anything else.
Mike Frysinger0c0efa22014-02-09 23:32:23 -050093DEDUPE_LIMIT = 100
94
95# How long to wait for the server to respond with the results. Note that the
96# larger the limit above, the larger this will need to be. So we give it ~1
97# second per item max.
98DEDUPE_TIMEOUT = DEDUPE_LIMIT
99
Don Garretta28be6d2016-06-16 18:09:35 -0700100# How long to wait for the notification to finish (in seconds).
101DEDUPE_NOTIFY_TIMEOUT = 240
Mike Frysinger4dd462e2014-04-30 16:21:51 -0400102
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500103# The unique namespace in the dedupe server that only we use. Helps avoid
104# collisions with all the hashed values and unrelated content.
Don Garrett747cc4b2015-10-07 14:48:48 -0700105OFFICIAL_DEDUPE_NAMESPACE_TMPL = '%s-upload-symbols'
106STAGING_DEDUPE_NAMESPACE_TMPL = '%s-staging' % OFFICIAL_DEDUPE_NAMESPACE_TMPL
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500107
108
Mike Frysinger71046662014-09-12 18:15:15 -0700109# The minimum average rate (in bytes per second) that we expect to maintain
110# when uploading symbols. This has to allow for symbols that are up to
111# CRASH_SERVER_FILE_LIMIT in size.
112UPLOAD_MIN_RATE = CRASH_SERVER_FILE_LIMIT / (30 * 60)
113
114# The lowest timeout (in seconds) we'll allow. If the server is overloaded,
115# then there might be a delay in setting up the connection, not just with the
116# transfer. So even a small file might need a larger value.
Ryo Hashimotoc0049372017-02-16 18:50:00 +0900117UPLOAD_MIN_TIMEOUT = 5 * 60
Mike Frysingercd78a082013-06-26 17:13:04 -0400118
119
Don Garrett7a793092016-07-06 16:50:27 -0700120# Sleep for 500ms in between uploads to avoid DoS'ing symbol server.
121SLEEP_DELAY = 0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400122
123
124# Number of seconds to wait before retrying an upload. The delay will double
125# for each subsequent retry of the same symbol file.
126INITIAL_RETRY_DELAY = 1
127
128# Allow up to 7 attempts to upload a symbol file (total delay may be
129# 1+2+4+8+16+32=63 seconds).
130MAX_RETRIES = 6
131
Mike Frysingereb753bf2013-11-22 16:05:35 -0500132# Number of total errors, before uploads are no longer attempted.
133# This is used to avoid lots of errors causing unreasonable delays.
Mike Frysingereb753bf2013-11-22 16:05:35 -0500134MAX_TOTAL_ERRORS_FOR_RETRY = 30
135
Don Garrette1f47e92016-10-13 16:04:56 -0700136# Category to use for collection upload retry stats.
137UPLOAD_STATS = 'UPLOAD'
138
Don Garretta28be6d2016-06-16 18:09:35 -0700139
Don Garretta28be6d2016-06-16 18:09:35 -0700140def BatchGenerator(iterator, batch_size):
141 """Given an iterator, break into lists of size batch_size.
Fang Dengba680462015-08-16 20:34:11 -0700142
Don Garretta28be6d2016-06-16 18:09:35 -0700143 The result is a generator, that will only read in as many inputs as needed for
144 the current batch. The final result can be smaller than batch_size.
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700145 """
Don Garretta28be6d2016-06-16 18:09:35 -0700146 batch = []
147 for i in iterator:
148 batch.append(i)
149 if len(batch) >= batch_size:
150 yield batch
151 batch = []
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700152
Don Garretta28be6d2016-06-16 18:09:35 -0700153 if batch:
154 # if there was anything left in the final batch, yield it.
155 yield batch
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500156
157
Mike Frysingerd41938e2014-02-10 06:37:55 -0500158def IsTarball(path):
159 """Guess if this is a tarball based on the filename."""
160 parts = path.split('.')
161 if len(parts) <= 1:
162 return False
163
164 if parts[-1] == 'tar':
165 return True
166
167 if parts[-2] == 'tar':
168 return parts[-1] in ('bz2', 'gz', 'xz')
169
170 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
171
172
Don Garretta28be6d2016-06-16 18:09:35 -0700173class SymbolFile(object):
174 """This class represents the state of a symbol file during processing.
175
176 Properties:
177 display_path: Name of symbol file that should be consistent between builds.
178 file_name: Transient path of the symbol file.
179 header: ReadSymsHeader output. Dict with assorted meta-data.
180 status: INITIAL, DUPLICATE, or UPLOADED based on status of processing.
181 dedupe_item: None or instance of DedupeItem for this symbol file.
182 dedupe_push_state: Opaque value to return to dedupe code for file.
183 display_name: Read only friendly (short) file name for logging.
184 file_size: Read only size of the symbol file.
185 """
186 INITIAL = 'initial'
187 DUPLICATE = 'duplicate'
188 UPLOADED = 'uploaded'
Don Garrettdeb2e032016-07-06 16:44:14 -0700189 ERROR = 'error'
Don Garretta28be6d2016-06-16 18:09:35 -0700190
191 def __init__(self, display_path, file_name):
192 """An instance of this class represents a symbol file over time.
193
194 Args:
195 display_path: A unique/persistent between builds name to present to the
196 crash server. It is the file name, relative to where it
197 came from (tarball, breakpad dir, etc).
198 file_name: A the current location of the symbol file.
199 """
200 self.display_path = display_path
201 self.file_name = file_name
202 self.header = cros_generate_breakpad_symbols.ReadSymsHeader(file_name)
203 self.status = SymbolFile.INITIAL
204 self.dedupe_item = None
205 self.dedupe_push_state = None
206
207 @property
208 def display_name(self):
209 return os.path.basename(self.display_path)
210
211 def FileSize(self):
212 return os.path.getsize(self.file_name)
213
214
215class DedupeItem(isolateserver.BufferItem):
216 """Turn a SymbolFile into an isolateserver.Item"""
217
218 ALGO = hashlib.sha1
219
220 def __init__(self, symbol):
Mike Frysingerbbd1f112016-09-08 18:25:11 -0400221 isolateserver.BufferItem.__init__(self, str(symbol.header), self.ALGO)
Don Garretta28be6d2016-06-16 18:09:35 -0700222 self.symbol = symbol
223
224
225def FindSymbolFiles(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500226 """Locate symbol files in |paths|
227
Don Garretta28be6d2016-06-16 18:09:35 -0700228 This returns SymbolFile objects that contain file references which are valid
229 after this exits. Those files may exist externally, or be created in the
230 tempdir (say, when expanding tarballs). The caller must not consider
231 SymbolFile's valid after tempdir is cleaned up.
232
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500233 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700234 tempdir: Path to use for temporary files.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500235 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500236 Dirs are searched for files that end in ".sym". Urls are fetched and then
237 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500238
Don Garretta28be6d2016-06-16 18:09:35 -0700239 Yields:
240 A SymbolFile for every symbol file found in paths.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500241 """
Gilad Arnold83233ed2015-05-08 12:12:13 -0700242 cache_dir = path_util.GetCacheDir()
Mike Frysingere847efd2015-01-08 03:57:24 -0500243 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
244 tar_cache = cache.TarballCache(common_path)
245
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500246 for p in paths:
Don Garrett25f309a2014-03-19 14:02:12 -0700247 # Pylint is confused about members of ParseResult.
Don Garrettf8bf7842014-03-20 17:03:42 -0700248
Mike Frysingerd41938e2014-02-10 06:37:55 -0500249 o = urlparse.urlparse(p)
Don Garrettf8bf7842014-03-20 17:03:42 -0700250 if o.scheme: # pylint: disable=E1101
Mike Frysingerd41938e2014-02-10 06:37:55 -0500251 # Support globs of filenames.
252 ctx = gs.GSContext()
253 for p in ctx.LS(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700254 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500255 o = urlparse.urlparse(p)
Don Garrettf8bf7842014-03-20 17:03:42 -0700256 key = ('%s%s' % (o.netloc, o.path)).split('/') # pylint: disable=E1101
Mike Frysingerd41938e2014-02-10 06:37:55 -0500257 # The common cache will not be LRU, removing the need to hold a read
258 # lock on the cached gsutil.
259 ref = tar_cache.Lookup(key)
260 try:
261 ref.SetDefault(p)
262 except cros_build_lib.RunCommandError as e:
Ralph Nathan446aee92015-03-23 14:44:56 -0700263 logging.warning('ignoring %s\n%s', p, e)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500264 continue
Don Garretta28be6d2016-06-16 18:09:35 -0700265 for p in FindSymbolFiles(tempdir, [ref.path]):
Mike Frysingerd41938e2014-02-10 06:37:55 -0500266 yield p
267
268 elif os.path.isdir(p):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500269 for root, _, files in os.walk(p):
270 for f in files:
271 if f.endswith('.sym'):
Don Garretta28be6d2016-06-16 18:09:35 -0700272 # If p is '/tmp/foo' and filename is '/tmp/foo/bar/bar.sym',
273 # display_path = 'bar/bar.sym'
274 filename = os.path.join(root, f)
275 yield SymbolFile(display_path=filename[len(p):].lstrip('/'),
276 file_name=filename)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500277
278 elif IsTarball(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700279 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500280 tardir = tempfile.mkdtemp(dir=tempdir)
281 cache.Untar(os.path.realpath(p), tardir)
Don Garretta28be6d2016-06-16 18:09:35 -0700282 for p in FindSymbolFiles(tardir, [tardir]):
Mike Frysingerd41938e2014-02-10 06:37:55 -0500283 yield p
284
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500285 else:
Don Garretta28be6d2016-06-16 18:09:35 -0700286 yield SymbolFile(display_path=p, file_name=p)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500287
288
Don Garretta28be6d2016-06-16 18:09:35 -0700289def AdjustSymbolFileSize(symbol, tempdir, file_limit):
290 """Examine symbols files for size problems, and reduce if needed.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500291
Don Garretta28be6d2016-06-16 18:09:35 -0700292 If the symbols size is too big, strip out the call frame info. The CFI
293 is unnecessary for 32bit x86 targets where the frame pointer is used (as
294 all of ours have) and it accounts for over half the size of the symbols
295 uploaded.
296
297 Stripped files will be created inside tempdir, and will be the callers
298 responsibility to clean up.
299
300 We also warn, if a symbols file is still too large after stripping.
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500301
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500302 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700303 symbol: SymbolFile instance to be examined and modified as needed..
304 tempdir: A temporary directory we can create files in that the caller will
305 clean up.
306 file_limit: We only strip files which are larger than this limit.
307
308 Returns:
309 SymbolFile instance (original or modified as needed)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500310 """
Don Garretta28be6d2016-06-16 18:09:35 -0700311 file_size = symbol.FileSize()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500312
Don Garretta28be6d2016-06-16 18:09:35 -0700313 if file_limit and symbol.FileSize() > file_limit:
314 with tempfile.NamedTemporaryFile(
315 prefix='upload_symbols', bufsize=0,
316 dir=tempdir, delete=False) as temp_sym_file:
317
318 temp_sym_file.writelines(
319 [x for x in open(symbol.file_name, 'rb').readlines()
320 if not x.startswith('STACK CFI')]
321 )
322
323 original_file_size = file_size
324 symbol.file_name = temp_sym_file.name
325 file_size = symbol.FileSize()
326
327 logging.warning('stripped CFI for %s reducing size %s > %s',
328 symbol.display_name, original_file_size, file_size)
329
330 # Hopefully the crash server will let it through. But it probably won't.
331 # Not sure what the best answer is in this case.
332 if file_size >= CRASH_SERVER_FILE_LIMIT:
333 logging.PrintBuildbotStepWarnings()
334 logging.warning('upload file %s is awfully large, risking rejection by '
335 'the symbol server (%s > %s)', symbol.display_path,
336 file_size, CRASH_SERVER_FILE_LIMIT)
337
338 return symbol
339
340def OpenDeduplicateConnection(dedupe_namespace):
341 """Open a connection to the isolate server for Dedupe use.
342
343 Args:
344 dedupe_namespace: String id for the comparison namespace.
345
346 Returns:
347 Connection proxy, or None on failure.
348 """
349 try:
350 with timeout_util.Timeout(DEDUPE_TIMEOUT):
Xixuan Wu3e840592018-05-30 15:27:03 -0700351 return isolate_storage.get_storage_api(constants.ISOLATESERVER,
352 dedupe_namespace)
Don Garretta28be6d2016-06-16 18:09:35 -0700353 except Exception:
354 logging.warning('initializing isolate server connection failed',
355 exc_info=True)
356 return None
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500357
358
Don Garretta28be6d2016-06-16 18:09:35 -0700359def FindDuplicates(symbols, dedupe_namespace):
360 """Mark symbol files we've already uploaded as duplicates.
361
362 Using the swarming service, ask it to tell us which symbol files we've already
363 uploaded in previous runs and/or by other bots. If the query fails for any
364 reason, we'll just upload all symbols. This is fine as the symbol server will
365 do the right thing and this phase is purely an optimization.
366
367 Args:
368 symbols: An iterable of SymbolFiles to be uploaded.
369 dedupe_namespace: String id for the comparison namespace.
370
371 Yields:
372 All SymbolFiles from symbols, but duplicates have status updated to
373 DUPLICATE.
374 """
375 storage_query = OpenDeduplicateConnection(dedupe_namespace)
376
377 # We query isolate in batches, to reduce the number of network queries.
378 for batch in BatchGenerator(symbols, DEDUPE_LIMIT):
379 query_results = None
380
381 if storage_query:
382 # Convert SymbolFiles into DedupeItems.
383 items = [DedupeItem(x) for x in batch]
384 for item in items:
385 item.prepare(DedupeItem.ALGO)
386
387 # Look for duplicates.
388 try:
389 with timeout_util.Timeout(DEDUPE_TIMEOUT):
390 query_results = storage_query.contains(items)
391 except Exception:
392 logging.warning('talking to dedupe server failed', exc_info=True)
393 storage_query = None
394
395 if query_results is not None:
396 for b in batch:
397 b.status = SymbolFile.DUPLICATE
398
399 # Only the non-duplicates appear in the query_results.
400 for item, push_state in query_results.iteritems():
401 # Remember the dedupe state, so we can mark the symbol as uploaded
402 # later on.
403 item.symbol.status = SymbolFile.INITIAL
404 item.symbol.dedupe_item = item
405 item.symbol.dedupe_push_state = push_state
406
407 # Yield all symbols we haven't shown to be duplicates.
408 for b in batch:
409 if b.status == SymbolFile.DUPLICATE:
410 logging.debug('Found duplicate: %s', b.display_name)
411 yield b
412
413
414def PostForDeduplication(symbols, dedupe_namespace):
415 """Send a symbol file to the swarming service
416
417 Notify the isolate service of a successful upload. If the notification fails
418 for any reason, we ignore it. We don't care as it just means we'll upload it
419 again later on, and the symbol server will handle that graciously.
420
421 Args:
422 symbols: An iterable of SymbolFiles to be uploaded.
423 dedupe_namespace: String id for the comparison namespace.
424
425 Yields:
426 Each symbol from symbols, unmodified.
427 """
428 storage_query = OpenDeduplicateConnection(dedupe_namespace)
429
430 for s in symbols:
431 # If we can talk to isolate, and we uploaded this symbol, and we
432 # queried for it's presence before, upload to isolate now.
433
434 if storage_query and s.status == SymbolFile.UPLOADED and s.dedupe_item:
435 s.dedupe_item.prepare(DedupeItem.ALGO)
436 try:
437 with timeout_util.Timeout(DEDUPE_NOTIFY_TIMEOUT):
438 storage_query.push(s.dedupe_item, s.dedupe_push_state,
439 s.dedupe_item.content())
440 logging.info('sent %s', s.display_name)
441 except Exception:
442 logging.warning('posting %s to dedupe server failed',
443 os.path.basename(s.display_path), exc_info=True)
444 storage_query = None
445
446 yield s
447
448
449def GetUploadTimeout(symbol):
450 """How long to wait for a specific file to upload to the crash server.
451
452 This is a function largely to make unittesting easier.
453
454 Args:
455 symbol: A SymbolFile instance.
456
457 Returns:
458 Timeout length (in seconds)
459 """
460 # Scale the timeout based on the filesize.
461 return max(symbol.FileSize() / UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
462
463
464def UploadSymbolFile(upload_url, symbol, product_name):
465 """Upload a symbol file to the crash server.
466
467 The upload is a multipart/form-data POST with the following parameters:
468 code_file: the basename of the module, e.g. "app"
469 code_identifier: the module file's identifier
470 debug_file: the basename of the debugging file, e.g. "app"
471 debug_identifier: the debug file's identifier, usually consisting of
472 the guid and age embedded in the pdb, e.g.
473 "11111111BBBB3333DDDD555555555555F"
474 version: the file version of the module, e.g. "1.2.3.4"
475 product: HTTP-friendly product name
476 os: the operating system that the module was built for
477 cpu: the CPU that the module was built for
478 symbol_file: the contents of the breakpad-format symbol file
479
480 Args:
481 upload_url: The crash URL to POST the |sym_file| to
482 symbol: A SymbolFile instance.
483 product_name: A string for stats purposes. Usually 'ChromeOS' or 'Android'.
484 """
485 fields = (
486 ('code_file', symbol.header.name),
487 ('debug_file', symbol.header.name),
488 ('debug_identifier', symbol.header.id.replace('-', '')),
489 # The product/version fields are used by the server only for statistic
490 # purposes. They do not impact symbolization, so they're safe to set
491 # to any value all the time.
492 # In this case, we use it to help see the load our build system is
493 # placing on the server.
494 # Not sure what to set for the version. Maybe the git sha1 of this file.
495 # Note: the server restricts this to 30 chars.
496 #('version', None),
497 ('product', product_name),
498 ('os', symbol.header.os),
499 ('cpu', symbol.header.cpu),
500 poster.encode.MultipartParam.from_file('symbol_file', symbol.file_name),
501 )
502
503 data, headers = poster.encode.multipart_encode(fields)
504 request = urllib2.Request(upload_url, data, headers)
505 request.add_header('User-agent', 'chromite.upload_symbols')
506 urllib2.urlopen(request, timeout=GetUploadTimeout(symbol))
507
508
Don Garrettdeb2e032016-07-06 16:44:14 -0700509def PerformSymbolsFileUpload(symbols, upload_url, product_name='ChromeOS'):
Don Garretta28be6d2016-06-16 18:09:35 -0700510 """Upload the symbols to the crash server
511
512 Args:
Don Garrettdeb2e032016-07-06 16:44:14 -0700513 symbols: An iterable of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700514 upload_url: URL of crash server to upload too.
515 failures: Tracker for total upload failures.
516 product_name: A string for stats purposes. Usually 'ChromeOS' or 'Android'.
517
Don Garrettdeb2e032016-07-06 16:44:14 -0700518 Yields:
519 Each symbol from symbols, perhaps modified.
Don Garretta28be6d2016-06-16 18:09:35 -0700520 """
Don Garrettdeb2e032016-07-06 16:44:14 -0700521 failures = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700522
Don Garrettdeb2e032016-07-06 16:44:14 -0700523 for s in symbols:
524 if (failures < MAX_TOTAL_ERRORS_FOR_RETRY and
525 s.status in (SymbolFile.INITIAL, SymbolFile.ERROR)):
526 # Keeps us from DoS-ing the symbol server.
527 time.sleep(SLEEP_DELAY)
528 logging.info('Uploading symbol_file: %s', s.display_path)
529 try:
530 # This command retries the upload multiple times with growing delays. We
531 # only consider the upload a failure if these retries fail.
Don Garrette1f47e92016-10-13 16:04:56 -0700532 def ShouldRetryUpload(exception):
Ryo Hashimoto4ddf3f02017-03-22 18:25:27 +0900533 return isinstance(exception, (urllib2.HTTPError, urllib2.URLError,
534 httplib.HTTPException, socket.error))
Don Garrette1f47e92016-10-13 16:04:56 -0700535
Don Garrett440944e2016-10-03 16:33:31 -0700536 with cros_build_lib.TimedSection() as timer:
Don Garrette1f47e92016-10-13 16:04:56 -0700537 retry_stats.RetryWithStats(
538 UPLOAD_STATS, ShouldRetryUpload, MAX_RETRIES,
Don Garrett440944e2016-10-03 16:33:31 -0700539 UploadSymbolFile,
540 upload_url, s, product_name,
Luigi Semenzato5104f3c2016-10-12 12:37:42 -0700541 sleep=INITIAL_RETRY_DELAY,
542 log_all_retries=True)
Don Garrett440944e2016-10-03 16:33:31 -0700543 logging.info('upload of %10i bytes took %s', s.FileSize(), timer.delta)
Don Garrettdeb2e032016-07-06 16:44:14 -0700544 s.status = SymbolFile.UPLOADED
545 except urllib2.HTTPError as e:
546 logging.warning('could not upload: %s: HTTP %s: %s',
547 s.display_name, e.code, e.reason)
548 s.status = SymbolFile.ERROR
549 failures += 1
550 except (urllib2.URLError, httplib.HTTPException, socket.error) as e:
Ryo Hashimoto45273f02017-03-16 17:45:56 +0900551 logging.warning('could not upload: %s: %s %s', s.display_name,
552 type(e).__name__, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700553 s.status = SymbolFile.ERROR
554 failures += 1
555
556 # We pass the symbol along, on both success and failure.
557 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700558
559
Don Garrettdeb2e032016-07-06 16:44:14 -0700560def ReportResults(symbols, failed_list):
Don Garretta28be6d2016-06-16 18:09:35 -0700561 """Log a summary of the symbol uploading.
562
563 This has the side effect of fully consuming the symbols iterator.
564
565 Args:
566 symbols: An iterator of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700567 failed_list: A filename at which to write out a list of our failed uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700568
569 Returns:
570 The number of symbols not uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700571 """
572 upload_failures = []
573 result_counts = {
574 SymbolFile.INITIAL: 0,
575 SymbolFile.UPLOADED: 0,
576 SymbolFile.DUPLICATE: 0,
Don Garrettdeb2e032016-07-06 16:44:14 -0700577 SymbolFile.ERROR: 0,
Don Garretta28be6d2016-06-16 18:09:35 -0700578 }
579
580 for s in symbols:
581 result_counts[s.status] += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700582 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700583 upload_failures.append(s)
584
Don Garrette1f47e92016-10-13 16:04:56 -0700585 # Report retry numbers.
586 _, _, retries = retry_stats.CategoryStats(UPLOAD_STATS)
587 if retries:
588 logging.warning('%d upload retries performed.', retries)
589
Don Garretta28be6d2016-06-16 18:09:35 -0700590 logging.info('Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.',
591 result_counts)
592
Chris Ching91908032016-09-27 16:55:33 -0600593 if result_counts[SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700594 logging.PrintBuildbotStepWarnings()
Chris Ching91908032016-09-27 16:55:33 -0600595 logging.warning('%d non-recoverable upload errors',
596 result_counts[SymbolFile.ERROR])
597
598 if result_counts[SymbolFile.INITIAL]:
599 logging.PrintBuildbotStepWarnings()
600 logging.warning('%d upload(s) were skipped because of excessive errors',
Don Garrettdeb2e032016-07-06 16:44:14 -0700601 result_counts[SymbolFile.INITIAL])
Don Garretta28be6d2016-06-16 18:09:35 -0700602
603 if failed_list is not None:
604 with open(failed_list, 'w') as fl:
605 for s in upload_failures:
606 fl.write('%s\n' % s.display_path)
607
Don Garrettdeb2e032016-07-06 16:44:14 -0700608 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
609
Don Garretta28be6d2016-06-16 18:09:35 -0700610
611def UploadSymbols(sym_paths, upload_url, product_name, dedupe_namespace=None,
612 failed_list=None, upload_limit=None, strip_cfi=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400613 """Upload all the generated symbols for |board| to the crash server
614
615 Args:
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500616 sym_paths: Specific symbol files (or dirs of sym files) to upload,
617 otherwise search |breakpad_dir|
Don Garretta28be6d2016-06-16 18:09:35 -0700618 upload_url: URL of crash server to upload too.
619 product_name: A string for crash server stats purposes.
620 Usually 'ChromeOS' or 'Android'.
621 dedupe_namespace: None for no deduping, or string namespace in isolate.
622 failed_list: A filename at which to write out a list of our failed uploads.
623 upload_limit: Integer listing how many files to upload. None for no limit.
624 strip_cfi: File size at which we strip out CFI data. None for no limit.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500625
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400626 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400627 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400628 """
Don Garrette1f47e92016-10-13 16:04:56 -0700629 retry_stats.SetupStats()
630
Don Garretta28be6d2016-06-16 18:09:35 -0700631 # Note: This method looks like each step of processing is performed
632 # sequentially for all SymbolFiles, but instead each step is a generator that
633 # produces the next iteration only when it's read. This means that (except for
634 # some batching) each SymbolFile goes through all of these steps before the
635 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400636
Don Garretta28be6d2016-06-16 18:09:35 -0700637 # This is used to hold striped
638 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
639 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500640
Don Garrett1bc1e102016-07-06 17:06:10 -0700641 # Sort all of our symbols so the largest ones (probably the most important)
642 # are processed first.
643 symbols = list(symbols)
644 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
645
Don Garretta28be6d2016-06-16 18:09:35 -0700646 if upload_limit is not None:
647 # Restrict symbols processed to the limit.
648 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400649
Don Garretta28be6d2016-06-16 18:09:35 -0700650 # Strip CFI, if needed.
651 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500652
Don Garretta28be6d2016-06-16 18:09:35 -0700653 # Skip duplicates.
654 if dedupe_namespace:
655 symbols = FindDuplicates(symbols, dedupe_namespace)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500656
Don Garretta28be6d2016-06-16 18:09:35 -0700657 # Perform uploads
Don Garrettdeb2e032016-07-06 16:44:14 -0700658 symbols = PerformSymbolsFileUpload(symbols, upload_url, product_name)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400659
Don Garretta28be6d2016-06-16 18:09:35 -0700660 # Record for future deduping.
661 if dedupe_namespace:
662 symbols = PostForDeduplication(symbols, dedupe_namespace)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400663
Don Garretta28be6d2016-06-16 18:09:35 -0700664 # Log the final results, and consume the symbols generator fully.
Don Garrettdeb2e032016-07-06 16:44:14 -0700665 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500666
Don Garrettdeb2e032016-07-06 16:44:14 -0700667 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400668
669
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400670def main(argv):
671 parser = commandline.ArgumentParser(description=__doc__)
672
Don Garretta28be6d2016-06-16 18:09:35 -0700673 # TODO: Make sym_paths, breakpad_root, and root exclusive.
674
Mike Frysingerd41938e2014-02-10 06:37:55 -0500675 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
676 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400677 parser.add_argument('--board', default=None,
Don Garrett747cc4b2015-10-07 14:48:48 -0700678 help='Used to find default breakpad_root.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400679 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500680 help='full path to the breakpad symbol directory')
681 parser.add_argument('--root', type='path', default=None,
682 help='full path to the chroot dir')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400683 parser.add_argument('--official_build', action='store_true', default=False,
684 help='point to official symbol server')
Mike Frysinger38647542014-09-12 18:15:39 -0700685 parser.add_argument('--server', type=str, default=None,
686 help='URI for custom symbol server')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400687 parser.add_argument('--regenerate', action='store_true', default=False,
688 help='regenerate all symbols')
Don Garretta28be6d2016-06-16 18:09:35 -0700689 parser.add_argument('--upload-limit', type=int,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400690 help='only upload # number of symbols')
691 parser.add_argument('--strip_cfi', type=int,
Don Garretta28be6d2016-06-16 18:09:35 -0700692 default=DEFAULT_FILE_LIMIT,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400693 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500694 parser.add_argument('--failed-list', type='path',
695 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500696 parser.add_argument('--dedupe', action='store_true', default=False,
697 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400698 parser.add_argument('--yes', action='store_true', default=False,
699 help='answer yes to all prompts')
Don Garrett747cc4b2015-10-07 14:48:48 -0700700 parser.add_argument('--product_name', type=str, default='ChromeOS',
701 help='Produce Name for breakpad stats.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400702
703 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500704 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400705
Don Garretta28be6d2016-06-16 18:09:35 -0700706 # Figure out the symbol files/directories to upload.
707 if opts.sym_paths:
708 sym_paths = opts.sym_paths
709 elif opts.breakpad_root:
710 sym_paths = [opts.breakpad_root]
711 elif opts.root:
712 if not opts.board:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400713 cros_build_lib.Die('--board must be set if --root is used.')
Don Garretta28be6d2016-06-16 18:09:35 -0700714 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(opts.board)
715 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip('/'))]
716 else:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400717 cros_build_lib.Die('--sym_paths, --breakpad_root, or --root must be set.')
Don Garretta28be6d2016-06-16 18:09:35 -0700718
Don Garrett747cc4b2015-10-07 14:48:48 -0700719 if opts.sym_paths or opts.breakpad_root:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400720 if opts.regenerate:
Don Garrett747cc4b2015-10-07 14:48:48 -0700721 cros_build_lib.Die('--regenerate may not be used with specific files, '
722 'or breakpad_root')
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400723 else:
724 if opts.board is None:
725 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400726
Don Garretta28be6d2016-06-16 18:09:35 -0700727 # Figure out the dedupe namespace.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500728 dedupe_namespace = None
729 if opts.dedupe:
Don Garretta28be6d2016-06-16 18:09:35 -0700730 if opts.official_build:
Don Garrett747cc4b2015-10-07 14:48:48 -0700731 dedupe_namespace = OFFICIAL_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500732 else:
Don Garrett747cc4b2015-10-07 14:48:48 -0700733 dedupe_namespace = STAGING_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500734
Don Garretta28be6d2016-06-16 18:09:35 -0700735 # Figure out which crash server to upload too.
736 upload_url = opts.server
737 if not upload_url:
738 if opts.official_build:
739 upload_url = OFFICIAL_UPLOAD_URL
740 else:
741 logging.warning('unofficial builds upload to the staging server')
742 upload_url = STAGING_UPLOAD_URL
743
744 # Confirm we really want the long upload.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400745 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500746 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400747 Uploading symbols for an entire Chromium OS build is really only
748 necessary for release builds and in a few cases for developers
749 to debug problems. It will take considerable time to run. For
750 developer debugging purposes, consider instead passing specific
751 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500752 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400753 if not cros_build_lib.BooleanPrompt(
754 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500755 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400756 cros_build_lib.Die('better safe than sorry')
757
758 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700759
760 # Regenerate symbols from binaries.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400761 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400762 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
763 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400764
Don Garretta28be6d2016-06-16 18:09:35 -0700765 # Do the upload.
766 ret += UploadSymbols(
767 sym_paths=sym_paths,
768 upload_url=upload_url,
769 product_name=opts.product_name,
770 dedupe_namespace=dedupe_namespace,
771 failed_list=opts.failed_list,
772 upload_limit=opts.upload_limit,
773 strip_cfi=opts.strip_cfi)
774
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400775 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700776 logging.error('encountered %i problem(s)', ret)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400777 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
778 # return 0 in case we are a multiple of the mask.
Don Garretta28be6d2016-06-16 18:09:35 -0700779 return 1