blob: b40b148b4a765385d552e9b778adf74ade45e61f [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.
Mike Frysinger3fff4ff2018-07-24 19:24:58 -040036# pylint: disable=ungrouped-imports
Mike Frysingerbbd1f112016-09-08 18:25:11 -040037third_party = os.path.join(constants.CHROMITE_DIR, 'third_party')
38while True:
39 try:
40 sys.path.remove(third_party)
41 except ValueError:
42 break
43sys.path.insert(0, os.path.join(third_party, 'swarming.client'))
44sys.path.insert(0, os.path.join(third_party, 'upload_symbols'))
45del third_party
46
47# Has to be after sys.path manipulation above.
48# And our sys.path muckery confuses pylint.
49import poster # pylint: disable=import-error
50
Mike Frysingerd41938e2014-02-10 06:37:55 -050051from chromite.lib import cache
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040052from chromite.lib import commandline
53from chromite.lib import cros_build_lib
Ralph Nathan5a582ff2015-03-20 18:18:30 -070054from chromite.lib import cros_logging as logging
Mike Frysingerd41938e2014-02-10 06:37:55 -050055from chromite.lib import gs
56from chromite.lib import osutils
Gilad Arnold83233ed2015-05-08 12:12:13 -070057from chromite.lib import path_util
Don Garrette1f47e92016-10-13 16:04:56 -070058from chromite.lib import retry_stats
Mike Frysinger0c0efa22014-02-09 23:32:23 -050059from chromite.lib import timeout_util
Mike Frysinger69cb41d2013-08-11 20:08:19 -040060from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040061
Mike Frysinger0c0efa22014-02-09 23:32:23 -050062# Needs to be after chromite imports.
Mike Frysingerc5597f22014-11-27 15:39:15 -050063# We don't want to import the general keyring module as that will implicitly
64# try to import & connect to a dbus server. That's a waste of time.
65sys.modules['keyring'] = None
Mike Frysingerbbd1f112016-09-08 18:25:11 -040066# And our sys.path muckery confuses pylint.
Xixuan Wu3e840592018-05-30 15:27:03 -070067import isolate_storage # pylint: disable=import-error
Mike Frysingerbbd1f112016-09-08 18:25:11 -040068import isolateserver # pylint: disable=import-error
Mike Frysinger0c0efa22014-02-09 23:32:23 -050069
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040070
Don Garretta28be6d2016-06-16 18:09:35 -070071# We need this to run once per process. Do it at module import time as that
72# will let us avoid doing it inline at function call time (see UploadSymbolFile)
73# as that func might be called by the multiprocessing module which means we'll
74# do the opener logic multiple times overall. Plus, if you're importing this
75# module, it's a pretty good chance that you're going to need this.
76poster.streaminghttp.register_openers()
77
78
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040079# URLs used for uploading symbols.
80OFFICIAL_UPLOAD_URL = 'http://clients2.google.com/cr/symbol'
81STAGING_UPLOAD_URL = 'http://clients2.google.com/cr/staging_symbol'
82
83
84# The crash server rejects files that are this big.
Ryo Hashimotof864c0e2017-02-14 12:46:08 +090085CRASH_SERVER_FILE_LIMIT = 700 * 1024 * 1024
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040086# Give ourselves a little breathing room from what the server expects.
87DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
88
89
Mike Frysinger0c0efa22014-02-09 23:32:23 -050090# The batch limit when talking to the dedup server. We avoid sending one at a
91# time as the round trip overhead will dominate. Conversely, we avoid sending
92# all at once so we can start uploading symbols asap -- the symbol server is a
93# bit slow and will take longer than anything else.
Mike Frysinger0c0efa22014-02-09 23:32:23 -050094DEDUPE_LIMIT = 100
95
96# How long to wait for the server to respond with the results. Note that the
97# larger the limit above, the larger this will need to be. So we give it ~1
98# second per item max.
99DEDUPE_TIMEOUT = DEDUPE_LIMIT
100
Don Garretta28be6d2016-06-16 18:09:35 -0700101# How long to wait for the notification to finish (in seconds).
102DEDUPE_NOTIFY_TIMEOUT = 240
Mike Frysinger4dd462e2014-04-30 16:21:51 -0400103
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500104# The unique namespace in the dedupe server that only we use. Helps avoid
105# collisions with all the hashed values and unrelated content.
Don Garrett747cc4b2015-10-07 14:48:48 -0700106OFFICIAL_DEDUPE_NAMESPACE_TMPL = '%s-upload-symbols'
107STAGING_DEDUPE_NAMESPACE_TMPL = '%s-staging' % OFFICIAL_DEDUPE_NAMESPACE_TMPL
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500108
109
Mike Frysinger71046662014-09-12 18:15:15 -0700110# The minimum average rate (in bytes per second) that we expect to maintain
111# when uploading symbols. This has to allow for symbols that are up to
112# CRASH_SERVER_FILE_LIMIT in size.
113UPLOAD_MIN_RATE = CRASH_SERVER_FILE_LIMIT / (30 * 60)
114
115# The lowest timeout (in seconds) we'll allow. If the server is overloaded,
116# then there might be a delay in setting up the connection, not just with the
117# transfer. So even a small file might need a larger value.
Ryo Hashimotoc0049372017-02-16 18:50:00 +0900118UPLOAD_MIN_TIMEOUT = 5 * 60
Mike Frysingercd78a082013-06-26 17:13:04 -0400119
120
Don Garrett7a793092016-07-06 16:50:27 -0700121# Sleep for 500ms in between uploads to avoid DoS'ing symbol server.
122SLEEP_DELAY = 0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400123
124
125# Number of seconds to wait before retrying an upload. The delay will double
126# for each subsequent retry of the same symbol file.
127INITIAL_RETRY_DELAY = 1
128
129# Allow up to 7 attempts to upload a symbol file (total delay may be
130# 1+2+4+8+16+32=63 seconds).
131MAX_RETRIES = 6
132
Mike Frysingereb753bf2013-11-22 16:05:35 -0500133# Number of total errors, before uploads are no longer attempted.
134# This is used to avoid lots of errors causing unreasonable delays.
Mike Frysingereb753bf2013-11-22 16:05:35 -0500135MAX_TOTAL_ERRORS_FOR_RETRY = 30
136
Don Garrette1f47e92016-10-13 16:04:56 -0700137# Category to use for collection upload retry stats.
138UPLOAD_STATS = 'UPLOAD'
139
Don Garretta28be6d2016-06-16 18:09:35 -0700140
Don Garretta28be6d2016-06-16 18:09:35 -0700141def BatchGenerator(iterator, batch_size):
142 """Given an iterator, break into lists of size batch_size.
Fang Dengba680462015-08-16 20:34:11 -0700143
Don Garretta28be6d2016-06-16 18:09:35 -0700144 The result is a generator, that will only read in as many inputs as needed for
145 the current batch. The final result can be smaller than batch_size.
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700146 """
Don Garretta28be6d2016-06-16 18:09:35 -0700147 batch = []
148 for i in iterator:
149 batch.append(i)
150 if len(batch) >= batch_size:
151 yield batch
152 batch = []
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700153
Don Garretta28be6d2016-06-16 18:09:35 -0700154 if batch:
155 # if there was anything left in the final batch, yield it.
156 yield batch
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500157
158
Mike Frysingerd41938e2014-02-10 06:37:55 -0500159def IsTarball(path):
160 """Guess if this is a tarball based on the filename."""
161 parts = path.split('.')
162 if len(parts) <= 1:
163 return False
164
165 if parts[-1] == 'tar':
166 return True
167
168 if parts[-2] == 'tar':
169 return parts[-1] in ('bz2', 'gz', 'xz')
170
171 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
172
173
Don Garretta28be6d2016-06-16 18:09:35 -0700174class SymbolFile(object):
175 """This class represents the state of a symbol file during processing.
176
177 Properties:
178 display_path: Name of symbol file that should be consistent between builds.
179 file_name: Transient path of the symbol file.
180 header: ReadSymsHeader output. Dict with assorted meta-data.
181 status: INITIAL, DUPLICATE, or UPLOADED based on status of processing.
182 dedupe_item: None or instance of DedupeItem for this symbol file.
183 dedupe_push_state: Opaque value to return to dedupe code for file.
184 display_name: Read only friendly (short) file name for logging.
185 file_size: Read only size of the symbol file.
186 """
187 INITIAL = 'initial'
188 DUPLICATE = 'duplicate'
189 UPLOADED = 'uploaded'
Don Garrettdeb2e032016-07-06 16:44:14 -0700190 ERROR = 'error'
Don Garretta28be6d2016-06-16 18:09:35 -0700191
192 def __init__(self, display_path, file_name):
193 """An instance of this class represents a symbol file over time.
194
195 Args:
196 display_path: A unique/persistent between builds name to present to the
197 crash server. It is the file name, relative to where it
198 came from (tarball, breakpad dir, etc).
199 file_name: A the current location of the symbol file.
200 """
201 self.display_path = display_path
202 self.file_name = file_name
203 self.header = cros_generate_breakpad_symbols.ReadSymsHeader(file_name)
204 self.status = SymbolFile.INITIAL
205 self.dedupe_item = None
206 self.dedupe_push_state = None
207
208 @property
209 def display_name(self):
210 return os.path.basename(self.display_path)
211
212 def FileSize(self):
213 return os.path.getsize(self.file_name)
214
215
216class DedupeItem(isolateserver.BufferItem):
217 """Turn a SymbolFile into an isolateserver.Item"""
218
219 ALGO = hashlib.sha1
220
221 def __init__(self, symbol):
Mike Frysingerbbd1f112016-09-08 18:25:11 -0400222 isolateserver.BufferItem.__init__(self, str(symbol.header), self.ALGO)
Don Garretta28be6d2016-06-16 18:09:35 -0700223 self.symbol = symbol
224
225
226def FindSymbolFiles(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500227 """Locate symbol files in |paths|
228
Don Garretta28be6d2016-06-16 18:09:35 -0700229 This returns SymbolFile objects that contain file references which are valid
230 after this exits. Those files may exist externally, or be created in the
231 tempdir (say, when expanding tarballs). The caller must not consider
232 SymbolFile's valid after tempdir is cleaned up.
233
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500234 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700235 tempdir: Path to use for temporary files.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500236 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500237 Dirs are searched for files that end in ".sym". Urls are fetched and then
238 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500239
Don Garretta28be6d2016-06-16 18:09:35 -0700240 Yields:
241 A SymbolFile for every symbol file found in paths.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500242 """
Gilad Arnold83233ed2015-05-08 12:12:13 -0700243 cache_dir = path_util.GetCacheDir()
Mike Frysingere847efd2015-01-08 03:57:24 -0500244 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
245 tar_cache = cache.TarballCache(common_path)
246
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500247 for p in paths:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500248 o = urlparse.urlparse(p)
Mike Frysinger27e21b72018-07-12 14:20:21 -0400249 if o.scheme:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500250 # Support globs of filenames.
251 ctx = gs.GSContext()
252 for p in ctx.LS(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700253 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500254 o = urlparse.urlparse(p)
Mike Frysinger27e21b72018-07-12 14:20:21 -0400255 key = ('%s%s' % (o.netloc, o.path)).split('/')
Mike Frysingerd41938e2014-02-10 06:37:55 -0500256 # The common cache will not be LRU, removing the need to hold a read
257 # lock on the cached gsutil.
258 ref = tar_cache.Lookup(key)
259 try:
260 ref.SetDefault(p)
261 except cros_build_lib.RunCommandError as e:
Ralph Nathan446aee92015-03-23 14:44:56 -0700262 logging.warning('ignoring %s\n%s', p, e)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500263 continue
Don Garretta28be6d2016-06-16 18:09:35 -0700264 for p in FindSymbolFiles(tempdir, [ref.path]):
Mike Frysingerd41938e2014-02-10 06:37:55 -0500265 yield p
266
267 elif os.path.isdir(p):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500268 for root, _, files in os.walk(p):
269 for f in files:
270 if f.endswith('.sym'):
Don Garretta28be6d2016-06-16 18:09:35 -0700271 # If p is '/tmp/foo' and filename is '/tmp/foo/bar/bar.sym',
272 # display_path = 'bar/bar.sym'
273 filename = os.path.join(root, f)
274 yield SymbolFile(display_path=filename[len(p):].lstrip('/'),
275 file_name=filename)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500276
277 elif IsTarball(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700278 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500279 tardir = tempfile.mkdtemp(dir=tempdir)
280 cache.Untar(os.path.realpath(p), tardir)
Don Garretta28be6d2016-06-16 18:09:35 -0700281 for p in FindSymbolFiles(tardir, [tardir]):
Mike Frysingerd41938e2014-02-10 06:37:55 -0500282 yield p
283
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500284 else:
Don Garretta28be6d2016-06-16 18:09:35 -0700285 yield SymbolFile(display_path=p, file_name=p)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500286
287
Don Garretta28be6d2016-06-16 18:09:35 -0700288def AdjustSymbolFileSize(symbol, tempdir, file_limit):
289 """Examine symbols files for size problems, and reduce if needed.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500290
Don Garretta28be6d2016-06-16 18:09:35 -0700291 If the symbols size is too big, strip out the call frame info. The CFI
292 is unnecessary for 32bit x86 targets where the frame pointer is used (as
293 all of ours have) and it accounts for over half the size of the symbols
294 uploaded.
295
296 Stripped files will be created inside tempdir, and will be the callers
297 responsibility to clean up.
298
299 We also warn, if a symbols file is still too large after stripping.
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500300
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500301 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700302 symbol: SymbolFile instance to be examined and modified as needed..
303 tempdir: A temporary directory we can create files in that the caller will
304 clean up.
305 file_limit: We only strip files which are larger than this limit.
306
307 Returns:
308 SymbolFile instance (original or modified as needed)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500309 """
Don Garretta28be6d2016-06-16 18:09:35 -0700310 file_size = symbol.FileSize()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500311
Don Garretta28be6d2016-06-16 18:09:35 -0700312 if file_limit and symbol.FileSize() > file_limit:
313 with tempfile.NamedTemporaryFile(
314 prefix='upload_symbols', bufsize=0,
315 dir=tempdir, delete=False) as temp_sym_file:
316
317 temp_sym_file.writelines(
318 [x for x in open(symbol.file_name, 'rb').readlines()
319 if not x.startswith('STACK CFI')]
320 )
321
322 original_file_size = file_size
323 symbol.file_name = temp_sym_file.name
324 file_size = symbol.FileSize()
325
326 logging.warning('stripped CFI for %s reducing size %s > %s',
327 symbol.display_name, original_file_size, file_size)
328
329 # Hopefully the crash server will let it through. But it probably won't.
330 # Not sure what the best answer is in this case.
331 if file_size >= CRASH_SERVER_FILE_LIMIT:
332 logging.PrintBuildbotStepWarnings()
333 logging.warning('upload file %s is awfully large, risking rejection by '
334 'the symbol server (%s > %s)', symbol.display_path,
335 file_size, CRASH_SERVER_FILE_LIMIT)
336
337 return symbol
338
339def OpenDeduplicateConnection(dedupe_namespace):
340 """Open a connection to the isolate server for Dedupe use.
341
342 Args:
343 dedupe_namespace: String id for the comparison namespace.
344
345 Returns:
346 Connection proxy, or None on failure.
347 """
348 try:
349 with timeout_util.Timeout(DEDUPE_TIMEOUT):
Xixuan Wu3e840592018-05-30 15:27:03 -0700350 return isolate_storage.get_storage_api(constants.ISOLATESERVER,
351 dedupe_namespace)
Don Garretta28be6d2016-06-16 18:09:35 -0700352 except Exception:
353 logging.warning('initializing isolate server connection failed',
354 exc_info=True)
355 return None
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500356
357
Don Garretta28be6d2016-06-16 18:09:35 -0700358def FindDuplicates(symbols, dedupe_namespace):
359 """Mark symbol files we've already uploaded as duplicates.
360
361 Using the swarming service, ask it to tell us which symbol files we've already
362 uploaded in previous runs and/or by other bots. If the query fails for any
363 reason, we'll just upload all symbols. This is fine as the symbol server will
364 do the right thing and this phase is purely an optimization.
365
366 Args:
367 symbols: An iterable of SymbolFiles to be uploaded.
368 dedupe_namespace: String id for the comparison namespace.
369
370 Yields:
371 All SymbolFiles from symbols, but duplicates have status updated to
372 DUPLICATE.
373 """
374 storage_query = OpenDeduplicateConnection(dedupe_namespace)
375
376 # We query isolate in batches, to reduce the number of network queries.
377 for batch in BatchGenerator(symbols, DEDUPE_LIMIT):
378 query_results = None
379
380 if storage_query:
381 # Convert SymbolFiles into DedupeItems.
382 items = [DedupeItem(x) for x in batch]
Don Garretta28be6d2016-06-16 18:09:35 -0700383
384 # Look for duplicates.
385 try:
386 with timeout_util.Timeout(DEDUPE_TIMEOUT):
387 query_results = storage_query.contains(items)
388 except Exception:
389 logging.warning('talking to dedupe server failed', exc_info=True)
390 storage_query = None
391
392 if query_results is not None:
393 for b in batch:
394 b.status = SymbolFile.DUPLICATE
395
396 # Only the non-duplicates appear in the query_results.
397 for item, push_state in query_results.iteritems():
398 # Remember the dedupe state, so we can mark the symbol as uploaded
399 # later on.
400 item.symbol.status = SymbolFile.INITIAL
401 item.symbol.dedupe_item = item
402 item.symbol.dedupe_push_state = push_state
403
404 # Yield all symbols we haven't shown to be duplicates.
405 for b in batch:
406 if b.status == SymbolFile.DUPLICATE:
407 logging.debug('Found duplicate: %s', b.display_name)
408 yield b
409
410
411def PostForDeduplication(symbols, dedupe_namespace):
412 """Send a symbol file to the swarming service
413
414 Notify the isolate service of a successful upload. If the notification fails
415 for any reason, we ignore it. We don't care as it just means we'll upload it
416 again later on, and the symbol server will handle that graciously.
417
418 Args:
419 symbols: An iterable of SymbolFiles to be uploaded.
420 dedupe_namespace: String id for the comparison namespace.
421
422 Yields:
423 Each symbol from symbols, unmodified.
424 """
425 storage_query = OpenDeduplicateConnection(dedupe_namespace)
426
427 for s in symbols:
428 # If we can talk to isolate, and we uploaded this symbol, and we
429 # queried for it's presence before, upload to isolate now.
430
431 if storage_query and s.status == SymbolFile.UPLOADED and s.dedupe_item:
Don Garretta28be6d2016-06-16 18:09:35 -0700432 try:
433 with timeout_util.Timeout(DEDUPE_NOTIFY_TIMEOUT):
434 storage_query.push(s.dedupe_item, s.dedupe_push_state,
435 s.dedupe_item.content())
436 logging.info('sent %s', s.display_name)
437 except Exception:
438 logging.warning('posting %s to dedupe server failed',
439 os.path.basename(s.display_path), exc_info=True)
440 storage_query = None
441
442 yield s
443
444
445def GetUploadTimeout(symbol):
446 """How long to wait for a specific file to upload to the crash server.
447
448 This is a function largely to make unittesting easier.
449
450 Args:
451 symbol: A SymbolFile instance.
452
453 Returns:
454 Timeout length (in seconds)
455 """
456 # Scale the timeout based on the filesize.
457 return max(symbol.FileSize() / UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
458
459
460def UploadSymbolFile(upload_url, symbol, product_name):
461 """Upload a symbol file to the crash server.
462
463 The upload is a multipart/form-data POST with the following parameters:
464 code_file: the basename of the module, e.g. "app"
465 code_identifier: the module file's identifier
466 debug_file: the basename of the debugging file, e.g. "app"
467 debug_identifier: the debug file's identifier, usually consisting of
468 the guid and age embedded in the pdb, e.g.
469 "11111111BBBB3333DDDD555555555555F"
470 version: the file version of the module, e.g. "1.2.3.4"
471 product: HTTP-friendly product name
472 os: the operating system that the module was built for
473 cpu: the CPU that the module was built for
474 symbol_file: the contents of the breakpad-format symbol file
475
476 Args:
477 upload_url: The crash URL to POST the |sym_file| to
478 symbol: A SymbolFile instance.
479 product_name: A string for stats purposes. Usually 'ChromeOS' or 'Android'.
480 """
481 fields = (
482 ('code_file', symbol.header.name),
483 ('debug_file', symbol.header.name),
484 ('debug_identifier', symbol.header.id.replace('-', '')),
485 # The product/version fields are used by the server only for statistic
486 # purposes. They do not impact symbolization, so they're safe to set
487 # to any value all the time.
488 # In this case, we use it to help see the load our build system is
489 # placing on the server.
490 # Not sure what to set for the version. Maybe the git sha1 of this file.
491 # Note: the server restricts this to 30 chars.
492 #('version', None),
493 ('product', product_name),
494 ('os', symbol.header.os),
495 ('cpu', symbol.header.cpu),
496 poster.encode.MultipartParam.from_file('symbol_file', symbol.file_name),
497 )
498
499 data, headers = poster.encode.multipart_encode(fields)
500 request = urllib2.Request(upload_url, data, headers)
501 request.add_header('User-agent', 'chromite.upload_symbols')
502 urllib2.urlopen(request, timeout=GetUploadTimeout(symbol))
503
504
Don Garrettdeb2e032016-07-06 16:44:14 -0700505def PerformSymbolsFileUpload(symbols, upload_url, product_name='ChromeOS'):
Don Garretta28be6d2016-06-16 18:09:35 -0700506 """Upload the symbols to the crash server
507
508 Args:
Don Garrettdeb2e032016-07-06 16:44:14 -0700509 symbols: An iterable of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700510 upload_url: URL of crash server to upload too.
511 failures: Tracker for total upload failures.
512 product_name: A string for stats purposes. Usually 'ChromeOS' or 'Android'.
513
Don Garrettdeb2e032016-07-06 16:44:14 -0700514 Yields:
515 Each symbol from symbols, perhaps modified.
Don Garretta28be6d2016-06-16 18:09:35 -0700516 """
Don Garrettdeb2e032016-07-06 16:44:14 -0700517 failures = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700518
Don Garrettdeb2e032016-07-06 16:44:14 -0700519 for s in symbols:
520 if (failures < MAX_TOTAL_ERRORS_FOR_RETRY and
521 s.status in (SymbolFile.INITIAL, SymbolFile.ERROR)):
522 # Keeps us from DoS-ing the symbol server.
523 time.sleep(SLEEP_DELAY)
524 logging.info('Uploading symbol_file: %s', s.display_path)
525 try:
526 # This command retries the upload multiple times with growing delays. We
527 # only consider the upload a failure if these retries fail.
Don Garrette1f47e92016-10-13 16:04:56 -0700528 def ShouldRetryUpload(exception):
Ryo Hashimoto4ddf3f02017-03-22 18:25:27 +0900529 return isinstance(exception, (urllib2.HTTPError, urllib2.URLError,
530 httplib.HTTPException, socket.error))
Don Garrette1f47e92016-10-13 16:04:56 -0700531
Don Garrett440944e2016-10-03 16:33:31 -0700532 with cros_build_lib.TimedSection() as timer:
Don Garrette1f47e92016-10-13 16:04:56 -0700533 retry_stats.RetryWithStats(
534 UPLOAD_STATS, ShouldRetryUpload, MAX_RETRIES,
Don Garrett440944e2016-10-03 16:33:31 -0700535 UploadSymbolFile,
536 upload_url, s, product_name,
Luigi Semenzato5104f3c2016-10-12 12:37:42 -0700537 sleep=INITIAL_RETRY_DELAY,
538 log_all_retries=True)
Don Garrett440944e2016-10-03 16:33:31 -0700539 logging.info('upload of %10i bytes took %s', s.FileSize(), timer.delta)
Don Garrettdeb2e032016-07-06 16:44:14 -0700540 s.status = SymbolFile.UPLOADED
541 except urllib2.HTTPError as e:
542 logging.warning('could not upload: %s: HTTP %s: %s',
543 s.display_name, e.code, e.reason)
544 s.status = SymbolFile.ERROR
545 failures += 1
546 except (urllib2.URLError, httplib.HTTPException, socket.error) as e:
Ryo Hashimoto45273f02017-03-16 17:45:56 +0900547 logging.warning('could not upload: %s: %s %s', s.display_name,
548 type(e).__name__, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700549 s.status = SymbolFile.ERROR
550 failures += 1
551
552 # We pass the symbol along, on both success and failure.
553 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700554
555
Don Garrettdeb2e032016-07-06 16:44:14 -0700556def ReportResults(symbols, failed_list):
Don Garretta28be6d2016-06-16 18:09:35 -0700557 """Log a summary of the symbol uploading.
558
559 This has the side effect of fully consuming the symbols iterator.
560
561 Args:
562 symbols: An iterator of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700563 failed_list: A filename at which to write out a list of our failed uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700564
565 Returns:
566 The number of symbols not uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700567 """
568 upload_failures = []
569 result_counts = {
570 SymbolFile.INITIAL: 0,
571 SymbolFile.UPLOADED: 0,
572 SymbolFile.DUPLICATE: 0,
Don Garrettdeb2e032016-07-06 16:44:14 -0700573 SymbolFile.ERROR: 0,
Don Garretta28be6d2016-06-16 18:09:35 -0700574 }
575
576 for s in symbols:
577 result_counts[s.status] += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700578 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700579 upload_failures.append(s)
580
Don Garrette1f47e92016-10-13 16:04:56 -0700581 # Report retry numbers.
582 _, _, retries = retry_stats.CategoryStats(UPLOAD_STATS)
583 if retries:
584 logging.warning('%d upload retries performed.', retries)
585
Don Garretta28be6d2016-06-16 18:09:35 -0700586 logging.info('Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.',
587 result_counts)
588
Chris Ching91908032016-09-27 16:55:33 -0600589 if result_counts[SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700590 logging.PrintBuildbotStepWarnings()
Chris Ching91908032016-09-27 16:55:33 -0600591 logging.warning('%d non-recoverable upload errors',
592 result_counts[SymbolFile.ERROR])
593
594 if result_counts[SymbolFile.INITIAL]:
595 logging.PrintBuildbotStepWarnings()
596 logging.warning('%d upload(s) were skipped because of excessive errors',
Don Garrettdeb2e032016-07-06 16:44:14 -0700597 result_counts[SymbolFile.INITIAL])
Don Garretta28be6d2016-06-16 18:09:35 -0700598
599 if failed_list is not None:
600 with open(failed_list, 'w') as fl:
601 for s in upload_failures:
602 fl.write('%s\n' % s.display_path)
603
Don Garrettdeb2e032016-07-06 16:44:14 -0700604 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
605
Don Garretta28be6d2016-06-16 18:09:35 -0700606
607def UploadSymbols(sym_paths, upload_url, product_name, dedupe_namespace=None,
608 failed_list=None, upload_limit=None, strip_cfi=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400609 """Upload all the generated symbols for |board| to the crash server
610
611 Args:
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500612 sym_paths: Specific symbol files (or dirs of sym files) to upload,
613 otherwise search |breakpad_dir|
Don Garretta28be6d2016-06-16 18:09:35 -0700614 upload_url: URL of crash server to upload too.
615 product_name: A string for crash server stats purposes.
616 Usually 'ChromeOS' or 'Android'.
617 dedupe_namespace: None for no deduping, or string namespace in isolate.
618 failed_list: A filename at which to write out a list of our failed uploads.
619 upload_limit: Integer listing how many files to upload. None for no limit.
620 strip_cfi: File size at which we strip out CFI data. None for no limit.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500621
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400622 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400623 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400624 """
Don Garrette1f47e92016-10-13 16:04:56 -0700625 retry_stats.SetupStats()
626
Don Garretta28be6d2016-06-16 18:09:35 -0700627 # Note: This method looks like each step of processing is performed
628 # sequentially for all SymbolFiles, but instead each step is a generator that
629 # produces the next iteration only when it's read. This means that (except for
630 # some batching) each SymbolFile goes through all of these steps before the
631 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400632
Don Garretta28be6d2016-06-16 18:09:35 -0700633 # This is used to hold striped
634 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
635 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500636
Don Garrett1bc1e102016-07-06 17:06:10 -0700637 # Sort all of our symbols so the largest ones (probably the most important)
638 # are processed first.
639 symbols = list(symbols)
640 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
641
Don Garretta28be6d2016-06-16 18:09:35 -0700642 if upload_limit is not None:
643 # Restrict symbols processed to the limit.
644 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400645
Don Garretta28be6d2016-06-16 18:09:35 -0700646 # Strip CFI, if needed.
647 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500648
Don Garretta28be6d2016-06-16 18:09:35 -0700649 # Skip duplicates.
650 if dedupe_namespace:
651 symbols = FindDuplicates(symbols, dedupe_namespace)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500652
Don Garretta28be6d2016-06-16 18:09:35 -0700653 # Perform uploads
Don Garrettdeb2e032016-07-06 16:44:14 -0700654 symbols = PerformSymbolsFileUpload(symbols, upload_url, product_name)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400655
Don Garretta28be6d2016-06-16 18:09:35 -0700656 # Record for future deduping.
657 if dedupe_namespace:
658 symbols = PostForDeduplication(symbols, dedupe_namespace)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400659
Don Garretta28be6d2016-06-16 18:09:35 -0700660 # Log the final results, and consume the symbols generator fully.
Don Garrettdeb2e032016-07-06 16:44:14 -0700661 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500662
Don Garrettdeb2e032016-07-06 16:44:14 -0700663 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400664
665
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400666def main(argv):
667 parser = commandline.ArgumentParser(description=__doc__)
668
Don Garretta28be6d2016-06-16 18:09:35 -0700669 # TODO: Make sym_paths, breakpad_root, and root exclusive.
670
Mike Frysingerd41938e2014-02-10 06:37:55 -0500671 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
672 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400673 parser.add_argument('--board', default=None,
Don Garrett747cc4b2015-10-07 14:48:48 -0700674 help='Used to find default breakpad_root.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400675 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500676 help='full path to the breakpad symbol directory')
677 parser.add_argument('--root', type='path', default=None,
678 help='full path to the chroot dir')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400679 parser.add_argument('--official_build', action='store_true', default=False,
680 help='point to official symbol server')
Mike Frysinger38647542014-09-12 18:15:39 -0700681 parser.add_argument('--server', type=str, default=None,
682 help='URI for custom symbol server')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400683 parser.add_argument('--regenerate', action='store_true', default=False,
684 help='regenerate all symbols')
Don Garretta28be6d2016-06-16 18:09:35 -0700685 parser.add_argument('--upload-limit', type=int,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400686 help='only upload # number of symbols')
687 parser.add_argument('--strip_cfi', type=int,
Don Garretta28be6d2016-06-16 18:09:35 -0700688 default=DEFAULT_FILE_LIMIT,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400689 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500690 parser.add_argument('--failed-list', type='path',
691 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500692 parser.add_argument('--dedupe', action='store_true', default=False,
693 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400694 parser.add_argument('--yes', action='store_true', default=False,
695 help='answer yes to all prompts')
Don Garrett747cc4b2015-10-07 14:48:48 -0700696 parser.add_argument('--product_name', type=str, default='ChromeOS',
697 help='Produce Name for breakpad stats.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400698
699 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500700 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400701
Don Garretta28be6d2016-06-16 18:09:35 -0700702 # Figure out the symbol files/directories to upload.
703 if opts.sym_paths:
704 sym_paths = opts.sym_paths
705 elif opts.breakpad_root:
706 sym_paths = [opts.breakpad_root]
707 elif opts.root:
708 if not opts.board:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400709 cros_build_lib.Die('--board must be set if --root is used.')
Don Garretta28be6d2016-06-16 18:09:35 -0700710 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(opts.board)
711 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip('/'))]
712 else:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400713 cros_build_lib.Die('--sym_paths, --breakpad_root, or --root must be set.')
Don Garretta28be6d2016-06-16 18:09:35 -0700714
Don Garrett747cc4b2015-10-07 14:48:48 -0700715 if opts.sym_paths or opts.breakpad_root:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400716 if opts.regenerate:
Don Garrett747cc4b2015-10-07 14:48:48 -0700717 cros_build_lib.Die('--regenerate may not be used with specific files, '
718 'or breakpad_root')
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400719 else:
720 if opts.board is None:
721 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400722
Don Garretta28be6d2016-06-16 18:09:35 -0700723 # Figure out the dedupe namespace.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500724 dedupe_namespace = None
725 if opts.dedupe:
Don Garretta28be6d2016-06-16 18:09:35 -0700726 if opts.official_build:
Don Garrett747cc4b2015-10-07 14:48:48 -0700727 dedupe_namespace = OFFICIAL_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500728 else:
Don Garrett747cc4b2015-10-07 14:48:48 -0700729 dedupe_namespace = STAGING_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500730
Don Garretta28be6d2016-06-16 18:09:35 -0700731 # Figure out which crash server to upload too.
732 upload_url = opts.server
733 if not upload_url:
734 if opts.official_build:
735 upload_url = OFFICIAL_UPLOAD_URL
736 else:
737 logging.warning('unofficial builds upload to the staging server')
738 upload_url = STAGING_UPLOAD_URL
739
740 # Confirm we really want the long upload.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400741 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500742 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400743 Uploading symbols for an entire Chromium OS build is really only
744 necessary for release builds and in a few cases for developers
745 to debug problems. It will take considerable time to run. For
746 developer debugging purposes, consider instead passing specific
747 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500748 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400749 if not cros_build_lib.BooleanPrompt(
750 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500751 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400752 cros_build_lib.Die('better safe than sorry')
753
754 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700755
756 # Regenerate symbols from binaries.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400757 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400758 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
759 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400760
Don Garretta28be6d2016-06-16 18:09:35 -0700761 # Do the upload.
762 ret += UploadSymbols(
763 sym_paths=sym_paths,
764 upload_url=upload_url,
765 product_name=opts.product_name,
766 dedupe_namespace=dedupe_namespace,
767 failed_list=opts.failed_list,
768 upload_limit=opts.upload_limit,
769 strip_cfi=opts.strip_cfi)
770
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400771 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700772 logging.error('encountered %i problem(s)', ret)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400773 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
774 # return 0 in case we are a multiple of the mask.
Don Garretta28be6d2016-06-16 18:09:35 -0700775 return 1