blob: f577132e13352a0ddc2ff625e7bba702889e7690 [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
Don Garretta28be6d2016-06-16 18:09:35 -0700123def BatchGenerator(iterator, batch_size):
124 """Given an iterator, break into lists of size batch_size.
Fang Dengba680462015-08-16 20:34:11 -0700125
Don Garretta28be6d2016-06-16 18:09:35 -0700126 The result is a generator, that will only read in as many inputs as needed for
127 the current batch. The final result can be smaller than batch_size.
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700128 """
Don Garretta28be6d2016-06-16 18:09:35 -0700129 batch = []
130 for i in iterator:
131 batch.append(i)
132 if len(batch) >= batch_size:
133 yield batch
134 batch = []
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700135
Don Garretta28be6d2016-06-16 18:09:35 -0700136 if batch:
137 # if there was anything left in the final batch, yield it.
138 yield batch
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500139
140
Mike Frysingerd41938e2014-02-10 06:37:55 -0500141def IsTarball(path):
142 """Guess if this is a tarball based on the filename."""
143 parts = path.split('.')
144 if len(parts) <= 1:
145 return False
146
147 if parts[-1] == 'tar':
148 return True
149
150 if parts[-2] == 'tar':
151 return parts[-1] in ('bz2', 'gz', 'xz')
152
153 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
154
155
Don Garretta28be6d2016-06-16 18:09:35 -0700156class SymbolFile(object):
157 """This class represents the state of a symbol file during processing.
158
159 Properties:
160 display_path: Name of symbol file that should be consistent between builds.
161 file_name: Transient path of the symbol file.
162 header: ReadSymsHeader output. Dict with assorted meta-data.
163 status: INITIAL, DUPLICATE, or UPLOADED based on status of processing.
164 dedupe_item: None or instance of DedupeItem for this symbol file.
165 dedupe_push_state: Opaque value to return to dedupe code for file.
166 display_name: Read only friendly (short) file name for logging.
167 file_size: Read only size of the symbol file.
168 """
169 INITIAL = 'initial'
170 DUPLICATE = 'duplicate'
171 UPLOADED = 'uploaded'
Don Garrettdeb2e032016-07-06 16:44:14 -0700172 ERROR = 'error'
Don Garretta28be6d2016-06-16 18:09:35 -0700173
174 def __init__(self, display_path, file_name):
175 """An instance of this class represents a symbol file over time.
176
177 Args:
178 display_path: A unique/persistent between builds name to present to the
179 crash server. It is the file name, relative to where it
180 came from (tarball, breakpad dir, etc).
181 file_name: A the current location of the symbol file.
182 """
183 self.display_path = display_path
184 self.file_name = file_name
185 self.header = cros_generate_breakpad_symbols.ReadSymsHeader(file_name)
186 self.status = SymbolFile.INITIAL
Don Garretta28be6d2016-06-16 18:09:35 -0700187
188 @property
189 def display_name(self):
190 return os.path.basename(self.display_path)
191
192 def FileSize(self):
193 return os.path.getsize(self.file_name)
194
195
Don Garretta28be6d2016-06-16 18:09:35 -0700196def FindSymbolFiles(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500197 """Locate symbol files in |paths|
198
Don Garretta28be6d2016-06-16 18:09:35 -0700199 This returns SymbolFile objects that contain file references which are valid
200 after this exits. Those files may exist externally, or be created in the
201 tempdir (say, when expanding tarballs). The caller must not consider
202 SymbolFile's valid after tempdir is cleaned up.
203
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500204 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700205 tempdir: Path to use for temporary files.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500206 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500207 Dirs are searched for files that end in ".sym". Urls are fetched and then
208 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500209
Don Garretta28be6d2016-06-16 18:09:35 -0700210 Yields:
211 A SymbolFile for every symbol file found in paths.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500212 """
Gilad Arnold83233ed2015-05-08 12:12:13 -0700213 cache_dir = path_util.GetCacheDir()
Mike Frysingere847efd2015-01-08 03:57:24 -0500214 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
215 tar_cache = cache.TarballCache(common_path)
216
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500217 for p in paths:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500218 o = urlparse.urlparse(p)
Mike Frysinger27e21b72018-07-12 14:20:21 -0400219 if o.scheme:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500220 # Support globs of filenames.
221 ctx = gs.GSContext()
222 for p in ctx.LS(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700223 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500224 o = urlparse.urlparse(p)
Mike Frysinger27e21b72018-07-12 14:20:21 -0400225 key = ('%s%s' % (o.netloc, o.path)).split('/')
Mike Frysingerd41938e2014-02-10 06:37:55 -0500226 # The common cache will not be LRU, removing the need to hold a read
227 # lock on the cached gsutil.
228 ref = tar_cache.Lookup(key)
229 try:
230 ref.SetDefault(p)
231 except cros_build_lib.RunCommandError as e:
Ralph Nathan446aee92015-03-23 14:44:56 -0700232 logging.warning('ignoring %s\n%s', p, e)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500233 continue
Don Garretta28be6d2016-06-16 18:09:35 -0700234 for p in FindSymbolFiles(tempdir, [ref.path]):
Mike Frysingerd41938e2014-02-10 06:37:55 -0500235 yield p
236
237 elif os.path.isdir(p):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500238 for root, _, files in os.walk(p):
239 for f in files:
240 if f.endswith('.sym'):
Don Garretta28be6d2016-06-16 18:09:35 -0700241 # If p is '/tmp/foo' and filename is '/tmp/foo/bar/bar.sym',
242 # display_path = 'bar/bar.sym'
243 filename = os.path.join(root, f)
244 yield SymbolFile(display_path=filename[len(p):].lstrip('/'),
245 file_name=filename)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500246
247 elif IsTarball(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700248 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500249 tardir = tempfile.mkdtemp(dir=tempdir)
250 cache.Untar(os.path.realpath(p), tardir)
Don Garretta28be6d2016-06-16 18:09:35 -0700251 for p in FindSymbolFiles(tardir, [tardir]):
Mike Frysingerd41938e2014-02-10 06:37:55 -0500252 yield p
253
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500254 else:
Don Garretta28be6d2016-06-16 18:09:35 -0700255 yield SymbolFile(display_path=p, file_name=p)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500256
257
Don Garretta28be6d2016-06-16 18:09:35 -0700258def AdjustSymbolFileSize(symbol, tempdir, file_limit):
259 """Examine symbols files for size problems, and reduce if needed.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500260
Don Garretta28be6d2016-06-16 18:09:35 -0700261 If the symbols size is too big, strip out the call frame info. The CFI
262 is unnecessary for 32bit x86 targets where the frame pointer is used (as
263 all of ours have) and it accounts for over half the size of the symbols
264 uploaded.
265
266 Stripped files will be created inside tempdir, and will be the callers
267 responsibility to clean up.
268
269 We also warn, if a symbols file is still too large after stripping.
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500270
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500271 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700272 symbol: SymbolFile instance to be examined and modified as needed..
273 tempdir: A temporary directory we can create files in that the caller will
274 clean up.
275 file_limit: We only strip files which are larger than this limit.
276
277 Returns:
278 SymbolFile instance (original or modified as needed)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500279 """
Don Garretta28be6d2016-06-16 18:09:35 -0700280 file_size = symbol.FileSize()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500281
Don Garretta28be6d2016-06-16 18:09:35 -0700282 if file_limit and symbol.FileSize() > file_limit:
283 with tempfile.NamedTemporaryFile(
284 prefix='upload_symbols', bufsize=0,
285 dir=tempdir, delete=False) as temp_sym_file:
286
287 temp_sym_file.writelines(
288 [x for x in open(symbol.file_name, 'rb').readlines()
289 if not x.startswith('STACK CFI')]
290 )
291
292 original_file_size = file_size
293 symbol.file_name = temp_sym_file.name
294 file_size = symbol.FileSize()
295
296 logging.warning('stripped CFI for %s reducing size %s > %s',
297 symbol.display_name, original_file_size, file_size)
298
299 # Hopefully the crash server will let it through. But it probably won't.
300 # Not sure what the best answer is in this case.
301 if file_size >= CRASH_SERVER_FILE_LIMIT:
302 logging.PrintBuildbotStepWarnings()
303 logging.warning('upload file %s is awfully large, risking rejection by '
304 'the symbol server (%s > %s)', symbol.display_path,
305 file_size, CRASH_SERVER_FILE_LIMIT)
306
307 return symbol
308
Don Garretta28be6d2016-06-16 18:09:35 -0700309
310def GetUploadTimeout(symbol):
311 """How long to wait for a specific file to upload to the crash server.
312
313 This is a function largely to make unittesting easier.
314
315 Args:
316 symbol: A SymbolFile instance.
317
318 Returns:
319 Timeout length (in seconds)
320 """
321 # Scale the timeout based on the filesize.
322 return max(symbol.FileSize() / UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
323
324
Mike Nichols90f7c152019-04-09 15:14:08 -0600325def ExecRequest(operator, url, timeout, api_key, **kwargs):
326 """Makes a web request with default timeout, returning the json result.
Don Garretta28be6d2016-06-16 18:09:35 -0700327
Mike Nichols90f7c152019-04-09 15:14:08 -0600328 This method will raise a requests.exceptions.HTTPError if the status
329 code is not 4XX, 5XX
330
331 Note: If you are using verbose logging it is entirely possible that the
332 subsystem will write your api key to the logs!
333
334 Args:
Mike Nichols649e6a82019-04-23 11:44:48 -0600335 operator: HTTP method.
336 url: Endpoint URL.
337 timeout: HTTP timeout for request.
338 api_key: Authentication key.
Mike Nichols90f7c152019-04-09 15:14:08 -0600339
340 Returns:
341 HTTP response content
342 """
343 resp = requests.request(operator, url,
344 params={'key': api_key},
345 headers={'User-agent': 'chromite.upload_symbols'},
346 timeout=timeout, **kwargs)
347 # Make sure we don't leak secret keys by accident.
348 if resp.status_code > 399:
349 resp.url = resp.url.replace(urllib2.quote(api_key), 'XX-HIDDEN-XX')
Mike Nicholscf5b7a92019-04-25 12:01:28 -0600350 logging.warning('Url: %s, Status: %s, response: "%s", in: %s',
351 resp.url, resp.status_code, resp.text, resp.elapsed)
352 elif resp.content:
Mike Nichols90f7c152019-04-09 15:14:08 -0600353 return resp.json()
Mike Nicholscf5b7a92019-04-25 12:01:28 -0600354
Mike Nichols90f7c152019-04-09 15:14:08 -0600355 return {}
356
357
Mike Nichols137e82d2019-05-15 18:40:34 -0600358def FindDuplicates(symbols, status_url, api_key, timeout=DEDUPE_TIMEOUT):
359 """Check whether the symbol files have already been uploaded.
Mike Nichols649e6a82019-04-23 11:44:48 -0600360
361 Args:
Mike Nichols137e82d2019-05-15 18:40:34 -0600362 symbols: A iterable of SymbolFiles to be uploaded
Mike Nichols649e6a82019-04-23 11:44:48 -0600363 status_url: The crash URL to validate the file existence.
Mike Nichols649e6a82019-04-23 11:44:48 -0600364 api_key: Authentication key.
Mike Nichols137e82d2019-05-15 18:40:34 -0600365 timeout: HTTP timeout for request.
Mike Nichols649e6a82019-04-23 11:44:48 -0600366
367 Yields:
Mike Nichols137e82d2019-05-15 18:40:34 -0600368 All SymbolFiles from symbols, but duplicates have status updated to
369 DUPLICATE.
Mike Nichols649e6a82019-04-23 11:44:48 -0600370 """
Mike Nichols137e82d2019-05-15 18:40:34 -0600371 for batch in BatchGenerator(symbols, DEDUPE_LIMIT):
372 items = []
373 result = {}
374 for x in batch:
375 items.append({'debug_file': x.header.name,
376 'debug_id': x.header.id.replace('-', '')})
377 symbol_data = {'symbol_ids': items}
378 try:
379 result = ExecRequest('post', '%s/symbols:checkStatuses' %
380 status_url,
381 timeout,
382 api_key=api_key,
383 data=json.dumps(symbol_data))
384 except (requests.exceptions.HTTPError,
385 requests.exceptions.Timeout,
386 requests.exceptions.RequestException) as e:
387 logging.warning('could not identify duplicates: HTTP error: %s', e)
388 for b in batch:
389 b.status = SymbolFile.INITIAL
390 set_match = {'debugId': b.header.id.replace('-', ''),
391 'debugFile': b.header.name}
392 for cs_result in result.get('pairs', []):
Mike Frysingerca227ec2019-07-02 17:59:21 -0400393 if set_match == cs_result.get('symbolId'):
Mike Nichols137e82d2019-05-15 18:40:34 -0600394 if cs_result.get('status') == 'FOUND':
395 logging.debug('Found duplicate: %s', b.display_name)
396 b.status = SymbolFile.DUPLICATE
397 break
398 yield b
399
Mike Nichols649e6a82019-04-23 11:44:48 -0600400
Mike Nichols90f7c152019-04-09 15:14:08 -0600401def UploadSymbolFile(upload_url, symbol, api_key):
402 '''Upload a symbol file to the crash server, returning the status result.
Don Garretta28be6d2016-06-16 18:09:35 -0700403
404 Args:
405 upload_url: The crash URL to POST the |sym_file| to
406 symbol: A SymbolFile instance.
Mike Nichols90f7c152019-04-09 15:14:08 -0600407 api_key: Authentication key
408 '''
409 timeout = GetUploadTimeout(symbol)
Mike Nichols137e82d2019-05-15 18:40:34 -0600410 upload = ExecRequest('post',
411 '%s/uploads:create' % upload_url, timeout, api_key)
Mike Nichols649e6a82019-04-23 11:44:48 -0600412
Mike Nichols137e82d2019-05-15 18:40:34 -0600413 if upload and 'uploadUrl' in upload.keys():
414 symbol_data = {'symbol_id':
415 {'debug_file': symbol.header.name,
416 'debug_id': symbol.header.id.replace('-', '')}
417 }
418 ExecRequest('put',
419 upload['uploadUrl'], timeout,
420 api_key=api_key,
421 data=open(symbol.file_name, 'r'))
422 ExecRequest('post',
423 '%s/uploads/%s:complete' % (
424 upload_url, upload['uploadKey']),
425 timeout, api_key=api_key,
426 # TODO(mikenichols): Validate product_name once it is added
427 # to the proto; currently unsupported.
428 data=json.dumps(symbol_data))
429 else:
430 raise requests.exceptions.HTTPError
Don Garretta28be6d2016-06-16 18:09:35 -0700431
432
Mike Nichols90f7c152019-04-09 15:14:08 -0600433def PerformSymbolsFileUpload(symbols, upload_url, api_key):
Don Garretta28be6d2016-06-16 18:09:35 -0700434 """Upload the symbols to the crash server
435
436 Args:
Don Garrettdeb2e032016-07-06 16:44:14 -0700437 symbols: An iterable of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700438 upload_url: URL of crash server to upload too.
Mike Nichols90f7c152019-04-09 15:14:08 -0600439 api_key: Authentication key.
Don Garretta28be6d2016-06-16 18:09:35 -0700440 failures: Tracker for total upload failures.
Don Garretta28be6d2016-06-16 18:09:35 -0700441
Don Garrettdeb2e032016-07-06 16:44:14 -0700442 Yields:
443 Each symbol from symbols, perhaps modified.
Don Garretta28be6d2016-06-16 18:09:35 -0700444 """
Don Garrettdeb2e032016-07-06 16:44:14 -0700445 failures = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700446
Don Garrettdeb2e032016-07-06 16:44:14 -0700447 for s in symbols:
448 if (failures < MAX_TOTAL_ERRORS_FOR_RETRY and
449 s.status in (SymbolFile.INITIAL, SymbolFile.ERROR)):
450 # Keeps us from DoS-ing the symbol server.
451 time.sleep(SLEEP_DELAY)
452 logging.info('Uploading symbol_file: %s', s.display_path)
453 try:
454 # This command retries the upload multiple times with growing delays. We
455 # only consider the upload a failure if these retries fail.
Don Garrette1f47e92016-10-13 16:04:56 -0700456 def ShouldRetryUpload(exception):
Mike Nichols0abb8d32019-04-26 14:55:08 -0600457 if isinstance(exception, (requests.exceptions.RequestException,
458 urllib2.URLError,
459 httplib.HTTPException, socket.error)):
460 logging.info('Request failed, retrying: %s', exception)
461 return True
462 return False
Don Garrette1f47e92016-10-13 16:04:56 -0700463
Don Garrett440944e2016-10-03 16:33:31 -0700464 with cros_build_lib.TimedSection() as timer:
Don Garrette1f47e92016-10-13 16:04:56 -0700465 retry_stats.RetryWithStats(
466 UPLOAD_STATS, ShouldRetryUpload, MAX_RETRIES,
Don Garrett440944e2016-10-03 16:33:31 -0700467 UploadSymbolFile,
Mike Nichols90f7c152019-04-09 15:14:08 -0600468 upload_url, s, api_key,
Luigi Semenzato5104f3c2016-10-12 12:37:42 -0700469 sleep=INITIAL_RETRY_DELAY,
470 log_all_retries=True)
Mike Nicholsb03b2c62019-05-02 18:46:04 -0600471 if s.status != SymbolFile.DUPLICATE:
472 logging.info('upload of %10i bytes took %s', s.FileSize(),
473 timer.delta)
474 s.status = SymbolFile.UPLOADED
Mike Nichols90f7c152019-04-09 15:14:08 -0600475 except (requests.exceptions.HTTPError,
476 requests.exceptions.Timeout,
477 requests.exceptions.RequestException) as e:
478 logging.warning('could not upload: %s: HTTP error: %s',
479 s.display_name, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700480 s.status = SymbolFile.ERROR
481 failures += 1
Mike Nichols90f7c152019-04-09 15:14:08 -0600482 except (httplib.HTTPException, urllib2.URLError, socket.error) as e:
Ryo Hashimoto45273f02017-03-16 17:45:56 +0900483 logging.warning('could not upload: %s: %s %s', s.display_name,
484 type(e).__name__, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700485 s.status = SymbolFile.ERROR
486 failures += 1
487
488 # We pass the symbol along, on both success and failure.
489 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700490
491
Don Garrettdeb2e032016-07-06 16:44:14 -0700492def ReportResults(symbols, failed_list):
Don Garretta28be6d2016-06-16 18:09:35 -0700493 """Log a summary of the symbol uploading.
494
495 This has the side effect of fully consuming the symbols iterator.
496
497 Args:
498 symbols: An iterator of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700499 failed_list: A filename at which to write out a list of our failed uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700500
501 Returns:
502 The number of symbols not uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700503 """
504 upload_failures = []
505 result_counts = {
506 SymbolFile.INITIAL: 0,
507 SymbolFile.UPLOADED: 0,
508 SymbolFile.DUPLICATE: 0,
Don Garrettdeb2e032016-07-06 16:44:14 -0700509 SymbolFile.ERROR: 0,
Don Garretta28be6d2016-06-16 18:09:35 -0700510 }
511
512 for s in symbols:
513 result_counts[s.status] += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700514 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700515 upload_failures.append(s)
516
Don Garrette1f47e92016-10-13 16:04:56 -0700517 # Report retry numbers.
518 _, _, retries = retry_stats.CategoryStats(UPLOAD_STATS)
519 if retries:
520 logging.warning('%d upload retries performed.', retries)
521
Don Garretta28be6d2016-06-16 18:09:35 -0700522 logging.info('Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.',
523 result_counts)
524
Chris Ching91908032016-09-27 16:55:33 -0600525 if result_counts[SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700526 logging.PrintBuildbotStepWarnings()
Chris Ching91908032016-09-27 16:55:33 -0600527 logging.warning('%d non-recoverable upload errors',
528 result_counts[SymbolFile.ERROR])
529
530 if result_counts[SymbolFile.INITIAL]:
531 logging.PrintBuildbotStepWarnings()
532 logging.warning('%d upload(s) were skipped because of excessive errors',
Don Garrettdeb2e032016-07-06 16:44:14 -0700533 result_counts[SymbolFile.INITIAL])
Don Garretta28be6d2016-06-16 18:09:35 -0700534
535 if failed_list is not None:
536 with open(failed_list, 'w') as fl:
537 for s in upload_failures:
538 fl.write('%s\n' % s.display_path)
539
Don Garrettdeb2e032016-07-06 16:44:14 -0700540 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
541
Don Garretta28be6d2016-06-16 18:09:35 -0700542
Mike Nichols649e6a82019-04-23 11:44:48 -0600543def UploadSymbols(sym_paths, upload_url, failed_list=None,
Mike Nichols137e82d2019-05-15 18:40:34 -0600544 upload_limit=None, strip_cfi=None, timeout=None,
Mike Nichols90f7c152019-04-09 15:14:08 -0600545 api_key=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400546 """Upload all the generated symbols for |board| to the crash server
547
548 Args:
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500549 sym_paths: Specific symbol files (or dirs of sym files) to upload,
550 otherwise search |breakpad_dir|
Don Garretta28be6d2016-06-16 18:09:35 -0700551 upload_url: URL of crash server to upload too.
Don Garretta28be6d2016-06-16 18:09:35 -0700552 failed_list: A filename at which to write out a list of our failed uploads.
553 upload_limit: Integer listing how many files to upload. None for no limit.
554 strip_cfi: File size at which we strip out CFI data. None for no limit.
Mike Nichols137e82d2019-05-15 18:40:34 -0600555 timeout: HTTP timeout for request.
Mike Nichols90f7c152019-04-09 15:14:08 -0600556 api_key: A string based authentication key
Mike Frysinger1a736a82013-12-12 01:50:59 -0500557
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400558 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400559 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400560 """
Don Garrette1f47e92016-10-13 16:04:56 -0700561 retry_stats.SetupStats()
562
Don Garretta28be6d2016-06-16 18:09:35 -0700563 # Note: This method looks like each step of processing is performed
564 # sequentially for all SymbolFiles, but instead each step is a generator that
565 # produces the next iteration only when it's read. This means that (except for
566 # some batching) each SymbolFile goes through all of these steps before the
567 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400568
Don Garretta28be6d2016-06-16 18:09:35 -0700569 # This is used to hold striped
570 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
571 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500572
Don Garrett1bc1e102016-07-06 17:06:10 -0700573 # Sort all of our symbols so the largest ones (probably the most important)
574 # are processed first.
575 symbols = list(symbols)
576 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
577
Don Garretta28be6d2016-06-16 18:09:35 -0700578 if upload_limit is not None:
579 # Restrict symbols processed to the limit.
580 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400581
Don Garretta28be6d2016-06-16 18:09:35 -0700582 # Strip CFI, if needed.
583 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500584
Mike Nichols137e82d2019-05-15 18:40:34 -0600585 # Find duplicates via batch API
586 symbols = FindDuplicates(symbols, upload_url, api_key, timeout)
587
Don Garretta28be6d2016-06-16 18:09:35 -0700588 # Perform uploads
Mike Nichols90f7c152019-04-09 15:14:08 -0600589 symbols = PerformSymbolsFileUpload(symbols, upload_url, api_key)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400590
Don Garretta28be6d2016-06-16 18:09:35 -0700591 # Log the final results, and consume the symbols generator fully.
Don Garrettdeb2e032016-07-06 16:44:14 -0700592 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500593
Don Garrettdeb2e032016-07-06 16:44:14 -0700594 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400595
596
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400597def main(argv):
598 parser = commandline.ArgumentParser(description=__doc__)
599
Don Garretta28be6d2016-06-16 18:09:35 -0700600 # TODO: Make sym_paths, breakpad_root, and root exclusive.
601
Mike Frysingerd41938e2014-02-10 06:37:55 -0500602 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
603 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400604 parser.add_argument('--board', default=None,
Don Garrett747cc4b2015-10-07 14:48:48 -0700605 help='Used to find default breakpad_root.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400606 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500607 help='full path to the breakpad symbol directory')
608 parser.add_argument('--root', type='path', default=None,
609 help='full path to the chroot dir')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400610 parser.add_argument('--official_build', action='store_true', default=False,
611 help='point to official symbol server')
Mike Frysinger38647542014-09-12 18:15:39 -0700612 parser.add_argument('--server', type=str, default=None,
613 help='URI for custom symbol server')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400614 parser.add_argument('--regenerate', action='store_true', default=False,
615 help='regenerate all symbols')
Don Garretta28be6d2016-06-16 18:09:35 -0700616 parser.add_argument('--upload-limit', type=int,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400617 help='only upload # number of symbols')
618 parser.add_argument('--strip_cfi', type=int,
Don Garretta28be6d2016-06-16 18:09:35 -0700619 default=DEFAULT_FILE_LIMIT,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400620 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500621 parser.add_argument('--failed-list', type='path',
622 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500623 parser.add_argument('--dedupe', action='store_true', default=False,
624 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400625 parser.add_argument('--yes', action='store_true', default=False,
626 help='answer yes to all prompts')
Don Garrett747cc4b2015-10-07 14:48:48 -0700627 parser.add_argument('--product_name', type=str, default='ChromeOS',
628 help='Produce Name for breakpad stats.')
Mike Nichols90f7c152019-04-09 15:14:08 -0600629 parser.add_argument('--api_key', type=str, default=None,
630 help='full path to the API key file')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400631
632 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500633 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400634
Don Garretta28be6d2016-06-16 18:09:35 -0700635 # Figure out the symbol files/directories to upload.
636 if opts.sym_paths:
637 sym_paths = opts.sym_paths
638 elif opts.breakpad_root:
639 sym_paths = [opts.breakpad_root]
640 elif opts.root:
641 if not opts.board:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400642 cros_build_lib.Die('--board must be set if --root is used.')
Don Garretta28be6d2016-06-16 18:09:35 -0700643 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(opts.board)
644 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip('/'))]
645 else:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400646 cros_build_lib.Die('--sym_paths, --breakpad_root, or --root must be set.')
Don Garretta28be6d2016-06-16 18:09:35 -0700647
Don Garrett747cc4b2015-10-07 14:48:48 -0700648 if opts.sym_paths or opts.breakpad_root:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400649 if opts.regenerate:
Don Garrett747cc4b2015-10-07 14:48:48 -0700650 cros_build_lib.Die('--regenerate may not be used with specific files, '
651 'or breakpad_root')
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400652 else:
653 if opts.board is None:
654 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400655
Don Garretta28be6d2016-06-16 18:09:35 -0700656 # Figure out which crash server to upload too.
657 upload_url = opts.server
658 if not upload_url:
659 if opts.official_build:
660 upload_url = OFFICIAL_UPLOAD_URL
661 else:
662 logging.warning('unofficial builds upload to the staging server')
663 upload_url = STAGING_UPLOAD_URL
664
Mike Nichols90f7c152019-04-09 15:14:08 -0600665 # Set up the API key needed to authenticate to Crash server.
666 # Allow for a local key file for testing purposes.
667 if opts.api_key:
668 api_key_file = opts.api_key
669 else:
670 api_key_file = constants.CRASH_API_KEY
671
672 api_key = osutils.ReadFile(api_key_file)
673
Don Garretta28be6d2016-06-16 18:09:35 -0700674 # Confirm we really want the long upload.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400675 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500676 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400677 Uploading symbols for an entire Chromium OS build is really only
678 necessary for release builds and in a few cases for developers
679 to debug problems. It will take considerable time to run. For
680 developer debugging purposes, consider instead passing specific
681 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500682 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400683 if not cros_build_lib.BooleanPrompt(
684 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500685 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400686 cros_build_lib.Die('better safe than sorry')
687
688 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700689
690 # Regenerate symbols from binaries.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400691 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400692 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
693 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400694
Don Garretta28be6d2016-06-16 18:09:35 -0700695 # Do the upload.
696 ret += UploadSymbols(
697 sym_paths=sym_paths,
698 upload_url=upload_url,
Don Garretta28be6d2016-06-16 18:09:35 -0700699 failed_list=opts.failed_list,
700 upload_limit=opts.upload_limit,
Mike Nichols90f7c152019-04-09 15:14:08 -0600701 strip_cfi=opts.strip_cfi,
702 api_key=api_key)
Don Garretta28be6d2016-06-16 18:09:35 -0700703
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400704 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700705 logging.error('encountered %i problem(s)', ret)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400706 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
707 # return 0 in case we are a multiple of the mask.
Don Garretta28be6d2016-06-16 18:09:35 -0700708 return 1