blob: 2190553623190327676a7ec162cb5f680db96148 [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2013 The ChromiumOS Authors
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -04002# 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
Alex Klein18ef1212021-10-14 12:49:02 -060036from chromite.utils import timer
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040037
Mike Frysinger2688ef62020-02-16 00:00:46 -050038
Mike Frysinger0c0efa22014-02-09 23:32:23 -050039# Needs to be after chromite imports.
Mike Frysingerc5597f22014-11-27 15:39:15 -050040# We don't want to import the general keyring module as that will implicitly
41# try to import & connect to a dbus server. That's a waste of time.
Alex Klein1699fab2022-09-08 08:46:06 -060042sys.modules["keyring"] = None
Mike Frysinger0c0efa22014-02-09 23:32:23 -050043
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040044
45# URLs used for uploading symbols.
Alex Klein1699fab2022-09-08 08:46:06 -060046OFFICIAL_UPLOAD_URL = "https://prod-crashsymbolcollector-pa.googleapis.com/v1"
47STAGING_UPLOAD_URL = "https://staging-crashsymbolcollector-pa.googleapis.com/v1"
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040048
Ian Barkley-Yeung22ba8122020-02-05 15:39:02 -080049# The crash server rejects files that are bigger than 1GB.
50CRASH_SERVER_FILE_LIMIT = 1024 * 1024 * 1024
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040051# Give ourselves a little breathing room from what the server expects.
52DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
53
54
Mike Frysinger0c0efa22014-02-09 23:32:23 -050055# The batch limit when talking to the dedup server. We avoid sending one at a
56# time as the round trip overhead will dominate. Conversely, we avoid sending
57# all at once so we can start uploading symbols asap -- the symbol server is a
58# bit slow and will take longer than anything else.
Mike Frysinger0c0efa22014-02-09 23:32:23 -050059DEDUPE_LIMIT = 100
60
61# How long to wait for the server to respond with the results. Note that the
62# larger the limit above, the larger this will need to be. So we give it ~1
63# second per item max.
64DEDUPE_TIMEOUT = DEDUPE_LIMIT
65
Don Garretta28be6d2016-06-16 18:09:35 -070066# How long to wait for the notification to finish (in seconds).
67DEDUPE_NOTIFY_TIMEOUT = 240
Mike Frysinger4dd462e2014-04-30 16:21:51 -040068
Mike Frysinger71046662014-09-12 18:15:15 -070069# The minimum average rate (in bytes per second) that we expect to maintain
70# when uploading symbols. This has to allow for symbols that are up to
71# CRASH_SERVER_FILE_LIMIT in size.
Mike Frysinger93e8ffa2019-07-03 20:24:18 -040072UPLOAD_MIN_RATE = CRASH_SERVER_FILE_LIMIT // (30 * 60)
Mike Frysinger71046662014-09-12 18:15:15 -070073
74# The lowest timeout (in seconds) we'll allow. If the server is overloaded,
75# then there might be a delay in setting up the connection, not just with the
76# transfer. So even a small file might need a larger value.
Ryo Hashimotoc0049372017-02-16 18:50:00 +090077UPLOAD_MIN_TIMEOUT = 5 * 60
Mike Frysingercd78a082013-06-26 17:13:04 -040078
79
Don Garrett7a793092016-07-06 16:50:27 -070080# Sleep for 500ms in between uploads to avoid DoS'ing symbol server.
81SLEEP_DELAY = 0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040082
83
84# Number of seconds to wait before retrying an upload. The delay will double
85# for each subsequent retry of the same symbol file.
86INITIAL_RETRY_DELAY = 1
87
88# Allow up to 7 attempts to upload a symbol file (total delay may be
89# 1+2+4+8+16+32=63 seconds).
Mike Nichols2a6f86f2020-08-18 15:56:07 -060090MAX_RETRIES = 5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040091
Mike Frysingereb753bf2013-11-22 16:05:35 -050092# Number of total errors, before uploads are no longer attempted.
93# This is used to avoid lots of errors causing unreasonable delays.
Mike Nichols2a6f86f2020-08-18 15:56:07 -060094MAX_TOTAL_ERRORS_FOR_RETRY = 6
Mike Frysingereb753bf2013-11-22 16:05:35 -050095
Don Garrette1f47e92016-10-13 16:04:56 -070096# Category to use for collection upload retry stats.
Alex Klein1699fab2022-09-08 08:46:06 -060097UPLOAD_STATS = "UPLOAD"
Don Garrette1f47e92016-10-13 16:04:56 -070098
Don Garretta28be6d2016-06-16 18:09:35 -070099
Don Garretta28be6d2016-06-16 18:09:35 -0700100def BatchGenerator(iterator, batch_size):
Alex Klein1699fab2022-09-08 08:46:06 -0600101 """Given an iterator, break into lists of size batch_size.
Fang Dengba680462015-08-16 20:34:11 -0700102
Alex Klein1699fab2022-09-08 08:46:06 -0600103 The result is a generator, that will only read in as many inputs as needed for
104 the current batch. The final result can be smaller than batch_size.
105 """
106 batch = []
107 for i in iterator:
108 batch.append(i)
109 if len(batch) >= batch_size:
110 yield batch
111 batch = []
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700112
Alex Klein1699fab2022-09-08 08:46:06 -0600113 if batch:
114 # if there was anything left in the final batch, yield it.
115 yield batch
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500116
117
Mike Frysingerd41938e2014-02-10 06:37:55 -0500118def IsTarball(path):
Alex Klein1699fab2022-09-08 08:46:06 -0600119 """Guess if this is a tarball based on the filename."""
120 parts = path.split(".")
121 if len(parts) <= 1:
122 return False
Mike Frysingerd41938e2014-02-10 06:37:55 -0500123
Alex Klein1699fab2022-09-08 08:46:06 -0600124 if parts[-1] == "tar":
125 return True
Mike Frysingerd41938e2014-02-10 06:37:55 -0500126
Alex Klein1699fab2022-09-08 08:46:06 -0600127 if parts[-2] == "tar":
128 return parts[-1] in ("bz2", "gz", "xz")
Mike Frysingerd41938e2014-02-10 06:37:55 -0500129
Alex Klein1699fab2022-09-08 08:46:06 -0600130 return parts[-1] in ("tbz2", "tbz", "tgz", "txz")
Mike Frysingerd41938e2014-02-10 06:37:55 -0500131
132
Don Garretta28be6d2016-06-16 18:09:35 -0700133class SymbolFile(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600134 """This class represents the state of a symbol file during processing.
Don Garretta28be6d2016-06-16 18:09:35 -0700135
Alex Klein1699fab2022-09-08 08:46:06 -0600136 Attributes:
137 display_path: Name of symbol file that should be consistent between builds.
138 file_name: Transient path of the symbol file.
139 header: ReadSymsHeader output. Dict with assorted meta-data.
140 status: INITIAL, DUPLICATE, or UPLOADED based on status of processing.
141 dedupe_item: None or instance of DedupeItem for this symbol file.
142 dedupe_push_state: Opaque value to return to dedupe code for file.
143 display_name: Read only friendly (short) file name for logging.
144 file_size: Read only size of the symbol file.
Don Garretta28be6d2016-06-16 18:09:35 -0700145 """
Don Garretta28be6d2016-06-16 18:09:35 -0700146
Alex Klein1699fab2022-09-08 08:46:06 -0600147 INITIAL = "initial"
148 DUPLICATE = "duplicate"
149 UPLOADED = "uploaded"
150 ERROR = "error"
Don Garretta28be6d2016-06-16 18:09:35 -0700151
Alex Klein1699fab2022-09-08 08:46:06 -0600152 def __init__(self, display_path, file_name):
153 """An instance of this class represents a symbol file over time.
154
155 Args:
156 display_path: A unique/persistent between builds name to present to the
157 crash server. It is the file name, relative to where it
158 came from (tarball, breakpad dir, etc).
159 file_name: A the current location of the symbol file.
160 """
161 self.display_path = display_path
162 self.file_name = file_name
163 self.header = cros_generate_breakpad_symbols.ReadSymsHeader(file_name)
164 self.status = SymbolFile.INITIAL
165
166 @property
167 def display_name(self):
168 return os.path.basename(self.display_path)
169
170 def FileSize(self):
171 return os.path.getsize(self.file_name)
Don Garretta28be6d2016-06-16 18:09:35 -0700172
173
Don Garretta28be6d2016-06-16 18:09:35 -0700174def FindSymbolFiles(tempdir, paths):
Alex Klein1699fab2022-09-08 08:46:06 -0600175 """Locate symbol files in |paths|
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500176
Alex Klein1699fab2022-09-08 08:46:06 -0600177 This returns SymbolFile objects that contain file references which are valid
178 after this exits. Those files may exist externally, or be created in the
179 tempdir (say, when expanding tarballs). The caller must not consider
180 SymbolFile's valid after tempdir is cleaned up.
Don Garretta28be6d2016-06-16 18:09:35 -0700181
Alex Klein1699fab2022-09-08 08:46:06 -0600182 Args:
183 tempdir: Path to use for temporary files.
184 paths: A list of input paths to walk. Files are returned w/out any checks.
185 Dirs are searched for files that end in ".sym". Urls are fetched and then
186 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500187
Alex Klein1699fab2022-09-08 08:46:06 -0600188 Yields:
189 A SymbolFile for every symbol file found in paths.
190 """
191 cache_dir = path_util.GetCacheDir()
192 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
193 tar_cache = cache.TarballCache(common_path)
Mike Frysingere847efd2015-01-08 03:57:24 -0500194
Alex Klein1699fab2022-09-08 08:46:06 -0600195 for p in paths:
196 o = urllib.parse.urlparse(p)
197 if o.scheme:
198 # Support globs of filenames.
199 ctx = gs.GSContext()
200 for gspath in ctx.LS(p):
201 logging.info("processing files inside %s", gspath)
202 o = urllib.parse.urlparse(gspath)
203 key = ("%s%s" % (o.netloc, o.path)).split("/")
204 # The common cache will not be LRU, removing the need to hold a read
205 # lock on the cached gsutil.
206 ref = tar_cache.Lookup(key)
207 try:
208 ref.SetDefault(gspath)
209 except cros_build_lib.RunCommandError as e:
210 logging.warning("ignoring %s\n%s", gspath, e)
211 continue
212 for sym in FindSymbolFiles(tempdir, [ref.path]):
213 yield sym
Mike Frysingerd41938e2014-02-10 06:37:55 -0500214
Alex Klein1699fab2022-09-08 08:46:06 -0600215 elif os.path.isdir(p):
216 for root, _, files in os.walk(p):
217 for f in files:
218 if f.endswith(".sym"):
219 # If p is '/tmp/foo' and filename is '/tmp/foo/bar/bar.sym',
220 # display_path = 'bar/bar.sym'
221 filename = os.path.join(root, f)
222 yield SymbolFile(
223 display_path=filename[len(p) :].lstrip("/"),
224 file_name=filename,
225 )
Mike Frysingerd41938e2014-02-10 06:37:55 -0500226
Alex Klein1699fab2022-09-08 08:46:06 -0600227 elif IsTarball(p):
228 logging.info("processing files inside %s", p)
229 tardir = tempfile.mkdtemp(dir=tempdir)
230 cache.Untar(os.path.realpath(p), tardir)
231 for sym in FindSymbolFiles(tardir, [tardir]):
232 yield sym
Mike Frysingerd41938e2014-02-10 06:37:55 -0500233
Alex Klein1699fab2022-09-08 08:46:06 -0600234 else:
235 yield SymbolFile(display_path=p, file_name=p)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500236
237
Don Garretta28be6d2016-06-16 18:09:35 -0700238def AdjustSymbolFileSize(symbol, tempdir, file_limit):
Alex Klein1699fab2022-09-08 08:46:06 -0600239 """Examine symbols files for size problems, and reduce if needed.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500240
Alex Klein1699fab2022-09-08 08:46:06 -0600241 If the symbols size is too big, strip out the call frame info. The CFI
242 is unnecessary for 32bit x86 targets where the frame pointer is used (as
243 all of ours have) and it accounts for over half the size of the symbols
244 uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700245
Alex Klein1699fab2022-09-08 08:46:06 -0600246 Stripped files will be created inside tempdir, and will be the callers
247 responsibility to clean up.
Don Garretta28be6d2016-06-16 18:09:35 -0700248
Alex Klein1699fab2022-09-08 08:46:06 -0600249 We also warn, if a symbols file is still too large after stripping.
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500250
Alex Klein1699fab2022-09-08 08:46:06 -0600251 Args:
252 symbol: SymbolFile instance to be examined and modified as needed..
253 tempdir: A temporary directory we can create files in that the caller will
254 clean up.
255 file_limit: We only strip files which are larger than this limit.
Don Garretta28be6d2016-06-16 18:09:35 -0700256
Alex Klein1699fab2022-09-08 08:46:06 -0600257 Returns:
258 SymbolFile instance (original or modified as needed)
259 """
260 file_size = symbol.FileSize()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500261
Alex Klein1699fab2022-09-08 08:46:06 -0600262 if file_limit and symbol.FileSize() > file_limit:
263 with cros_build_lib.UnbufferedNamedTemporaryFile(
264 prefix="upload_symbols", dir=tempdir, delete=False
265 ) as temp_sym_file:
Alex Klein1699fab2022-09-08 08:46:06 -0600266 with open(symbol.file_name, "rb") as fp:
267 temp_sym_file.writelines(
268 x for x in fp.readlines() if not x.startswith(b"STACK CFI")
269 )
270
271 original_file_size = file_size
272 symbol.file_name = temp_sym_file.name
273 file_size = symbol.FileSize()
274
275 logging.warning(
276 "stripped CFI for %s reducing size %s > %s",
277 symbol.display_name,
278 original_file_size,
279 file_size,
280 )
281
282 # Hopefully the crash server will let it through. But it probably won't.
283 # Not sure what the best answer is in this case.
284 if file_size >= CRASH_SERVER_FILE_LIMIT:
285 cbuildbot_alerts.PrintBuildbotStepWarnings()
286 logging.warning(
287 "upload file %s is awfully large, risking rejection by "
288 "the symbol server (%s > %s)",
289 symbol.display_path,
290 file_size,
291 CRASH_SERVER_FILE_LIMIT,
Mike Frysingerac75f602019-11-14 23:18:51 -0500292 )
Don Garretta28be6d2016-06-16 18:09:35 -0700293
Alex Klein1699fab2022-09-08 08:46:06 -0600294 return symbol
Don Garretta28be6d2016-06-16 18:09:35 -0700295
Don Garretta28be6d2016-06-16 18:09:35 -0700296
297def GetUploadTimeout(symbol):
Alex Klein1699fab2022-09-08 08:46:06 -0600298 """How long to wait for a specific file to upload to the crash server.
Don Garretta28be6d2016-06-16 18:09:35 -0700299
Alex Klein1699fab2022-09-08 08:46:06 -0600300 This is a function largely to make unittesting easier.
Don Garretta28be6d2016-06-16 18:09:35 -0700301
Alex Klein1699fab2022-09-08 08:46:06 -0600302 Args:
303 symbol: A SymbolFile instance.
Don Garretta28be6d2016-06-16 18:09:35 -0700304
Alex Klein1699fab2022-09-08 08:46:06 -0600305 Returns:
306 Timeout length (in seconds)
307 """
308 # Scale the timeout based on the filesize.
309 return max(symbol.FileSize() // UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
Don Garretta28be6d2016-06-16 18:09:35 -0700310
311
Mike Nichols90f7c152019-04-09 15:14:08 -0600312def ExecRequest(operator, url, timeout, api_key, **kwargs):
Alex Klein1699fab2022-09-08 08:46:06 -0600313 """Makes a web request with default timeout, returning the json result.
Don Garretta28be6d2016-06-16 18:09:35 -0700314
Alex Klein1699fab2022-09-08 08:46:06 -0600315 This method will raise a requests.exceptions.HTTPError if the status
316 code is not 4XX, 5XX
Mike Nichols90f7c152019-04-09 15:14:08 -0600317
Alex Klein1699fab2022-09-08 08:46:06 -0600318 Note: If you are using verbose logging it is entirely possible that the
319 subsystem will write your api key to the logs!
Mike Nichols90f7c152019-04-09 15:14:08 -0600320
Alex Klein1699fab2022-09-08 08:46:06 -0600321 Args:
322 operator: HTTP method.
323 url: Endpoint URL.
324 timeout: HTTP timeout for request.
325 api_key: Authentication key.
Mike Nichols90f7c152019-04-09 15:14:08 -0600326
Alex Klein1699fab2022-09-08 08:46:06 -0600327 Returns:
328 HTTP response content
329 """
330 resp = requests.request(
331 operator,
332 url,
333 params={"key": api_key},
334 headers={"User-agent": "chromite.upload_symbols"},
335 timeout=timeout,
336 **kwargs,
337 )
338 # Make sure we don't leak secret keys by accident.
339 if resp.status_code > 399:
340 resp.url = resp.url.replace(urllib.parse.quote(api_key), "XX-HIDDEN-XX")
341 logging.warning(
342 'Url: %s, Status: %s, response: "%s", in: %s',
343 resp.url,
344 resp.status_code,
345 resp.text,
346 resp.elapsed,
347 )
348 elif resp.content:
349 return resp.json()
Mike Nicholscf5b7a92019-04-25 12:01:28 -0600350
Alex Klein1699fab2022-09-08 08:46:06 -0600351 return {}
Mike Nichols90f7c152019-04-09 15:14:08 -0600352
353
Mike Nichols137e82d2019-05-15 18:40:34 -0600354def FindDuplicates(symbols, status_url, api_key, timeout=DEDUPE_TIMEOUT):
Alex Klein1699fab2022-09-08 08:46:06 -0600355 """Check whether the symbol files have already been uploaded.
Mike Nichols649e6a82019-04-23 11:44:48 -0600356
Alex Klein1699fab2022-09-08 08:46:06 -0600357 Args:
358 symbols: A iterable of SymbolFiles to be uploaded
359 status_url: The crash URL to validate the file existence.
360 api_key: Authentication key.
361 timeout: HTTP timeout for request.
Mike Nichols649e6a82019-04-23 11:44:48 -0600362
Alex Klein1699fab2022-09-08 08:46:06 -0600363 Yields:
364 All SymbolFiles from symbols, but duplicates have status updated to
365 DUPLICATE.
366 """
367 for batch in BatchGenerator(symbols, DEDUPE_LIMIT):
368 items = []
369 result = {}
370 for x in batch:
371 items.append(
372 {
373 "debug_file": x.header.name,
374 "debug_id": x.header.id.replace("-", ""),
375 }
376 )
377 symbol_data = {"symbol_ids": items}
378 try:
379 result = ExecRequest(
380 "post",
381 "%s/symbols:checkStatuses" % status_url,
382 timeout,
383 api_key=api_key,
384 data=json.dumps(symbol_data),
385 )
386 except requests.exceptions.RequestException as e:
387 logging.warning("could not identify duplicates: HTTP error: %s", e)
388 for b in batch:
389 b.status = SymbolFile.INITIAL
390 set_match = {
391 "debugId": b.header.id.replace("-", ""),
392 "debugFile": b.header.name,
393 }
394 for cs_result in result.get("pairs", []):
395 if set_match == cs_result.get("symbolId"):
396 if cs_result.get("status") == "FOUND":
397 logging.debug("Found duplicate: %s", b.display_name)
398 b.status = SymbolFile.DUPLICATE
399 break
400 yield b
Mike Nichols137e82d2019-05-15 18:40:34 -0600401
Mike Nichols649e6a82019-04-23 11:44:48 -0600402
Mike Nichols90f7c152019-04-09 15:14:08 -0600403def UploadSymbolFile(upload_url, symbol, api_key):
Alex Klein1699fab2022-09-08 08:46:06 -0600404 """Upload a symbol file to the crash server, returning the status result.
Don Garretta28be6d2016-06-16 18:09:35 -0700405
Alex Klein1699fab2022-09-08 08:46:06 -0600406 Args:
407 upload_url: The crash URL to POST the |sym_file| to
408 symbol: A SymbolFile instance.
409 api_key: Authentication key
410 """
411 timeout = GetUploadTimeout(symbol)
412 logging.debug("Executing post to uploads:create: %s", symbol.display_name)
413 upload = ExecRequest(
414 "post", "%s/uploads:create" % upload_url, timeout, api_key
415 )
Mike Nichols649e6a82019-04-23 11:44:48 -0600416
Alex Klein1699fab2022-09-08 08:46:06 -0600417 if upload and "uploadUrl" in upload.keys():
418 symbol_data = {
419 "symbol_id": {
420 "debug_file": symbol.header.name,
421 "debug_id": symbol.header.id.replace("-", ""),
422 }
423 }
Mike Frysinger31fdddd2023-02-24 15:50:55 -0500424 with open(symbol.file_name, "r", encoding="utf-8") as fp:
Alex Klein1699fab2022-09-08 08:46:06 -0600425 logging.debug("Executing put to uploadUrl: %s", symbol.display_name)
426 ExecRequest(
427 "put",
428 upload["uploadUrl"],
429 timeout,
430 api_key=api_key,
431 data=fp.read(),
432 )
433 logging.debug(
434 "Executing post to uploads/complete: %s", symbol.display_name
435 )
436 ExecRequest(
437 "post",
438 "%s/uploads/%s:complete" % (upload_url, upload["uploadKey"]),
439 timeout,
440 api_key=api_key,
441 # TODO(mikenichols): Validate product_name once it is added
442 # to the proto; currently unsupported.
443 data=json.dumps(symbol_data),
444 )
445 else:
446 raise requests.exceptions.HTTPError
Don Garretta28be6d2016-06-16 18:09:35 -0700447
448
Mike Nichols90f7c152019-04-09 15:14:08 -0600449def PerformSymbolsFileUpload(symbols, upload_url, api_key):
Alex Klein1699fab2022-09-08 08:46:06 -0600450 """Upload the symbols to the crash server
Don Garretta28be6d2016-06-16 18:09:35 -0700451
Alex Klein1699fab2022-09-08 08:46:06 -0600452 Args:
453 symbols: An iterable of SymbolFiles to be uploaded.
454 upload_url: URL of crash server to upload too.
455 api_key: Authentication key.
456 failures: Tracker for total upload failures.
Don Garretta28be6d2016-06-16 18:09:35 -0700457
Alex Klein1699fab2022-09-08 08:46:06 -0600458 Yields:
459 Each symbol from symbols, perhaps modified.
460 """
461 failures = 0
462 # Failures are figured per request, therefore each HTTP failure
463 # counts against the max. This means that the process will exit
464 # at the point when HTTP errors hit the defined limit.
465 for s in symbols:
466 if failures < MAX_TOTAL_ERRORS_FOR_RETRY and s.status in (
467 SymbolFile.INITIAL,
468 SymbolFile.ERROR,
469 ):
470 # Keeps us from DoS-ing the symbol server.
471 time.sleep(SLEEP_DELAY)
472 logging.info("Uploading symbol_file: %s", s.display_path)
473 try:
474 # This command retries the upload multiple times with growing delays. We
475 # only consider the upload a failure if these retries fail.
476 def ShouldRetryUpload(exception):
477 if isinstance(
478 exception,
479 (
480 requests.exceptions.RequestException,
481 IOError,
482 http.client.HTTPException,
483 socket.error,
484 ),
485 ):
486 logging.info("Request failed, retrying: %s", exception)
487 return True
488 return False
Don Garrette1f47e92016-10-13 16:04:56 -0700489
Alex Klein1699fab2022-09-08 08:46:06 -0600490 with timer.Timer() as t:
491 retry_stats.RetryWithStats(
492 UPLOAD_STATS,
493 ShouldRetryUpload,
494 MAX_RETRIES,
495 UploadSymbolFile,
496 upload_url,
497 s,
498 api_key,
499 sleep=INITIAL_RETRY_DELAY,
500 log_all_retries=True,
501 )
502 if s.status != SymbolFile.DUPLICATE:
503 logging.info(
504 "upload of %s with size %10i bytes took %s",
505 s.display_name,
506 s.FileSize(),
507 t,
508 )
509 s.status = SymbolFile.UPLOADED
510 except requests.exceptions.RequestException as e:
511 logging.warning(
512 "could not upload: %s: HTTP error: %s", s.display_name, e
513 )
514 s.status = SymbolFile.ERROR
515 failures += 1
516 except (http.client.HTTPException, OSError) as e:
517 logging.warning(
518 "could not upload: %s: %s %s",
519 s.display_name,
520 type(e).__name__,
521 e,
522 )
523 s.status = SymbolFile.ERROR
524 failures += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700525
Alex Klein1699fab2022-09-08 08:46:06 -0600526 # We pass the symbol along, on both success and failure.
527 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700528
529
Don Garrettdeb2e032016-07-06 16:44:14 -0700530def ReportResults(symbols, failed_list):
Alex Klein1699fab2022-09-08 08:46:06 -0600531 """Log a summary of the symbol uploading.
Don Garretta28be6d2016-06-16 18:09:35 -0700532
Alex Klein1699fab2022-09-08 08:46:06 -0600533 This has the side effect of fully consuming the symbols iterator.
Don Garretta28be6d2016-06-16 18:09:35 -0700534
Alex Klein1699fab2022-09-08 08:46:06 -0600535 Args:
536 symbols: An iterator of SymbolFiles to be uploaded.
537 failed_list: A filename at which to write out a list of our failed uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700538
Alex Klein1699fab2022-09-08 08:46:06 -0600539 Returns:
540 The number of symbols not uploaded.
541 """
542 upload_failures = []
543 result_counts = {
544 SymbolFile.INITIAL: 0,
545 SymbolFile.UPLOADED: 0,
546 SymbolFile.DUPLICATE: 0,
547 SymbolFile.ERROR: 0,
548 }
Don Garretta28be6d2016-06-16 18:09:35 -0700549
Alex Klein1699fab2022-09-08 08:46:06 -0600550 for s in symbols:
551 result_counts[s.status] += 1
552 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
553 upload_failures.append(s)
Don Garretta28be6d2016-06-16 18:09:35 -0700554
Alex Klein1699fab2022-09-08 08:46:06 -0600555 # Report retry numbers.
556 _, _, retries = retry_stats.CategoryStats(UPLOAD_STATS)
557 if retries:
558 logging.warning("%d upload retries performed.", retries)
Don Garrette1f47e92016-10-13 16:04:56 -0700559
Alex Klein1699fab2022-09-08 08:46:06 -0600560 logging.info(
561 "Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.",
562 result_counts,
563 )
Don Garretta28be6d2016-06-16 18:09:35 -0700564
Alex Klein1699fab2022-09-08 08:46:06 -0600565 if result_counts[SymbolFile.ERROR]:
566 cbuildbot_alerts.PrintBuildbotStepWarnings()
567 logging.warning(
568 "%d non-recoverable upload errors", result_counts[SymbolFile.ERROR]
569 )
Chris Ching91908032016-09-27 16:55:33 -0600570
Alex Klein1699fab2022-09-08 08:46:06 -0600571 if result_counts[SymbolFile.INITIAL]:
572 cbuildbot_alerts.PrintBuildbotStepWarnings()
573 logging.warning(
574 "%d upload(s) were skipped because of excessive errors",
575 result_counts[SymbolFile.INITIAL],
576 )
Don Garretta28be6d2016-06-16 18:09:35 -0700577
Alex Klein1699fab2022-09-08 08:46:06 -0600578 if failed_list is not None:
Mike Frysinger31fdddd2023-02-24 15:50:55 -0500579 with open(failed_list, "w", encoding="utf-8") as fl:
Alex Klein1699fab2022-09-08 08:46:06 -0600580 for s in upload_failures:
581 fl.write("%s\n" % s.display_path)
Don Garretta28be6d2016-06-16 18:09:35 -0700582
Alex Klein1699fab2022-09-08 08:46:06 -0600583 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
Don Garrettdeb2e032016-07-06 16:44:14 -0700584
Don Garretta28be6d2016-06-16 18:09:35 -0700585
Alex Klein1699fab2022-09-08 08:46:06 -0600586def UploadSymbols(
587 sym_paths,
588 upload_url,
589 failed_list=None,
590 upload_limit=None,
591 strip_cfi=None,
592 timeout=None,
593 api_key=None,
594):
595 """Upload all the generated symbols for |board| to the crash server
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400596
Alex Klein1699fab2022-09-08 08:46:06 -0600597 Args:
598 sym_paths: Specific symbol files (or dirs of sym files) to upload,
599 otherwise search |breakpad_dir|
600 upload_url: URL of crash server to upload too.
601 failed_list: A filename at which to write out a list of our failed uploads.
602 upload_limit: Integer listing how many files to upload. None for no limit.
603 strip_cfi: File size at which we strip out CFI data. None for no limit.
604 timeout: HTTP timeout for request.
605 api_key: A string based authentication key
Mike Frysinger1a736a82013-12-12 01:50:59 -0500606
Alex Klein1699fab2022-09-08 08:46:06 -0600607 Returns:
608 The number of errors that were encountered.
609 """
610 retry_stats.SetupStats()
Don Garrette1f47e92016-10-13 16:04:56 -0700611
Alex Klein1699fab2022-09-08 08:46:06 -0600612 # Note: This method looks like each step of processing is performed
613 # sequentially for all SymbolFiles, but instead each step is a generator that
614 # produces the next iteration only when it's read. This means that (except for
615 # some batching) each SymbolFile goes through all of these steps before the
616 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400617
Alex Klein1699fab2022-09-08 08:46:06 -0600618 # This is used to hold striped
619 with osutils.TempDir(prefix="upload_symbols.") as tempdir:
620 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500621
Alex Klein1699fab2022-09-08 08:46:06 -0600622 # Sort all of our symbols so the largest ones (probably the most important)
623 # are processed first.
624 symbols = list(symbols)
625 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
Don Garrett1bc1e102016-07-06 17:06:10 -0700626
Alex Klein1699fab2022-09-08 08:46:06 -0600627 if upload_limit is not None:
628 # Restrict symbols processed to the limit.
629 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400630
Alex Klein1699fab2022-09-08 08:46:06 -0600631 # Strip CFI, if needed.
632 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500633
Alex Klein1699fab2022-09-08 08:46:06 -0600634 # Find duplicates via batch API
635 symbols = FindDuplicates(symbols, upload_url, api_key, timeout)
Mike Nichols137e82d2019-05-15 18:40:34 -0600636
Alex Klein1699fab2022-09-08 08:46:06 -0600637 # Perform uploads
638 symbols = PerformSymbolsFileUpload(symbols, upload_url, api_key)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400639
Alex Klein1699fab2022-09-08 08:46:06 -0600640 # Log the final results, and consume the symbols generator fully.
641 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500642
Alex Klein1699fab2022-09-08 08:46:06 -0600643 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400644
645
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400646def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600647 parser = commandline.ArgumentParser(description=__doc__)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400648
Alex Klein1699fab2022-09-08 08:46:06 -0600649 # TODO: Make sym_paths, breakpad_root, and root exclusive.
Don Garretta28be6d2016-06-16 18:09:35 -0700650
Alex Klein1699fab2022-09-08 08:46:06 -0600651 parser.add_argument(
652 "sym_paths",
653 type="path_or_uri",
654 nargs="*",
655 default=None,
656 help="symbol file or directory or URL or tarball",
657 )
658 parser.add_argument(
659 "--board", default=None, help="Used to find default breakpad_root."
660 )
661 parser.add_argument(
662 "--breakpad_root",
663 type="path",
664 default=None,
665 help="full path to the breakpad symbol directory",
666 )
667 parser.add_argument(
668 "--root", type="path", default=None, help="full path to the chroot dir"
669 )
670 parser.add_argument(
671 "--official_build",
672 action="store_true",
673 default=False,
674 help="point to official symbol server",
675 )
676 parser.add_argument(
677 "--server", type=str, default=None, help="URI for custom symbol server"
678 )
679 parser.add_argument(
680 "--regenerate",
681 action="store_true",
682 default=False,
683 help="regenerate all symbols",
684 )
685 parser.add_argument(
686 "--upload-limit", type=int, help="only upload # number of symbols"
687 )
688 parser.add_argument(
689 "--strip_cfi",
690 type=int,
691 default=DEFAULT_FILE_LIMIT,
692 help="strip CFI data for files above this size",
693 )
694 parser.add_argument(
695 "--failed-list",
696 type="path",
697 help="where to save a list of failed symbols",
698 )
699 parser.add_argument(
700 "--dedupe",
701 action="store_true",
702 default=False,
703 help="use the swarming service to avoid re-uploading",
704 )
705 parser.add_argument(
706 "--yes",
707 action="store_true",
708 default=False,
709 help="answer yes to all prompts",
710 )
711 parser.add_argument(
712 "--product_name",
713 type=str,
714 default="ChromeOS",
715 help="Produce Name for breakpad stats.",
716 )
717 parser.add_argument(
718 "--api_key",
719 type=str,
720 default=None,
721 help="full path to the API key file",
722 )
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400723
Alex Klein1699fab2022-09-08 08:46:06 -0600724 opts = parser.parse_args(argv)
725 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400726
Alex Klein1699fab2022-09-08 08:46:06 -0600727 # Figure out the symbol files/directories to upload.
728 if opts.sym_paths:
729 sym_paths = opts.sym_paths
730 elif opts.breakpad_root:
731 sym_paths = [opts.breakpad_root]
732 elif opts.root:
733 if not opts.board:
734 cros_build_lib.Die("--board must be set if --root is used.")
735 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(
736 opts.board
737 )
738 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip("/"))]
Don Garretta28be6d2016-06-16 18:09:35 -0700739 else:
Alex Klein1699fab2022-09-08 08:46:06 -0600740 cros_build_lib.Die(
741 "--sym_paths, --breakpad_root, or --root must be set."
742 )
Don Garretta28be6d2016-06-16 18:09:35 -0700743
Alex Klein1699fab2022-09-08 08:46:06 -0600744 if opts.sym_paths or opts.breakpad_root:
745 if opts.regenerate:
746 cros_build_lib.Die(
747 "--regenerate may not be used with specific files, "
748 "or breakpad_root"
749 )
750 else:
751 if opts.board is None:
752 cros_build_lib.Die("--board is required")
Mike Nichols90f7c152019-04-09 15:14:08 -0600753
Alex Klein1699fab2022-09-08 08:46:06 -0600754 # Figure out which crash server to upload too.
755 upload_url = opts.server
756 if not upload_url:
757 if opts.official_build:
758 upload_url = OFFICIAL_UPLOAD_URL
759 else:
760 logging.warning("unofficial builds upload to the staging server")
761 upload_url = STAGING_UPLOAD_URL
Mike Nichols90f7c152019-04-09 15:14:08 -0600762
Alex Klein1699fab2022-09-08 08:46:06 -0600763 # Set up the API key needed to authenticate to Crash server.
764 # Allow for a local key file for testing purposes.
765 if opts.api_key:
766 api_key_file = opts.api_key
767 else:
768 api_key_file = constants.CRASH_API_KEY
769
770 api_key = osutils.ReadFile(api_key_file)
771
772 # Confirm we really want the long upload.
773 if not opts.yes:
774 prolog = "\n".join(
775 textwrap.wrap(
776 textwrap.dedent(
777 """
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400778 Uploading symbols for an entire Chromium OS build is really only
779 necessary for release builds and in a few cases for developers
780 to debug problems. It will take considerable time to run. For
781 developer debugging purposes, consider instead passing specific
782 files to upload.
Alex Klein1699fab2022-09-08 08:46:06 -0600783 """
784 ),
785 80,
786 )
787 ).strip()
788 if not cros_build_lib.BooleanPrompt(
789 prompt="Are you sure you want to upload all build symbols",
790 default=False,
791 prolog=prolog,
792 ):
793 cros_build_lib.Die("better safe than sorry")
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400794
Alex Klein1699fab2022-09-08 08:46:06 -0600795 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700796
Alex Klein1699fab2022-09-08 08:46:06 -0600797 # Regenerate symbols from binaries.
798 if opts.regenerate:
799 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
800 opts.board, breakpad_dir=opts.breakpad_root
801 )
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400802
Alex Klein1699fab2022-09-08 08:46:06 -0600803 # Do the upload.
804 ret += UploadSymbols(
805 sym_paths=sym_paths,
806 upload_url=upload_url,
807 failed_list=opts.failed_list,
808 upload_limit=opts.upload_limit,
809 strip_cfi=opts.strip_cfi,
810 api_key=api_key,
811 )
Don Garretta28be6d2016-06-16 18:09:35 -0700812
Alex Klein1699fab2022-09-08 08:46:06 -0600813 if ret:
814 logging.error("encountered %i problem(s)", ret)
815 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
816 # return 0 in case we are a multiple of the mask.
817 return 1