blob: 8fa55ffcfa3bc8ae647af727bd36a49f1c648888 [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.
66import isolateserver # pylint: disable=import-error
Mike Frysinger0c0efa22014-02-09 23:32:23 -050067
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040068
Don Garretta28be6d2016-06-16 18:09:35 -070069# We need this to run once per process. Do it at module import time as that
70# will let us avoid doing it inline at function call time (see UploadSymbolFile)
71# as that func might be called by the multiprocessing module which means we'll
72# do the opener logic multiple times overall. Plus, if you're importing this
73# module, it's a pretty good chance that you're going to need this.
74poster.streaminghttp.register_openers()
75
76
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040077# URLs used for uploading symbols.
78OFFICIAL_UPLOAD_URL = 'http://clients2.google.com/cr/symbol'
79STAGING_UPLOAD_URL = 'http://clients2.google.com/cr/staging_symbol'
80
81
82# The crash server rejects files that are this big.
Ryo Hashimotof864c0e2017-02-14 12:46:08 +090083CRASH_SERVER_FILE_LIMIT = 700 * 1024 * 1024
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040084# Give ourselves a little breathing room from what the server expects.
85DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
86
87
Mike Frysinger0c0efa22014-02-09 23:32:23 -050088# The batch limit when talking to the dedup server. We avoid sending one at a
89# time as the round trip overhead will dominate. Conversely, we avoid sending
90# all at once so we can start uploading symbols asap -- the symbol server is a
91# bit slow and will take longer than anything else.
Mike Frysinger0c0efa22014-02-09 23:32:23 -050092DEDUPE_LIMIT = 100
93
94# How long to wait for the server to respond with the results. Note that the
95# larger the limit above, the larger this will need to be. So we give it ~1
96# second per item max.
97DEDUPE_TIMEOUT = DEDUPE_LIMIT
98
Don Garretta28be6d2016-06-16 18:09:35 -070099# How long to wait for the notification to finish (in seconds).
100DEDUPE_NOTIFY_TIMEOUT = 240
Mike Frysinger4dd462e2014-04-30 16:21:51 -0400101
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500102# The unique namespace in the dedupe server that only we use. Helps avoid
103# collisions with all the hashed values and unrelated content.
Don Garrett747cc4b2015-10-07 14:48:48 -0700104OFFICIAL_DEDUPE_NAMESPACE_TMPL = '%s-upload-symbols'
105STAGING_DEDUPE_NAMESPACE_TMPL = '%s-staging' % OFFICIAL_DEDUPE_NAMESPACE_TMPL
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500106
107
Mike Frysinger71046662014-09-12 18:15:15 -0700108# The minimum average rate (in bytes per second) that we expect to maintain
109# when uploading symbols. This has to allow for symbols that are up to
110# CRASH_SERVER_FILE_LIMIT in size.
111UPLOAD_MIN_RATE = CRASH_SERVER_FILE_LIMIT / (30 * 60)
112
113# The lowest timeout (in seconds) we'll allow. If the server is overloaded,
114# then there might be a delay in setting up the connection, not just with the
115# transfer. So even a small file might need a larger value.
Ryo Hashimotoc0049372017-02-16 18:50:00 +0900116UPLOAD_MIN_TIMEOUT = 5 * 60
Mike Frysingercd78a082013-06-26 17:13:04 -0400117
118
Don Garrett7a793092016-07-06 16:50:27 -0700119# Sleep for 500ms in between uploads to avoid DoS'ing symbol server.
120SLEEP_DELAY = 0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400121
122
123# Number of seconds to wait before retrying an upload. The delay will double
124# for each subsequent retry of the same symbol file.
125INITIAL_RETRY_DELAY = 1
126
127# Allow up to 7 attempts to upload a symbol file (total delay may be
128# 1+2+4+8+16+32=63 seconds).
129MAX_RETRIES = 6
130
Mike Frysingereb753bf2013-11-22 16:05:35 -0500131# Number of total errors, before uploads are no longer attempted.
132# This is used to avoid lots of errors causing unreasonable delays.
Mike Frysingereb753bf2013-11-22 16:05:35 -0500133MAX_TOTAL_ERRORS_FOR_RETRY = 30
134
Don Garrette1f47e92016-10-13 16:04:56 -0700135# Category to use for collection upload retry stats.
136UPLOAD_STATS = 'UPLOAD'
137
Don Garretta28be6d2016-06-16 18:09:35 -0700138
Don Garretta28be6d2016-06-16 18:09:35 -0700139def BatchGenerator(iterator, batch_size):
140 """Given an iterator, break into lists of size batch_size.
Fang Dengba680462015-08-16 20:34:11 -0700141
Don Garretta28be6d2016-06-16 18:09:35 -0700142 The result is a generator, that will only read in as many inputs as needed for
143 the current batch. The final result can be smaller than batch_size.
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700144 """
Don Garretta28be6d2016-06-16 18:09:35 -0700145 batch = []
146 for i in iterator:
147 batch.append(i)
148 if len(batch) >= batch_size:
149 yield batch
150 batch = []
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700151
Don Garretta28be6d2016-06-16 18:09:35 -0700152 if batch:
153 # if there was anything left in the final batch, yield it.
154 yield batch
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500155
156
Mike Frysingerd41938e2014-02-10 06:37:55 -0500157def IsTarball(path):
158 """Guess if this is a tarball based on the filename."""
159 parts = path.split('.')
160 if len(parts) <= 1:
161 return False
162
163 if parts[-1] == 'tar':
164 return True
165
166 if parts[-2] == 'tar':
167 return parts[-1] in ('bz2', 'gz', 'xz')
168
169 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
170
171
Don Garretta28be6d2016-06-16 18:09:35 -0700172class SymbolFile(object):
173 """This class represents the state of a symbol file during processing.
174
175 Properties:
176 display_path: Name of symbol file that should be consistent between builds.
177 file_name: Transient path of the symbol file.
178 header: ReadSymsHeader output. Dict with assorted meta-data.
179 status: INITIAL, DUPLICATE, or UPLOADED based on status of processing.
180 dedupe_item: None or instance of DedupeItem for this symbol file.
181 dedupe_push_state: Opaque value to return to dedupe code for file.
182 display_name: Read only friendly (short) file name for logging.
183 file_size: Read only size of the symbol file.
184 """
185 INITIAL = 'initial'
186 DUPLICATE = 'duplicate'
187 UPLOADED = 'uploaded'
Don Garrettdeb2e032016-07-06 16:44:14 -0700188 ERROR = 'error'
Don Garretta28be6d2016-06-16 18:09:35 -0700189
190 def __init__(self, display_path, file_name):
191 """An instance of this class represents a symbol file over time.
192
193 Args:
194 display_path: A unique/persistent between builds name to present to the
195 crash server. It is the file name, relative to where it
196 came from (tarball, breakpad dir, etc).
197 file_name: A the current location of the symbol file.
198 """
199 self.display_path = display_path
200 self.file_name = file_name
201 self.header = cros_generate_breakpad_symbols.ReadSymsHeader(file_name)
202 self.status = SymbolFile.INITIAL
203 self.dedupe_item = None
204 self.dedupe_push_state = None
205
206 @property
207 def display_name(self):
208 return os.path.basename(self.display_path)
209
210 def FileSize(self):
211 return os.path.getsize(self.file_name)
212
213
214class DedupeItem(isolateserver.BufferItem):
215 """Turn a SymbolFile into an isolateserver.Item"""
216
217 ALGO = hashlib.sha1
218
219 def __init__(self, symbol):
Mike Frysingerbbd1f112016-09-08 18:25:11 -0400220 isolateserver.BufferItem.__init__(self, str(symbol.header), self.ALGO)
Don Garretta28be6d2016-06-16 18:09:35 -0700221 self.symbol = symbol
222
223
224def FindSymbolFiles(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500225 """Locate symbol files in |paths|
226
Don Garretta28be6d2016-06-16 18:09:35 -0700227 This returns SymbolFile objects that contain file references which are valid
228 after this exits. Those files may exist externally, or be created in the
229 tempdir (say, when expanding tarballs). The caller must not consider
230 SymbolFile's valid after tempdir is cleaned up.
231
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500232 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700233 tempdir: Path to use for temporary files.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500234 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500235 Dirs are searched for files that end in ".sym". Urls are fetched and then
236 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500237
Don Garretta28be6d2016-06-16 18:09:35 -0700238 Yields:
239 A SymbolFile for every symbol file found in paths.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500240 """
Gilad Arnold83233ed2015-05-08 12:12:13 -0700241 cache_dir = path_util.GetCacheDir()
Mike Frysingere847efd2015-01-08 03:57:24 -0500242 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
243 tar_cache = cache.TarballCache(common_path)
244
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500245 for p in paths:
Don Garrett25f309a2014-03-19 14:02:12 -0700246 # Pylint is confused about members of ParseResult.
Don Garrettf8bf7842014-03-20 17:03:42 -0700247
Mike Frysingerd41938e2014-02-10 06:37:55 -0500248 o = urlparse.urlparse(p)
Don Garrettf8bf7842014-03-20 17:03:42 -0700249 if o.scheme: # pylint: disable=E1101
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)
Don Garrettf8bf7842014-03-20 17:03:42 -0700255 key = ('%s%s' % (o.netloc, o.path)).split('/') # pylint: disable=E1101
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):
350 return isolateserver.get_storage_api(constants.ISOLATESERVER,
351 dedupe_namespace)
352 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]
383 for item in items:
384 item.prepare(DedupeItem.ALGO)
385
386 # Look for duplicates.
387 try:
388 with timeout_util.Timeout(DEDUPE_TIMEOUT):
389 query_results = storage_query.contains(items)
390 except Exception:
391 logging.warning('talking to dedupe server failed', exc_info=True)
392 storage_query = None
393
394 if query_results is not None:
395 for b in batch:
396 b.status = SymbolFile.DUPLICATE
397
398 # Only the non-duplicates appear in the query_results.
399 for item, push_state in query_results.iteritems():
400 # Remember the dedupe state, so we can mark the symbol as uploaded
401 # later on.
402 item.symbol.status = SymbolFile.INITIAL
403 item.symbol.dedupe_item = item
404 item.symbol.dedupe_push_state = push_state
405
406 # Yield all symbols we haven't shown to be duplicates.
407 for b in batch:
408 if b.status == SymbolFile.DUPLICATE:
409 logging.debug('Found duplicate: %s', b.display_name)
410 yield b
411
412
413def PostForDeduplication(symbols, dedupe_namespace):
414 """Send a symbol file to the swarming service
415
416 Notify the isolate service of a successful upload. If the notification fails
417 for any reason, we ignore it. We don't care as it just means we'll upload it
418 again later on, and the symbol server will handle that graciously.
419
420 Args:
421 symbols: An iterable of SymbolFiles to be uploaded.
422 dedupe_namespace: String id for the comparison namespace.
423
424 Yields:
425 Each symbol from symbols, unmodified.
426 """
427 storage_query = OpenDeduplicateConnection(dedupe_namespace)
428
429 for s in symbols:
430 # If we can talk to isolate, and we uploaded this symbol, and we
431 # queried for it's presence before, upload to isolate now.
432
433 if storage_query and s.status == SymbolFile.UPLOADED and s.dedupe_item:
434 s.dedupe_item.prepare(DedupeItem.ALGO)
435 try:
436 with timeout_util.Timeout(DEDUPE_NOTIFY_TIMEOUT):
437 storage_query.push(s.dedupe_item, s.dedupe_push_state,
438 s.dedupe_item.content())
439 logging.info('sent %s', s.display_name)
440 except Exception:
441 logging.warning('posting %s to dedupe server failed',
442 os.path.basename(s.display_path), exc_info=True)
443 storage_query = None
444
445 yield s
446
447
448def GetUploadTimeout(symbol):
449 """How long to wait for a specific file to upload to the crash server.
450
451 This is a function largely to make unittesting easier.
452
453 Args:
454 symbol: A SymbolFile instance.
455
456 Returns:
457 Timeout length (in seconds)
458 """
459 # Scale the timeout based on the filesize.
460 return max(symbol.FileSize() / UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
461
462
463def UploadSymbolFile(upload_url, symbol, product_name):
464 """Upload a symbol file to the crash server.
465
466 The upload is a multipart/form-data POST with the following parameters:
467 code_file: the basename of the module, e.g. "app"
468 code_identifier: the module file's identifier
469 debug_file: the basename of the debugging file, e.g. "app"
470 debug_identifier: the debug file's identifier, usually consisting of
471 the guid and age embedded in the pdb, e.g.
472 "11111111BBBB3333DDDD555555555555F"
473 version: the file version of the module, e.g. "1.2.3.4"
474 product: HTTP-friendly product name
475 os: the operating system that the module was built for
476 cpu: the CPU that the module was built for
477 symbol_file: the contents of the breakpad-format symbol file
478
479 Args:
480 upload_url: The crash URL to POST the |sym_file| to
481 symbol: A SymbolFile instance.
482 product_name: A string for stats purposes. Usually 'ChromeOS' or 'Android'.
483 """
484 fields = (
485 ('code_file', symbol.header.name),
486 ('debug_file', symbol.header.name),
487 ('debug_identifier', symbol.header.id.replace('-', '')),
488 # The product/version fields are used by the server only for statistic
489 # purposes. They do not impact symbolization, so they're safe to set
490 # to any value all the time.
491 # In this case, we use it to help see the load our build system is
492 # placing on the server.
493 # Not sure what to set for the version. Maybe the git sha1 of this file.
494 # Note: the server restricts this to 30 chars.
495 #('version', None),
496 ('product', product_name),
497 ('os', symbol.header.os),
498 ('cpu', symbol.header.cpu),
499 poster.encode.MultipartParam.from_file('symbol_file', symbol.file_name),
500 )
501
502 data, headers = poster.encode.multipart_encode(fields)
503 request = urllib2.Request(upload_url, data, headers)
504 request.add_header('User-agent', 'chromite.upload_symbols')
505 urllib2.urlopen(request, timeout=GetUploadTimeout(symbol))
506
507
Don Garrettdeb2e032016-07-06 16:44:14 -0700508def PerformSymbolsFileUpload(symbols, upload_url, product_name='ChromeOS'):
Don Garretta28be6d2016-06-16 18:09:35 -0700509 """Upload the symbols to the crash server
510
511 Args:
Don Garrettdeb2e032016-07-06 16:44:14 -0700512 symbols: An iterable of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700513 upload_url: URL of crash server to upload too.
514 failures: Tracker for total upload failures.
515 product_name: A string for stats purposes. Usually 'ChromeOS' or 'Android'.
516
Don Garrettdeb2e032016-07-06 16:44:14 -0700517 Yields:
518 Each symbol from symbols, perhaps modified.
Don Garretta28be6d2016-06-16 18:09:35 -0700519 """
Don Garrettdeb2e032016-07-06 16:44:14 -0700520 failures = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700521
Don Garrettdeb2e032016-07-06 16:44:14 -0700522 for s in symbols:
523 if (failures < MAX_TOTAL_ERRORS_FOR_RETRY and
524 s.status in (SymbolFile.INITIAL, SymbolFile.ERROR)):
525 # Keeps us from DoS-ing the symbol server.
526 time.sleep(SLEEP_DELAY)
527 logging.info('Uploading symbol_file: %s', s.display_path)
528 try:
529 # This command retries the upload multiple times with growing delays. We
530 # only consider the upload a failure if these retries fail.
Don Garrette1f47e92016-10-13 16:04:56 -0700531 def ShouldRetryUpload(exception):
Ryo Hashimoto4ddf3f02017-03-22 18:25:27 +0900532 return isinstance(exception, (urllib2.HTTPError, urllib2.URLError,
533 httplib.HTTPException, socket.error))
Don Garrette1f47e92016-10-13 16:04:56 -0700534
Don Garrett440944e2016-10-03 16:33:31 -0700535 with cros_build_lib.TimedSection() as timer:
Don Garrette1f47e92016-10-13 16:04:56 -0700536 retry_stats.RetryWithStats(
537 UPLOAD_STATS, ShouldRetryUpload, MAX_RETRIES,
Don Garrett440944e2016-10-03 16:33:31 -0700538 UploadSymbolFile,
539 upload_url, s, product_name,
Luigi Semenzato5104f3c2016-10-12 12:37:42 -0700540 sleep=INITIAL_RETRY_DELAY,
541 log_all_retries=True)
Don Garrett440944e2016-10-03 16:33:31 -0700542 logging.info('upload of %10i bytes took %s', s.FileSize(), timer.delta)
Don Garrettdeb2e032016-07-06 16:44:14 -0700543 s.status = SymbolFile.UPLOADED
544 except urllib2.HTTPError as e:
545 logging.warning('could not upload: %s: HTTP %s: %s',
546 s.display_name, e.code, e.reason)
547 s.status = SymbolFile.ERROR
548 failures += 1
549 except (urllib2.URLError, httplib.HTTPException, socket.error) as e:
Ryo Hashimoto45273f02017-03-16 17:45:56 +0900550 logging.warning('could not upload: %s: %s %s', s.display_name,
551 type(e).__name__, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700552 s.status = SymbolFile.ERROR
553 failures += 1
554
555 # We pass the symbol along, on both success and failure.
556 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700557
558
Don Garrettdeb2e032016-07-06 16:44:14 -0700559def ReportResults(symbols, failed_list):
Don Garretta28be6d2016-06-16 18:09:35 -0700560 """Log a summary of the symbol uploading.
561
562 This has the side effect of fully consuming the symbols iterator.
563
564 Args:
565 symbols: An iterator of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700566 failed_list: A filename at which to write out a list of our failed uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700567
568 Returns:
569 The number of symbols not uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700570 """
571 upload_failures = []
572 result_counts = {
573 SymbolFile.INITIAL: 0,
574 SymbolFile.UPLOADED: 0,
575 SymbolFile.DUPLICATE: 0,
Don Garrettdeb2e032016-07-06 16:44:14 -0700576 SymbolFile.ERROR: 0,
Don Garretta28be6d2016-06-16 18:09:35 -0700577 }
578
579 for s in symbols:
580 result_counts[s.status] += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700581 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700582 upload_failures.append(s)
583
Don Garrette1f47e92016-10-13 16:04:56 -0700584 # Report retry numbers.
585 _, _, retries = retry_stats.CategoryStats(UPLOAD_STATS)
586 if retries:
587 logging.warning('%d upload retries performed.', retries)
588
Don Garretta28be6d2016-06-16 18:09:35 -0700589 logging.info('Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.',
590 result_counts)
591
Chris Ching91908032016-09-27 16:55:33 -0600592 if result_counts[SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700593 logging.PrintBuildbotStepWarnings()
Chris Ching91908032016-09-27 16:55:33 -0600594 logging.warning('%d non-recoverable upload errors',
595 result_counts[SymbolFile.ERROR])
596
597 if result_counts[SymbolFile.INITIAL]:
598 logging.PrintBuildbotStepWarnings()
599 logging.warning('%d upload(s) were skipped because of excessive errors',
Don Garrettdeb2e032016-07-06 16:44:14 -0700600 result_counts[SymbolFile.INITIAL])
Don Garretta28be6d2016-06-16 18:09:35 -0700601
602 if failed_list is not None:
603 with open(failed_list, 'w') as fl:
604 for s in upload_failures:
605 fl.write('%s\n' % s.display_path)
606
Don Garrettdeb2e032016-07-06 16:44:14 -0700607 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
608
Don Garretta28be6d2016-06-16 18:09:35 -0700609
610def UploadSymbols(sym_paths, upload_url, product_name, dedupe_namespace=None,
611 failed_list=None, upload_limit=None, strip_cfi=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400612 """Upload all the generated symbols for |board| to the crash server
613
614 Args:
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500615 sym_paths: Specific symbol files (or dirs of sym files) to upload,
616 otherwise search |breakpad_dir|
Don Garretta28be6d2016-06-16 18:09:35 -0700617 upload_url: URL of crash server to upload too.
618 product_name: A string for crash server stats purposes.
619 Usually 'ChromeOS' or 'Android'.
620 dedupe_namespace: None for no deduping, or string namespace in isolate.
621 failed_list: A filename at which to write out a list of our failed uploads.
622 upload_limit: Integer listing how many files to upload. None for no limit.
623 strip_cfi: File size at which we strip out CFI data. None for no limit.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500624
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400625 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400626 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400627 """
Don Garrette1f47e92016-10-13 16:04:56 -0700628 retry_stats.SetupStats()
629
Don Garretta28be6d2016-06-16 18:09:35 -0700630 # Note: This method looks like each step of processing is performed
631 # sequentially for all SymbolFiles, but instead each step is a generator that
632 # produces the next iteration only when it's read. This means that (except for
633 # some batching) each SymbolFile goes through all of these steps before the
634 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400635
Don Garretta28be6d2016-06-16 18:09:35 -0700636 # This is used to hold striped
637 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
638 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500639
Don Garrett1bc1e102016-07-06 17:06:10 -0700640 # Sort all of our symbols so the largest ones (probably the most important)
641 # are processed first.
642 symbols = list(symbols)
643 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
644
Don Garretta28be6d2016-06-16 18:09:35 -0700645 if upload_limit is not None:
646 # Restrict symbols processed to the limit.
647 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400648
Don Garretta28be6d2016-06-16 18:09:35 -0700649 # Strip CFI, if needed.
650 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500651
Don Garretta28be6d2016-06-16 18:09:35 -0700652 # Skip duplicates.
653 if dedupe_namespace:
654 symbols = FindDuplicates(symbols, dedupe_namespace)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500655
Don Garretta28be6d2016-06-16 18:09:35 -0700656 # Perform uploads
Don Garrettdeb2e032016-07-06 16:44:14 -0700657 symbols = PerformSymbolsFileUpload(symbols, upload_url, product_name)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400658
Don Garretta28be6d2016-06-16 18:09:35 -0700659 # Record for future deduping.
660 if dedupe_namespace:
661 symbols = PostForDeduplication(symbols, dedupe_namespace)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400662
Don Garretta28be6d2016-06-16 18:09:35 -0700663 # Log the final results, and consume the symbols generator fully.
Don Garrettdeb2e032016-07-06 16:44:14 -0700664 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500665
Don Garrettdeb2e032016-07-06 16:44:14 -0700666 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400667
668
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400669def main(argv):
670 parser = commandline.ArgumentParser(description=__doc__)
671
Don Garretta28be6d2016-06-16 18:09:35 -0700672 # TODO: Make sym_paths, breakpad_root, and root exclusive.
673
Mike Frysingerd41938e2014-02-10 06:37:55 -0500674 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
675 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400676 parser.add_argument('--board', default=None,
Don Garrett747cc4b2015-10-07 14:48:48 -0700677 help='Used to find default breakpad_root.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400678 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500679 help='full path to the breakpad symbol directory')
680 parser.add_argument('--root', type='path', default=None,
681 help='full path to the chroot dir')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400682 parser.add_argument('--official_build', action='store_true', default=False,
683 help='point to official symbol server')
Mike Frysinger38647542014-09-12 18:15:39 -0700684 parser.add_argument('--server', type=str, default=None,
685 help='URI for custom symbol server')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400686 parser.add_argument('--regenerate', action='store_true', default=False,
687 help='regenerate all symbols')
Don Garretta28be6d2016-06-16 18:09:35 -0700688 parser.add_argument('--upload-limit', type=int,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400689 help='only upload # number of symbols')
690 parser.add_argument('--strip_cfi', type=int,
Don Garretta28be6d2016-06-16 18:09:35 -0700691 default=DEFAULT_FILE_LIMIT,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400692 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500693 parser.add_argument('--failed-list', type='path',
694 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500695 parser.add_argument('--dedupe', action='store_true', default=False,
696 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400697 parser.add_argument('--yes', action='store_true', default=False,
698 help='answer yes to all prompts')
Don Garrett747cc4b2015-10-07 14:48:48 -0700699 parser.add_argument('--product_name', type=str, default='ChromeOS',
700 help='Produce Name for breakpad stats.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400701
702 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500703 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400704
Don Garretta28be6d2016-06-16 18:09:35 -0700705 # Figure out the symbol files/directories to upload.
706 if opts.sym_paths:
707 sym_paths = opts.sym_paths
708 elif opts.breakpad_root:
709 sym_paths = [opts.breakpad_root]
710 elif opts.root:
711 if not opts.board:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400712 cros_build_lib.Die('--board must be set if --root is used.')
Don Garretta28be6d2016-06-16 18:09:35 -0700713 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(opts.board)
714 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip('/'))]
715 else:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400716 cros_build_lib.Die('--sym_paths, --breakpad_root, or --root must be set.')
Don Garretta28be6d2016-06-16 18:09:35 -0700717
Don Garrett747cc4b2015-10-07 14:48:48 -0700718 if opts.sym_paths or opts.breakpad_root:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400719 if opts.regenerate:
Don Garrett747cc4b2015-10-07 14:48:48 -0700720 cros_build_lib.Die('--regenerate may not be used with specific files, '
721 'or breakpad_root')
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400722 else:
723 if opts.board is None:
724 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400725
Don Garretta28be6d2016-06-16 18:09:35 -0700726 # Figure out the dedupe namespace.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500727 dedupe_namespace = None
728 if opts.dedupe:
Don Garretta28be6d2016-06-16 18:09:35 -0700729 if opts.official_build:
Don Garrett747cc4b2015-10-07 14:48:48 -0700730 dedupe_namespace = OFFICIAL_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500731 else:
Don Garrett747cc4b2015-10-07 14:48:48 -0700732 dedupe_namespace = STAGING_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500733
Don Garretta28be6d2016-06-16 18:09:35 -0700734 # Figure out which crash server to upload too.
735 upload_url = opts.server
736 if not upload_url:
737 if opts.official_build:
738 upload_url = OFFICIAL_UPLOAD_URL
739 else:
740 logging.warning('unofficial builds upload to the staging server')
741 upload_url = STAGING_UPLOAD_URL
742
743 # Confirm we really want the long upload.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400744 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500745 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400746 Uploading symbols for an entire Chromium OS build is really only
747 necessary for release builds and in a few cases for developers
748 to debug problems. It will take considerable time to run. For
749 developer debugging purposes, consider instead passing specific
750 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500751 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400752 if not cros_build_lib.BooleanPrompt(
753 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500754 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400755 cros_build_lib.Die('better safe than sorry')
756
757 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700758
759 # Regenerate symbols from binaries.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400760 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400761 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
762 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400763
Don Garretta28be6d2016-06-16 18:09:35 -0700764 # Do the upload.
765 ret += UploadSymbols(
766 sym_paths=sym_paths,
767 upload_url=upload_url,
768 product_name=opts.product_name,
769 dedupe_namespace=dedupe_namespace,
770 failed_list=opts.failed_list,
771 upload_limit=opts.upload_limit,
772 strip_cfi=opts.strip_cfi)
773
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400774 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700775 logging.error('encountered %i problem(s)', ret)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400776 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
777 # return 0 in case we are a multiple of the mask.
Don Garretta28be6d2016-06-16 18:09:35 -0700778 return 1