blob: 6896f8aabe454098fa4215e50184f59f9fb15077 [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:
Don Garretta28be6d2016-06-16 18:09:35 -0700266
Alex Klein1699fab2022-09-08 08:46:06 -0600267 with open(symbol.file_name, "rb") as fp:
268 temp_sym_file.writelines(
269 x for x in fp.readlines() if not x.startswith(b"STACK CFI")
270 )
271
272 original_file_size = file_size
273 symbol.file_name = temp_sym_file.name
274 file_size = symbol.FileSize()
275
276 logging.warning(
277 "stripped CFI for %s reducing size %s > %s",
278 symbol.display_name,
279 original_file_size,
280 file_size,
281 )
282
283 # Hopefully the crash server will let it through. But it probably won't.
284 # Not sure what the best answer is in this case.
285 if file_size >= CRASH_SERVER_FILE_LIMIT:
286 cbuildbot_alerts.PrintBuildbotStepWarnings()
287 logging.warning(
288 "upload file %s is awfully large, risking rejection by "
289 "the symbol server (%s > %s)",
290 symbol.display_path,
291 file_size,
292 CRASH_SERVER_FILE_LIMIT,
Mike Frysingerac75f602019-11-14 23:18:51 -0500293 )
Don Garretta28be6d2016-06-16 18:09:35 -0700294
Alex Klein1699fab2022-09-08 08:46:06 -0600295 return symbol
Don Garretta28be6d2016-06-16 18:09:35 -0700296
Don Garretta28be6d2016-06-16 18:09:35 -0700297
298def GetUploadTimeout(symbol):
Alex Klein1699fab2022-09-08 08:46:06 -0600299 """How long to wait for a specific file to upload to the crash server.
Don Garretta28be6d2016-06-16 18:09:35 -0700300
Alex Klein1699fab2022-09-08 08:46:06 -0600301 This is a function largely to make unittesting easier.
Don Garretta28be6d2016-06-16 18:09:35 -0700302
Alex Klein1699fab2022-09-08 08:46:06 -0600303 Args:
304 symbol: A SymbolFile instance.
Don Garretta28be6d2016-06-16 18:09:35 -0700305
Alex Klein1699fab2022-09-08 08:46:06 -0600306 Returns:
307 Timeout length (in seconds)
308 """
309 # Scale the timeout based on the filesize.
310 return max(symbol.FileSize() // UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
Don Garretta28be6d2016-06-16 18:09:35 -0700311
312
Mike Nichols90f7c152019-04-09 15:14:08 -0600313def ExecRequest(operator, url, timeout, api_key, **kwargs):
Alex Klein1699fab2022-09-08 08:46:06 -0600314 """Makes a web request with default timeout, returning the json result.
Don Garretta28be6d2016-06-16 18:09:35 -0700315
Alex Klein1699fab2022-09-08 08:46:06 -0600316 This method will raise a requests.exceptions.HTTPError if the status
317 code is not 4XX, 5XX
Mike Nichols90f7c152019-04-09 15:14:08 -0600318
Alex Klein1699fab2022-09-08 08:46:06 -0600319 Note: If you are using verbose logging it is entirely possible that the
320 subsystem will write your api key to the logs!
Mike Nichols90f7c152019-04-09 15:14:08 -0600321
Alex Klein1699fab2022-09-08 08:46:06 -0600322 Args:
323 operator: HTTP method.
324 url: Endpoint URL.
325 timeout: HTTP timeout for request.
326 api_key: Authentication key.
Mike Nichols90f7c152019-04-09 15:14:08 -0600327
Alex Klein1699fab2022-09-08 08:46:06 -0600328 Returns:
329 HTTP response content
330 """
331 resp = requests.request(
332 operator,
333 url,
334 params={"key": api_key},
335 headers={"User-agent": "chromite.upload_symbols"},
336 timeout=timeout,
337 **kwargs,
338 )
339 # Make sure we don't leak secret keys by accident.
340 if resp.status_code > 399:
341 resp.url = resp.url.replace(urllib.parse.quote(api_key), "XX-HIDDEN-XX")
342 logging.warning(
343 'Url: %s, Status: %s, response: "%s", in: %s',
344 resp.url,
345 resp.status_code,
346 resp.text,
347 resp.elapsed,
348 )
349 elif resp.content:
350 return resp.json()
Mike Nicholscf5b7a92019-04-25 12:01:28 -0600351
Alex Klein1699fab2022-09-08 08:46:06 -0600352 return {}
Mike Nichols90f7c152019-04-09 15:14:08 -0600353
354
Mike Nichols137e82d2019-05-15 18:40:34 -0600355def FindDuplicates(symbols, status_url, api_key, timeout=DEDUPE_TIMEOUT):
Alex Klein1699fab2022-09-08 08:46:06 -0600356 """Check whether the symbol files have already been uploaded.
Mike Nichols649e6a82019-04-23 11:44:48 -0600357
Alex Klein1699fab2022-09-08 08:46:06 -0600358 Args:
359 symbols: A iterable of SymbolFiles to be uploaded
360 status_url: The crash URL to validate the file existence.
361 api_key: Authentication key.
362 timeout: HTTP timeout for request.
Mike Nichols649e6a82019-04-23 11:44:48 -0600363
Alex Klein1699fab2022-09-08 08:46:06 -0600364 Yields:
365 All SymbolFiles from symbols, but duplicates have status updated to
366 DUPLICATE.
367 """
368 for batch in BatchGenerator(symbols, DEDUPE_LIMIT):
369 items = []
370 result = {}
371 for x in batch:
372 items.append(
373 {
374 "debug_file": x.header.name,
375 "debug_id": x.header.id.replace("-", ""),
376 }
377 )
378 symbol_data = {"symbol_ids": items}
379 try:
380 result = ExecRequest(
381 "post",
382 "%s/symbols:checkStatuses" % status_url,
383 timeout,
384 api_key=api_key,
385 data=json.dumps(symbol_data),
386 )
387 except requests.exceptions.RequestException as e:
388 logging.warning("could not identify duplicates: HTTP error: %s", e)
389 for b in batch:
390 b.status = SymbolFile.INITIAL
391 set_match = {
392 "debugId": b.header.id.replace("-", ""),
393 "debugFile": b.header.name,
394 }
395 for cs_result in result.get("pairs", []):
396 if set_match == cs_result.get("symbolId"):
397 if cs_result.get("status") == "FOUND":
398 logging.debug("Found duplicate: %s", b.display_name)
399 b.status = SymbolFile.DUPLICATE
400 break
401 yield b
Mike Nichols137e82d2019-05-15 18:40:34 -0600402
Mike Nichols649e6a82019-04-23 11:44:48 -0600403
Mike Nichols90f7c152019-04-09 15:14:08 -0600404def UploadSymbolFile(upload_url, symbol, api_key):
Alex Klein1699fab2022-09-08 08:46:06 -0600405 """Upload a symbol file to the crash server, returning the status result.
Don Garretta28be6d2016-06-16 18:09:35 -0700406
Alex Klein1699fab2022-09-08 08:46:06 -0600407 Args:
408 upload_url: The crash URL to POST the |sym_file| to
409 symbol: A SymbolFile instance.
410 api_key: Authentication key
411 """
412 timeout = GetUploadTimeout(symbol)
413 logging.debug("Executing post to uploads:create: %s", symbol.display_name)
414 upload = ExecRequest(
415 "post", "%s/uploads:create" % upload_url, timeout, api_key
416 )
Mike Nichols649e6a82019-04-23 11:44:48 -0600417
Alex Klein1699fab2022-09-08 08:46:06 -0600418 if upload and "uploadUrl" in upload.keys():
419 symbol_data = {
420 "symbol_id": {
421 "debug_file": symbol.header.name,
422 "debug_id": symbol.header.id.replace("-", ""),
423 }
424 }
425 with open(symbol.file_name, "r") as fp:
426 logging.debug("Executing put to uploadUrl: %s", symbol.display_name)
427 ExecRequest(
428 "put",
429 upload["uploadUrl"],
430 timeout,
431 api_key=api_key,
432 data=fp.read(),
433 )
434 logging.debug(
435 "Executing post to uploads/complete: %s", symbol.display_name
436 )
437 ExecRequest(
438 "post",
439 "%s/uploads/%s:complete" % (upload_url, upload["uploadKey"]),
440 timeout,
441 api_key=api_key,
442 # TODO(mikenichols): Validate product_name once it is added
443 # to the proto; currently unsupported.
444 data=json.dumps(symbol_data),
445 )
446 else:
447 raise requests.exceptions.HTTPError
Don Garretta28be6d2016-06-16 18:09:35 -0700448
449
Mike Nichols90f7c152019-04-09 15:14:08 -0600450def PerformSymbolsFileUpload(symbols, upload_url, api_key):
Alex Klein1699fab2022-09-08 08:46:06 -0600451 """Upload the symbols to the crash server
Don Garretta28be6d2016-06-16 18:09:35 -0700452
Alex Klein1699fab2022-09-08 08:46:06 -0600453 Args:
454 symbols: An iterable of SymbolFiles to be uploaded.
455 upload_url: URL of crash server to upload too.
456 api_key: Authentication key.
457 failures: Tracker for total upload failures.
Don Garretta28be6d2016-06-16 18:09:35 -0700458
Alex Klein1699fab2022-09-08 08:46:06 -0600459 Yields:
460 Each symbol from symbols, perhaps modified.
461 """
462 failures = 0
463 # Failures are figured per request, therefore each HTTP failure
464 # counts against the max. This means that the process will exit
465 # at the point when HTTP errors hit the defined limit.
466 for s in symbols:
467 if failures < MAX_TOTAL_ERRORS_FOR_RETRY and s.status in (
468 SymbolFile.INITIAL,
469 SymbolFile.ERROR,
470 ):
471 # Keeps us from DoS-ing the symbol server.
472 time.sleep(SLEEP_DELAY)
473 logging.info("Uploading symbol_file: %s", s.display_path)
474 try:
475 # This command retries the upload multiple times with growing delays. We
476 # only consider the upload a failure if these retries fail.
477 def ShouldRetryUpload(exception):
478 if isinstance(
479 exception,
480 (
481 requests.exceptions.RequestException,
482 IOError,
483 http.client.HTTPException,
484 socket.error,
485 ),
486 ):
487 logging.info("Request failed, retrying: %s", exception)
488 return True
489 return False
Don Garrette1f47e92016-10-13 16:04:56 -0700490
Alex Klein1699fab2022-09-08 08:46:06 -0600491 with timer.Timer() as t:
492 retry_stats.RetryWithStats(
493 UPLOAD_STATS,
494 ShouldRetryUpload,
495 MAX_RETRIES,
496 UploadSymbolFile,
497 upload_url,
498 s,
499 api_key,
500 sleep=INITIAL_RETRY_DELAY,
501 log_all_retries=True,
502 )
503 if s.status != SymbolFile.DUPLICATE:
504 logging.info(
505 "upload of %s with size %10i bytes took %s",
506 s.display_name,
507 s.FileSize(),
508 t,
509 )
510 s.status = SymbolFile.UPLOADED
511 except requests.exceptions.RequestException as e:
512 logging.warning(
513 "could not upload: %s: HTTP error: %s", s.display_name, e
514 )
515 s.status = SymbolFile.ERROR
516 failures += 1
517 except (http.client.HTTPException, OSError) as e:
518 logging.warning(
519 "could not upload: %s: %s %s",
520 s.display_name,
521 type(e).__name__,
522 e,
523 )
524 s.status = SymbolFile.ERROR
525 failures += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700526
Alex Klein1699fab2022-09-08 08:46:06 -0600527 # We pass the symbol along, on both success and failure.
528 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700529
530
Don Garrettdeb2e032016-07-06 16:44:14 -0700531def ReportResults(symbols, failed_list):
Alex Klein1699fab2022-09-08 08:46:06 -0600532 """Log a summary of the symbol uploading.
Don Garretta28be6d2016-06-16 18:09:35 -0700533
Alex Klein1699fab2022-09-08 08:46:06 -0600534 This has the side effect of fully consuming the symbols iterator.
Don Garretta28be6d2016-06-16 18:09:35 -0700535
Alex Klein1699fab2022-09-08 08:46:06 -0600536 Args:
537 symbols: An iterator of SymbolFiles to be uploaded.
538 failed_list: A filename at which to write out a list of our failed uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700539
Alex Klein1699fab2022-09-08 08:46:06 -0600540 Returns:
541 The number of symbols not uploaded.
542 """
543 upload_failures = []
544 result_counts = {
545 SymbolFile.INITIAL: 0,
546 SymbolFile.UPLOADED: 0,
547 SymbolFile.DUPLICATE: 0,
548 SymbolFile.ERROR: 0,
549 }
Don Garretta28be6d2016-06-16 18:09:35 -0700550
Alex Klein1699fab2022-09-08 08:46:06 -0600551 for s in symbols:
552 result_counts[s.status] += 1
553 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
554 upload_failures.append(s)
Don Garretta28be6d2016-06-16 18:09:35 -0700555
Alex Klein1699fab2022-09-08 08:46:06 -0600556 # Report retry numbers.
557 _, _, retries = retry_stats.CategoryStats(UPLOAD_STATS)
558 if retries:
559 logging.warning("%d upload retries performed.", retries)
Don Garrette1f47e92016-10-13 16:04:56 -0700560
Alex Klein1699fab2022-09-08 08:46:06 -0600561 logging.info(
562 "Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.",
563 result_counts,
564 )
Don Garretta28be6d2016-06-16 18:09:35 -0700565
Alex Klein1699fab2022-09-08 08:46:06 -0600566 if result_counts[SymbolFile.ERROR]:
567 cbuildbot_alerts.PrintBuildbotStepWarnings()
568 logging.warning(
569 "%d non-recoverable upload errors", result_counts[SymbolFile.ERROR]
570 )
Chris Ching91908032016-09-27 16:55:33 -0600571
Alex Klein1699fab2022-09-08 08:46:06 -0600572 if result_counts[SymbolFile.INITIAL]:
573 cbuildbot_alerts.PrintBuildbotStepWarnings()
574 logging.warning(
575 "%d upload(s) were skipped because of excessive errors",
576 result_counts[SymbolFile.INITIAL],
577 )
Don Garretta28be6d2016-06-16 18:09:35 -0700578
Alex Klein1699fab2022-09-08 08:46:06 -0600579 if failed_list is not None:
580 with open(failed_list, "w") as fl:
581 for s in upload_failures:
582 fl.write("%s\n" % s.display_path)
Don Garretta28be6d2016-06-16 18:09:35 -0700583
Alex Klein1699fab2022-09-08 08:46:06 -0600584 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
Don Garrettdeb2e032016-07-06 16:44:14 -0700585
Don Garretta28be6d2016-06-16 18:09:35 -0700586
Alex Klein1699fab2022-09-08 08:46:06 -0600587def UploadSymbols(
588 sym_paths,
589 upload_url,
590 failed_list=None,
591 upload_limit=None,
592 strip_cfi=None,
593 timeout=None,
594 api_key=None,
595):
596 """Upload all the generated symbols for |board| to the crash server
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400597
Alex Klein1699fab2022-09-08 08:46:06 -0600598 Args:
599 sym_paths: Specific symbol files (or dirs of sym files) to upload,
600 otherwise search |breakpad_dir|
601 upload_url: URL of crash server to upload too.
602 failed_list: A filename at which to write out a list of our failed uploads.
603 upload_limit: Integer listing how many files to upload. None for no limit.
604 strip_cfi: File size at which we strip out CFI data. None for no limit.
605 timeout: HTTP timeout for request.
606 api_key: A string based authentication key
Mike Frysinger1a736a82013-12-12 01:50:59 -0500607
Alex Klein1699fab2022-09-08 08:46:06 -0600608 Returns:
609 The number of errors that were encountered.
610 """
611 retry_stats.SetupStats()
Don Garrette1f47e92016-10-13 16:04:56 -0700612
Alex Klein1699fab2022-09-08 08:46:06 -0600613 # Note: This method looks like each step of processing is performed
614 # sequentially for all SymbolFiles, but instead each step is a generator that
615 # produces the next iteration only when it's read. This means that (except for
616 # some batching) each SymbolFile goes through all of these steps before the
617 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400618
Alex Klein1699fab2022-09-08 08:46:06 -0600619 # This is used to hold striped
620 with osutils.TempDir(prefix="upload_symbols.") as tempdir:
621 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500622
Alex Klein1699fab2022-09-08 08:46:06 -0600623 # Sort all of our symbols so the largest ones (probably the most important)
624 # are processed first.
625 symbols = list(symbols)
626 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
Don Garrett1bc1e102016-07-06 17:06:10 -0700627
Alex Klein1699fab2022-09-08 08:46:06 -0600628 if upload_limit is not None:
629 # Restrict symbols processed to the limit.
630 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400631
Alex Klein1699fab2022-09-08 08:46:06 -0600632 # Strip CFI, if needed.
633 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500634
Alex Klein1699fab2022-09-08 08:46:06 -0600635 # Find duplicates via batch API
636 symbols = FindDuplicates(symbols, upload_url, api_key, timeout)
Mike Nichols137e82d2019-05-15 18:40:34 -0600637
Alex Klein1699fab2022-09-08 08:46:06 -0600638 # Perform uploads
639 symbols = PerformSymbolsFileUpload(symbols, upload_url, api_key)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400640
Alex Klein1699fab2022-09-08 08:46:06 -0600641 # Log the final results, and consume the symbols generator fully.
642 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500643
Alex Klein1699fab2022-09-08 08:46:06 -0600644 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400645
646
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400647def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600648 parser = commandline.ArgumentParser(description=__doc__)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400649
Alex Klein1699fab2022-09-08 08:46:06 -0600650 # TODO: Make sym_paths, breakpad_root, and root exclusive.
Don Garretta28be6d2016-06-16 18:09:35 -0700651
Alex Klein1699fab2022-09-08 08:46:06 -0600652 parser.add_argument(
653 "sym_paths",
654 type="path_or_uri",
655 nargs="*",
656 default=None,
657 help="symbol file or directory or URL or tarball",
658 )
659 parser.add_argument(
660 "--board", default=None, help="Used to find default breakpad_root."
661 )
662 parser.add_argument(
663 "--breakpad_root",
664 type="path",
665 default=None,
666 help="full path to the breakpad symbol directory",
667 )
668 parser.add_argument(
669 "--root", type="path", default=None, help="full path to the chroot dir"
670 )
671 parser.add_argument(
672 "--official_build",
673 action="store_true",
674 default=False,
675 help="point to official symbol server",
676 )
677 parser.add_argument(
678 "--server", type=str, default=None, help="URI for custom symbol server"
679 )
680 parser.add_argument(
681 "--regenerate",
682 action="store_true",
683 default=False,
684 help="regenerate all symbols",
685 )
686 parser.add_argument(
687 "--upload-limit", type=int, help="only upload # number of symbols"
688 )
689 parser.add_argument(
690 "--strip_cfi",
691 type=int,
692 default=DEFAULT_FILE_LIMIT,
693 help="strip CFI data for files above this size",
694 )
695 parser.add_argument(
696 "--failed-list",
697 type="path",
698 help="where to save a list of failed symbols",
699 )
700 parser.add_argument(
701 "--dedupe",
702 action="store_true",
703 default=False,
704 help="use the swarming service to avoid re-uploading",
705 )
706 parser.add_argument(
707 "--yes",
708 action="store_true",
709 default=False,
710 help="answer yes to all prompts",
711 )
712 parser.add_argument(
713 "--product_name",
714 type=str,
715 default="ChromeOS",
716 help="Produce Name for breakpad stats.",
717 )
718 parser.add_argument(
719 "--api_key",
720 type=str,
721 default=None,
722 help="full path to the API key file",
723 )
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400724
Alex Klein1699fab2022-09-08 08:46:06 -0600725 opts = parser.parse_args(argv)
726 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400727
Alex Klein1699fab2022-09-08 08:46:06 -0600728 # Figure out the symbol files/directories to upload.
729 if opts.sym_paths:
730 sym_paths = opts.sym_paths
731 elif opts.breakpad_root:
732 sym_paths = [opts.breakpad_root]
733 elif opts.root:
734 if not opts.board:
735 cros_build_lib.Die("--board must be set if --root is used.")
736 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(
737 opts.board
738 )
739 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip("/"))]
Don Garretta28be6d2016-06-16 18:09:35 -0700740 else:
Alex Klein1699fab2022-09-08 08:46:06 -0600741 cros_build_lib.Die(
742 "--sym_paths, --breakpad_root, or --root must be set."
743 )
Don Garretta28be6d2016-06-16 18:09:35 -0700744
Alex Klein1699fab2022-09-08 08:46:06 -0600745 if opts.sym_paths or opts.breakpad_root:
746 if opts.regenerate:
747 cros_build_lib.Die(
748 "--regenerate may not be used with specific files, "
749 "or breakpad_root"
750 )
751 else:
752 if opts.board is None:
753 cros_build_lib.Die("--board is required")
Mike Nichols90f7c152019-04-09 15:14:08 -0600754
Alex Klein1699fab2022-09-08 08:46:06 -0600755 # Figure out which crash server to upload too.
756 upload_url = opts.server
757 if not upload_url:
758 if opts.official_build:
759 upload_url = OFFICIAL_UPLOAD_URL
760 else:
761 logging.warning("unofficial builds upload to the staging server")
762 upload_url = STAGING_UPLOAD_URL
Mike Nichols90f7c152019-04-09 15:14:08 -0600763
Alex Klein1699fab2022-09-08 08:46:06 -0600764 # Set up the API key needed to authenticate to Crash server.
765 # Allow for a local key file for testing purposes.
766 if opts.api_key:
767 api_key_file = opts.api_key
768 else:
769 api_key_file = constants.CRASH_API_KEY
770
771 api_key = osutils.ReadFile(api_key_file)
772
773 # Confirm we really want the long upload.
774 if not opts.yes:
775 prolog = "\n".join(
776 textwrap.wrap(
777 textwrap.dedent(
778 """
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400779 Uploading symbols for an entire Chromium OS build is really only
780 necessary for release builds and in a few cases for developers
781 to debug problems. It will take considerable time to run. For
782 developer debugging purposes, consider instead passing specific
783 files to upload.
Alex Klein1699fab2022-09-08 08:46:06 -0600784 """
785 ),
786 80,
787 )
788 ).strip()
789 if not cros_build_lib.BooleanPrompt(
790 prompt="Are you sure you want to upload all build symbols",
791 default=False,
792 prolog=prolog,
793 ):
794 cros_build_lib.Die("better safe than sorry")
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400795
Alex Klein1699fab2022-09-08 08:46:06 -0600796 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700797
Alex Klein1699fab2022-09-08 08:46:06 -0600798 # Regenerate symbols from binaries.
799 if opts.regenerate:
800 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
801 opts.board, breakpad_dir=opts.breakpad_root
802 )
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400803
Alex Klein1699fab2022-09-08 08:46:06 -0600804 # Do the upload.
805 ret += UploadSymbols(
806 sym_paths=sym_paths,
807 upload_url=upload_url,
808 failed_list=opts.failed_list,
809 upload_limit=opts.upload_limit,
810 strip_cfi=opts.strip_cfi,
811 api_key=api_key,
812 )
Don Garretta28be6d2016-06-16 18:09:35 -0700813
Alex Klein1699fab2022-09-08 08:46:06 -0600814 if ret:
815 logging.error("encountered %i problem(s)", ret)
816 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
817 # return 0 in case we are a multiple of the mask.
818 return 1