blob: a00006b01ad643e6ee8821743cadb77754e92dfe [file] [log] [blame]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -04001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Upload all debug symbols required for crash reporting purposes.
6
7This script need only be used to upload release builds symbols or to debug
8crashes on non-release builds (in which case try to only upload the symbols
Mike Frysinger02e1e072013-11-10 22:11:34 -05009for those executables involved).
10"""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040011
Mike Frysingere852b072021-05-21 12:39:03 -040012import http.client
Don Garretta28be6d2016-06-16 18:09:35 -070013import itertools
Mike Nichols90f7c152019-04-09 15:14:08 -060014import json
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040015import os
Mike Frysingerfd355652014-01-23 02:57:48 -050016import socket
Mike Frysingerc5597f22014-11-27 15:39:15 -050017import sys
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040018import tempfile
Mike Frysinger71bf7862021-02-12 07:38:57 -050019import textwrap
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040020import time
Mike Frysingere852b072021-05-21 12:39:03 -040021import urllib.parse
Mike Frysinger6db648e2018-07-24 19:57:58 -040022
Mike Frysingerd41938e2014-02-10 06:37:55 -050023from chromite.lib import cache
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040024from chromite.lib import commandline
Mike Frysinger38002522019-10-13 23:34:35 -040025from chromite.lib import constants
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040026from chromite.lib import cros_build_lib
Ralph Nathan5a582ff2015-03-20 18:18:30 -070027from chromite.lib import cros_logging as logging
Mike Frysingerd41938e2014-02-10 06:37:55 -050028from chromite.lib import gs
29from chromite.lib import osutils
Gilad Arnold83233ed2015-05-08 12:12:13 -070030from chromite.lib import path_util
Don Garrette1f47e92016-10-13 16:04:56 -070031from chromite.lib import retry_stats
Mike Frysinger69cb41d2013-08-11 20:08:19 -040032from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysinger71bf7862021-02-12 07:38:57 -050033from chromite.third_party import requests
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040034
Mike Frysinger2688ef62020-02-16 00:00:46 -050035
Mike Frysinger0c0efa22014-02-09 23:32:23 -050036# Needs to be after chromite imports.
Mike Frysingerc5597f22014-11-27 15:39:15 -050037# We don't want to import the general keyring module as that will implicitly
38# try to import & connect to a dbus server. That's a waste of time.
39sys.modules['keyring'] = None
Mike Frysinger0c0efa22014-02-09 23:32:23 -050040
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040041
42# URLs used for uploading symbols.
Mike Nichols90f7c152019-04-09 15:14:08 -060043OFFICIAL_UPLOAD_URL = 'https://prod-crashsymbolcollector-pa.googleapis.com/v1'
44STAGING_UPLOAD_URL = 'https://staging-crashsymbolcollector-pa.googleapis.com/v1'
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040045
Ian Barkley-Yeung22ba8122020-02-05 15:39:02 -080046# The crash server rejects files that are bigger than 1GB.
47CRASH_SERVER_FILE_LIMIT = 1024 * 1024 * 1024
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040048# Give ourselves a little breathing room from what the server expects.
49DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
50
51
Mike Frysinger0c0efa22014-02-09 23:32:23 -050052# The batch limit when talking to the dedup server. We avoid sending one at a
53# time as the round trip overhead will dominate. Conversely, we avoid sending
54# all at once so we can start uploading symbols asap -- the symbol server is a
55# bit slow and will take longer than anything else.
Mike Frysinger0c0efa22014-02-09 23:32:23 -050056DEDUPE_LIMIT = 100
57
58# How long to wait for the server to respond with the results. Note that the
59# larger the limit above, the larger this will need to be. So we give it ~1
60# second per item max.
61DEDUPE_TIMEOUT = DEDUPE_LIMIT
62
Don Garretta28be6d2016-06-16 18:09:35 -070063# How long to wait for the notification to finish (in seconds).
64DEDUPE_NOTIFY_TIMEOUT = 240
Mike Frysinger4dd462e2014-04-30 16:21:51 -040065
Mike Frysinger71046662014-09-12 18:15:15 -070066# The minimum average rate (in bytes per second) that we expect to maintain
67# when uploading symbols. This has to allow for symbols that are up to
68# CRASH_SERVER_FILE_LIMIT in size.
Mike Frysinger93e8ffa2019-07-03 20:24:18 -040069UPLOAD_MIN_RATE = CRASH_SERVER_FILE_LIMIT // (30 * 60)
Mike Frysinger71046662014-09-12 18:15:15 -070070
71# The lowest timeout (in seconds) we'll allow. If the server is overloaded,
72# then there might be a delay in setting up the connection, not just with the
73# transfer. So even a small file might need a larger value.
Ryo Hashimotoc0049372017-02-16 18:50:00 +090074UPLOAD_MIN_TIMEOUT = 5 * 60
Mike Frysingercd78a082013-06-26 17:13:04 -040075
76
Don Garrett7a793092016-07-06 16:50:27 -070077# Sleep for 500ms in between uploads to avoid DoS'ing symbol server.
78SLEEP_DELAY = 0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040079
80
81# Number of seconds to wait before retrying an upload. The delay will double
82# for each subsequent retry of the same symbol file.
83INITIAL_RETRY_DELAY = 1
84
85# Allow up to 7 attempts to upload a symbol file (total delay may be
86# 1+2+4+8+16+32=63 seconds).
Mike Nichols2a6f86f2020-08-18 15:56:07 -060087MAX_RETRIES = 5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040088
Mike Frysingereb753bf2013-11-22 16:05:35 -050089# Number of total errors, before uploads are no longer attempted.
90# This is used to avoid lots of errors causing unreasonable delays.
Mike Nichols2a6f86f2020-08-18 15:56:07 -060091MAX_TOTAL_ERRORS_FOR_RETRY = 6
Mike Frysingereb753bf2013-11-22 16:05:35 -050092
Don Garrette1f47e92016-10-13 16:04:56 -070093# Category to use for collection upload retry stats.
94UPLOAD_STATS = 'UPLOAD'
95
Don Garretta28be6d2016-06-16 18:09:35 -070096
Don Garretta28be6d2016-06-16 18:09:35 -070097def BatchGenerator(iterator, batch_size):
98 """Given an iterator, break into lists of size batch_size.
Fang Dengba680462015-08-16 20:34:11 -070099
Don Garretta28be6d2016-06-16 18:09:35 -0700100 The result is a generator, that will only read in as many inputs as needed for
101 the current batch. The final result can be smaller than batch_size.
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700102 """
Don Garretta28be6d2016-06-16 18:09:35 -0700103 batch = []
104 for i in iterator:
105 batch.append(i)
106 if len(batch) >= batch_size:
107 yield batch
108 batch = []
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700109
Don Garretta28be6d2016-06-16 18:09:35 -0700110 if batch:
111 # if there was anything left in the final batch, yield it.
112 yield batch
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500113
114
Mike Frysingerd41938e2014-02-10 06:37:55 -0500115def IsTarball(path):
116 """Guess if this is a tarball based on the filename."""
117 parts = path.split('.')
118 if len(parts) <= 1:
119 return False
120
121 if parts[-1] == 'tar':
122 return True
123
124 if parts[-2] == 'tar':
125 return parts[-1] in ('bz2', 'gz', 'xz')
126
127 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
128
129
Don Garretta28be6d2016-06-16 18:09:35 -0700130class SymbolFile(object):
131 """This class represents the state of a symbol file during processing.
132
Mike Frysinger309f6ab2019-09-22 13:33:54 -0400133 Attributes:
Don Garretta28be6d2016-06-16 18:09:35 -0700134 display_path: Name of symbol file that should be consistent between builds.
135 file_name: Transient path of the symbol file.
136 header: ReadSymsHeader output. Dict with assorted meta-data.
137 status: INITIAL, DUPLICATE, or UPLOADED based on status of processing.
138 dedupe_item: None or instance of DedupeItem for this symbol file.
139 dedupe_push_state: Opaque value to return to dedupe code for file.
140 display_name: Read only friendly (short) file name for logging.
141 file_size: Read only size of the symbol file.
142 """
143 INITIAL = 'initial'
144 DUPLICATE = 'duplicate'
145 UPLOADED = 'uploaded'
Don Garrettdeb2e032016-07-06 16:44:14 -0700146 ERROR = 'error'
Don Garretta28be6d2016-06-16 18:09:35 -0700147
148 def __init__(self, display_path, file_name):
149 """An instance of this class represents a symbol file over time.
150
151 Args:
152 display_path: A unique/persistent between builds name to present to the
153 crash server. It is the file name, relative to where it
154 came from (tarball, breakpad dir, etc).
155 file_name: A the current location of the symbol file.
156 """
157 self.display_path = display_path
158 self.file_name = file_name
159 self.header = cros_generate_breakpad_symbols.ReadSymsHeader(file_name)
160 self.status = SymbolFile.INITIAL
Don Garretta28be6d2016-06-16 18:09:35 -0700161
162 @property
163 def display_name(self):
164 return os.path.basename(self.display_path)
165
166 def FileSize(self):
167 return os.path.getsize(self.file_name)
168
169
Don Garretta28be6d2016-06-16 18:09:35 -0700170def FindSymbolFiles(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500171 """Locate symbol files in |paths|
172
Don Garretta28be6d2016-06-16 18:09:35 -0700173 This returns SymbolFile objects that contain file references which are valid
174 after this exits. Those files may exist externally, or be created in the
175 tempdir (say, when expanding tarballs). The caller must not consider
176 SymbolFile's valid after tempdir is cleaned up.
177
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500178 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700179 tempdir: Path to use for temporary files.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500180 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500181 Dirs are searched for files that end in ".sym". Urls are fetched and then
182 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500183
Don Garretta28be6d2016-06-16 18:09:35 -0700184 Yields:
185 A SymbolFile for every symbol file found in paths.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500186 """
Gilad Arnold83233ed2015-05-08 12:12:13 -0700187 cache_dir = path_util.GetCacheDir()
Mike Frysingere847efd2015-01-08 03:57:24 -0500188 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
189 tar_cache = cache.TarballCache(common_path)
190
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500191 for p in paths:
Mike Frysinger3dcacee2019-08-23 17:09:11 -0400192 o = urllib.parse.urlparse(p)
Mike Frysinger27e21b72018-07-12 14:20:21 -0400193 if o.scheme:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500194 # Support globs of filenames.
195 ctx = gs.GSContext()
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400196 for gspath in ctx.LS(p):
197 logging.info('processing files inside %s', gspath)
198 o = urllib.parse.urlparse(gspath)
Mike Frysinger27e21b72018-07-12 14:20:21 -0400199 key = ('%s%s' % (o.netloc, o.path)).split('/')
Mike Frysingerd41938e2014-02-10 06:37:55 -0500200 # The common cache will not be LRU, removing the need to hold a read
201 # lock on the cached gsutil.
202 ref = tar_cache.Lookup(key)
203 try:
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400204 ref.SetDefault(gspath)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500205 except cros_build_lib.RunCommandError as e:
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400206 logging.warning('ignoring %s\n%s', gspath, e)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500207 continue
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400208 for sym in FindSymbolFiles(tempdir, [ref.path]):
209 yield sym
Mike Frysingerd41938e2014-02-10 06:37:55 -0500210
211 elif os.path.isdir(p):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500212 for root, _, files in os.walk(p):
213 for f in files:
214 if f.endswith('.sym'):
Don Garretta28be6d2016-06-16 18:09:35 -0700215 # If p is '/tmp/foo' and filename is '/tmp/foo/bar/bar.sym',
216 # display_path = 'bar/bar.sym'
217 filename = os.path.join(root, f)
218 yield SymbolFile(display_path=filename[len(p):].lstrip('/'),
219 file_name=filename)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500220
221 elif IsTarball(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700222 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500223 tardir = tempfile.mkdtemp(dir=tempdir)
224 cache.Untar(os.path.realpath(p), tardir)
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400225 for sym in FindSymbolFiles(tardir, [tardir]):
226 yield sym
Mike Frysingerd41938e2014-02-10 06:37:55 -0500227
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500228 else:
Don Garretta28be6d2016-06-16 18:09:35 -0700229 yield SymbolFile(display_path=p, file_name=p)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500230
231
Don Garretta28be6d2016-06-16 18:09:35 -0700232def AdjustSymbolFileSize(symbol, tempdir, file_limit):
233 """Examine symbols files for size problems, and reduce if needed.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500234
Don Garretta28be6d2016-06-16 18:09:35 -0700235 If the symbols size is too big, strip out the call frame info. The CFI
236 is unnecessary for 32bit x86 targets where the frame pointer is used (as
237 all of ours have) and it accounts for over half the size of the symbols
238 uploaded.
239
240 Stripped files will be created inside tempdir, and will be the callers
241 responsibility to clean up.
242
243 We also warn, if a symbols file is still too large after stripping.
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500244
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500245 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700246 symbol: SymbolFile instance to be examined and modified as needed..
247 tempdir: A temporary directory we can create files in that the caller will
248 clean up.
249 file_limit: We only strip files which are larger than this limit.
250
251 Returns:
252 SymbolFile instance (original or modified as needed)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500253 """
Don Garretta28be6d2016-06-16 18:09:35 -0700254 file_size = symbol.FileSize()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500255
Don Garretta28be6d2016-06-16 18:09:35 -0700256 if file_limit and symbol.FileSize() > file_limit:
Mike Frysinger59babdb2019-09-06 06:25:50 -0400257 with cros_build_lib.UnbufferedNamedTemporaryFile(
258 prefix='upload_symbols',
Don Garretta28be6d2016-06-16 18:09:35 -0700259 dir=tempdir, delete=False) as temp_sym_file:
260
Mike Frysingerac75f602019-11-14 23:18:51 -0500261 with open(symbol.file_name, 'rb') as fp:
262 temp_sym_file.writelines(
263 x for x in fp.readlines() if not x.startswith(b'STACK CFI')
264 )
Don Garretta28be6d2016-06-16 18:09:35 -0700265
266 original_file_size = file_size
267 symbol.file_name = temp_sym_file.name
268 file_size = symbol.FileSize()
269
270 logging.warning('stripped CFI for %s reducing size %s > %s',
271 symbol.display_name, original_file_size, file_size)
272
273 # Hopefully the crash server will let it through. But it probably won't.
274 # Not sure what the best answer is in this case.
275 if file_size >= CRASH_SERVER_FILE_LIMIT:
276 logging.PrintBuildbotStepWarnings()
277 logging.warning('upload file %s is awfully large, risking rejection by '
278 'the symbol server (%s > %s)', symbol.display_path,
279 file_size, CRASH_SERVER_FILE_LIMIT)
280
281 return symbol
282
Don Garretta28be6d2016-06-16 18:09:35 -0700283
284def GetUploadTimeout(symbol):
285 """How long to wait for a specific file to upload to the crash server.
286
287 This is a function largely to make unittesting easier.
288
289 Args:
290 symbol: A SymbolFile instance.
291
292 Returns:
293 Timeout length (in seconds)
294 """
295 # Scale the timeout based on the filesize.
Mike Frysinger93e8ffa2019-07-03 20:24:18 -0400296 return max(symbol.FileSize() // UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
Don Garretta28be6d2016-06-16 18:09:35 -0700297
298
Mike Nichols90f7c152019-04-09 15:14:08 -0600299def ExecRequest(operator, url, timeout, api_key, **kwargs):
300 """Makes a web request with default timeout, returning the json result.
Don Garretta28be6d2016-06-16 18:09:35 -0700301
Mike Nichols90f7c152019-04-09 15:14:08 -0600302 This method will raise a requests.exceptions.HTTPError if the status
303 code is not 4XX, 5XX
304
305 Note: If you are using verbose logging it is entirely possible that the
306 subsystem will write your api key to the logs!
307
308 Args:
Mike Nichols649e6a82019-04-23 11:44:48 -0600309 operator: HTTP method.
310 url: Endpoint URL.
311 timeout: HTTP timeout for request.
312 api_key: Authentication key.
Mike Nichols90f7c152019-04-09 15:14:08 -0600313
314 Returns:
315 HTTP response content
316 """
317 resp = requests.request(operator, url,
318 params={'key': api_key},
319 headers={'User-agent': 'chromite.upload_symbols'},
320 timeout=timeout, **kwargs)
321 # Make sure we don't leak secret keys by accident.
322 if resp.status_code > 399:
Mike Frysinger3dcacee2019-08-23 17:09:11 -0400323 resp.url = resp.url.replace(urllib.parse.quote(api_key), 'XX-HIDDEN-XX')
Mike Nicholscf5b7a92019-04-25 12:01:28 -0600324 logging.warning('Url: %s, Status: %s, response: "%s", in: %s',
325 resp.url, resp.status_code, resp.text, resp.elapsed)
326 elif resp.content:
Mike Nichols90f7c152019-04-09 15:14:08 -0600327 return resp.json()
Mike Nicholscf5b7a92019-04-25 12:01:28 -0600328
Mike Nichols90f7c152019-04-09 15:14:08 -0600329 return {}
330
331
Mike Nichols137e82d2019-05-15 18:40:34 -0600332def FindDuplicates(symbols, status_url, api_key, timeout=DEDUPE_TIMEOUT):
333 """Check whether the symbol files have already been uploaded.
Mike Nichols649e6a82019-04-23 11:44:48 -0600334
335 Args:
Mike Nichols137e82d2019-05-15 18:40:34 -0600336 symbols: A iterable of SymbolFiles to be uploaded
Mike Nichols649e6a82019-04-23 11:44:48 -0600337 status_url: The crash URL to validate the file existence.
Mike Nichols649e6a82019-04-23 11:44:48 -0600338 api_key: Authentication key.
Mike Nichols137e82d2019-05-15 18:40:34 -0600339 timeout: HTTP timeout for request.
Mike Nichols649e6a82019-04-23 11:44:48 -0600340
341 Yields:
Mike Nichols137e82d2019-05-15 18:40:34 -0600342 All SymbolFiles from symbols, but duplicates have status updated to
343 DUPLICATE.
Mike Nichols649e6a82019-04-23 11:44:48 -0600344 """
Mike Nichols137e82d2019-05-15 18:40:34 -0600345 for batch in BatchGenerator(symbols, DEDUPE_LIMIT):
346 items = []
347 result = {}
348 for x in batch:
349 items.append({'debug_file': x.header.name,
350 'debug_id': x.header.id.replace('-', '')})
351 symbol_data = {'symbol_ids': items}
352 try:
353 result = ExecRequest('post', '%s/symbols:checkStatuses' %
354 status_url,
355 timeout,
356 api_key=api_key,
357 data=json.dumps(symbol_data))
Mike Frysingerc7d164a2019-11-06 17:09:45 -0500358 except requests.exceptions.RequestException as e:
Mike Nichols137e82d2019-05-15 18:40:34 -0600359 logging.warning('could not identify duplicates: HTTP error: %s', e)
360 for b in batch:
361 b.status = SymbolFile.INITIAL
362 set_match = {'debugId': b.header.id.replace('-', ''),
363 'debugFile': b.header.name}
364 for cs_result in result.get('pairs', []):
Mike Frysingerca227ec2019-07-02 17:59:21 -0400365 if set_match == cs_result.get('symbolId'):
Mike Nichols137e82d2019-05-15 18:40:34 -0600366 if cs_result.get('status') == 'FOUND':
367 logging.debug('Found duplicate: %s', b.display_name)
368 b.status = SymbolFile.DUPLICATE
369 break
370 yield b
371
Mike Nichols649e6a82019-04-23 11:44:48 -0600372
Mike Nichols90f7c152019-04-09 15:14:08 -0600373def UploadSymbolFile(upload_url, symbol, api_key):
Mike Frysinger80de5012019-08-01 14:10:53 -0400374 """Upload a symbol file to the crash server, returning the status result.
Don Garretta28be6d2016-06-16 18:09:35 -0700375
376 Args:
377 upload_url: The crash URL to POST the |sym_file| to
378 symbol: A SymbolFile instance.
Mike Nichols90f7c152019-04-09 15:14:08 -0600379 api_key: Authentication key
Mike Frysinger80de5012019-08-01 14:10:53 -0400380 """
Mike Nichols90f7c152019-04-09 15:14:08 -0600381 timeout = GetUploadTimeout(symbol)
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600382 logging.debug('Executing post to uploads:create: %s', symbol.display_name)
Mike Nichols137e82d2019-05-15 18:40:34 -0600383 upload = ExecRequest('post',
384 '%s/uploads:create' % upload_url, timeout, api_key)
Mike Nichols649e6a82019-04-23 11:44:48 -0600385
Mike Nichols137e82d2019-05-15 18:40:34 -0600386 if upload and 'uploadUrl' in upload.keys():
387 symbol_data = {'symbol_id':
388 {'debug_file': symbol.header.name,
389 'debug_id': symbol.header.id.replace('-', '')}
390 }
Mike Frysingerac75f602019-11-14 23:18:51 -0500391 with open(symbol.file_name, 'r') as fp:
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600392 logging.debug('Executing put to uploadUrl: %s', symbol.display_name)
Mike Frysingerac75f602019-11-14 23:18:51 -0500393 ExecRequest('put',
394 upload['uploadUrl'], timeout,
395 api_key=api_key,
Mike Nicholsb99f86c2020-02-24 18:36:16 -0700396 data=fp.read())
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600397 logging.debug('Executing post to uploads/complete: %s', symbol.display_name)
Mike Nichols137e82d2019-05-15 18:40:34 -0600398 ExecRequest('post',
399 '%s/uploads/%s:complete' % (
400 upload_url, upload['uploadKey']),
401 timeout, api_key=api_key,
402 # TODO(mikenichols): Validate product_name once it is added
403 # to the proto; currently unsupported.
404 data=json.dumps(symbol_data))
405 else:
406 raise requests.exceptions.HTTPError
Don Garretta28be6d2016-06-16 18:09:35 -0700407
408
Mike Nichols90f7c152019-04-09 15:14:08 -0600409def PerformSymbolsFileUpload(symbols, upload_url, api_key):
Don Garretta28be6d2016-06-16 18:09:35 -0700410 """Upload the symbols to the crash server
411
412 Args:
Don Garrettdeb2e032016-07-06 16:44:14 -0700413 symbols: An iterable of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700414 upload_url: URL of crash server to upload too.
Mike Nichols90f7c152019-04-09 15:14:08 -0600415 api_key: Authentication key.
Don Garretta28be6d2016-06-16 18:09:35 -0700416 failures: Tracker for total upload failures.
Don Garretta28be6d2016-06-16 18:09:35 -0700417
Don Garrettdeb2e032016-07-06 16:44:14 -0700418 Yields:
419 Each symbol from symbols, perhaps modified.
Don Garretta28be6d2016-06-16 18:09:35 -0700420 """
Don Garrettdeb2e032016-07-06 16:44:14 -0700421 failures = 0
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600422 # Failures are figured per request, therefore each HTTP failure
423 # counts against the max. This means that the process will exit
424 # at the point when HTTP errors hit the defined limit.
Don Garrettdeb2e032016-07-06 16:44:14 -0700425 for s in symbols:
426 if (failures < MAX_TOTAL_ERRORS_FOR_RETRY and
427 s.status in (SymbolFile.INITIAL, SymbolFile.ERROR)):
428 # Keeps us from DoS-ing the symbol server.
429 time.sleep(SLEEP_DELAY)
430 logging.info('Uploading symbol_file: %s', s.display_path)
431 try:
432 # This command retries the upload multiple times with growing delays. We
433 # only consider the upload a failure if these retries fail.
Don Garrette1f47e92016-10-13 16:04:56 -0700434 def ShouldRetryUpload(exception):
Mike Nichols0abb8d32019-04-26 14:55:08 -0600435 if isinstance(exception, (requests.exceptions.RequestException,
Mike Frysinger3dcacee2019-08-23 17:09:11 -0400436 IOError,
Mike Frysingere852b072021-05-21 12:39:03 -0400437 http.client.HTTPException, socket.error)):
Mike Nichols0abb8d32019-04-26 14:55:08 -0600438 logging.info('Request failed, retrying: %s', exception)
439 return True
440 return False
Don Garrette1f47e92016-10-13 16:04:56 -0700441
Don Garrett440944e2016-10-03 16:33:31 -0700442 with cros_build_lib.TimedSection() as timer:
Don Garrette1f47e92016-10-13 16:04:56 -0700443 retry_stats.RetryWithStats(
444 UPLOAD_STATS, ShouldRetryUpload, MAX_RETRIES,
Don Garrett440944e2016-10-03 16:33:31 -0700445 UploadSymbolFile,
Mike Nichols90f7c152019-04-09 15:14:08 -0600446 upload_url, s, api_key,
Luigi Semenzato5104f3c2016-10-12 12:37:42 -0700447 sleep=INITIAL_RETRY_DELAY,
448 log_all_retries=True)
Mike Nicholsb03b2c62019-05-02 18:46:04 -0600449 if s.status != SymbolFile.DUPLICATE:
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600450 logging.info('upload of %s with size %10i bytes took %s',
451 s.display_name, s.FileSize(), timer.delta)
Mike Nicholsb03b2c62019-05-02 18:46:04 -0600452 s.status = SymbolFile.UPLOADED
Mike Frysingerc7d164a2019-11-06 17:09:45 -0500453 except requests.exceptions.RequestException as e:
Mike Nichols90f7c152019-04-09 15:14:08 -0600454 logging.warning('could not upload: %s: HTTP error: %s',
455 s.display_name, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700456 s.status = SymbolFile.ERROR
457 failures += 1
Mike Frysingere852b072021-05-21 12:39:03 -0400458 except (http.client.HTTPException, OSError) as e:
Ryo Hashimoto45273f02017-03-16 17:45:56 +0900459 logging.warning('could not upload: %s: %s %s', s.display_name,
460 type(e).__name__, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700461 s.status = SymbolFile.ERROR
462 failures += 1
463
464 # We pass the symbol along, on both success and failure.
465 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700466
467
Don Garrettdeb2e032016-07-06 16:44:14 -0700468def ReportResults(symbols, failed_list):
Don Garretta28be6d2016-06-16 18:09:35 -0700469 """Log a summary of the symbol uploading.
470
471 This has the side effect of fully consuming the symbols iterator.
472
473 Args:
474 symbols: An iterator of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700475 failed_list: A filename at which to write out a list of our failed uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700476
477 Returns:
478 The number of symbols not uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700479 """
480 upload_failures = []
481 result_counts = {
482 SymbolFile.INITIAL: 0,
483 SymbolFile.UPLOADED: 0,
484 SymbolFile.DUPLICATE: 0,
Don Garrettdeb2e032016-07-06 16:44:14 -0700485 SymbolFile.ERROR: 0,
Don Garretta28be6d2016-06-16 18:09:35 -0700486 }
487
488 for s in symbols:
489 result_counts[s.status] += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700490 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700491 upload_failures.append(s)
492
Don Garrette1f47e92016-10-13 16:04:56 -0700493 # Report retry numbers.
494 _, _, retries = retry_stats.CategoryStats(UPLOAD_STATS)
495 if retries:
496 logging.warning('%d upload retries performed.', retries)
497
Don Garretta28be6d2016-06-16 18:09:35 -0700498 logging.info('Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.',
499 result_counts)
500
Chris Ching91908032016-09-27 16:55:33 -0600501 if result_counts[SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700502 logging.PrintBuildbotStepWarnings()
Chris Ching91908032016-09-27 16:55:33 -0600503 logging.warning('%d non-recoverable upload errors',
504 result_counts[SymbolFile.ERROR])
505
506 if result_counts[SymbolFile.INITIAL]:
507 logging.PrintBuildbotStepWarnings()
508 logging.warning('%d upload(s) were skipped because of excessive errors',
Don Garrettdeb2e032016-07-06 16:44:14 -0700509 result_counts[SymbolFile.INITIAL])
Don Garretta28be6d2016-06-16 18:09:35 -0700510
511 if failed_list is not None:
512 with open(failed_list, 'w') as fl:
513 for s in upload_failures:
514 fl.write('%s\n' % s.display_path)
515
Don Garrettdeb2e032016-07-06 16:44:14 -0700516 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
517
Don Garretta28be6d2016-06-16 18:09:35 -0700518
Mike Nichols649e6a82019-04-23 11:44:48 -0600519def UploadSymbols(sym_paths, upload_url, failed_list=None,
Mike Nichols137e82d2019-05-15 18:40:34 -0600520 upload_limit=None, strip_cfi=None, timeout=None,
Mike Nichols90f7c152019-04-09 15:14:08 -0600521 api_key=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400522 """Upload all the generated symbols for |board| to the crash server
523
524 Args:
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500525 sym_paths: Specific symbol files (or dirs of sym files) to upload,
526 otherwise search |breakpad_dir|
Don Garretta28be6d2016-06-16 18:09:35 -0700527 upload_url: URL of crash server to upload too.
Don Garretta28be6d2016-06-16 18:09:35 -0700528 failed_list: A filename at which to write out a list of our failed uploads.
529 upload_limit: Integer listing how many files to upload. None for no limit.
530 strip_cfi: File size at which we strip out CFI data. None for no limit.
Mike Nichols137e82d2019-05-15 18:40:34 -0600531 timeout: HTTP timeout for request.
Mike Nichols90f7c152019-04-09 15:14:08 -0600532 api_key: A string based authentication key
Mike Frysinger1a736a82013-12-12 01:50:59 -0500533
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400534 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400535 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400536 """
Don Garrette1f47e92016-10-13 16:04:56 -0700537 retry_stats.SetupStats()
538
Don Garretta28be6d2016-06-16 18:09:35 -0700539 # Note: This method looks like each step of processing is performed
540 # sequentially for all SymbolFiles, but instead each step is a generator that
541 # produces the next iteration only when it's read. This means that (except for
542 # some batching) each SymbolFile goes through all of these steps before the
543 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400544
Don Garretta28be6d2016-06-16 18:09:35 -0700545 # This is used to hold striped
546 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
547 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500548
Don Garrett1bc1e102016-07-06 17:06:10 -0700549 # Sort all of our symbols so the largest ones (probably the most important)
550 # are processed first.
551 symbols = list(symbols)
552 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
553
Don Garretta28be6d2016-06-16 18:09:35 -0700554 if upload_limit is not None:
555 # Restrict symbols processed to the limit.
556 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400557
Don Garretta28be6d2016-06-16 18:09:35 -0700558 # Strip CFI, if needed.
559 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500560
Mike Nichols137e82d2019-05-15 18:40:34 -0600561 # Find duplicates via batch API
562 symbols = FindDuplicates(symbols, upload_url, api_key, timeout)
563
Don Garretta28be6d2016-06-16 18:09:35 -0700564 # Perform uploads
Mike Nichols90f7c152019-04-09 15:14:08 -0600565 symbols = PerformSymbolsFileUpload(symbols, upload_url, api_key)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400566
Don Garretta28be6d2016-06-16 18:09:35 -0700567 # Log the final results, and consume the symbols generator fully.
Don Garrettdeb2e032016-07-06 16:44:14 -0700568 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500569
Don Garrettdeb2e032016-07-06 16:44:14 -0700570 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400571
572
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400573def main(argv):
574 parser = commandline.ArgumentParser(description=__doc__)
575
Don Garretta28be6d2016-06-16 18:09:35 -0700576 # TODO: Make sym_paths, breakpad_root, and root exclusive.
577
Mike Frysingerd41938e2014-02-10 06:37:55 -0500578 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
579 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400580 parser.add_argument('--board', default=None,
Don Garrett747cc4b2015-10-07 14:48:48 -0700581 help='Used to find default breakpad_root.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400582 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500583 help='full path to the breakpad symbol directory')
584 parser.add_argument('--root', type='path', default=None,
585 help='full path to the chroot dir')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400586 parser.add_argument('--official_build', action='store_true', default=False,
587 help='point to official symbol server')
Mike Frysinger38647542014-09-12 18:15:39 -0700588 parser.add_argument('--server', type=str, default=None,
589 help='URI for custom symbol server')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400590 parser.add_argument('--regenerate', action='store_true', default=False,
591 help='regenerate all symbols')
Don Garretta28be6d2016-06-16 18:09:35 -0700592 parser.add_argument('--upload-limit', type=int,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400593 help='only upload # number of symbols')
594 parser.add_argument('--strip_cfi', type=int,
Don Garretta28be6d2016-06-16 18:09:35 -0700595 default=DEFAULT_FILE_LIMIT,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400596 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500597 parser.add_argument('--failed-list', type='path',
598 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500599 parser.add_argument('--dedupe', action='store_true', default=False,
600 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400601 parser.add_argument('--yes', action='store_true', default=False,
602 help='answer yes to all prompts')
Don Garrett747cc4b2015-10-07 14:48:48 -0700603 parser.add_argument('--product_name', type=str, default='ChromeOS',
604 help='Produce Name for breakpad stats.')
Mike Nichols90f7c152019-04-09 15:14:08 -0600605 parser.add_argument('--api_key', type=str, default=None,
606 help='full path to the API key file')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400607
608 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500609 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400610
Don Garretta28be6d2016-06-16 18:09:35 -0700611 # Figure out the symbol files/directories to upload.
612 if opts.sym_paths:
613 sym_paths = opts.sym_paths
614 elif opts.breakpad_root:
615 sym_paths = [opts.breakpad_root]
616 elif opts.root:
617 if not opts.board:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400618 cros_build_lib.Die('--board must be set if --root is used.')
Don Garretta28be6d2016-06-16 18:09:35 -0700619 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(opts.board)
620 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip('/'))]
621 else:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400622 cros_build_lib.Die('--sym_paths, --breakpad_root, or --root must be set.')
Don Garretta28be6d2016-06-16 18:09:35 -0700623
Don Garrett747cc4b2015-10-07 14:48:48 -0700624 if opts.sym_paths or opts.breakpad_root:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400625 if opts.regenerate:
Don Garrett747cc4b2015-10-07 14:48:48 -0700626 cros_build_lib.Die('--regenerate may not be used with specific files, '
627 'or breakpad_root')
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400628 else:
629 if opts.board is None:
630 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400631
Don Garretta28be6d2016-06-16 18:09:35 -0700632 # Figure out which crash server to upload too.
633 upload_url = opts.server
634 if not upload_url:
635 if opts.official_build:
636 upload_url = OFFICIAL_UPLOAD_URL
637 else:
638 logging.warning('unofficial builds upload to the staging server')
639 upload_url = STAGING_UPLOAD_URL
640
Mike Nichols90f7c152019-04-09 15:14:08 -0600641 # Set up the API key needed to authenticate to Crash server.
642 # Allow for a local key file for testing purposes.
643 if opts.api_key:
644 api_key_file = opts.api_key
645 else:
646 api_key_file = constants.CRASH_API_KEY
647
648 api_key = osutils.ReadFile(api_key_file)
649
Don Garretta28be6d2016-06-16 18:09:35 -0700650 # Confirm we really want the long upload.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400651 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500652 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400653 Uploading symbols for an entire Chromium OS build is really only
654 necessary for release builds and in a few cases for developers
655 to debug problems. It will take considerable time to run. For
656 developer debugging purposes, consider instead passing specific
657 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500658 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400659 if not cros_build_lib.BooleanPrompt(
660 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500661 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400662 cros_build_lib.Die('better safe than sorry')
663
664 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700665
666 # Regenerate symbols from binaries.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400667 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400668 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
669 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400670
Don Garretta28be6d2016-06-16 18:09:35 -0700671 # Do the upload.
672 ret += UploadSymbols(
673 sym_paths=sym_paths,
674 upload_url=upload_url,
Don Garretta28be6d2016-06-16 18:09:35 -0700675 failed_list=opts.failed_list,
676 upload_limit=opts.upload_limit,
Mike Nichols90f7c152019-04-09 15:14:08 -0600677 strip_cfi=opts.strip_cfi,
678 api_key=api_key)
Don Garretta28be6d2016-06-16 18:09:35 -0700679
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400680 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700681 logging.error('encountered %i problem(s)', ret)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400682 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
683 # return 0 in case we are a multiple of the mask.
Don Garretta28be6d2016-06-16 18:09:35 -0700684 return 1