blob: 9953fdc3a6f326508d5c942317b9a815237f509e [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 Frysingera4fa1e82014-01-15 01:45:56 -050015import httplib
Don Garretta28be6d2016-06-16 18:09:35 -070016import itertools
Mike Nichols90f7c152019-04-09 15:14:08 -060017import json
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040018import os
Mike Nichols90f7c152019-04-09 15:14:08 -060019import requests
Mike Frysingerfd355652014-01-23 02:57:48 -050020import socket
Mike Frysingerc5597f22014-11-27 15:39:15 -050021import sys
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040022import textwrap
23import tempfile
24import time
Mike Frysinger094a2172013-08-14 12:54:35 -040025import urllib2
Mike Frysingerd41938e2014-02-10 06:37:55 -050026import urlparse
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040027
Aviv Keshetb7519e12016-10-04 00:50:00 -070028from chromite.lib import constants
Mike Frysingerbbd1f112016-09-08 18:25:11 -040029
Mike Frysinger3fff4ff2018-07-24 19:24:58 -040030# pylint: disable=ungrouped-imports
Mike Frysingerbbd1f112016-09-08 18:25:11 -040031third_party = os.path.join(constants.CHROMITE_DIR, 'third_party')
32while True:
33 try:
34 sys.path.remove(third_party)
35 except ValueError:
36 break
Mike Frysingerbbd1f112016-09-08 18:25:11 -040037sys.path.insert(0, os.path.join(third_party, 'upload_symbols'))
38del third_party
39
40# Has to be after sys.path manipulation above.
41# And our sys.path muckery confuses pylint.
42import poster # pylint: disable=import-error
43
Mike Frysingerd41938e2014-02-10 06:37:55 -050044from chromite.lib import cache
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040045from chromite.lib import commandline
46from chromite.lib import cros_build_lib
Ralph Nathan5a582ff2015-03-20 18:18:30 -070047from chromite.lib import cros_logging as logging
Mike Frysingerd41938e2014-02-10 06:37:55 -050048from chromite.lib import gs
49from chromite.lib import osutils
Gilad Arnold83233ed2015-05-08 12:12:13 -070050from chromite.lib import path_util
Don Garrette1f47e92016-10-13 16:04:56 -070051from chromite.lib import retry_stats
Mike Frysinger69cb41d2013-08-11 20:08:19 -040052from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040053
Mike Frysinger0c0efa22014-02-09 23:32:23 -050054# Needs to be after chromite imports.
Mike Frysingerc5597f22014-11-27 15:39:15 -050055# We don't want to import the general keyring module as that will implicitly
56# try to import & connect to a dbus server. That's a waste of time.
57sys.modules['keyring'] = None
Mike Frysinger0c0efa22014-02-09 23:32:23 -050058
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040059
Don Garretta28be6d2016-06-16 18:09:35 -070060# We need this to run once per process. Do it at module import time as that
61# will let us avoid doing it inline at function call time (see UploadSymbolFile)
62# as that func might be called by the multiprocessing module which means we'll
63# do the opener logic multiple times overall. Plus, if you're importing this
64# module, it's a pretty good chance that you're going to need this.
65poster.streaminghttp.register_openers()
66
67
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040068# URLs used for uploading symbols.
Mike Nichols90f7c152019-04-09 15:14:08 -060069OFFICIAL_UPLOAD_URL = 'https://prod-crashsymbolcollector-pa.googleapis.com/v1'
70STAGING_UPLOAD_URL = 'https://staging-crashsymbolcollector-pa.googleapis.com/v1'
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040071
72# The crash server rejects files that are this big.
Ryo Hashimotof864c0e2017-02-14 12:46:08 +090073CRASH_SERVER_FILE_LIMIT = 700 * 1024 * 1024
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040074# Give ourselves a little breathing room from what the server expects.
75DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
76
77
Mike Frysinger0c0efa22014-02-09 23:32:23 -050078# The batch limit when talking to the dedup server. We avoid sending one at a
79# time as the round trip overhead will dominate. Conversely, we avoid sending
80# all at once so we can start uploading symbols asap -- the symbol server is a
81# bit slow and will take longer than anything else.
Mike Frysinger0c0efa22014-02-09 23:32:23 -050082DEDUPE_LIMIT = 100
83
84# How long to wait for the server to respond with the results. Note that the
85# larger the limit above, the larger this will need to be. So we give it ~1
86# second per item max.
87DEDUPE_TIMEOUT = DEDUPE_LIMIT
88
Don Garretta28be6d2016-06-16 18:09:35 -070089# How long to wait for the notification to finish (in seconds).
90DEDUPE_NOTIFY_TIMEOUT = 240
Mike Frysinger4dd462e2014-04-30 16:21:51 -040091
Mike Frysinger71046662014-09-12 18:15:15 -070092# The minimum average rate (in bytes per second) that we expect to maintain
93# when uploading symbols. This has to allow for symbols that are up to
94# CRASH_SERVER_FILE_LIMIT in size.
95UPLOAD_MIN_RATE = CRASH_SERVER_FILE_LIMIT / (30 * 60)
96
97# The lowest timeout (in seconds) we'll allow. If the server is overloaded,
98# then there might be a delay in setting up the connection, not just with the
99# transfer. So even a small file might need a larger value.
Ryo Hashimotoc0049372017-02-16 18:50:00 +0900100UPLOAD_MIN_TIMEOUT = 5 * 60
Mike Frysingercd78a082013-06-26 17:13:04 -0400101
102
Don Garrett7a793092016-07-06 16:50:27 -0700103# Sleep for 500ms in between uploads to avoid DoS'ing symbol server.
104SLEEP_DELAY = 0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400105
106
107# Number of seconds to wait before retrying an upload. The delay will double
108# for each subsequent retry of the same symbol file.
109INITIAL_RETRY_DELAY = 1
110
111# Allow up to 7 attempts to upload a symbol file (total delay may be
112# 1+2+4+8+16+32=63 seconds).
113MAX_RETRIES = 6
114
Mike Frysingereb753bf2013-11-22 16:05:35 -0500115# Number of total errors, before uploads are no longer attempted.
116# This is used to avoid lots of errors causing unreasonable delays.
Mike Frysingereb753bf2013-11-22 16:05:35 -0500117MAX_TOTAL_ERRORS_FOR_RETRY = 30
118
Don Garrette1f47e92016-10-13 16:04:56 -0700119# Category to use for collection upload retry stats.
120UPLOAD_STATS = 'UPLOAD'
121
Don Garretta28be6d2016-06-16 18:09:35 -0700122
Mike Nichols649e6a82019-04-23 11:44:48 -0600123class DuplicateFile(Exception):
124 """Thrown when a symbol file already exists on the server."""
125
Don Garretta28be6d2016-06-16 18:09:35 -0700126def BatchGenerator(iterator, batch_size):
127 """Given an iterator, break into lists of size batch_size.
Fang Dengba680462015-08-16 20:34:11 -0700128
Don Garretta28be6d2016-06-16 18:09:35 -0700129 The result is a generator, that will only read in as many inputs as needed for
130 the current batch. The final result can be smaller than batch_size.
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700131 """
Don Garretta28be6d2016-06-16 18:09:35 -0700132 batch = []
133 for i in iterator:
134 batch.append(i)
135 if len(batch) >= batch_size:
136 yield batch
137 batch = []
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700138
Don Garretta28be6d2016-06-16 18:09:35 -0700139 if batch:
140 # if there was anything left in the final batch, yield it.
141 yield batch
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500142
143
Mike Frysingerd41938e2014-02-10 06:37:55 -0500144def IsTarball(path):
145 """Guess if this is a tarball based on the filename."""
146 parts = path.split('.')
147 if len(parts) <= 1:
148 return False
149
150 if parts[-1] == 'tar':
151 return True
152
153 if parts[-2] == 'tar':
154 return parts[-1] in ('bz2', 'gz', 'xz')
155
156 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
157
158
Don Garretta28be6d2016-06-16 18:09:35 -0700159class SymbolFile(object):
160 """This class represents the state of a symbol file during processing.
161
162 Properties:
163 display_path: Name of symbol file that should be consistent between builds.
164 file_name: Transient path of the symbol file.
165 header: ReadSymsHeader output. Dict with assorted meta-data.
166 status: INITIAL, DUPLICATE, or UPLOADED based on status of processing.
167 dedupe_item: None or instance of DedupeItem for this symbol file.
168 dedupe_push_state: Opaque value to return to dedupe code for file.
169 display_name: Read only friendly (short) file name for logging.
170 file_size: Read only size of the symbol file.
171 """
172 INITIAL = 'initial'
173 DUPLICATE = 'duplicate'
174 UPLOADED = 'uploaded'
Don Garrettdeb2e032016-07-06 16:44:14 -0700175 ERROR = 'error'
Don Garretta28be6d2016-06-16 18:09:35 -0700176
177 def __init__(self, display_path, file_name):
178 """An instance of this class represents a symbol file over time.
179
180 Args:
181 display_path: A unique/persistent between builds name to present to the
182 crash server. It is the file name, relative to where it
183 came from (tarball, breakpad dir, etc).
184 file_name: A the current location of the symbol file.
185 """
186 self.display_path = display_path
187 self.file_name = file_name
188 self.header = cros_generate_breakpad_symbols.ReadSymsHeader(file_name)
189 self.status = SymbolFile.INITIAL
Don Garretta28be6d2016-06-16 18:09:35 -0700190
191 @property
192 def display_name(self):
193 return os.path.basename(self.display_path)
194
195 def FileSize(self):
196 return os.path.getsize(self.file_name)
197
198
Don Garretta28be6d2016-06-16 18:09:35 -0700199def FindSymbolFiles(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500200 """Locate symbol files in |paths|
201
Don Garretta28be6d2016-06-16 18:09:35 -0700202 This returns SymbolFile objects that contain file references which are valid
203 after this exits. Those files may exist externally, or be created in the
204 tempdir (say, when expanding tarballs). The caller must not consider
205 SymbolFile's valid after tempdir is cleaned up.
206
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500207 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700208 tempdir: Path to use for temporary files.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500209 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500210 Dirs are searched for files that end in ".sym". Urls are fetched and then
211 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500212
Don Garretta28be6d2016-06-16 18:09:35 -0700213 Yields:
214 A SymbolFile for every symbol file found in paths.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500215 """
Gilad Arnold83233ed2015-05-08 12:12:13 -0700216 cache_dir = path_util.GetCacheDir()
Mike Frysingere847efd2015-01-08 03:57:24 -0500217 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
218 tar_cache = cache.TarballCache(common_path)
219
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500220 for p in paths:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500221 o = urlparse.urlparse(p)
Mike Frysinger27e21b72018-07-12 14:20:21 -0400222 if o.scheme:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500223 # Support globs of filenames.
224 ctx = gs.GSContext()
225 for p in ctx.LS(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700226 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500227 o = urlparse.urlparse(p)
Mike Frysinger27e21b72018-07-12 14:20:21 -0400228 key = ('%s%s' % (o.netloc, o.path)).split('/')
Mike Frysingerd41938e2014-02-10 06:37:55 -0500229 # The common cache will not be LRU, removing the need to hold a read
230 # lock on the cached gsutil.
231 ref = tar_cache.Lookup(key)
232 try:
233 ref.SetDefault(p)
234 except cros_build_lib.RunCommandError as e:
Ralph Nathan446aee92015-03-23 14:44:56 -0700235 logging.warning('ignoring %s\n%s', p, e)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500236 continue
Don Garretta28be6d2016-06-16 18:09:35 -0700237 for p in FindSymbolFiles(tempdir, [ref.path]):
Mike Frysingerd41938e2014-02-10 06:37:55 -0500238 yield p
239
240 elif os.path.isdir(p):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500241 for root, _, files in os.walk(p):
242 for f in files:
243 if f.endswith('.sym'):
Don Garretta28be6d2016-06-16 18:09:35 -0700244 # If p is '/tmp/foo' and filename is '/tmp/foo/bar/bar.sym',
245 # display_path = 'bar/bar.sym'
246 filename = os.path.join(root, f)
247 yield SymbolFile(display_path=filename[len(p):].lstrip('/'),
248 file_name=filename)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500249
250 elif IsTarball(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700251 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500252 tardir = tempfile.mkdtemp(dir=tempdir)
253 cache.Untar(os.path.realpath(p), tardir)
Don Garretta28be6d2016-06-16 18:09:35 -0700254 for p in FindSymbolFiles(tardir, [tardir]):
Mike Frysingerd41938e2014-02-10 06:37:55 -0500255 yield p
256
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500257 else:
Don Garretta28be6d2016-06-16 18:09:35 -0700258 yield SymbolFile(display_path=p, file_name=p)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500259
260
Don Garretta28be6d2016-06-16 18:09:35 -0700261def AdjustSymbolFileSize(symbol, tempdir, file_limit):
262 """Examine symbols files for size problems, and reduce if needed.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500263
Don Garretta28be6d2016-06-16 18:09:35 -0700264 If the symbols size is too big, strip out the call frame info. The CFI
265 is unnecessary for 32bit x86 targets where the frame pointer is used (as
266 all of ours have) and it accounts for over half the size of the symbols
267 uploaded.
268
269 Stripped files will be created inside tempdir, and will be the callers
270 responsibility to clean up.
271
272 We also warn, if a symbols file is still too large after stripping.
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500273
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500274 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700275 symbol: SymbolFile instance to be examined and modified as needed..
276 tempdir: A temporary directory we can create files in that the caller will
277 clean up.
278 file_limit: We only strip files which are larger than this limit.
279
280 Returns:
281 SymbolFile instance (original or modified as needed)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500282 """
Don Garretta28be6d2016-06-16 18:09:35 -0700283 file_size = symbol.FileSize()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500284
Don Garretta28be6d2016-06-16 18:09:35 -0700285 if file_limit and symbol.FileSize() > file_limit:
286 with tempfile.NamedTemporaryFile(
287 prefix='upload_symbols', bufsize=0,
288 dir=tempdir, delete=False) as temp_sym_file:
289
290 temp_sym_file.writelines(
291 [x for x in open(symbol.file_name, 'rb').readlines()
292 if not x.startswith('STACK CFI')]
293 )
294
295 original_file_size = file_size
296 symbol.file_name = temp_sym_file.name
297 file_size = symbol.FileSize()
298
299 logging.warning('stripped CFI for %s reducing size %s > %s',
300 symbol.display_name, original_file_size, file_size)
301
302 # Hopefully the crash server will let it through. But it probably won't.
303 # Not sure what the best answer is in this case.
304 if file_size >= CRASH_SERVER_FILE_LIMIT:
305 logging.PrintBuildbotStepWarnings()
306 logging.warning('upload file %s is awfully large, risking rejection by '
307 'the symbol server (%s > %s)', symbol.display_path,
308 file_size, CRASH_SERVER_FILE_LIMIT)
309
310 return symbol
311
Don Garretta28be6d2016-06-16 18:09:35 -0700312
313def GetUploadTimeout(symbol):
314 """How long to wait for a specific file to upload to the crash server.
315
316 This is a function largely to make unittesting easier.
317
318 Args:
319 symbol: A SymbolFile instance.
320
321 Returns:
322 Timeout length (in seconds)
323 """
324 # Scale the timeout based on the filesize.
325 return max(symbol.FileSize() / UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
326
327
Mike Nichols90f7c152019-04-09 15:14:08 -0600328def ExecRequest(operator, url, timeout, api_key, **kwargs):
329 """Makes a web request with default timeout, returning the json result.
Don Garretta28be6d2016-06-16 18:09:35 -0700330
Mike Nichols90f7c152019-04-09 15:14:08 -0600331 This method will raise a requests.exceptions.HTTPError if the status
332 code is not 4XX, 5XX
333
334 Note: If you are using verbose logging it is entirely possible that the
335 subsystem will write your api key to the logs!
336
337 Args:
Mike Nichols649e6a82019-04-23 11:44:48 -0600338 operator: HTTP method.
339 url: Endpoint URL.
340 timeout: HTTP timeout for request.
341 api_key: Authentication key.
Mike Nichols90f7c152019-04-09 15:14:08 -0600342
343 Returns:
344 HTTP response content
345 """
346 resp = requests.request(operator, url,
347 params={'key': api_key},
348 headers={'User-agent': 'chromite.upload_symbols'},
349 timeout=timeout, **kwargs)
350 # Make sure we don't leak secret keys by accident.
351 if resp.status_code > 399:
352 resp.url = resp.url.replace(urllib2.quote(api_key), 'XX-HIDDEN-XX')
Mike Nicholscf5b7a92019-04-25 12:01:28 -0600353 logging.warning('Url: %s, Status: %s, response: "%s", in: %s',
354 resp.url, resp.status_code, resp.text, resp.elapsed)
355 elif resp.content:
Mike Nichols90f7c152019-04-09 15:14:08 -0600356 return resp.json()
Mike Nicholscf5b7a92019-04-25 12:01:28 -0600357
Mike Nichols90f7c152019-04-09 15:14:08 -0600358 return {}
359
360
Mike Nichols649e6a82019-04-23 11:44:48 -0600361def UploadExists(symbol, status_url, timeout, api_key):
362 """Skip a symbol files if we've already uploaded.
363
364 Args:
365 symbol: A SymbolFile instance.
366 status_url: The crash URL to validate the file existence.
367 timeout: HTTP timeout for request.
368 api_key: Authentication key.
369
370 Yields:
371 Boolean, true if it is available on server, false if
372 it is not.
373 """
374 result = ExecRequest('get', '%s/symbols/%s/%s:checkStatus' %
375 (status_url, symbol.header.name,
376 symbol.header.id.replace('-', '')),
377 timeout, api_key)
378 return (result and 'status' in result.keys()
379 and result['status'] == 'FOUND')
380
Mike Nichols90f7c152019-04-09 15:14:08 -0600381def UploadSymbolFile(upload_url, symbol, api_key):
382 '''Upload a symbol file to the crash server, returning the status result.
Don Garretta28be6d2016-06-16 18:09:35 -0700383
384 Args:
385 upload_url: The crash URL to POST the |sym_file| to
386 symbol: A SymbolFile instance.
Mike Nichols90f7c152019-04-09 15:14:08 -0600387 api_key: Authentication key
388 '''
389 timeout = GetUploadTimeout(symbol)
Mike Nichols649e6a82019-04-23 11:44:48 -0600390 if UploadExists(symbol, upload_url, timeout, api_key):
391 logging.info('%s symbol file already uploaded, skipping', symbol.header.id)
392 raise DuplicateFile
Mike Nichols90f7c152019-04-09 15:14:08 -0600393 else:
Mike Nichols649e6a82019-04-23 11:44:48 -0600394 upload = ExecRequest('post',
395 '%s/uploads:create' % upload_url, timeout, api_key)
396
397 if upload and 'uploadUrl' in upload.keys():
398 symbol_data = {'symbol_id':
399 {'debug_file': symbol.header.name,
400 'debug_id': symbol.header.id.replace('-', '')}
401 }
402 ExecRequest('put',
403 upload['uploadUrl'], timeout,
404 api_key=api_key,
405 data=open(symbol.file_name, 'r'))
406 ExecRequest('post',
407 '%s/uploads/%s:complete' % (
408 upload_url, upload['uploadKey']),
409 timeout, api_key=api_key,
410 # TODO(mikenichols): Validate product_name once it is added
411 # to the proto; currently unsupported.
412 data=json.dumps(symbol_data))
413 else:
414 raise requests.exceptions.HTTPError
Don Garretta28be6d2016-06-16 18:09:35 -0700415
416
Mike Nichols90f7c152019-04-09 15:14:08 -0600417def PerformSymbolsFileUpload(symbols, upload_url, api_key):
Don Garretta28be6d2016-06-16 18:09:35 -0700418 """Upload the symbols to the crash server
419
420 Args:
Don Garrettdeb2e032016-07-06 16:44:14 -0700421 symbols: An iterable of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700422 upload_url: URL of crash server to upload too.
Mike Nichols90f7c152019-04-09 15:14:08 -0600423 api_key: Authentication key.
Don Garretta28be6d2016-06-16 18:09:35 -0700424 failures: Tracker for total upload failures.
Don Garretta28be6d2016-06-16 18:09:35 -0700425
Don Garrettdeb2e032016-07-06 16:44:14 -0700426 Yields:
427 Each symbol from symbols, perhaps modified.
Don Garretta28be6d2016-06-16 18:09:35 -0700428 """
Don Garrettdeb2e032016-07-06 16:44:14 -0700429 failures = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700430
Don Garrettdeb2e032016-07-06 16:44:14 -0700431 for s in symbols:
432 if (failures < MAX_TOTAL_ERRORS_FOR_RETRY and
433 s.status in (SymbolFile.INITIAL, SymbolFile.ERROR)):
434 # Keeps us from DoS-ing the symbol server.
435 time.sleep(SLEEP_DELAY)
436 logging.info('Uploading symbol_file: %s', s.display_path)
437 try:
438 # This command retries the upload multiple times with growing delays. We
439 # only consider the upload a failure if these retries fail.
Don Garrette1f47e92016-10-13 16:04:56 -0700440 def ShouldRetryUpload(exception):
Mike Nicholscf5b7a92019-04-25 12:01:28 -0600441 return isinstance(exception, (requests.exceptions.RequestException,
Mike Nichols90f7c152019-04-09 15:14:08 -0600442 urllib2.URLError,
Ryo Hashimoto4ddf3f02017-03-22 18:25:27 +0900443 httplib.HTTPException, socket.error))
Don Garrette1f47e92016-10-13 16:04:56 -0700444
Don Garrett440944e2016-10-03 16:33:31 -0700445 with cros_build_lib.TimedSection() as timer:
Don Garrette1f47e92016-10-13 16:04:56 -0700446 retry_stats.RetryWithStats(
447 UPLOAD_STATS, ShouldRetryUpload, MAX_RETRIES,
Don Garrett440944e2016-10-03 16:33:31 -0700448 UploadSymbolFile,
Mike Nichols90f7c152019-04-09 15:14:08 -0600449 upload_url, s, api_key,
Luigi Semenzato5104f3c2016-10-12 12:37:42 -0700450 sleep=INITIAL_RETRY_DELAY,
451 log_all_retries=True)
Don Garrett440944e2016-10-03 16:33:31 -0700452 logging.info('upload of %10i bytes took %s', s.FileSize(), timer.delta)
Don Garrettdeb2e032016-07-06 16:44:14 -0700453 s.status = SymbolFile.UPLOADED
Mike Nichols649e6a82019-04-23 11:44:48 -0600454 except DuplicateFile:
455 s.status = SymbolFile.DUPLICATE
Mike Nichols90f7c152019-04-09 15:14:08 -0600456 except (requests.exceptions.HTTPError,
457 requests.exceptions.Timeout,
458 requests.exceptions.RequestException) as e:
459 logging.warning('could not upload: %s: HTTP error: %s',
460 s.display_name, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700461 s.status = SymbolFile.ERROR
462 failures += 1
Mike Nichols90f7c152019-04-09 15:14:08 -0600463 except (httplib.HTTPException, urllib2.URLError, socket.error) as e:
Ryo Hashimoto45273f02017-03-16 17:45:56 +0900464 logging.warning('could not upload: %s: %s %s', s.display_name,
465 type(e).__name__, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700466 s.status = SymbolFile.ERROR
467 failures += 1
468
469 # We pass the symbol along, on both success and failure.
470 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700471
472
Don Garrettdeb2e032016-07-06 16:44:14 -0700473def ReportResults(symbols, failed_list):
Don Garretta28be6d2016-06-16 18:09:35 -0700474 """Log a summary of the symbol uploading.
475
476 This has the side effect of fully consuming the symbols iterator.
477
478 Args:
479 symbols: An iterator of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700480 failed_list: A filename at which to write out a list of our failed uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700481
482 Returns:
483 The number of symbols not uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700484 """
485 upload_failures = []
486 result_counts = {
487 SymbolFile.INITIAL: 0,
488 SymbolFile.UPLOADED: 0,
489 SymbolFile.DUPLICATE: 0,
Don Garrettdeb2e032016-07-06 16:44:14 -0700490 SymbolFile.ERROR: 0,
Don Garretta28be6d2016-06-16 18:09:35 -0700491 }
492
493 for s in symbols:
494 result_counts[s.status] += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700495 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700496 upload_failures.append(s)
497
Don Garrette1f47e92016-10-13 16:04:56 -0700498 # Report retry numbers.
499 _, _, retries = retry_stats.CategoryStats(UPLOAD_STATS)
500 if retries:
501 logging.warning('%d upload retries performed.', retries)
502
Don Garretta28be6d2016-06-16 18:09:35 -0700503 logging.info('Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.',
504 result_counts)
505
Chris Ching91908032016-09-27 16:55:33 -0600506 if result_counts[SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700507 logging.PrintBuildbotStepWarnings()
Chris Ching91908032016-09-27 16:55:33 -0600508 logging.warning('%d non-recoverable upload errors',
509 result_counts[SymbolFile.ERROR])
510
511 if result_counts[SymbolFile.INITIAL]:
512 logging.PrintBuildbotStepWarnings()
513 logging.warning('%d upload(s) were skipped because of excessive errors',
Don Garrettdeb2e032016-07-06 16:44:14 -0700514 result_counts[SymbolFile.INITIAL])
Don Garretta28be6d2016-06-16 18:09:35 -0700515
516 if failed_list is not None:
517 with open(failed_list, 'w') as fl:
518 for s in upload_failures:
519 fl.write('%s\n' % s.display_path)
520
Don Garrettdeb2e032016-07-06 16:44:14 -0700521 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
522
Don Garretta28be6d2016-06-16 18:09:35 -0700523
Mike Nichols649e6a82019-04-23 11:44:48 -0600524def UploadSymbols(sym_paths, upload_url, failed_list=None,
525 upload_limit=None, strip_cfi=None,
Mike Nichols90f7c152019-04-09 15:14:08 -0600526 api_key=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400527 """Upload all the generated symbols for |board| to the crash server
528
529 Args:
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500530 sym_paths: Specific symbol files (or dirs of sym files) to upload,
531 otherwise search |breakpad_dir|
Don Garretta28be6d2016-06-16 18:09:35 -0700532 upload_url: URL of crash server to upload too.
Don Garretta28be6d2016-06-16 18:09:35 -0700533 failed_list: A filename at which to write out a list of our failed uploads.
534 upload_limit: Integer listing how many files to upload. None for no limit.
535 strip_cfi: File size at which we strip out CFI data. None for no limit.
Mike Nichols90f7c152019-04-09 15:14:08 -0600536 api_key: A string based authentication key
Mike Frysinger1a736a82013-12-12 01:50:59 -0500537
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400538 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400539 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400540 """
Don Garrette1f47e92016-10-13 16:04:56 -0700541 retry_stats.SetupStats()
542
Don Garretta28be6d2016-06-16 18:09:35 -0700543 # Note: This method looks like each step of processing is performed
544 # sequentially for all SymbolFiles, but instead each step is a generator that
545 # produces the next iteration only when it's read. This means that (except for
546 # some batching) each SymbolFile goes through all of these steps before the
547 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400548
Don Garretta28be6d2016-06-16 18:09:35 -0700549 # This is used to hold striped
550 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
551 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500552
Don Garrett1bc1e102016-07-06 17:06:10 -0700553 # Sort all of our symbols so the largest ones (probably the most important)
554 # are processed first.
555 symbols = list(symbols)
556 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
557
Don Garretta28be6d2016-06-16 18:09:35 -0700558 if upload_limit is not None:
559 # Restrict symbols processed to the limit.
560 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400561
Don Garretta28be6d2016-06-16 18:09:35 -0700562 # Strip CFI, if needed.
563 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500564
Don Garretta28be6d2016-06-16 18:09:35 -0700565 # Perform uploads
Mike Nichols90f7c152019-04-09 15:14:08 -0600566 symbols = PerformSymbolsFileUpload(symbols, upload_url, api_key)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400567
Don Garretta28be6d2016-06-16 18:09:35 -0700568 # Log the final results, and consume the symbols generator fully.
Don Garrettdeb2e032016-07-06 16:44:14 -0700569 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500570
Don Garrettdeb2e032016-07-06 16:44:14 -0700571 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400572
573
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400574def main(argv):
575 parser = commandline.ArgumentParser(description=__doc__)
576
Don Garretta28be6d2016-06-16 18:09:35 -0700577 # TODO: Make sym_paths, breakpad_root, and root exclusive.
578
Mike Frysingerd41938e2014-02-10 06:37:55 -0500579 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
580 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400581 parser.add_argument('--board', default=None,
Don Garrett747cc4b2015-10-07 14:48:48 -0700582 help='Used to find default breakpad_root.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400583 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500584 help='full path to the breakpad symbol directory')
585 parser.add_argument('--root', type='path', default=None,
586 help='full path to the chroot dir')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400587 parser.add_argument('--official_build', action='store_true', default=False,
588 help='point to official symbol server')
Mike Frysinger38647542014-09-12 18:15:39 -0700589 parser.add_argument('--server', type=str, default=None,
590 help='URI for custom symbol server')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400591 parser.add_argument('--regenerate', action='store_true', default=False,
592 help='regenerate all symbols')
Don Garretta28be6d2016-06-16 18:09:35 -0700593 parser.add_argument('--upload-limit', type=int,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400594 help='only upload # number of symbols')
595 parser.add_argument('--strip_cfi', type=int,
Don Garretta28be6d2016-06-16 18:09:35 -0700596 default=DEFAULT_FILE_LIMIT,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400597 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500598 parser.add_argument('--failed-list', type='path',
599 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500600 parser.add_argument('--dedupe', action='store_true', default=False,
601 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400602 parser.add_argument('--yes', action='store_true', default=False,
603 help='answer yes to all prompts')
Don Garrett747cc4b2015-10-07 14:48:48 -0700604 parser.add_argument('--product_name', type=str, default='ChromeOS',
605 help='Produce Name for breakpad stats.')
Mike Nichols90f7c152019-04-09 15:14:08 -0600606 parser.add_argument('--api_key', type=str, default=None,
607 help='full path to the API key file')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400608
609 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500610 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400611
Don Garretta28be6d2016-06-16 18:09:35 -0700612 # Figure out the symbol files/directories to upload.
613 if opts.sym_paths:
614 sym_paths = opts.sym_paths
615 elif opts.breakpad_root:
616 sym_paths = [opts.breakpad_root]
617 elif opts.root:
618 if not opts.board:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400619 cros_build_lib.Die('--board must be set if --root is used.')
Don Garretta28be6d2016-06-16 18:09:35 -0700620 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(opts.board)
621 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip('/'))]
622 else:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400623 cros_build_lib.Die('--sym_paths, --breakpad_root, or --root must be set.')
Don Garretta28be6d2016-06-16 18:09:35 -0700624
Don Garrett747cc4b2015-10-07 14:48:48 -0700625 if opts.sym_paths or opts.breakpad_root:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400626 if opts.regenerate:
Don Garrett747cc4b2015-10-07 14:48:48 -0700627 cros_build_lib.Die('--regenerate may not be used with specific files, '
628 'or breakpad_root')
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400629 else:
630 if opts.board is None:
631 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400632
Don Garretta28be6d2016-06-16 18:09:35 -0700633 # Figure out which crash server to upload too.
634 upload_url = opts.server
635 if not upload_url:
636 if opts.official_build:
637 upload_url = OFFICIAL_UPLOAD_URL
638 else:
639 logging.warning('unofficial builds upload to the staging server')
640 upload_url = STAGING_UPLOAD_URL
641
Mike Nichols90f7c152019-04-09 15:14:08 -0600642 # Set up the API key needed to authenticate to Crash server.
643 # Allow for a local key file for testing purposes.
644 if opts.api_key:
645 api_key_file = opts.api_key
646 else:
647 api_key_file = constants.CRASH_API_KEY
648
649 api_key = osutils.ReadFile(api_key_file)
650
Don Garretta28be6d2016-06-16 18:09:35 -0700651 # Confirm we really want the long upload.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400652 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500653 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400654 Uploading symbols for an entire Chromium OS build is really only
655 necessary for release builds and in a few cases for developers
656 to debug problems. It will take considerable time to run. For
657 developer debugging purposes, consider instead passing specific
658 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500659 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400660 if not cros_build_lib.BooleanPrompt(
661 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500662 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400663 cros_build_lib.Die('better safe than sorry')
664
665 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700666
667 # Regenerate symbols from binaries.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400668 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400669 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
670 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400671
Don Garretta28be6d2016-06-16 18:09:35 -0700672 # Do the upload.
673 ret += UploadSymbols(
674 sym_paths=sym_paths,
675 upload_url=upload_url,
Don Garretta28be6d2016-06-16 18:09:35 -0700676 failed_list=opts.failed_list,
677 upload_limit=opts.upload_limit,
Mike Nichols90f7c152019-04-09 15:14:08 -0600678 strip_cfi=opts.strip_cfi,
679 api_key=api_key)
Don Garretta28be6d2016-06-16 18:09:35 -0700680
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400681 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700682 logging.error('encountered %i problem(s)', ret)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400683 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
684 # return 0 in case we are a multiple of the mask.
Don Garretta28be6d2016-06-16 18:09:35 -0700685 return 1