blob: 4d52e88667108c47a54644fe8925bdd43647e246 [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -04002# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Upload all debug symbols required for crash reporting purposes.
7
8This script need only be used to upload release builds symbols or to debug
9crashes on non-release builds (in which case try to only upload the symbols
Mike Frysinger02e1e072013-11-10 22:11:34 -050010for those executables involved).
11"""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040012
Mike Frysingera4fa1e82014-01-15 01:45:56 -050013from __future__ import print_function
14
Don Garretta28be6d2016-06-16 18:09:35 -070015import itertools
Mike Nichols90f7c152019-04-09 15:14:08 -060016import json
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040017import os
Mike Frysingerfd355652014-01-23 02:57:48 -050018import socket
Mike Frysingerc5597f22014-11-27 15:39:15 -050019import sys
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040020import tempfile
Mike Frysinger71bf7862021-02-12 07:38:57 -050021import textwrap
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040022import time
23
Mike Frysinger3d465162019-08-28 00:40:23 -040024from six.moves import http_client as httplib
Mike Frysinger3dcacee2019-08-23 17:09:11 -040025from six.moves import urllib
Mike Frysinger6db648e2018-07-24 19:57:58 -040026
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
Ralph Nathan5a582ff2015-03-20 18:18:30 -070031from chromite.lib import cros_logging as logging
Mike Frysingerd41938e2014-02-10 06:37:55 -050032from chromite.lib import gs
33from chromite.lib import osutils
Gilad Arnold83233ed2015-05-08 12:12:13 -070034from chromite.lib import path_util
Don Garrette1f47e92016-10-13 16:04:56 -070035from chromite.lib import retry_stats
Mike Frysinger69cb41d2013-08-11 20:08:19 -040036from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysinger71bf7862021-02-12 07:38:57 -050037from chromite.third_party import requests
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040038
Mike Frysinger2688ef62020-02-16 00:00:46 -050039
40assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
41
42
Mike Frysinger0c0efa22014-02-09 23:32:23 -050043# Needs to be after chromite imports.
Mike Frysingerc5597f22014-11-27 15:39:15 -050044# We don't want to import the general keyring module as that will implicitly
45# try to import & connect to a dbus server. That's a waste of time.
46sys.modules['keyring'] = None
Mike Frysinger0c0efa22014-02-09 23:32:23 -050047
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040048
49# URLs used for uploading symbols.
Mike Nichols90f7c152019-04-09 15:14:08 -060050OFFICIAL_UPLOAD_URL = 'https://prod-crashsymbolcollector-pa.googleapis.com/v1'
51STAGING_UPLOAD_URL = 'https://staging-crashsymbolcollector-pa.googleapis.com/v1'
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040052
Ian Barkley-Yeung22ba8122020-02-05 15:39:02 -080053# The crash server rejects files that are bigger than 1GB.
54CRASH_SERVER_FILE_LIMIT = 1024 * 1024 * 1024
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040055# Give ourselves a little breathing room from what the server expects.
56DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
57
58
Mike Frysinger0c0efa22014-02-09 23:32:23 -050059# The batch limit when talking to the dedup server. We avoid sending one at a
60# time as the round trip overhead will dominate. Conversely, we avoid sending
61# all at once so we can start uploading symbols asap -- the symbol server is a
62# bit slow and will take longer than anything else.
Mike Frysinger0c0efa22014-02-09 23:32:23 -050063DEDUPE_LIMIT = 100
64
65# How long to wait for the server to respond with the results. Note that the
66# larger the limit above, the larger this will need to be. So we give it ~1
67# second per item max.
68DEDUPE_TIMEOUT = DEDUPE_LIMIT
69
Don Garretta28be6d2016-06-16 18:09:35 -070070# How long to wait for the notification to finish (in seconds).
71DEDUPE_NOTIFY_TIMEOUT = 240
Mike Frysinger4dd462e2014-04-30 16:21:51 -040072
Mike Frysinger71046662014-09-12 18:15:15 -070073# The minimum average rate (in bytes per second) that we expect to maintain
74# when uploading symbols. This has to allow for symbols that are up to
75# CRASH_SERVER_FILE_LIMIT in size.
Mike Frysinger93e8ffa2019-07-03 20:24:18 -040076UPLOAD_MIN_RATE = CRASH_SERVER_FILE_LIMIT // (30 * 60)
Mike Frysinger71046662014-09-12 18:15:15 -070077
78# The lowest timeout (in seconds) we'll allow. If the server is overloaded,
79# then there might be a delay in setting up the connection, not just with the
80# transfer. So even a small file might need a larger value.
Ryo Hashimotoc0049372017-02-16 18:50:00 +090081UPLOAD_MIN_TIMEOUT = 5 * 60
Mike Frysingercd78a082013-06-26 17:13:04 -040082
83
Don Garrett7a793092016-07-06 16:50:27 -070084# Sleep for 500ms in between uploads to avoid DoS'ing symbol server.
85SLEEP_DELAY = 0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040086
87
88# Number of seconds to wait before retrying an upload. The delay will double
89# for each subsequent retry of the same symbol file.
90INITIAL_RETRY_DELAY = 1
91
92# Allow up to 7 attempts to upload a symbol file (total delay may be
93# 1+2+4+8+16+32=63 seconds).
Mike Nichols2a6f86f2020-08-18 15:56:07 -060094MAX_RETRIES = 5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040095
Mike Frysingereb753bf2013-11-22 16:05:35 -050096# Number of total errors, before uploads are no longer attempted.
97# This is used to avoid lots of errors causing unreasonable delays.
Mike Nichols2a6f86f2020-08-18 15:56:07 -060098MAX_TOTAL_ERRORS_FOR_RETRY = 6
Mike Frysingereb753bf2013-11-22 16:05:35 -050099
Don Garrette1f47e92016-10-13 16:04:56 -0700100# Category to use for collection upload retry stats.
101UPLOAD_STATS = 'UPLOAD'
102
Don Garretta28be6d2016-06-16 18:09:35 -0700103
Don Garretta28be6d2016-06-16 18:09:35 -0700104def BatchGenerator(iterator, batch_size):
105 """Given an iterator, break into lists of size batch_size.
Fang Dengba680462015-08-16 20:34:11 -0700106
Don Garretta28be6d2016-06-16 18:09:35 -0700107 The result is a generator, that will only read in as many inputs as needed for
108 the current batch. The final result can be smaller than batch_size.
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700109 """
Don Garretta28be6d2016-06-16 18:09:35 -0700110 batch = []
111 for i in iterator:
112 batch.append(i)
113 if len(batch) >= batch_size:
114 yield batch
115 batch = []
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700116
Don Garretta28be6d2016-06-16 18:09:35 -0700117 if batch:
118 # if there was anything left in the final batch, yield it.
119 yield batch
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500120
121
Mike Frysingerd41938e2014-02-10 06:37:55 -0500122def IsTarball(path):
123 """Guess if this is a tarball based on the filename."""
124 parts = path.split('.')
125 if len(parts) <= 1:
126 return False
127
128 if parts[-1] == 'tar':
129 return True
130
131 if parts[-2] == 'tar':
132 return parts[-1] in ('bz2', 'gz', 'xz')
133
134 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
135
136
Don Garretta28be6d2016-06-16 18:09:35 -0700137class SymbolFile(object):
138 """This class represents the state of a symbol file during processing.
139
Mike Frysinger309f6ab2019-09-22 13:33:54 -0400140 Attributes:
Don Garretta28be6d2016-06-16 18:09:35 -0700141 display_path: Name of symbol file that should be consistent between builds.
142 file_name: Transient path of the symbol file.
143 header: ReadSymsHeader output. Dict with assorted meta-data.
144 status: INITIAL, DUPLICATE, or UPLOADED based on status of processing.
145 dedupe_item: None or instance of DedupeItem for this symbol file.
146 dedupe_push_state: Opaque value to return to dedupe code for file.
147 display_name: Read only friendly (short) file name for logging.
148 file_size: Read only size of the symbol file.
149 """
150 INITIAL = 'initial'
151 DUPLICATE = 'duplicate'
152 UPLOADED = 'uploaded'
Don Garrettdeb2e032016-07-06 16:44:14 -0700153 ERROR = 'error'
Don Garretta28be6d2016-06-16 18:09:35 -0700154
155 def __init__(self, display_path, file_name):
156 """An instance of this class represents a symbol file over time.
157
158 Args:
159 display_path: A unique/persistent between builds name to present to the
160 crash server. It is the file name, relative to where it
161 came from (tarball, breakpad dir, etc).
162 file_name: A the current location of the symbol file.
163 """
164 self.display_path = display_path
165 self.file_name = file_name
166 self.header = cros_generate_breakpad_symbols.ReadSymsHeader(file_name)
167 self.status = SymbolFile.INITIAL
Don Garretta28be6d2016-06-16 18:09:35 -0700168
169 @property
170 def display_name(self):
171 return os.path.basename(self.display_path)
172
173 def FileSize(self):
174 return os.path.getsize(self.file_name)
175
176
Don Garretta28be6d2016-06-16 18:09:35 -0700177def FindSymbolFiles(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500178 """Locate symbol files in |paths|
179
Don Garretta28be6d2016-06-16 18:09:35 -0700180 This returns SymbolFile objects that contain file references which are valid
181 after this exits. Those files may exist externally, or be created in the
182 tempdir (say, when expanding tarballs). The caller must not consider
183 SymbolFile's valid after tempdir is cleaned up.
184
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500185 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700186 tempdir: Path to use for temporary files.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500187 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500188 Dirs are searched for files that end in ".sym". Urls are fetched and then
189 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500190
Don Garretta28be6d2016-06-16 18:09:35 -0700191 Yields:
192 A SymbolFile for every symbol file found in paths.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500193 """
Gilad Arnold83233ed2015-05-08 12:12:13 -0700194 cache_dir = path_util.GetCacheDir()
Mike Frysingere847efd2015-01-08 03:57:24 -0500195 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
196 tar_cache = cache.TarballCache(common_path)
197
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500198 for p in paths:
Mike Frysinger3dcacee2019-08-23 17:09:11 -0400199 o = urllib.parse.urlparse(p)
Mike Frysinger27e21b72018-07-12 14:20:21 -0400200 if o.scheme:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500201 # Support globs of filenames.
202 ctx = gs.GSContext()
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400203 for gspath in ctx.LS(p):
204 logging.info('processing files inside %s', gspath)
205 o = urllib.parse.urlparse(gspath)
Mike Frysinger27e21b72018-07-12 14:20:21 -0400206 key = ('%s%s' % (o.netloc, o.path)).split('/')
Mike Frysingerd41938e2014-02-10 06:37:55 -0500207 # The common cache will not be LRU, removing the need to hold a read
208 # lock on the cached gsutil.
209 ref = tar_cache.Lookup(key)
210 try:
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400211 ref.SetDefault(gspath)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500212 except cros_build_lib.RunCommandError as e:
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400213 logging.warning('ignoring %s\n%s', gspath, e)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500214 continue
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400215 for sym in FindSymbolFiles(tempdir, [ref.path]):
216 yield sym
Mike Frysingerd41938e2014-02-10 06:37:55 -0500217
218 elif os.path.isdir(p):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500219 for root, _, files in os.walk(p):
220 for f in files:
221 if f.endswith('.sym'):
Don Garretta28be6d2016-06-16 18:09:35 -0700222 # If p is '/tmp/foo' and filename is '/tmp/foo/bar/bar.sym',
223 # display_path = 'bar/bar.sym'
224 filename = os.path.join(root, f)
225 yield SymbolFile(display_path=filename[len(p):].lstrip('/'),
226 file_name=filename)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500227
228 elif IsTarball(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700229 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500230 tardir = tempfile.mkdtemp(dir=tempdir)
231 cache.Untar(os.path.realpath(p), tardir)
Mike Frysinger8ab15bb2019-09-18 17:24:36 -0400232 for sym in FindSymbolFiles(tardir, [tardir]):
233 yield sym
Mike Frysingerd41938e2014-02-10 06:37:55 -0500234
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500235 else:
Don Garretta28be6d2016-06-16 18:09:35 -0700236 yield SymbolFile(display_path=p, file_name=p)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500237
238
Don Garretta28be6d2016-06-16 18:09:35 -0700239def AdjustSymbolFileSize(symbol, tempdir, file_limit):
240 """Examine symbols files for size problems, and reduce if needed.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500241
Don Garretta28be6d2016-06-16 18:09:35 -0700242 If the symbols size is too big, strip out the call frame info. The CFI
243 is unnecessary for 32bit x86 targets where the frame pointer is used (as
244 all of ours have) and it accounts for over half the size of the symbols
245 uploaded.
246
247 Stripped files will be created inside tempdir, and will be the callers
248 responsibility to clean up.
249
250 We also warn, if a symbols file is still too large after stripping.
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500251
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500252 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700253 symbol: SymbolFile instance to be examined and modified as needed..
254 tempdir: A temporary directory we can create files in that the caller will
255 clean up.
256 file_limit: We only strip files which are larger than this limit.
257
258 Returns:
259 SymbolFile instance (original or modified as needed)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500260 """
Don Garretta28be6d2016-06-16 18:09:35 -0700261 file_size = symbol.FileSize()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500262
Don Garretta28be6d2016-06-16 18:09:35 -0700263 if file_limit and symbol.FileSize() > file_limit:
Mike Frysinger59babdb2019-09-06 06:25:50 -0400264 with cros_build_lib.UnbufferedNamedTemporaryFile(
265 prefix='upload_symbols',
Don Garretta28be6d2016-06-16 18:09:35 -0700266 dir=tempdir, delete=False) as temp_sym_file:
267
Mike Frysingerac75f602019-11-14 23:18:51 -0500268 with open(symbol.file_name, 'rb') as fp:
269 temp_sym_file.writelines(
270 x for x in fp.readlines() if not x.startswith(b'STACK CFI')
271 )
Don Garretta28be6d2016-06-16 18:09:35 -0700272
273 original_file_size = file_size
274 symbol.file_name = temp_sym_file.name
275 file_size = symbol.FileSize()
276
277 logging.warning('stripped CFI for %s reducing size %s > %s',
278 symbol.display_name, original_file_size, file_size)
279
280 # Hopefully the crash server will let it through. But it probably won't.
281 # Not sure what the best answer is in this case.
282 if file_size >= CRASH_SERVER_FILE_LIMIT:
283 logging.PrintBuildbotStepWarnings()
284 logging.warning('upload file %s is awfully large, risking rejection by '
285 'the symbol server (%s > %s)', symbol.display_path,
286 file_size, CRASH_SERVER_FILE_LIMIT)
287
288 return symbol
289
Don Garretta28be6d2016-06-16 18:09:35 -0700290
291def GetUploadTimeout(symbol):
292 """How long to wait for a specific file to upload to the crash server.
293
294 This is a function largely to make unittesting easier.
295
296 Args:
297 symbol: A SymbolFile instance.
298
299 Returns:
300 Timeout length (in seconds)
301 """
302 # Scale the timeout based on the filesize.
Mike Frysinger93e8ffa2019-07-03 20:24:18 -0400303 return max(symbol.FileSize() // UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
Don Garretta28be6d2016-06-16 18:09:35 -0700304
305
Mike Nichols90f7c152019-04-09 15:14:08 -0600306def ExecRequest(operator, url, timeout, api_key, **kwargs):
307 """Makes a web request with default timeout, returning the json result.
Don Garretta28be6d2016-06-16 18:09:35 -0700308
Mike Nichols90f7c152019-04-09 15:14:08 -0600309 This method will raise a requests.exceptions.HTTPError if the status
310 code is not 4XX, 5XX
311
312 Note: If you are using verbose logging it is entirely possible that the
313 subsystem will write your api key to the logs!
314
315 Args:
Mike Nichols649e6a82019-04-23 11:44:48 -0600316 operator: HTTP method.
317 url: Endpoint URL.
318 timeout: HTTP timeout for request.
319 api_key: Authentication key.
Mike Nichols90f7c152019-04-09 15:14:08 -0600320
321 Returns:
322 HTTP response content
323 """
324 resp = requests.request(operator, url,
325 params={'key': api_key},
326 headers={'User-agent': 'chromite.upload_symbols'},
327 timeout=timeout, **kwargs)
328 # Make sure we don't leak secret keys by accident.
329 if resp.status_code > 399:
Mike Frysinger3dcacee2019-08-23 17:09:11 -0400330 resp.url = resp.url.replace(urllib.parse.quote(api_key), 'XX-HIDDEN-XX')
Mike Nicholscf5b7a92019-04-25 12:01:28 -0600331 logging.warning('Url: %s, Status: %s, response: "%s", in: %s',
332 resp.url, resp.status_code, resp.text, resp.elapsed)
333 elif resp.content:
Mike Nichols90f7c152019-04-09 15:14:08 -0600334 return resp.json()
Mike Nicholscf5b7a92019-04-25 12:01:28 -0600335
Mike Nichols90f7c152019-04-09 15:14:08 -0600336 return {}
337
338
Mike Nichols137e82d2019-05-15 18:40:34 -0600339def FindDuplicates(symbols, status_url, api_key, timeout=DEDUPE_TIMEOUT):
340 """Check whether the symbol files have already been uploaded.
Mike Nichols649e6a82019-04-23 11:44:48 -0600341
342 Args:
Mike Nichols137e82d2019-05-15 18:40:34 -0600343 symbols: A iterable of SymbolFiles to be uploaded
Mike Nichols649e6a82019-04-23 11:44:48 -0600344 status_url: The crash URL to validate the file existence.
Mike Nichols649e6a82019-04-23 11:44:48 -0600345 api_key: Authentication key.
Mike Nichols137e82d2019-05-15 18:40:34 -0600346 timeout: HTTP timeout for request.
Mike Nichols649e6a82019-04-23 11:44:48 -0600347
348 Yields:
Mike Nichols137e82d2019-05-15 18:40:34 -0600349 All SymbolFiles from symbols, but duplicates have status updated to
350 DUPLICATE.
Mike Nichols649e6a82019-04-23 11:44:48 -0600351 """
Mike Nichols137e82d2019-05-15 18:40:34 -0600352 for batch in BatchGenerator(symbols, DEDUPE_LIMIT):
353 items = []
354 result = {}
355 for x in batch:
356 items.append({'debug_file': x.header.name,
357 'debug_id': x.header.id.replace('-', '')})
358 symbol_data = {'symbol_ids': items}
359 try:
360 result = ExecRequest('post', '%s/symbols:checkStatuses' %
361 status_url,
362 timeout,
363 api_key=api_key,
364 data=json.dumps(symbol_data))
Mike Frysingerc7d164a2019-11-06 17:09:45 -0500365 except requests.exceptions.RequestException as e:
Mike Nichols137e82d2019-05-15 18:40:34 -0600366 logging.warning('could not identify duplicates: HTTP error: %s', e)
367 for b in batch:
368 b.status = SymbolFile.INITIAL
369 set_match = {'debugId': b.header.id.replace('-', ''),
370 'debugFile': b.header.name}
371 for cs_result in result.get('pairs', []):
Mike Frysingerca227ec2019-07-02 17:59:21 -0400372 if set_match == cs_result.get('symbolId'):
Mike Nichols137e82d2019-05-15 18:40:34 -0600373 if cs_result.get('status') == 'FOUND':
374 logging.debug('Found duplicate: %s', b.display_name)
375 b.status = SymbolFile.DUPLICATE
376 break
377 yield b
378
Mike Nichols649e6a82019-04-23 11:44:48 -0600379
Mike Nichols90f7c152019-04-09 15:14:08 -0600380def UploadSymbolFile(upload_url, symbol, api_key):
Mike Frysinger80de5012019-08-01 14:10:53 -0400381 """Upload a symbol file to the crash server, returning the status result.
Don Garretta28be6d2016-06-16 18:09:35 -0700382
383 Args:
384 upload_url: The crash URL to POST the |sym_file| to
385 symbol: A SymbolFile instance.
Mike Nichols90f7c152019-04-09 15:14:08 -0600386 api_key: Authentication key
Mike Frysinger80de5012019-08-01 14:10:53 -0400387 """
Mike Nichols90f7c152019-04-09 15:14:08 -0600388 timeout = GetUploadTimeout(symbol)
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600389 logging.debug('Executing post to uploads:create: %s', symbol.display_name)
Mike Nichols137e82d2019-05-15 18:40:34 -0600390 upload = ExecRequest('post',
391 '%s/uploads:create' % upload_url, timeout, api_key)
Mike Nichols649e6a82019-04-23 11:44:48 -0600392
Mike Nichols137e82d2019-05-15 18:40:34 -0600393 if upload and 'uploadUrl' in upload.keys():
394 symbol_data = {'symbol_id':
395 {'debug_file': symbol.header.name,
396 'debug_id': symbol.header.id.replace('-', '')}
397 }
Mike Frysingerac75f602019-11-14 23:18:51 -0500398 with open(symbol.file_name, 'r') as fp:
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600399 logging.debug('Executing put to uploadUrl: %s', symbol.display_name)
Mike Frysingerac75f602019-11-14 23:18:51 -0500400 ExecRequest('put',
401 upload['uploadUrl'], timeout,
402 api_key=api_key,
Mike Nicholsb99f86c2020-02-24 18:36:16 -0700403 data=fp.read())
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600404 logging.debug('Executing post to uploads/complete: %s', symbol.display_name)
Mike Nichols137e82d2019-05-15 18:40:34 -0600405 ExecRequest('post',
406 '%s/uploads/%s:complete' % (
407 upload_url, upload['uploadKey']),
408 timeout, api_key=api_key,
409 # TODO(mikenichols): Validate product_name once it is added
410 # to the proto; currently unsupported.
411 data=json.dumps(symbol_data))
412 else:
413 raise requests.exceptions.HTTPError
Don Garretta28be6d2016-06-16 18:09:35 -0700414
415
Mike Nichols90f7c152019-04-09 15:14:08 -0600416def PerformSymbolsFileUpload(symbols, upload_url, api_key):
Don Garretta28be6d2016-06-16 18:09:35 -0700417 """Upload the symbols to the crash server
418
419 Args:
Don Garrettdeb2e032016-07-06 16:44:14 -0700420 symbols: An iterable of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700421 upload_url: URL of crash server to upload too.
Mike Nichols90f7c152019-04-09 15:14:08 -0600422 api_key: Authentication key.
Don Garretta28be6d2016-06-16 18:09:35 -0700423 failures: Tracker for total upload failures.
Don Garretta28be6d2016-06-16 18:09:35 -0700424
Don Garrettdeb2e032016-07-06 16:44:14 -0700425 Yields:
426 Each symbol from symbols, perhaps modified.
Don Garretta28be6d2016-06-16 18:09:35 -0700427 """
Don Garrettdeb2e032016-07-06 16:44:14 -0700428 failures = 0
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600429 # Failures are figured per request, therefore each HTTP failure
430 # counts against the max. This means that the process will exit
431 # at the point when HTTP errors hit the defined limit.
Don Garrettdeb2e032016-07-06 16:44:14 -0700432 for s in symbols:
433 if (failures < MAX_TOTAL_ERRORS_FOR_RETRY and
434 s.status in (SymbolFile.INITIAL, SymbolFile.ERROR)):
435 # Keeps us from DoS-ing the symbol server.
436 time.sleep(SLEEP_DELAY)
437 logging.info('Uploading symbol_file: %s', s.display_path)
438 try:
439 # This command retries the upload multiple times with growing delays. We
440 # only consider the upload a failure if these retries fail.
Don Garrette1f47e92016-10-13 16:04:56 -0700441 def ShouldRetryUpload(exception):
Mike Nichols0abb8d32019-04-26 14:55:08 -0600442 if isinstance(exception, (requests.exceptions.RequestException,
Mike Frysinger3dcacee2019-08-23 17:09:11 -0400443 IOError,
Mike Nichols0abb8d32019-04-26 14:55:08 -0600444 httplib.HTTPException, socket.error)):
445 logging.info('Request failed, retrying: %s', exception)
446 return True
447 return False
Don Garrette1f47e92016-10-13 16:04:56 -0700448
Don Garrett440944e2016-10-03 16:33:31 -0700449 with cros_build_lib.TimedSection() as timer:
Don Garrette1f47e92016-10-13 16:04:56 -0700450 retry_stats.RetryWithStats(
451 UPLOAD_STATS, ShouldRetryUpload, MAX_RETRIES,
Don Garrett440944e2016-10-03 16:33:31 -0700452 UploadSymbolFile,
Mike Nichols90f7c152019-04-09 15:14:08 -0600453 upload_url, s, api_key,
Luigi Semenzato5104f3c2016-10-12 12:37:42 -0700454 sleep=INITIAL_RETRY_DELAY,
455 log_all_retries=True)
Mike Nicholsb03b2c62019-05-02 18:46:04 -0600456 if s.status != SymbolFile.DUPLICATE:
Mike Nichols2a6f86f2020-08-18 15:56:07 -0600457 logging.info('upload of %s with size %10i bytes took %s',
458 s.display_name, s.FileSize(), timer.delta)
Mike Nicholsb03b2c62019-05-02 18:46:04 -0600459 s.status = SymbolFile.UPLOADED
Mike Frysingerc7d164a2019-11-06 17:09:45 -0500460 except requests.exceptions.RequestException as e:
Mike Nichols90f7c152019-04-09 15:14:08 -0600461 logging.warning('could not upload: %s: HTTP error: %s',
462 s.display_name, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700463 s.status = SymbolFile.ERROR
464 failures += 1
Mike Frysingerb32cc472020-05-15 00:17:54 -0400465 except (httplib.HTTPException, OSError) as e:
Ryo Hashimoto45273f02017-03-16 17:45:56 +0900466 logging.warning('could not upload: %s: %s %s', s.display_name,
467 type(e).__name__, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700468 s.status = SymbolFile.ERROR
469 failures += 1
470
471 # We pass the symbol along, on both success and failure.
472 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700473
474
Don Garrettdeb2e032016-07-06 16:44:14 -0700475def ReportResults(symbols, failed_list):
Don Garretta28be6d2016-06-16 18:09:35 -0700476 """Log a summary of the symbol uploading.
477
478 This has the side effect of fully consuming the symbols iterator.
479
480 Args:
481 symbols: An iterator of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700482 failed_list: A filename at which to write out a list of our failed uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700483
484 Returns:
485 The number of symbols not uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700486 """
487 upload_failures = []
488 result_counts = {
489 SymbolFile.INITIAL: 0,
490 SymbolFile.UPLOADED: 0,
491 SymbolFile.DUPLICATE: 0,
Don Garrettdeb2e032016-07-06 16:44:14 -0700492 SymbolFile.ERROR: 0,
Don Garretta28be6d2016-06-16 18:09:35 -0700493 }
494
495 for s in symbols:
496 result_counts[s.status] += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700497 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700498 upload_failures.append(s)
499
Don Garrette1f47e92016-10-13 16:04:56 -0700500 # Report retry numbers.
501 _, _, retries = retry_stats.CategoryStats(UPLOAD_STATS)
502 if retries:
503 logging.warning('%d upload retries performed.', retries)
504
Don Garretta28be6d2016-06-16 18:09:35 -0700505 logging.info('Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.',
506 result_counts)
507
Chris Ching91908032016-09-27 16:55:33 -0600508 if result_counts[SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700509 logging.PrintBuildbotStepWarnings()
Chris Ching91908032016-09-27 16:55:33 -0600510 logging.warning('%d non-recoverable upload errors',
511 result_counts[SymbolFile.ERROR])
512
513 if result_counts[SymbolFile.INITIAL]:
514 logging.PrintBuildbotStepWarnings()
515 logging.warning('%d upload(s) were skipped because of excessive errors',
Don Garrettdeb2e032016-07-06 16:44:14 -0700516 result_counts[SymbolFile.INITIAL])
Don Garretta28be6d2016-06-16 18:09:35 -0700517
518 if failed_list is not None:
519 with open(failed_list, 'w') as fl:
520 for s in upload_failures:
521 fl.write('%s\n' % s.display_path)
522
Don Garrettdeb2e032016-07-06 16:44:14 -0700523 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
524
Don Garretta28be6d2016-06-16 18:09:35 -0700525
Mike Nichols649e6a82019-04-23 11:44:48 -0600526def UploadSymbols(sym_paths, upload_url, failed_list=None,
Mike Nichols137e82d2019-05-15 18:40:34 -0600527 upload_limit=None, strip_cfi=None, timeout=None,
Mike Nichols90f7c152019-04-09 15:14:08 -0600528 api_key=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400529 """Upload all the generated symbols for |board| to the crash server
530
531 Args:
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500532 sym_paths: Specific symbol files (or dirs of sym files) to upload,
533 otherwise search |breakpad_dir|
Don Garretta28be6d2016-06-16 18:09:35 -0700534 upload_url: URL of crash server to upload too.
Don Garretta28be6d2016-06-16 18:09:35 -0700535 failed_list: A filename at which to write out a list of our failed uploads.
536 upload_limit: Integer listing how many files to upload. None for no limit.
537 strip_cfi: File size at which we strip out CFI data. None for no limit.
Mike Nichols137e82d2019-05-15 18:40:34 -0600538 timeout: HTTP timeout for request.
Mike Nichols90f7c152019-04-09 15:14:08 -0600539 api_key: A string based authentication key
Mike Frysinger1a736a82013-12-12 01:50:59 -0500540
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400541 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400542 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400543 """
Don Garrette1f47e92016-10-13 16:04:56 -0700544 retry_stats.SetupStats()
545
Don Garretta28be6d2016-06-16 18:09:35 -0700546 # Note: This method looks like each step of processing is performed
547 # sequentially for all SymbolFiles, but instead each step is a generator that
548 # produces the next iteration only when it's read. This means that (except for
549 # some batching) each SymbolFile goes through all of these steps before the
550 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400551
Don Garretta28be6d2016-06-16 18:09:35 -0700552 # This is used to hold striped
553 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
554 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500555
Don Garrett1bc1e102016-07-06 17:06:10 -0700556 # Sort all of our symbols so the largest ones (probably the most important)
557 # are processed first.
558 symbols = list(symbols)
559 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
560
Don Garretta28be6d2016-06-16 18:09:35 -0700561 if upload_limit is not None:
562 # Restrict symbols processed to the limit.
563 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400564
Don Garretta28be6d2016-06-16 18:09:35 -0700565 # Strip CFI, if needed.
566 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500567
Mike Nichols137e82d2019-05-15 18:40:34 -0600568 # Find duplicates via batch API
569 symbols = FindDuplicates(symbols, upload_url, api_key, timeout)
570
Don Garretta28be6d2016-06-16 18:09:35 -0700571 # Perform uploads
Mike Nichols90f7c152019-04-09 15:14:08 -0600572 symbols = PerformSymbolsFileUpload(symbols, upload_url, api_key)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400573
Don Garretta28be6d2016-06-16 18:09:35 -0700574 # Log the final results, and consume the symbols generator fully.
Don Garrettdeb2e032016-07-06 16:44:14 -0700575 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500576
Don Garrettdeb2e032016-07-06 16:44:14 -0700577 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400578
579
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400580def main(argv):
581 parser = commandline.ArgumentParser(description=__doc__)
582
Don Garretta28be6d2016-06-16 18:09:35 -0700583 # TODO: Make sym_paths, breakpad_root, and root exclusive.
584
Mike Frysingerd41938e2014-02-10 06:37:55 -0500585 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
586 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400587 parser.add_argument('--board', default=None,
Don Garrett747cc4b2015-10-07 14:48:48 -0700588 help='Used to find default breakpad_root.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400589 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500590 help='full path to the breakpad symbol directory')
591 parser.add_argument('--root', type='path', default=None,
592 help='full path to the chroot dir')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400593 parser.add_argument('--official_build', action='store_true', default=False,
594 help='point to official symbol server')
Mike Frysinger38647542014-09-12 18:15:39 -0700595 parser.add_argument('--server', type=str, default=None,
596 help='URI for custom symbol server')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400597 parser.add_argument('--regenerate', action='store_true', default=False,
598 help='regenerate all symbols')
Don Garretta28be6d2016-06-16 18:09:35 -0700599 parser.add_argument('--upload-limit', type=int,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400600 help='only upload # number of symbols')
601 parser.add_argument('--strip_cfi', type=int,
Don Garretta28be6d2016-06-16 18:09:35 -0700602 default=DEFAULT_FILE_LIMIT,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400603 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500604 parser.add_argument('--failed-list', type='path',
605 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500606 parser.add_argument('--dedupe', action='store_true', default=False,
607 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400608 parser.add_argument('--yes', action='store_true', default=False,
609 help='answer yes to all prompts')
Don Garrett747cc4b2015-10-07 14:48:48 -0700610 parser.add_argument('--product_name', type=str, default='ChromeOS',
611 help='Produce Name for breakpad stats.')
Mike Nichols90f7c152019-04-09 15:14:08 -0600612 parser.add_argument('--api_key', type=str, default=None,
613 help='full path to the API key file')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400614
615 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500616 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400617
Don Garretta28be6d2016-06-16 18:09:35 -0700618 # Figure out the symbol files/directories to upload.
619 if opts.sym_paths:
620 sym_paths = opts.sym_paths
621 elif opts.breakpad_root:
622 sym_paths = [opts.breakpad_root]
623 elif opts.root:
624 if not opts.board:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400625 cros_build_lib.Die('--board must be set if --root is used.')
Don Garretta28be6d2016-06-16 18:09:35 -0700626 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(opts.board)
627 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip('/'))]
628 else:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400629 cros_build_lib.Die('--sym_paths, --breakpad_root, or --root must be set.')
Don Garretta28be6d2016-06-16 18:09:35 -0700630
Don Garrett747cc4b2015-10-07 14:48:48 -0700631 if opts.sym_paths or opts.breakpad_root:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400632 if opts.regenerate:
Don Garrett747cc4b2015-10-07 14:48:48 -0700633 cros_build_lib.Die('--regenerate may not be used with specific files, '
634 'or breakpad_root')
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400635 else:
636 if opts.board is None:
637 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400638
Don Garretta28be6d2016-06-16 18:09:35 -0700639 # Figure out which crash server to upload too.
640 upload_url = opts.server
641 if not upload_url:
642 if opts.official_build:
643 upload_url = OFFICIAL_UPLOAD_URL
644 else:
645 logging.warning('unofficial builds upload to the staging server')
646 upload_url = STAGING_UPLOAD_URL
647
Mike Nichols90f7c152019-04-09 15:14:08 -0600648 # Set up the API key needed to authenticate to Crash server.
649 # Allow for a local key file for testing purposes.
650 if opts.api_key:
651 api_key_file = opts.api_key
652 else:
653 api_key_file = constants.CRASH_API_KEY
654
655 api_key = osutils.ReadFile(api_key_file)
656
Don Garretta28be6d2016-06-16 18:09:35 -0700657 # Confirm we really want the long upload.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400658 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500659 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400660 Uploading symbols for an entire Chromium OS build is really only
661 necessary for release builds and in a few cases for developers
662 to debug problems. It will take considerable time to run. For
663 developer debugging purposes, consider instead passing specific
664 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500665 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400666 if not cros_build_lib.BooleanPrompt(
667 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500668 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400669 cros_build_lib.Die('better safe than sorry')
670
671 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700672
673 # Regenerate symbols from binaries.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400674 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400675 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
676 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400677
Don Garretta28be6d2016-06-16 18:09:35 -0700678 # Do the upload.
679 ret += UploadSymbols(
680 sym_paths=sym_paths,
681 upload_url=upload_url,
Don Garretta28be6d2016-06-16 18:09:35 -0700682 failed_list=opts.failed_list,
683 upload_limit=opts.upload_limit,
Mike Nichols90f7c152019-04-09 15:14:08 -0600684 strip_cfi=opts.strip_cfi,
685 api_key=api_key)
Don Garretta28be6d2016-06-16 18:09:35 -0700686
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400687 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700688 logging.error('encountered %i problem(s)', ret)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400689 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
690 # return 0 in case we are a multiple of the mask.
Don Garretta28be6d2016-06-16 18:09:35 -0700691 return 1