blob: 2d94fc62311635bfaced028cc567bc728aa1594b [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
Chris McDonaldb55b7032021-06-17 16:41:32 -060015import logging
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040016import os
Mike Frysingerfd355652014-01-23 02:57:48 -050017import socket
Mike Frysingerc5597f22014-11-27 15:39:15 -050018import sys
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040019import tempfile
Mike Frysinger71bf7862021-02-12 07:38:57 -050020import textwrap
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040021import time
Mike Frysingere852b072021-05-21 12:39:03 -040022import urllib.parse
Mike Frysinger6db648e2018-07-24 19:57:58 -040023
Chris McDonaldb55b7032021-06-17 16:41:32 -060024from chromite.third_party import requests
25
26from chromite.cbuildbot import cbuildbot_alerts
Mike Frysingerd41938e2014-02-10 06:37:55 -050027from chromite.lib import cache
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040028from chromite.lib import commandline
Mike Frysinger38002522019-10-13 23:34:35 -040029from chromite.lib import constants
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040030from chromite.lib import cros_build_lib
Mike Frysingerd41938e2014-02-10 06:37:55 -050031from chromite.lib import gs
32from chromite.lib import osutils
Gilad Arnold83233ed2015-05-08 12:12:13 -070033from chromite.lib import path_util
Don Garrette1f47e92016-10-13 16:04:56 -070034from chromite.lib import retry_stats
Mike Frysinger69cb41d2013-08-11 20:08:19 -040035from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040036
Mike Frysinger2688ef62020-02-16 00:00:46 -050037
Mike Frysinger0c0efa22014-02-09 23:32:23 -050038# Needs to be after chromite imports.
Mike Frysingerc5597f22014-11-27 15:39:15 -050039# We don't want to import the general keyring module as that will implicitly
40# try to import & connect to a dbus server. That's a waste of time.
41sys.modules['keyring'] = None
Mike Frysinger0c0efa22014-02-09 23:32:23 -050042
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040043
44# URLs used for uploading symbols.
Mike Nichols90f7c152019-04-09 15:14:08 -060045OFFICIAL_UPLOAD_URL = 'https://prod-crashsymbolcollector-pa.googleapis.com/v1'
46STAGING_UPLOAD_URL = 'https://staging-crashsymbolcollector-pa.googleapis.com/v1'
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040047
Ian Barkley-Yeung22ba8122020-02-05 15:39:02 -080048# The crash server rejects files that are bigger than 1GB.
49CRASH_SERVER_FILE_LIMIT = 1024 * 1024 * 1024
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040050# Give ourselves a little breathing room from what the server expects.
51DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
52
53
Mike Frysinger0c0efa22014-02-09 23:32:23 -050054# The batch limit when talking to the dedup server. We avoid sending one at a
55# time as the round trip overhead will dominate. Conversely, we avoid sending
56# all at once so we can start uploading symbols asap -- the symbol server is a
57# bit slow and will take longer than anything else.
Mike Frysinger0c0efa22014-02-09 23:32:23 -050058DEDUPE_LIMIT = 100
59
60# How long to wait for the server to respond with the results. Note that the
61# larger the limit above, the larger this will need to be. So we give it ~1
62# second per item max.
63DEDUPE_TIMEOUT = DEDUPE_LIMIT
64
Don Garretta28be6d2016-06-16 18:09:35 -070065# How long to wait for the notification to finish (in seconds).
66DEDUPE_NOTIFY_TIMEOUT = 240
Mike Frysinger4dd462e2014-04-30 16:21:51 -040067
Mike Frysinger71046662014-09-12 18:15:15 -070068# The minimum average rate (in bytes per second) that we expect to maintain
69# when uploading symbols. This has to allow for symbols that are up to
70# CRASH_SERVER_FILE_LIMIT in size.
Mike Frysinger93e8ffa2019-07-03 20:24:18 -040071UPLOAD_MIN_RATE = CRASH_SERVER_FILE_LIMIT // (30 * 60)
Mike Frysinger71046662014-09-12 18:15:15 -070072
73# The lowest timeout (in seconds) we'll allow. If the server is overloaded,
74# then there might be a delay in setting up the connection, not just with the
75# transfer. So even a small file might need a larger value.
Ryo Hashimotoc0049372017-02-16 18:50:00 +090076UPLOAD_MIN_TIMEOUT = 5 * 60
Mike Frysingercd78a082013-06-26 17:13:04 -040077
78
Don Garrett7a793092016-07-06 16:50:27 -070079# Sleep for 500ms in between uploads to avoid DoS'ing symbol server.
80SLEEP_DELAY = 0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040081
82
83# Number of seconds to wait before retrying an upload. The delay will double
84# for each subsequent retry of the same symbol file.
85INITIAL_RETRY_DELAY = 1
86
87# Allow up to 7 attempts to upload a symbol file (total delay may be
88# 1+2+4+8+16+32=63 seconds).
Mike Nichols2a6f86f2020-08-18 15:56:07 -060089MAX_RETRIES = 5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040090
Mike Frysingereb753bf2013-11-22 16:05:35 -050091# Number of total errors, before uploads are no longer attempted.
92# This is used to avoid lots of errors causing unreasonable delays.
Mike Nichols2a6f86f2020-08-18 15:56:07 -060093MAX_TOTAL_ERRORS_FOR_RETRY = 6
Mike Frysingereb753bf2013-11-22 16:05:35 -050094
Don Garrette1f47e92016-10-13 16:04:56 -070095# Category to use for collection upload retry stats.
96UPLOAD_STATS = 'UPLOAD'
97
Don Garretta28be6d2016-06-16 18:09:35 -070098
Don Garretta28be6d2016-06-16 18:09:35 -070099def BatchGenerator(iterator, batch_size):
100 """Given an iterator, break into lists of size batch_size.
Fang Dengba680462015-08-16 20:34:11 -0700101
Don Garretta28be6d2016-06-16 18:09:35 -0700102 The result is a generator, that will only read in as many inputs as needed for
103 the current batch. The final result can be smaller than batch_size.
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700104 """
Don Garretta28be6d2016-06-16 18:09:35 -0700105 batch = []
106 for i in iterator:
107 batch.append(i)
108 if len(batch) >= batch_size:
109 yield batch
110 batch = []
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700111
Don Garretta28be6d2016-06-16 18:09:35 -0700112 if batch:
113 # if there was anything left in the final batch, yield it.
114 yield batch
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500115
116
Mike Frysingerd41938e2014-02-10 06:37:55 -0500117def IsTarball(path):
118 """Guess if this is a tarball based on the filename."""
119 parts = path.split('.')
120 if len(parts) <= 1:
121 return False
122
123 if parts[-1] == 'tar':
124 return True
125
126 if parts[-2] == 'tar':
127 return parts[-1] in ('bz2', 'gz', 'xz')
128
129 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
130
131
Don Garretta28be6d2016-06-16 18:09:35 -0700132class SymbolFile(object):
133 """This class represents the state of a symbol file during processing.
134
Mike Frysinger309f6ab2019-09-22 13:33:54 -0400135 Attributes:
Don Garretta28be6d2016-06-16 18:09:35 -0700136 display_path: Name of symbol file that should be consistent between builds.
137 file_name: Transient path of the symbol file.
138 header: ReadSymsHeader output. Dict with assorted meta-data.
139 status: INITIAL, DUPLICATE, or UPLOADED based on status of processing.
140 dedupe_item: None or instance of DedupeItem for this symbol file.
141 dedupe_push_state: Opaque value to return to dedupe code for file.
142 display_name: Read only friendly (short) file name for logging.
143 file_size: Read only size of the symbol file.
144 """
145 INITIAL = 'initial'
146 DUPLICATE = 'duplicate'
147 UPLOADED = 'uploaded'
Don Garrettdeb2e032016-07-06 16:44:14 -0700148 ERROR = 'error'
Don Garretta28be6d2016-06-16 18:09:35 -0700149
150 def __init__(self, display_path, file_name):
151 """An instance of this class represents a symbol file over time.
152
153 Args:
154 display_path: A unique/persistent between builds name to present to the
155 crash server. It is the file name, relative to where it
156 came from (tarball, breakpad dir, etc).
157 file_name: A the current location of the symbol file.
158 """
159 self.display_path = display_path
160 self.file_name = file_name
161 self.header = cros_generate_breakpad_symbols.ReadSymsHeader(file_name)
162 self.status = SymbolFile.INITIAL
Don Garretta28be6d2016-06-16 18:09:35 -0700163
164 @property
165 def display_name(self):
166 return os.path.basename(self.display_path)
167
168 def FileSize(self):
169 return os.path.getsize(self.file_name)
170
171
Don Garretta28be6d2016-06-16 18:09:35 -0700172def FindSymbolFiles(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500173 """Locate symbol files in |paths|
174
Don Garretta28be6d2016-06-16 18:09:35 -0700175 This returns SymbolFile objects that contain file references which are valid
176 after this exits. Those files may exist externally, or be created in the
177 tempdir (say, when expanding tarballs). The caller must not consider
178 SymbolFile's valid after tempdir is cleaned up.
179
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500180 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700181 tempdir: Path to use for temporary files.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500182 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500183 Dirs are searched for files that end in ".sym". Urls are fetched and then
184 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500185
Don Garretta28be6d2016-06-16 18:09:35 -0700186 Yields:
187 A SymbolFile for every symbol file found in paths.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500188 """
Gilad Arnold83233ed2015-05-08 12:12:13 -0700189 cache_dir = path_util.GetCacheDir()
Mike Frysingere847efd2015-01-08 03:57:24 -0500190 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
191 tar_cache = cache.TarballCache(common_path)
192
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500193 for p in paths:
Mike Frysinger3dcacee2019-08-23 17:09:11 -0400194 o = urllib.parse.urlparse(p)
Mike Frysinger27e21b72018-07-12 14:20:21 -0400195 if o.scheme:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500196 # Support globs of filenames.
197 ctx = gs.GSContext()
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400198 for gspath in ctx.LS(p):
199 logging.info('processing files inside %s', gspath)
200 o = urllib.parse.urlparse(gspath)
Mike Frysinger27e21b72018-07-12 14:20:21 -0400201 key = ('%s%s' % (o.netloc, o.path)).split('/')
Mike Frysingerd41938e2014-02-10 06:37:55 -0500202 # The common cache will not be LRU, removing the need to hold a read
203 # lock on the cached gsutil.
204 ref = tar_cache.Lookup(key)
205 try:
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400206 ref.SetDefault(gspath)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500207 except cros_build_lib.RunCommandError as e:
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400208 logging.warning('ignoring %s\n%s', gspath, e)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500209 continue
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400210 for sym in FindSymbolFiles(tempdir, [ref.path]):
211 yield sym
Mike Frysingerd41938e2014-02-10 06:37:55 -0500212
213 elif os.path.isdir(p):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500214 for root, _, files in os.walk(p):
215 for f in files:
216 if f.endswith('.sym'):
Don Garretta28be6d2016-06-16 18:09:35 -0700217 # If p is '/tmp/foo' and filename is '/tmp/foo/bar/bar.sym',
218 # display_path = 'bar/bar.sym'
219 filename = os.path.join(root, f)
220 yield SymbolFile(display_path=filename[len(p):].lstrip('/'),
221 file_name=filename)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500222
223 elif IsTarball(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700224 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500225 tardir = tempfile.mkdtemp(dir=tempdir)
226 cache.Untar(os.path.realpath(p), tardir)
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400227 for sym in FindSymbolFiles(tardir, [tardir]):
228 yield sym
Mike Frysingerd41938e2014-02-10 06:37:55 -0500229
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500230 else:
Don Garretta28be6d2016-06-16 18:09:35 -0700231 yield SymbolFile(display_path=p, file_name=p)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500232
233
Don Garretta28be6d2016-06-16 18:09:35 -0700234def AdjustSymbolFileSize(symbol, tempdir, file_limit):
235 """Examine symbols files for size problems, and reduce if needed.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500236
Don Garretta28be6d2016-06-16 18:09:35 -0700237 If the symbols size is too big, strip out the call frame info. The CFI
238 is unnecessary for 32bit x86 targets where the frame pointer is used (as
239 all of ours have) and it accounts for over half the size of the symbols
240 uploaded.
241
242 Stripped files will be created inside tempdir, and will be the callers
243 responsibility to clean up.
244
245 We also warn, if a symbols file is still too large after stripping.
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500246
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500247 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700248 symbol: SymbolFile instance to be examined and modified as needed..
249 tempdir: A temporary directory we can create files in that the caller will
250 clean up.
251 file_limit: We only strip files which are larger than this limit.
252
253 Returns:
254 SymbolFile instance (original or modified as needed)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500255 """
Don Garretta28be6d2016-06-16 18:09:35 -0700256 file_size = symbol.FileSize()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500257
Don Garretta28be6d2016-06-16 18:09:35 -0700258 if file_limit and symbol.FileSize() > file_limit:
Mike Frysinger59babdb2019-09-06 06:25:50 -0400259 with cros_build_lib.UnbufferedNamedTemporaryFile(
260 prefix='upload_symbols',
Don Garretta28be6d2016-06-16 18:09:35 -0700261 dir=tempdir, delete=False) as temp_sym_file:
262
Mike Frysingerac75f602019-11-14 23:18:51 -0500263 with open(symbol.file_name, 'rb') as fp:
264 temp_sym_file.writelines(
265 x for x in fp.readlines() if not x.startswith(b'STACK CFI')
266 )
Don Garretta28be6d2016-06-16 18:09:35 -0700267
268 original_file_size = file_size
269 symbol.file_name = temp_sym_file.name
270 file_size = symbol.FileSize()
271
272 logging.warning('stripped CFI for %s reducing size %s > %s',
273 symbol.display_name, original_file_size, file_size)
274
275 # Hopefully the crash server will let it through. But it probably won't.
276 # Not sure what the best answer is in this case.
277 if file_size >= CRASH_SERVER_FILE_LIMIT:
Chris McDonaldb55b7032021-06-17 16:41:32 -0600278 cbuildbot_alerts.PrintBuildbotStepWarnings()
Don Garretta28be6d2016-06-16 18:09:35 -0700279 logging.warning('upload file %s is awfully large, risking rejection by '
280 'the symbol server (%s > %s)', symbol.display_path,
281 file_size, CRASH_SERVER_FILE_LIMIT)
282
283 return symbol
284
Don Garretta28be6d2016-06-16 18:09:35 -0700285
286def GetUploadTimeout(symbol):
287 """How long to wait for a specific file to upload to the crash server.
288
289 This is a function largely to make unittesting easier.
290
291 Args:
292 symbol: A SymbolFile instance.
293
294 Returns:
295 Timeout length (in seconds)
296 """
297 # Scale the timeout based on the filesize.
Mike Frysinger93e8ffa2019-07-03 20:24:18 -0400298 return max(symbol.FileSize() // UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
Don Garretta28be6d2016-06-16 18:09:35 -0700299
300
Mike Nichols90f7c152019-04-09 15:14:08 -0600301def ExecRequest(operator, url, timeout, api_key, **kwargs):
302 """Makes a web request with default timeout, returning the json result.
Don Garretta28be6d2016-06-16 18:09:35 -0700303
Mike Nichols90f7c152019-04-09 15:14:08 -0600304 This method will raise a requests.exceptions.HTTPError if the status
305 code is not 4XX, 5XX
306
307 Note: If you are using verbose logging it is entirely possible that the
308 subsystem will write your api key to the logs!
309
310 Args:
Mike Nichols649e6a82019-04-23 11:44:48 -0600311 operator: HTTP method.
312 url: Endpoint URL.
313 timeout: HTTP timeout for request.
314 api_key: Authentication key.
Mike Nichols90f7c152019-04-09 15:14:08 -0600315
316 Returns:
317 HTTP response content
318 """
319 resp = requests.request(operator, url,
320 params={'key': api_key},
321 headers={'User-agent': 'chromite.upload_symbols'},
322 timeout=timeout, **kwargs)
323 # Make sure we don't leak secret keys by accident.
324 if resp.status_code > 399:
Mike Frysinger3dcacee2019-08-23 17:09:11 -0400325 resp.url = resp.url.replace(urllib.parse.quote(api_key), 'XX-HIDDEN-XX')
Mike Nicholscf5b7a92019-04-25 12:01:28 -0600326 logging.warning('Url: %s, Status: %s, response: "%s", in: %s',
327 resp.url, resp.status_code, resp.text, resp.elapsed)
328 elif resp.content:
Mike Nichols90f7c152019-04-09 15:14:08 -0600329 return resp.json()
Mike Nicholscf5b7a92019-04-25 12:01:28 -0600330
Mike Nichols90f7c152019-04-09 15:14:08 -0600331 return {}
332
333
Mike Nichols137e82d2019-05-15 18:40:34 -0600334def FindDuplicates(symbols, status_url, api_key, timeout=DEDUPE_TIMEOUT):
335 """Check whether the symbol files have already been uploaded.
Mike Nichols649e6a82019-04-23 11:44:48 -0600336
337 Args:
Mike Nichols137e82d2019-05-15 18:40:34 -0600338 symbols: A iterable of SymbolFiles to be uploaded
Mike Nichols649e6a82019-04-23 11:44:48 -0600339 status_url: The crash URL to validate the file existence.
Mike Nichols649e6a82019-04-23 11:44:48 -0600340 api_key: Authentication key.
Mike Nichols137e82d2019-05-15 18:40:34 -0600341 timeout: HTTP timeout for request.
Mike Nichols649e6a82019-04-23 11:44:48 -0600342
343 Yields:
Mike Nichols137e82d2019-05-15 18:40:34 -0600344 All SymbolFiles from symbols, but duplicates have status updated to
345 DUPLICATE.
Mike Nichols649e6a82019-04-23 11:44:48 -0600346 """
Mike Nichols137e82d2019-05-15 18:40:34 -0600347 for batch in BatchGenerator(symbols, DEDUPE_LIMIT):
348 items = []
349 result = {}
350 for x in batch:
351 items.append({'debug_file': x.header.name,
352 'debug_id': x.header.id.replace('-', '')})
353 symbol_data = {'symbol_ids': items}
354 try:
355 result = ExecRequest('post', '%s/symbols:checkStatuses' %
356 status_url,
357 timeout,
358 api_key=api_key,
359 data=json.dumps(symbol_data))
Mike Frysingerc7d164a2019-11-06 17:09:45 -0500360 except requests.exceptions.RequestException as e:
Mike Nichols137e82d2019-05-15 18:40:34 -0600361 logging.warning('could not identify duplicates: HTTP error: %s', e)
362 for b in batch:
363 b.status = SymbolFile.INITIAL
364 set_match = {'debugId': b.header.id.replace('-', ''),
365 'debugFile': b.header.name}
366 for cs_result in result.get('pairs', []):
Mike Frysingerca227ec2019-07-02 17:59:21 -0400367 if set_match == cs_result.get('symbolId'):
Mike Nichols137e82d2019-05-15 18:40:34 -0600368 if cs_result.get('status') == 'FOUND':
369 logging.debug('Found duplicate: %s', b.display_name)
370 b.status = SymbolFile.DUPLICATE
371 break
372 yield b
373
Mike Nichols649e6a82019-04-23 11:44:48 -0600374
Mike Nichols90f7c152019-04-09 15:14:08 -0600375def UploadSymbolFile(upload_url, symbol, api_key):
Mike Frysinger80de5012019-08-01 14:10:53 -0400376 """Upload a symbol file to the crash server, returning the status result.
Don Garretta28be6d2016-06-16 18:09:35 -0700377
378 Args:
379 upload_url: The crash URL to POST the |sym_file| to
380 symbol: A SymbolFile instance.
Mike Nichols90f7c152019-04-09 15:14:08 -0600381 api_key: Authentication key
Mike Frysinger80de5012019-08-01 14:10:53 -0400382 """
Mike Nichols90f7c152019-04-09 15:14:08 -0600383 timeout = GetUploadTimeout(symbol)
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600384 logging.debug('Executing post to uploads:create: %s', symbol.display_name)
Mike Nichols137e82d2019-05-15 18:40:34 -0600385 upload = ExecRequest('post',
386 '%s/uploads:create' % upload_url, timeout, api_key)
Mike Nichols649e6a82019-04-23 11:44:48 -0600387
Mike Nichols137e82d2019-05-15 18:40:34 -0600388 if upload and 'uploadUrl' in upload.keys():
389 symbol_data = {'symbol_id':
390 {'debug_file': symbol.header.name,
391 'debug_id': symbol.header.id.replace('-', '')}
392 }
Mike Frysingerac75f602019-11-14 23:18:51 -0500393 with open(symbol.file_name, 'r') as fp:
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600394 logging.debug('Executing put to uploadUrl: %s', symbol.display_name)
Mike Frysingerac75f602019-11-14 23:18:51 -0500395 ExecRequest('put',
396 upload['uploadUrl'], timeout,
397 api_key=api_key,
Mike Nicholsb99f86c2020-02-24 18:36:16 -0700398 data=fp.read())
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600399 logging.debug('Executing post to uploads/complete: %s', symbol.display_name)
Mike Nichols137e82d2019-05-15 18:40:34 -0600400 ExecRequest('post',
401 '%s/uploads/%s:complete' % (
402 upload_url, upload['uploadKey']),
403 timeout, api_key=api_key,
404 # TODO(mikenichols): Validate product_name once it is added
405 # to the proto; currently unsupported.
406 data=json.dumps(symbol_data))
407 else:
408 raise requests.exceptions.HTTPError
Don Garretta28be6d2016-06-16 18:09:35 -0700409
410
Mike Nichols90f7c152019-04-09 15:14:08 -0600411def PerformSymbolsFileUpload(symbols, upload_url, api_key):
Don Garretta28be6d2016-06-16 18:09:35 -0700412 """Upload the symbols to the crash server
413
414 Args:
Don Garrettdeb2e032016-07-06 16:44:14 -0700415 symbols: An iterable of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700416 upload_url: URL of crash server to upload too.
Mike Nichols90f7c152019-04-09 15:14:08 -0600417 api_key: Authentication key.
Don Garretta28be6d2016-06-16 18:09:35 -0700418 failures: Tracker for total upload failures.
Don Garretta28be6d2016-06-16 18:09:35 -0700419
Don Garrettdeb2e032016-07-06 16:44:14 -0700420 Yields:
421 Each symbol from symbols, perhaps modified.
Don Garretta28be6d2016-06-16 18:09:35 -0700422 """
Don Garrettdeb2e032016-07-06 16:44:14 -0700423 failures = 0
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600424 # Failures are figured per request, therefore each HTTP failure
425 # counts against the max. This means that the process will exit
426 # at the point when HTTP errors hit the defined limit.
Don Garrettdeb2e032016-07-06 16:44:14 -0700427 for s in symbols:
428 if (failures < MAX_TOTAL_ERRORS_FOR_RETRY and
429 s.status in (SymbolFile.INITIAL, SymbolFile.ERROR)):
430 # Keeps us from DoS-ing the symbol server.
431 time.sleep(SLEEP_DELAY)
432 logging.info('Uploading symbol_file: %s', s.display_path)
433 try:
434 # This command retries the upload multiple times with growing delays. We
435 # only consider the upload a failure if these retries fail.
Don Garrette1f47e92016-10-13 16:04:56 -0700436 def ShouldRetryUpload(exception):
Mike Nichols0abb8d32019-04-26 14:55:08 -0600437 if isinstance(exception, (requests.exceptions.RequestException,
Mike Frysinger3dcacee2019-08-23 17:09:11 -0400438 IOError,
Mike Frysingere852b072021-05-21 12:39:03 -0400439 http.client.HTTPException, socket.error)):
Mike Nichols0abb8d32019-04-26 14:55:08 -0600440 logging.info('Request failed, retrying: %s', exception)
441 return True
442 return False
Don Garrette1f47e92016-10-13 16:04:56 -0700443
Don Garrett440944e2016-10-03 16:33:31 -0700444 with cros_build_lib.TimedSection() as timer:
Don Garrette1f47e92016-10-13 16:04:56 -0700445 retry_stats.RetryWithStats(
446 UPLOAD_STATS, ShouldRetryUpload, MAX_RETRIES,
Don Garrett440944e2016-10-03 16:33:31 -0700447 UploadSymbolFile,
Mike Nichols90f7c152019-04-09 15:14:08 -0600448 upload_url, s, api_key,
Luigi Semenzato5104f3c2016-10-12 12:37:42 -0700449 sleep=INITIAL_RETRY_DELAY,
450 log_all_retries=True)
Mike Nicholsb03b2c62019-05-02 18:46:04 -0600451 if s.status != SymbolFile.DUPLICATE:
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600452 logging.info('upload of %s with size %10i bytes took %s',
453 s.display_name, s.FileSize(), timer.delta)
Mike Nicholsb03b2c62019-05-02 18:46:04 -0600454 s.status = SymbolFile.UPLOADED
Mike Frysingerc7d164a2019-11-06 17:09:45 -0500455 except requests.exceptions.RequestException as e:
Mike Nichols90f7c152019-04-09 15:14:08 -0600456 logging.warning('could not upload: %s: HTTP error: %s',
457 s.display_name, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700458 s.status = SymbolFile.ERROR
459 failures += 1
Mike Frysingere852b072021-05-21 12:39:03 -0400460 except (http.client.HTTPException, OSError) as e:
Ryo Hashimoto45273f02017-03-16 17:45:56 +0900461 logging.warning('could not upload: %s: %s %s', s.display_name,
462 type(e).__name__, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700463 s.status = SymbolFile.ERROR
464 failures += 1
465
466 # We pass the symbol along, on both success and failure.
467 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700468
469
Don Garrettdeb2e032016-07-06 16:44:14 -0700470def ReportResults(symbols, failed_list):
Don Garretta28be6d2016-06-16 18:09:35 -0700471 """Log a summary of the symbol uploading.
472
473 This has the side effect of fully consuming the symbols iterator.
474
475 Args:
476 symbols: An iterator of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700477 failed_list: A filename at which to write out a list of our failed uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700478
479 Returns:
480 The number of symbols not uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700481 """
482 upload_failures = []
483 result_counts = {
484 SymbolFile.INITIAL: 0,
485 SymbolFile.UPLOADED: 0,
486 SymbolFile.DUPLICATE: 0,
Don Garrettdeb2e032016-07-06 16:44:14 -0700487 SymbolFile.ERROR: 0,
Don Garretta28be6d2016-06-16 18:09:35 -0700488 }
489
490 for s in symbols:
491 result_counts[s.status] += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700492 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700493 upload_failures.append(s)
494
Don Garrette1f47e92016-10-13 16:04:56 -0700495 # Report retry numbers.
496 _, _, retries = retry_stats.CategoryStats(UPLOAD_STATS)
497 if retries:
498 logging.warning('%d upload retries performed.', retries)
499
Don Garretta28be6d2016-06-16 18:09:35 -0700500 logging.info('Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.',
501 result_counts)
502
Chris Ching91908032016-09-27 16:55:33 -0600503 if result_counts[SymbolFile.ERROR]:
Chris McDonaldb55b7032021-06-17 16:41:32 -0600504 cbuildbot_alerts.PrintBuildbotStepWarnings()
Chris Ching91908032016-09-27 16:55:33 -0600505 logging.warning('%d non-recoverable upload errors',
506 result_counts[SymbolFile.ERROR])
507
508 if result_counts[SymbolFile.INITIAL]:
Chris McDonaldb55b7032021-06-17 16:41:32 -0600509 cbuildbot_alerts.PrintBuildbotStepWarnings()
Chris Ching91908032016-09-27 16:55:33 -0600510 logging.warning('%d upload(s) were skipped because of excessive errors',
Don Garrettdeb2e032016-07-06 16:44:14 -0700511 result_counts[SymbolFile.INITIAL])
Don Garretta28be6d2016-06-16 18:09:35 -0700512
513 if failed_list is not None:
514 with open(failed_list, 'w') as fl:
515 for s in upload_failures:
516 fl.write('%s\n' % s.display_path)
517
Don Garrettdeb2e032016-07-06 16:44:14 -0700518 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
519
Don Garretta28be6d2016-06-16 18:09:35 -0700520
Mike Nichols649e6a82019-04-23 11:44:48 -0600521def UploadSymbols(sym_paths, upload_url, failed_list=None,
Mike Nichols137e82d2019-05-15 18:40:34 -0600522 upload_limit=None, strip_cfi=None, timeout=None,
Mike Nichols90f7c152019-04-09 15:14:08 -0600523 api_key=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400524 """Upload all the generated symbols for |board| to the crash server
525
526 Args:
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500527 sym_paths: Specific symbol files (or dirs of sym files) to upload,
528 otherwise search |breakpad_dir|
Don Garretta28be6d2016-06-16 18:09:35 -0700529 upload_url: URL of crash server to upload too.
Don Garretta28be6d2016-06-16 18:09:35 -0700530 failed_list: A filename at which to write out a list of our failed uploads.
531 upload_limit: Integer listing how many files to upload. None for no limit.
532 strip_cfi: File size at which we strip out CFI data. None for no limit.
Mike Nichols137e82d2019-05-15 18:40:34 -0600533 timeout: HTTP timeout for request.
Mike Nichols90f7c152019-04-09 15:14:08 -0600534 api_key: A string based authentication key
Mike Frysinger1a736a82013-12-12 01:50:59 -0500535
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400536 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400537 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400538 """
Don Garrette1f47e92016-10-13 16:04:56 -0700539 retry_stats.SetupStats()
540
Don Garretta28be6d2016-06-16 18:09:35 -0700541 # Note: This method looks like each step of processing is performed
542 # sequentially for all SymbolFiles, but instead each step is a generator that
543 # produces the next iteration only when it's read. This means that (except for
544 # some batching) each SymbolFile goes through all of these steps before the
545 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400546
Don Garretta28be6d2016-06-16 18:09:35 -0700547 # This is used to hold striped
548 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
549 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500550
Don Garrett1bc1e102016-07-06 17:06:10 -0700551 # Sort all of our symbols so the largest ones (probably the most important)
552 # are processed first.
553 symbols = list(symbols)
554 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
555
Don Garretta28be6d2016-06-16 18:09:35 -0700556 if upload_limit is not None:
557 # Restrict symbols processed to the limit.
558 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400559
Don Garretta28be6d2016-06-16 18:09:35 -0700560 # Strip CFI, if needed.
561 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500562
Mike Nichols137e82d2019-05-15 18:40:34 -0600563 # Find duplicates via batch API
564 symbols = FindDuplicates(symbols, upload_url, api_key, timeout)
565
Don Garretta28be6d2016-06-16 18:09:35 -0700566 # Perform uploads
Mike Nichols90f7c152019-04-09 15:14:08 -0600567 symbols = PerformSymbolsFileUpload(symbols, upload_url, api_key)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400568
Don Garretta28be6d2016-06-16 18:09:35 -0700569 # Log the final results, and consume the symbols generator fully.
Don Garrettdeb2e032016-07-06 16:44:14 -0700570 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500571
Don Garrettdeb2e032016-07-06 16:44:14 -0700572 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400573
574
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400575def main(argv):
576 parser = commandline.ArgumentParser(description=__doc__)
577
Don Garretta28be6d2016-06-16 18:09:35 -0700578 # TODO: Make sym_paths, breakpad_root, and root exclusive.
579
Mike Frysingerd41938e2014-02-10 06:37:55 -0500580 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
581 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400582 parser.add_argument('--board', default=None,
Don Garrett747cc4b2015-10-07 14:48:48 -0700583 help='Used to find default breakpad_root.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400584 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500585 help='full path to the breakpad symbol directory')
586 parser.add_argument('--root', type='path', default=None,
587 help='full path to the chroot dir')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400588 parser.add_argument('--official_build', action='store_true', default=False,
589 help='point to official symbol server')
Mike Frysinger38647542014-09-12 18:15:39 -0700590 parser.add_argument('--server', type=str, default=None,
591 help='URI for custom symbol server')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400592 parser.add_argument('--regenerate', action='store_true', default=False,
593 help='regenerate all symbols')
Don Garretta28be6d2016-06-16 18:09:35 -0700594 parser.add_argument('--upload-limit', type=int,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400595 help='only upload # number of symbols')
596 parser.add_argument('--strip_cfi', type=int,
Don Garretta28be6d2016-06-16 18:09:35 -0700597 default=DEFAULT_FILE_LIMIT,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400598 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500599 parser.add_argument('--failed-list', type='path',
600 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500601 parser.add_argument('--dedupe', action='store_true', default=False,
602 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400603 parser.add_argument('--yes', action='store_true', default=False,
604 help='answer yes to all prompts')
Don Garrett747cc4b2015-10-07 14:48:48 -0700605 parser.add_argument('--product_name', type=str, default='ChromeOS',
606 help='Produce Name for breakpad stats.')
Mike Nichols90f7c152019-04-09 15:14:08 -0600607 parser.add_argument('--api_key', type=str, default=None,
608 help='full path to the API key file')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400609
610 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500611 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400612
Don Garretta28be6d2016-06-16 18:09:35 -0700613 # Figure out the symbol files/directories to upload.
614 if opts.sym_paths:
615 sym_paths = opts.sym_paths
616 elif opts.breakpad_root:
617 sym_paths = [opts.breakpad_root]
618 elif opts.root:
619 if not opts.board:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400620 cros_build_lib.Die('--board must be set if --root is used.')
Don Garretta28be6d2016-06-16 18:09:35 -0700621 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(opts.board)
622 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip('/'))]
623 else:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400624 cros_build_lib.Die('--sym_paths, --breakpad_root, or --root must be set.')
Don Garretta28be6d2016-06-16 18:09:35 -0700625
Don Garrett747cc4b2015-10-07 14:48:48 -0700626 if opts.sym_paths or opts.breakpad_root:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400627 if opts.regenerate:
Don Garrett747cc4b2015-10-07 14:48:48 -0700628 cros_build_lib.Die('--regenerate may not be used with specific files, '
629 'or breakpad_root')
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400630 else:
631 if opts.board is None:
632 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400633
Don Garretta28be6d2016-06-16 18:09:35 -0700634 # Figure out which crash server to upload too.
635 upload_url = opts.server
636 if not upload_url:
637 if opts.official_build:
638 upload_url = OFFICIAL_UPLOAD_URL
639 else:
640 logging.warning('unofficial builds upload to the staging server')
641 upload_url = STAGING_UPLOAD_URL
642
Mike Nichols90f7c152019-04-09 15:14:08 -0600643 # Set up the API key needed to authenticate to Crash server.
644 # Allow for a local key file for testing purposes.
645 if opts.api_key:
646 api_key_file = opts.api_key
647 else:
648 api_key_file = constants.CRASH_API_KEY
649
650 api_key = osutils.ReadFile(api_key_file)
651
Don Garretta28be6d2016-06-16 18:09:35 -0700652 # Confirm we really want the long upload.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400653 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500654 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400655 Uploading symbols for an entire Chromium OS build is really only
656 necessary for release builds and in a few cases for developers
657 to debug problems. It will take considerable time to run. For
658 developer debugging purposes, consider instead passing specific
659 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500660 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400661 if not cros_build_lib.BooleanPrompt(
662 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500663 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400664 cros_build_lib.Die('better safe than sorry')
665
666 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700667
668 # Regenerate symbols from binaries.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400669 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400670 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
671 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400672
Don Garretta28be6d2016-06-16 18:09:35 -0700673 # Do the upload.
674 ret += UploadSymbols(
675 sym_paths=sym_paths,
676 upload_url=upload_url,
Don Garretta28be6d2016-06-16 18:09:35 -0700677 failed_list=opts.failed_list,
678 upload_limit=opts.upload_limit,
Mike Nichols90f7c152019-04-09 15:14:08 -0600679 strip_cfi=opts.strip_cfi,
680 api_key=api_key)
Don Garretta28be6d2016-06-16 18:09:35 -0700681
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400682 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700683 logging.error('encountered %i problem(s)', ret)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400684 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
685 # return 0 in case we are a multiple of the mask.
Don Garretta28be6d2016-06-16 18:09:35 -0700686 return 1