blob: bb803b3fb02bec72be5b8a05199edbd9f059c4d6 [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
Don Garretta28be6d2016-06-16 18:09:35 -070012import itertools
Mike Nichols90f7c152019-04-09 15:14:08 -060013import json
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040014import os
Mike Frysingerfd355652014-01-23 02:57:48 -050015import socket
Mike Frysingerc5597f22014-11-27 15:39:15 -050016import sys
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040017import tempfile
Mike Frysinger71bf7862021-02-12 07:38:57 -050018import textwrap
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040019import time
20
Mike Frysinger3d465162019-08-28 00:40:23 -040021from six.moves import http_client as httplib
Mike Frysinger3dcacee2019-08-23 17:09:11 -040022from six.moves import urllib
Mike Frysinger6db648e2018-07-24 19:57:58 -040023
Mike Frysingerd41938e2014-02-10 06:37:55 -050024from chromite.lib import cache
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040025from chromite.lib import commandline
Mike Frysinger38002522019-10-13 23:34:35 -040026from chromite.lib import constants
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040027from chromite.lib import cros_build_lib
Ralph Nathan5a582ff2015-03-20 18:18:30 -070028from chromite.lib import cros_logging as logging
Mike Frysingerd41938e2014-02-10 06:37:55 -050029from chromite.lib import gs
30from chromite.lib import osutils
Gilad Arnold83233ed2015-05-08 12:12:13 -070031from chromite.lib import path_util
Don Garrette1f47e92016-10-13 16:04:56 -070032from chromite.lib import retry_stats
Mike Frysinger69cb41d2013-08-11 20:08:19 -040033from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysinger71bf7862021-02-12 07:38:57 -050034from chromite.third_party import requests
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040035
Mike Frysinger2688ef62020-02-16 00:00:46 -050036
Mike Frysinger0c0efa22014-02-09 23:32:23 -050037# Needs to be after chromite imports.
Mike Frysingerc5597f22014-11-27 15:39:15 -050038# We don't want to import the general keyring module as that will implicitly
39# try to import & connect to a dbus server. That's a waste of time.
40sys.modules['keyring'] = None
Mike Frysinger0c0efa22014-02-09 23:32:23 -050041
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040042
43# URLs used for uploading symbols.
Mike Nichols90f7c152019-04-09 15:14:08 -060044OFFICIAL_UPLOAD_URL = 'https://prod-crashsymbolcollector-pa.googleapis.com/v1'
45STAGING_UPLOAD_URL = 'https://staging-crashsymbolcollector-pa.googleapis.com/v1'
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040046
Ian Barkley-Yeung22ba8122020-02-05 15:39:02 -080047# The crash server rejects files that are bigger than 1GB.
48CRASH_SERVER_FILE_LIMIT = 1024 * 1024 * 1024
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040049# Give ourselves a little breathing room from what the server expects.
50DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
51
52
Mike Frysinger0c0efa22014-02-09 23:32:23 -050053# The batch limit when talking to the dedup server. We avoid sending one at a
54# time as the round trip overhead will dominate. Conversely, we avoid sending
55# all at once so we can start uploading symbols asap -- the symbol server is a
56# bit slow and will take longer than anything else.
Mike Frysinger0c0efa22014-02-09 23:32:23 -050057DEDUPE_LIMIT = 100
58
59# How long to wait for the server to respond with the results. Note that the
60# larger the limit above, the larger this will need to be. So we give it ~1
61# second per item max.
62DEDUPE_TIMEOUT = DEDUPE_LIMIT
63
Don Garretta28be6d2016-06-16 18:09:35 -070064# How long to wait for the notification to finish (in seconds).
65DEDUPE_NOTIFY_TIMEOUT = 240
Mike Frysinger4dd462e2014-04-30 16:21:51 -040066
Mike Frysinger71046662014-09-12 18:15:15 -070067# The minimum average rate (in bytes per second) that we expect to maintain
68# when uploading symbols. This has to allow for symbols that are up to
69# CRASH_SERVER_FILE_LIMIT in size.
Mike Frysinger93e8ffa2019-07-03 20:24:18 -040070UPLOAD_MIN_RATE = CRASH_SERVER_FILE_LIMIT // (30 * 60)
Mike Frysinger71046662014-09-12 18:15:15 -070071
72# The lowest timeout (in seconds) we'll allow. If the server is overloaded,
73# then there might be a delay in setting up the connection, not just with the
74# transfer. So even a small file might need a larger value.
Ryo Hashimotoc0049372017-02-16 18:50:00 +090075UPLOAD_MIN_TIMEOUT = 5 * 60
Mike Frysingercd78a082013-06-26 17:13:04 -040076
77
Don Garrett7a793092016-07-06 16:50:27 -070078# Sleep for 500ms in between uploads to avoid DoS'ing symbol server.
79SLEEP_DELAY = 0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040080
81
82# Number of seconds to wait before retrying an upload. The delay will double
83# for each subsequent retry of the same symbol file.
84INITIAL_RETRY_DELAY = 1
85
86# Allow up to 7 attempts to upload a symbol file (total delay may be
87# 1+2+4+8+16+32=63 seconds).
Mike Nichols2a6f86f2020-08-18 15:56:07 -060088MAX_RETRIES = 5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040089
Mike Frysingereb753bf2013-11-22 16:05:35 -050090# Number of total errors, before uploads are no longer attempted.
91# This is used to avoid lots of errors causing unreasonable delays.
Mike Nichols2a6f86f2020-08-18 15:56:07 -060092MAX_TOTAL_ERRORS_FOR_RETRY = 6
Mike Frysingereb753bf2013-11-22 16:05:35 -050093
Don Garrette1f47e92016-10-13 16:04:56 -070094# Category to use for collection upload retry stats.
95UPLOAD_STATS = 'UPLOAD'
96
Don Garretta28be6d2016-06-16 18:09:35 -070097
Don Garretta28be6d2016-06-16 18:09:35 -070098def BatchGenerator(iterator, batch_size):
99 """Given an iterator, break into lists of size batch_size.
Fang Dengba680462015-08-16 20:34:11 -0700100
Don Garretta28be6d2016-06-16 18:09:35 -0700101 The result is a generator, that will only read in as many inputs as needed for
102 the current batch. The final result can be smaller than batch_size.
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700103 """
Don Garretta28be6d2016-06-16 18:09:35 -0700104 batch = []
105 for i in iterator:
106 batch.append(i)
107 if len(batch) >= batch_size:
108 yield batch
109 batch = []
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700110
Don Garretta28be6d2016-06-16 18:09:35 -0700111 if batch:
112 # if there was anything left in the final batch, yield it.
113 yield batch
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500114
115
Mike Frysingerd41938e2014-02-10 06:37:55 -0500116def IsTarball(path):
117 """Guess if this is a tarball based on the filename."""
118 parts = path.split('.')
119 if len(parts) <= 1:
120 return False
121
122 if parts[-1] == 'tar':
123 return True
124
125 if parts[-2] == 'tar':
126 return parts[-1] in ('bz2', 'gz', 'xz')
127
128 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
129
130
Don Garretta28be6d2016-06-16 18:09:35 -0700131class SymbolFile(object):
132 """This class represents the state of a symbol file during processing.
133
Mike Frysinger309f6ab2019-09-22 13:33:54 -0400134 Attributes:
Don Garretta28be6d2016-06-16 18:09:35 -0700135 display_path: Name of symbol file that should be consistent between builds.
136 file_name: Transient path of the symbol file.
137 header: ReadSymsHeader output. Dict with assorted meta-data.
138 status: INITIAL, DUPLICATE, or UPLOADED based on status of processing.
139 dedupe_item: None or instance of DedupeItem for this symbol file.
140 dedupe_push_state: Opaque value to return to dedupe code for file.
141 display_name: Read only friendly (short) file name for logging.
142 file_size: Read only size of the symbol file.
143 """
144 INITIAL = 'initial'
145 DUPLICATE = 'duplicate'
146 UPLOADED = 'uploaded'
Don Garrettdeb2e032016-07-06 16:44:14 -0700147 ERROR = 'error'
Don Garretta28be6d2016-06-16 18:09:35 -0700148
149 def __init__(self, display_path, file_name):
150 """An instance of this class represents a symbol file over time.
151
152 Args:
153 display_path: A unique/persistent between builds name to present to the
154 crash server. It is the file name, relative to where it
155 came from (tarball, breakpad dir, etc).
156 file_name: A the current location of the symbol file.
157 """
158 self.display_path = display_path
159 self.file_name = file_name
160 self.header = cros_generate_breakpad_symbols.ReadSymsHeader(file_name)
161 self.status = SymbolFile.INITIAL
Don Garretta28be6d2016-06-16 18:09:35 -0700162
163 @property
164 def display_name(self):
165 return os.path.basename(self.display_path)
166
167 def FileSize(self):
168 return os.path.getsize(self.file_name)
169
170
Don Garretta28be6d2016-06-16 18:09:35 -0700171def FindSymbolFiles(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500172 """Locate symbol files in |paths|
173
Don Garretta28be6d2016-06-16 18:09:35 -0700174 This returns SymbolFile objects that contain file references which are valid
175 after this exits. Those files may exist externally, or be created in the
176 tempdir (say, when expanding tarballs). The caller must not consider
177 SymbolFile's valid after tempdir is cleaned up.
178
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500179 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700180 tempdir: Path to use for temporary files.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500181 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500182 Dirs are searched for files that end in ".sym". Urls are fetched and then
183 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500184
Don Garretta28be6d2016-06-16 18:09:35 -0700185 Yields:
186 A SymbolFile for every symbol file found in paths.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500187 """
Gilad Arnold83233ed2015-05-08 12:12:13 -0700188 cache_dir = path_util.GetCacheDir()
Mike Frysingere847efd2015-01-08 03:57:24 -0500189 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
190 tar_cache = cache.TarballCache(common_path)
191
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500192 for p in paths:
Mike Frysinger3dcacee2019-08-23 17:09:11 -0400193 o = urllib.parse.urlparse(p)
Mike Frysinger27e21b72018-07-12 14:20:21 -0400194 if o.scheme:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500195 # Support globs of filenames.
196 ctx = gs.GSContext()
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400197 for gspath in ctx.LS(p):
198 logging.info('processing files inside %s', gspath)
199 o = urllib.parse.urlparse(gspath)
Mike Frysinger27e21b72018-07-12 14:20:21 -0400200 key = ('%s%s' % (o.netloc, o.path)).split('/')
Mike Frysingerd41938e2014-02-10 06:37:55 -0500201 # The common cache will not be LRU, removing the need to hold a read
202 # lock on the cached gsutil.
203 ref = tar_cache.Lookup(key)
204 try:
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400205 ref.SetDefault(gspath)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500206 except cros_build_lib.RunCommandError as e:
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400207 logging.warning('ignoring %s\n%s', gspath, e)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500208 continue
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400209 for sym in FindSymbolFiles(tempdir, [ref.path]):
210 yield sym
Mike Frysingerd41938e2014-02-10 06:37:55 -0500211
212 elif os.path.isdir(p):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500213 for root, _, files in os.walk(p):
214 for f in files:
215 if f.endswith('.sym'):
Don Garretta28be6d2016-06-16 18:09:35 -0700216 # If p is '/tmp/foo' and filename is '/tmp/foo/bar/bar.sym',
217 # display_path = 'bar/bar.sym'
218 filename = os.path.join(root, f)
219 yield SymbolFile(display_path=filename[len(p):].lstrip('/'),
220 file_name=filename)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500221
222 elif IsTarball(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700223 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500224 tardir = tempfile.mkdtemp(dir=tempdir)
225 cache.Untar(os.path.realpath(p), tardir)
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400226 for sym in FindSymbolFiles(tardir, [tardir]):
227 yield sym
Mike Frysingerd41938e2014-02-10 06:37:55 -0500228
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500229 else:
Don Garretta28be6d2016-06-16 18:09:35 -0700230 yield SymbolFile(display_path=p, file_name=p)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500231
232
Don Garretta28be6d2016-06-16 18:09:35 -0700233def AdjustSymbolFileSize(symbol, tempdir, file_limit):
234 """Examine symbols files for size problems, and reduce if needed.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500235
Don Garretta28be6d2016-06-16 18:09:35 -0700236 If the symbols size is too big, strip out the call frame info. The CFI
237 is unnecessary for 32bit x86 targets where the frame pointer is used (as
238 all of ours have) and it accounts for over half the size of the symbols
239 uploaded.
240
241 Stripped files will be created inside tempdir, and will be the callers
242 responsibility to clean up.
243
244 We also warn, if a symbols file is still too large after stripping.
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500245
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500246 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700247 symbol: SymbolFile instance to be examined and modified as needed..
248 tempdir: A temporary directory we can create files in that the caller will
249 clean up.
250 file_limit: We only strip files which are larger than this limit.
251
252 Returns:
253 SymbolFile instance (original or modified as needed)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500254 """
Don Garretta28be6d2016-06-16 18:09:35 -0700255 file_size = symbol.FileSize()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500256
Don Garretta28be6d2016-06-16 18:09:35 -0700257 if file_limit and symbol.FileSize() > file_limit:
Mike Frysinger59babdb2019-09-06 06:25:50 -0400258 with cros_build_lib.UnbufferedNamedTemporaryFile(
259 prefix='upload_symbols',
Don Garretta28be6d2016-06-16 18:09:35 -0700260 dir=tempdir, delete=False) as temp_sym_file:
261
Mike Frysingerac75f602019-11-14 23:18:51 -0500262 with open(symbol.file_name, 'rb') as fp:
263 temp_sym_file.writelines(
264 x for x in fp.readlines() if not x.startswith(b'STACK CFI')
265 )
Don Garretta28be6d2016-06-16 18:09:35 -0700266
267 original_file_size = file_size
268 symbol.file_name = temp_sym_file.name
269 file_size = symbol.FileSize()
270
271 logging.warning('stripped CFI for %s reducing size %s > %s',
272 symbol.display_name, original_file_size, file_size)
273
274 # Hopefully the crash server will let it through. But it probably won't.
275 # Not sure what the best answer is in this case.
276 if file_size >= CRASH_SERVER_FILE_LIMIT:
277 logging.PrintBuildbotStepWarnings()
278 logging.warning('upload file %s is awfully large, risking rejection by '
279 'the symbol server (%s > %s)', symbol.display_path,
280 file_size, CRASH_SERVER_FILE_LIMIT)
281
282 return symbol
283
Don Garretta28be6d2016-06-16 18:09:35 -0700284
285def GetUploadTimeout(symbol):
286 """How long to wait for a specific file to upload to the crash server.
287
288 This is a function largely to make unittesting easier.
289
290 Args:
291 symbol: A SymbolFile instance.
292
293 Returns:
294 Timeout length (in seconds)
295 """
296 # Scale the timeout based on the filesize.
Mike Frysinger93e8ffa2019-07-03 20:24:18 -0400297 return max(symbol.FileSize() // UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
Don Garretta28be6d2016-06-16 18:09:35 -0700298
299
Mike Nichols90f7c152019-04-09 15:14:08 -0600300def ExecRequest(operator, url, timeout, api_key, **kwargs):
301 """Makes a web request with default timeout, returning the json result.
Don Garretta28be6d2016-06-16 18:09:35 -0700302
Mike Nichols90f7c152019-04-09 15:14:08 -0600303 This method will raise a requests.exceptions.HTTPError if the status
304 code is not 4XX, 5XX
305
306 Note: If you are using verbose logging it is entirely possible that the
307 subsystem will write your api key to the logs!
308
309 Args:
Mike Nichols649e6a82019-04-23 11:44:48 -0600310 operator: HTTP method.
311 url: Endpoint URL.
312 timeout: HTTP timeout for request.
313 api_key: Authentication key.
Mike Nichols90f7c152019-04-09 15:14:08 -0600314
315 Returns:
316 HTTP response content
317 """
318 resp = requests.request(operator, url,
319 params={'key': api_key},
320 headers={'User-agent': 'chromite.upload_symbols'},
321 timeout=timeout, **kwargs)
322 # Make sure we don't leak secret keys by accident.
323 if resp.status_code > 399:
Mike Frysinger3dcacee2019-08-23 17:09:11 -0400324 resp.url = resp.url.replace(urllib.parse.quote(api_key), 'XX-HIDDEN-XX')
Mike Nicholscf5b7a92019-04-25 12:01:28 -0600325 logging.warning('Url: %s, Status: %s, response: "%s", in: %s',
326 resp.url, resp.status_code, resp.text, resp.elapsed)
327 elif resp.content:
Mike Nichols90f7c152019-04-09 15:14:08 -0600328 return resp.json()
Mike Nicholscf5b7a92019-04-25 12:01:28 -0600329
Mike Nichols90f7c152019-04-09 15:14:08 -0600330 return {}
331
332
Mike Nichols137e82d2019-05-15 18:40:34 -0600333def FindDuplicates(symbols, status_url, api_key, timeout=DEDUPE_TIMEOUT):
334 """Check whether the symbol files have already been uploaded.
Mike Nichols649e6a82019-04-23 11:44:48 -0600335
336 Args:
Mike Nichols137e82d2019-05-15 18:40:34 -0600337 symbols: A iterable of SymbolFiles to be uploaded
Mike Nichols649e6a82019-04-23 11:44:48 -0600338 status_url: The crash URL to validate the file existence.
Mike Nichols649e6a82019-04-23 11:44:48 -0600339 api_key: Authentication key.
Mike Nichols137e82d2019-05-15 18:40:34 -0600340 timeout: HTTP timeout for request.
Mike Nichols649e6a82019-04-23 11:44:48 -0600341
342 Yields:
Mike Nichols137e82d2019-05-15 18:40:34 -0600343 All SymbolFiles from symbols, but duplicates have status updated to
344 DUPLICATE.
Mike Nichols649e6a82019-04-23 11:44:48 -0600345 """
Mike Nichols137e82d2019-05-15 18:40:34 -0600346 for batch in BatchGenerator(symbols, DEDUPE_LIMIT):
347 items = []
348 result = {}
349 for x in batch:
350 items.append({'debug_file': x.header.name,
351 'debug_id': x.header.id.replace('-', '')})
352 symbol_data = {'symbol_ids': items}
353 try:
354 result = ExecRequest('post', '%s/symbols:checkStatuses' %
355 status_url,
356 timeout,
357 api_key=api_key,
358 data=json.dumps(symbol_data))
Mike Frysingerc7d164a2019-11-06 17:09:45 -0500359 except requests.exceptions.RequestException as e:
Mike Nichols137e82d2019-05-15 18:40:34 -0600360 logging.warning('could not identify duplicates: HTTP error: %s', e)
361 for b in batch:
362 b.status = SymbolFile.INITIAL
363 set_match = {'debugId': b.header.id.replace('-', ''),
364 'debugFile': b.header.name}
365 for cs_result in result.get('pairs', []):
Mike Frysingerca227ec2019-07-02 17:59:21 -0400366 if set_match == cs_result.get('symbolId'):
Mike Nichols137e82d2019-05-15 18:40:34 -0600367 if cs_result.get('status') == 'FOUND':
368 logging.debug('Found duplicate: %s', b.display_name)
369 b.status = SymbolFile.DUPLICATE
370 break
371 yield b
372
Mike Nichols649e6a82019-04-23 11:44:48 -0600373
Mike Nichols90f7c152019-04-09 15:14:08 -0600374def UploadSymbolFile(upload_url, symbol, api_key):
Mike Frysinger80de5012019-08-01 14:10:53 -0400375 """Upload a symbol file to the crash server, returning the status result.
Don Garretta28be6d2016-06-16 18:09:35 -0700376
377 Args:
378 upload_url: The crash URL to POST the |sym_file| to
379 symbol: A SymbolFile instance.
Mike Nichols90f7c152019-04-09 15:14:08 -0600380 api_key: Authentication key
Mike Frysinger80de5012019-08-01 14:10:53 -0400381 """
Mike Nichols90f7c152019-04-09 15:14:08 -0600382 timeout = GetUploadTimeout(symbol)
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600383 logging.debug('Executing post to uploads:create: %s', symbol.display_name)
Mike Nichols137e82d2019-05-15 18:40:34 -0600384 upload = ExecRequest('post',
385 '%s/uploads:create' % upload_url, timeout, api_key)
Mike Nichols649e6a82019-04-23 11:44:48 -0600386
Mike Nichols137e82d2019-05-15 18:40:34 -0600387 if upload and 'uploadUrl' in upload.keys():
388 symbol_data = {'symbol_id':
389 {'debug_file': symbol.header.name,
390 'debug_id': symbol.header.id.replace('-', '')}
391 }
Mike Frysingerac75f602019-11-14 23:18:51 -0500392 with open(symbol.file_name, 'r') as fp:
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600393 logging.debug('Executing put to uploadUrl: %s', symbol.display_name)
Mike Frysingerac75f602019-11-14 23:18:51 -0500394 ExecRequest('put',
395 upload['uploadUrl'], timeout,
396 api_key=api_key,
Mike Nicholsb99f86c2020-02-24 18:36:16 -0700397 data=fp.read())
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600398 logging.debug('Executing post to uploads/complete: %s', symbol.display_name)
Mike Nichols137e82d2019-05-15 18:40:34 -0600399 ExecRequest('post',
400 '%s/uploads/%s:complete' % (
401 upload_url, upload['uploadKey']),
402 timeout, api_key=api_key,
403 # TODO(mikenichols): Validate product_name once it is added
404 # to the proto; currently unsupported.
405 data=json.dumps(symbol_data))
406 else:
407 raise requests.exceptions.HTTPError
Don Garretta28be6d2016-06-16 18:09:35 -0700408
409
Mike Nichols90f7c152019-04-09 15:14:08 -0600410def PerformSymbolsFileUpload(symbols, upload_url, api_key):
Don Garretta28be6d2016-06-16 18:09:35 -0700411 """Upload the symbols to the crash server
412
413 Args:
Don Garrettdeb2e032016-07-06 16:44:14 -0700414 symbols: An iterable of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700415 upload_url: URL of crash server to upload too.
Mike Nichols90f7c152019-04-09 15:14:08 -0600416 api_key: Authentication key.
Don Garretta28be6d2016-06-16 18:09:35 -0700417 failures: Tracker for total upload failures.
Don Garretta28be6d2016-06-16 18:09:35 -0700418
Don Garrettdeb2e032016-07-06 16:44:14 -0700419 Yields:
420 Each symbol from symbols, perhaps modified.
Don Garretta28be6d2016-06-16 18:09:35 -0700421 """
Don Garrettdeb2e032016-07-06 16:44:14 -0700422 failures = 0
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600423 # Failures are figured per request, therefore each HTTP failure
424 # counts against the max. This means that the process will exit
425 # at the point when HTTP errors hit the defined limit.
Don Garrettdeb2e032016-07-06 16:44:14 -0700426 for s in symbols:
427 if (failures < MAX_TOTAL_ERRORS_FOR_RETRY and
428 s.status in (SymbolFile.INITIAL, SymbolFile.ERROR)):
429 # Keeps us from DoS-ing the symbol server.
430 time.sleep(SLEEP_DELAY)
431 logging.info('Uploading symbol_file: %s', s.display_path)
432 try:
433 # This command retries the upload multiple times with growing delays. We
434 # only consider the upload a failure if these retries fail.
Don Garrette1f47e92016-10-13 16:04:56 -0700435 def ShouldRetryUpload(exception):
Mike Nichols0abb8d32019-04-26 14:55:08 -0600436 if isinstance(exception, (requests.exceptions.RequestException,
Mike Frysinger3dcacee2019-08-23 17:09:11 -0400437 IOError,
Mike Nichols0abb8d32019-04-26 14:55:08 -0600438 httplib.HTTPException, socket.error)):
439 logging.info('Request failed, retrying: %s', exception)
440 return True
441 return False
Don Garrette1f47e92016-10-13 16:04:56 -0700442
Don Garrett440944e2016-10-03 16:33:31 -0700443 with cros_build_lib.TimedSection() as timer:
Don Garrette1f47e92016-10-13 16:04:56 -0700444 retry_stats.RetryWithStats(
445 UPLOAD_STATS, ShouldRetryUpload, MAX_RETRIES,
Don Garrett440944e2016-10-03 16:33:31 -0700446 UploadSymbolFile,
Mike Nichols90f7c152019-04-09 15:14:08 -0600447 upload_url, s, api_key,
Luigi Semenzato5104f3c2016-10-12 12:37:42 -0700448 sleep=INITIAL_RETRY_DELAY,
449 log_all_retries=True)
Mike Nicholsb03b2c62019-05-02 18:46:04 -0600450 if s.status != SymbolFile.DUPLICATE:
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600451 logging.info('upload of %s with size %10i bytes took %s',
452 s.display_name, s.FileSize(), timer.delta)
Mike Nicholsb03b2c62019-05-02 18:46:04 -0600453 s.status = SymbolFile.UPLOADED
Mike Frysingerc7d164a2019-11-06 17:09:45 -0500454 except requests.exceptions.RequestException as e:
Mike Nichols90f7c152019-04-09 15:14:08 -0600455 logging.warning('could not upload: %s: HTTP error: %s',
456 s.display_name, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700457 s.status = SymbolFile.ERROR
458 failures += 1
Mike Frysingerb32cc472020-05-15 00:17:54 -0400459 except (httplib.HTTPException, OSError) as e:
Ryo Hashimoto45273f02017-03-16 17:45:56 +0900460 logging.warning('could not upload: %s: %s %s', s.display_name,
461 type(e).__name__, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700462 s.status = SymbolFile.ERROR
463 failures += 1
464
465 # We pass the symbol along, on both success and failure.
466 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700467
468
Don Garrettdeb2e032016-07-06 16:44:14 -0700469def ReportResults(symbols, failed_list):
Don Garretta28be6d2016-06-16 18:09:35 -0700470 """Log a summary of the symbol uploading.
471
472 This has the side effect of fully consuming the symbols iterator.
473
474 Args:
475 symbols: An iterator of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700476 failed_list: A filename at which to write out a list of our failed uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700477
478 Returns:
479 The number of symbols not uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700480 """
481 upload_failures = []
482 result_counts = {
483 SymbolFile.INITIAL: 0,
484 SymbolFile.UPLOADED: 0,
485 SymbolFile.DUPLICATE: 0,
Don Garrettdeb2e032016-07-06 16:44:14 -0700486 SymbolFile.ERROR: 0,
Don Garretta28be6d2016-06-16 18:09:35 -0700487 }
488
489 for s in symbols:
490 result_counts[s.status] += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700491 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700492 upload_failures.append(s)
493
Don Garrette1f47e92016-10-13 16:04:56 -0700494 # Report retry numbers.
495 _, _, retries = retry_stats.CategoryStats(UPLOAD_STATS)
496 if retries:
497 logging.warning('%d upload retries performed.', retries)
498
Don Garretta28be6d2016-06-16 18:09:35 -0700499 logging.info('Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.',
500 result_counts)
501
Chris Ching91908032016-09-27 16:55:33 -0600502 if result_counts[SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700503 logging.PrintBuildbotStepWarnings()
Chris Ching91908032016-09-27 16:55:33 -0600504 logging.warning('%d non-recoverable upload errors',
505 result_counts[SymbolFile.ERROR])
506
507 if result_counts[SymbolFile.INITIAL]:
508 logging.PrintBuildbotStepWarnings()
509 logging.warning('%d upload(s) were skipped because of excessive errors',
Don Garrettdeb2e032016-07-06 16:44:14 -0700510 result_counts[SymbolFile.INITIAL])
Don Garretta28be6d2016-06-16 18:09:35 -0700511
512 if failed_list is not None:
513 with open(failed_list, 'w') as fl:
514 for s in upload_failures:
515 fl.write('%s\n' % s.display_path)
516
Don Garrettdeb2e032016-07-06 16:44:14 -0700517 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
518
Don Garretta28be6d2016-06-16 18:09:35 -0700519
Mike Nichols649e6a82019-04-23 11:44:48 -0600520def UploadSymbols(sym_paths, upload_url, failed_list=None,
Mike Nichols137e82d2019-05-15 18:40:34 -0600521 upload_limit=None, strip_cfi=None, timeout=None,
Mike Nichols90f7c152019-04-09 15:14:08 -0600522 api_key=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400523 """Upload all the generated symbols for |board| to the crash server
524
525 Args:
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500526 sym_paths: Specific symbol files (or dirs of sym files) to upload,
527 otherwise search |breakpad_dir|
Don Garretta28be6d2016-06-16 18:09:35 -0700528 upload_url: URL of crash server to upload too.
Don Garretta28be6d2016-06-16 18:09:35 -0700529 failed_list: A filename at which to write out a list of our failed uploads.
530 upload_limit: Integer listing how many files to upload. None for no limit.
531 strip_cfi: File size at which we strip out CFI data. None for no limit.
Mike Nichols137e82d2019-05-15 18:40:34 -0600532 timeout: HTTP timeout for request.
Mike Nichols90f7c152019-04-09 15:14:08 -0600533 api_key: A string based authentication key
Mike Frysinger1a736a82013-12-12 01:50:59 -0500534
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400535 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400536 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400537 """
Don Garrette1f47e92016-10-13 16:04:56 -0700538 retry_stats.SetupStats()
539
Don Garretta28be6d2016-06-16 18:09:35 -0700540 # Note: This method looks like each step of processing is performed
541 # sequentially for all SymbolFiles, but instead each step is a generator that
542 # produces the next iteration only when it's read. This means that (except for
543 # some batching) each SymbolFile goes through all of these steps before the
544 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400545
Don Garretta28be6d2016-06-16 18:09:35 -0700546 # This is used to hold striped
547 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
548 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500549
Don Garrett1bc1e102016-07-06 17:06:10 -0700550 # Sort all of our symbols so the largest ones (probably the most important)
551 # are processed first.
552 symbols = list(symbols)
553 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
554
Don Garretta28be6d2016-06-16 18:09:35 -0700555 if upload_limit is not None:
556 # Restrict symbols processed to the limit.
557 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400558
Don Garretta28be6d2016-06-16 18:09:35 -0700559 # Strip CFI, if needed.
560 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500561
Mike Nichols137e82d2019-05-15 18:40:34 -0600562 # Find duplicates via batch API
563 symbols = FindDuplicates(symbols, upload_url, api_key, timeout)
564
Don Garretta28be6d2016-06-16 18:09:35 -0700565 # Perform uploads
Mike Nichols90f7c152019-04-09 15:14:08 -0600566 symbols = PerformSymbolsFileUpload(symbols, upload_url, api_key)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400567
Don Garretta28be6d2016-06-16 18:09:35 -0700568 # Log the final results, and consume the symbols generator fully.
Don Garrettdeb2e032016-07-06 16:44:14 -0700569 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500570
Don Garrettdeb2e032016-07-06 16:44:14 -0700571 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400572
573
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400574def main(argv):
575 parser = commandline.ArgumentParser(description=__doc__)
576
Don Garretta28be6d2016-06-16 18:09:35 -0700577 # TODO: Make sym_paths, breakpad_root, and root exclusive.
578
Mike Frysingerd41938e2014-02-10 06:37:55 -0500579 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
580 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400581 parser.add_argument('--board', default=None,
Don Garrett747cc4b2015-10-07 14:48:48 -0700582 help='Used to find default breakpad_root.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400583 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500584 help='full path to the breakpad symbol directory')
585 parser.add_argument('--root', type='path', default=None,
586 help='full path to the chroot dir')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400587 parser.add_argument('--official_build', action='store_true', default=False,
588 help='point to official symbol server')
Mike Frysinger38647542014-09-12 18:15:39 -0700589 parser.add_argument('--server', type=str, default=None,
590 help='URI for custom symbol server')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400591 parser.add_argument('--regenerate', action='store_true', default=False,
592 help='regenerate all symbols')
Don Garretta28be6d2016-06-16 18:09:35 -0700593 parser.add_argument('--upload-limit', type=int,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400594 help='only upload # number of symbols')
595 parser.add_argument('--strip_cfi', type=int,
Don Garretta28be6d2016-06-16 18:09:35 -0700596 default=DEFAULT_FILE_LIMIT,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400597 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500598 parser.add_argument('--failed-list', type='path',
599 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500600 parser.add_argument('--dedupe', action='store_true', default=False,
601 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400602 parser.add_argument('--yes', action='store_true', default=False,
603 help='answer yes to all prompts')
Don Garrett747cc4b2015-10-07 14:48:48 -0700604 parser.add_argument('--product_name', type=str, default='ChromeOS',
605 help='Produce Name for breakpad stats.')
Mike Nichols90f7c152019-04-09 15:14:08 -0600606 parser.add_argument('--api_key', type=str, default=None,
607 help='full path to the API key file')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400608
609 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500610 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400611
Don Garretta28be6d2016-06-16 18:09:35 -0700612 # Figure out the symbol files/directories to upload.
613 if opts.sym_paths:
614 sym_paths = opts.sym_paths
615 elif opts.breakpad_root:
616 sym_paths = [opts.breakpad_root]
617 elif opts.root:
618 if not opts.board:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400619 cros_build_lib.Die('--board must be set if --root is used.')
Don Garretta28be6d2016-06-16 18:09:35 -0700620 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(opts.board)
621 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip('/'))]
622 else:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400623 cros_build_lib.Die('--sym_paths, --breakpad_root, or --root must be set.')
Don Garretta28be6d2016-06-16 18:09:35 -0700624
Don Garrett747cc4b2015-10-07 14:48:48 -0700625 if opts.sym_paths or opts.breakpad_root:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400626 if opts.regenerate:
Don Garrett747cc4b2015-10-07 14:48:48 -0700627 cros_build_lib.Die('--regenerate may not be used with specific files, '
628 'or breakpad_root')
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400629 else:
630 if opts.board is None:
631 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400632
Don Garretta28be6d2016-06-16 18:09:35 -0700633 # Figure out which crash server to upload too.
634 upload_url = opts.server
635 if not upload_url:
636 if opts.official_build:
637 upload_url = OFFICIAL_UPLOAD_URL
638 else:
639 logging.warning('unofficial builds upload to the staging server')
640 upload_url = STAGING_UPLOAD_URL
641
Mike Nichols90f7c152019-04-09 15:14:08 -0600642 # Set up the API key needed to authenticate to Crash server.
643 # Allow for a local key file for testing purposes.
644 if opts.api_key:
645 api_key_file = opts.api_key
646 else:
647 api_key_file = constants.CRASH_API_KEY
648
649 api_key = osutils.ReadFile(api_key_file)
650
Don Garretta28be6d2016-06-16 18:09:35 -0700651 # Confirm we really want the long upload.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400652 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500653 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400654 Uploading symbols for an entire Chromium OS build is really only
655 necessary for release builds and in a few cases for developers
656 to debug problems. It will take considerable time to run. For
657 developer debugging purposes, consider instead passing specific
658 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500659 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400660 if not cros_build_lib.BooleanPrompt(
661 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500662 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400663 cros_build_lib.Die('better safe than sorry')
664
665 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700666
667 # Regenerate symbols from binaries.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400668 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400669 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
670 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400671
Don Garretta28be6d2016-06-16 18:09:35 -0700672 # Do the upload.
673 ret += UploadSymbols(
674 sym_paths=sym_paths,
675 upload_url=upload_url,
Don Garretta28be6d2016-06-16 18:09:35 -0700676 failed_list=opts.failed_list,
677 upload_limit=opts.upload_limit,
Mike Nichols90f7c152019-04-09 15:14:08 -0600678 strip_cfi=opts.strip_cfi,
679 api_key=api_key)
Don Garretta28be6d2016-06-16 18:09:35 -0700680
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400681 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700682 logging.error('encountered %i problem(s)', ret)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400683 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
684 # return 0 in case we are a multiple of the mask.
Don Garretta28be6d2016-06-16 18:09:35 -0700685 return 1