blob: ec3652bf50a6dca2770980ef4afa8c8f4873fa37 [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
Mike Frysinger9c9fa1a2023-08-04 23:39:43 -040099# Crash Server upload API key.
100CRASH_API_KEY = os.path.join(
101 "/", "creds", "api_keys", "api_key-chromeos-crash-uploader"
102)
103
Don Garretta28be6d2016-06-16 18:09:35 -0700104
Don Garretta28be6d2016-06-16 18:09:35 -0700105def BatchGenerator(iterator, batch_size):
Alex Klein1699fab2022-09-08 08:46:06 -0600106 """Given an iterator, break into lists of size batch_size.
Fang Dengba680462015-08-16 20:34:11 -0700107
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000108 The result is a generator, that will only read in as many inputs as needed
109 for the current batch. The final result can be smaller than batch_size.
Alex Klein1699fab2022-09-08 08:46:06 -0600110 """
111 batch = []
112 for i in iterator:
113 batch.append(i)
114 if len(batch) >= batch_size:
115 yield batch
116 batch = []
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700117
Alex Klein1699fab2022-09-08 08:46:06 -0600118 if batch:
119 # if there was anything left in the final batch, yield it.
120 yield batch
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500121
122
Mike Frysingerd41938e2014-02-10 06:37:55 -0500123def IsTarball(path):
Alex Klein1699fab2022-09-08 08:46:06 -0600124 """Guess if this is a tarball based on the filename."""
125 parts = path.split(".")
126 if len(parts) <= 1:
127 return False
Mike Frysingerd41938e2014-02-10 06:37:55 -0500128
Alex Klein1699fab2022-09-08 08:46:06 -0600129 if parts[-1] == "tar":
130 return True
Mike Frysingerd41938e2014-02-10 06:37:55 -0500131
Alex Klein1699fab2022-09-08 08:46:06 -0600132 if parts[-2] == "tar":
133 return parts[-1] in ("bz2", "gz", "xz")
Mike Frysingerd41938e2014-02-10 06:37:55 -0500134
Alex Klein1699fab2022-09-08 08:46:06 -0600135 return parts[-1] in ("tbz2", "tbz", "tgz", "txz")
Mike Frysingerd41938e2014-02-10 06:37:55 -0500136
137
Alex Klein074f94f2023-06-22 10:32:06 -0600138class SymbolFile:
Alex Klein1699fab2022-09-08 08:46:06 -0600139 """This class represents the state of a symbol file during processing.
Don Garretta28be6d2016-06-16 18:09:35 -0700140
Alex Klein1699fab2022-09-08 08:46:06 -0600141 Attributes:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000142 display_path: Name of symbol file that should be consistent between
143 builds.
144 file_name: Transient path of the symbol file.
145 header: ReadSymsHeader output. Dict with assorted meta-data.
146 status: INITIAL, DUPLICATE, or UPLOADED based on status of processing.
147 dedupe_item: None or instance of DedupeItem for this symbol file.
148 dedupe_push_state: Opaque value to return to dedupe code for file.
149 display_name: Read only friendly (short) file name for logging.
150 file_size: Read only size of the symbol file.
Don Garretta28be6d2016-06-16 18:09:35 -0700151 """
Don Garretta28be6d2016-06-16 18:09:35 -0700152
Alex Klein1699fab2022-09-08 08:46:06 -0600153 INITIAL = "initial"
154 DUPLICATE = "duplicate"
155 UPLOADED = "uploaded"
156 ERROR = "error"
Don Garretta28be6d2016-06-16 18:09:35 -0700157
Alex Klein1699fab2022-09-08 08:46:06 -0600158 def __init__(self, display_path, file_name):
159 """An instance of this class represents a symbol file over time.
160
161 Args:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000162 display_path: A unique/persistent between builds name to present to
163 the crash server. It is the file name, relative to where it came
164 from (tarball, breakpad dir, etc).
165 file_name: A the current location of the symbol file.
Alex Klein1699fab2022-09-08 08:46:06 -0600166 """
167 self.display_path = display_path
168 self.file_name = file_name
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800169 self.header = cros_generate_breakpad_symbols.ReadSymsHeader(
170 file_name, file_name
171 )
Alex Klein1699fab2022-09-08 08:46:06 -0600172 self.status = SymbolFile.INITIAL
173
174 @property
175 def display_name(self):
176 return os.path.basename(self.display_path)
177
178 def FileSize(self):
179 return os.path.getsize(self.file_name)
Don Garretta28be6d2016-06-16 18:09:35 -0700180
181
Don Garretta28be6d2016-06-16 18:09:35 -0700182def FindSymbolFiles(tempdir, paths):
Alex Klein1699fab2022-09-08 08:46:06 -0600183 """Locate symbol files in |paths|
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500184
Alex Klein1699fab2022-09-08 08:46:06 -0600185 This returns SymbolFile objects that contain file references which are valid
186 after this exits. Those files may exist externally, or be created in the
187 tempdir (say, when expanding tarballs). The caller must not consider
188 SymbolFile's valid after tempdir is cleaned up.
Don Garretta28be6d2016-06-16 18:09:35 -0700189
Alex Klein1699fab2022-09-08 08:46:06 -0600190 Args:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000191 tempdir: Path to use for temporary files.
192 paths: A list of input paths to walk. Files are returned w/out any
193 checks. Dirs are searched for files that end in ".sym". Urls are
194 fetched and then processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500195
Alex Klein1699fab2022-09-08 08:46:06 -0600196 Yields:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000197 A SymbolFile for every symbol file found in paths.
Alex Klein1699fab2022-09-08 08:46:06 -0600198 """
199 cache_dir = path_util.GetCacheDir()
200 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
201 tar_cache = cache.TarballCache(common_path)
Mike Frysingere847efd2015-01-08 03:57:24 -0500202
Alex Klein1699fab2022-09-08 08:46:06 -0600203 for p in paths:
204 o = urllib.parse.urlparse(p)
205 if o.scheme:
206 # Support globs of filenames.
207 ctx = gs.GSContext()
208 for gspath in ctx.LS(p):
209 logging.info("processing files inside %s", gspath)
210 o = urllib.parse.urlparse(gspath)
211 key = ("%s%s" % (o.netloc, o.path)).split("/")
Trent Apted4a0812b2023-05-15 15:33:55 +1000212 # The common cache will not be LRU, removing the need to hold a
213 # read lock on the cached gsutil.
Alex Klein1699fab2022-09-08 08:46:06 -0600214 ref = tar_cache.Lookup(key)
215 try:
216 ref.SetDefault(gspath)
217 except cros_build_lib.RunCommandError as e:
218 logging.warning("ignoring %s\n%s", gspath, e)
219 continue
220 for sym in FindSymbolFiles(tempdir, [ref.path]):
221 yield sym
Mike Frysingerd41938e2014-02-10 06:37:55 -0500222
Alex Klein1699fab2022-09-08 08:46:06 -0600223 elif os.path.isdir(p):
224 for root, _, files in os.walk(p):
225 for f in files:
226 if f.endswith(".sym"):
Trent Apted4a0812b2023-05-15 15:33:55 +1000227 # If p is '/tmp/foo' and filename is
228 # '/tmp/foo/bar/bar.sym', display_path = 'bar/bar.sym'
Alex Klein1699fab2022-09-08 08:46:06 -0600229 filename = os.path.join(root, f)
230 yield SymbolFile(
231 display_path=filename[len(p) :].lstrip("/"),
232 file_name=filename,
233 )
Mike Frysingerd41938e2014-02-10 06:37:55 -0500234
Alex Klein1699fab2022-09-08 08:46:06 -0600235 elif IsTarball(p):
236 logging.info("processing files inside %s", p)
237 tardir = tempfile.mkdtemp(dir=tempdir)
238 cache.Untar(os.path.realpath(p), tardir)
239 for sym in FindSymbolFiles(tardir, [tardir]):
240 yield sym
Mike Frysingerd41938e2014-02-10 06:37:55 -0500241
Alex Klein1699fab2022-09-08 08:46:06 -0600242 else:
243 yield SymbolFile(display_path=p, file_name=p)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500244
245
Don Garretta28be6d2016-06-16 18:09:35 -0700246def AdjustSymbolFileSize(symbol, tempdir, file_limit):
Alex Klein1699fab2022-09-08 08:46:06 -0600247 """Examine symbols files for size problems, and reduce if needed.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500248
Alex Klein1699fab2022-09-08 08:46:06 -0600249 If the symbols size is too big, strip out the call frame info. The CFI
250 is unnecessary for 32bit x86 targets where the frame pointer is used (as
251 all of ours have) and it accounts for over half the size of the symbols
252 uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700253
Alex Klein1699fab2022-09-08 08:46:06 -0600254 Stripped files will be created inside tempdir, and will be the callers
255 responsibility to clean up.
Don Garretta28be6d2016-06-16 18:09:35 -0700256
Alex Klein1699fab2022-09-08 08:46:06 -0600257 We also warn, if a symbols file is still too large after stripping.
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500258
Alex Klein1699fab2022-09-08 08:46:06 -0600259 Args:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000260 symbol: SymbolFile instance to be examined and modified as needed..
261 tempdir: A temporary directory we can create files in that the caller
262 will clean up.
263 file_limit: We only strip files which are larger than this limit.
Don Garretta28be6d2016-06-16 18:09:35 -0700264
Alex Klein1699fab2022-09-08 08:46:06 -0600265 Returns:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000266 SymbolFile instance (original or modified as needed)
Alex Klein1699fab2022-09-08 08:46:06 -0600267 """
268 file_size = symbol.FileSize()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500269
Alex Klein1699fab2022-09-08 08:46:06 -0600270 if file_limit and symbol.FileSize() > file_limit:
271 with cros_build_lib.UnbufferedNamedTemporaryFile(
272 prefix="upload_symbols", dir=tempdir, delete=False
273 ) as temp_sym_file:
Alex Klein1699fab2022-09-08 08:46:06 -0600274 with open(symbol.file_name, "rb") as fp:
275 temp_sym_file.writelines(
276 x for x in fp.readlines() if not x.startswith(b"STACK CFI")
277 )
278
279 original_file_size = file_size
280 symbol.file_name = temp_sym_file.name
281 file_size = symbol.FileSize()
282
283 logging.warning(
284 "stripped CFI for %s reducing size %s > %s",
285 symbol.display_name,
286 original_file_size,
287 file_size,
288 )
289
290 # Hopefully the crash server will let it through. But it probably won't.
291 # Not sure what the best answer is in this case.
292 if file_size >= CRASH_SERVER_FILE_LIMIT:
293 cbuildbot_alerts.PrintBuildbotStepWarnings()
294 logging.warning(
295 "upload file %s is awfully large, risking rejection by "
296 "the symbol server (%s > %s)",
297 symbol.display_path,
298 file_size,
299 CRASH_SERVER_FILE_LIMIT,
Mike Frysingerac75f602019-11-14 23:18:51 -0500300 )
Don Garretta28be6d2016-06-16 18:09:35 -0700301
Alex Klein1699fab2022-09-08 08:46:06 -0600302 return symbol
Don Garretta28be6d2016-06-16 18:09:35 -0700303
Don Garretta28be6d2016-06-16 18:09:35 -0700304
305def GetUploadTimeout(symbol):
Alex Klein1699fab2022-09-08 08:46:06 -0600306 """How long to wait for a specific file to upload to the crash server.
Don Garretta28be6d2016-06-16 18:09:35 -0700307
Alex Klein1699fab2022-09-08 08:46:06 -0600308 This is a function largely to make unittesting easier.
Don Garretta28be6d2016-06-16 18:09:35 -0700309
Alex Klein1699fab2022-09-08 08:46:06 -0600310 Args:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000311 symbol: A SymbolFile instance.
Don Garretta28be6d2016-06-16 18:09:35 -0700312
Alex Klein1699fab2022-09-08 08:46:06 -0600313 Returns:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000314 Timeout length (in seconds)
Alex Klein1699fab2022-09-08 08:46:06 -0600315 """
316 # Scale the timeout based on the filesize.
317 return max(symbol.FileSize() // UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
Don Garretta28be6d2016-06-16 18:09:35 -0700318
319
Mike Nichols90f7c152019-04-09 15:14:08 -0600320def ExecRequest(operator, url, timeout, api_key, **kwargs):
Alex Klein1699fab2022-09-08 08:46:06 -0600321 """Makes a web request with default timeout, returning the json result.
Don Garretta28be6d2016-06-16 18:09:35 -0700322
Alex Klein1699fab2022-09-08 08:46:06 -0600323 This method will raise a requests.exceptions.HTTPError if the status
324 code is not 4XX, 5XX
Mike Nichols90f7c152019-04-09 15:14:08 -0600325
Alex Klein1699fab2022-09-08 08:46:06 -0600326 Note: If you are using verbose logging it is entirely possible that the
327 subsystem will write your api key to the logs!
Mike Nichols90f7c152019-04-09 15:14:08 -0600328
Alex Klein1699fab2022-09-08 08:46:06 -0600329 Args:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000330 operator: HTTP method.
331 url: Endpoint URL.
332 timeout: HTTP timeout for request.
333 api_key: Authentication key.
Mike Nichols90f7c152019-04-09 15:14:08 -0600334
Alex Klein1699fab2022-09-08 08:46:06 -0600335 Returns:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000336 HTTP response content
Alex Klein1699fab2022-09-08 08:46:06 -0600337 """
338 resp = requests.request(
339 operator,
340 url,
341 params={"key": api_key},
342 headers={"User-agent": "chromite.upload_symbols"},
343 timeout=timeout,
344 **kwargs,
345 )
346 # Make sure we don't leak secret keys by accident.
347 if resp.status_code > 399:
348 resp.url = resp.url.replace(urllib.parse.quote(api_key), "XX-HIDDEN-XX")
349 logging.warning(
350 'Url: %s, Status: %s, response: "%s", in: %s',
351 resp.url,
352 resp.status_code,
353 resp.text,
354 resp.elapsed,
355 )
356 elif resp.content:
357 return resp.json()
Mike Nicholscf5b7a92019-04-25 12:01:28 -0600358
Alex Klein1699fab2022-09-08 08:46:06 -0600359 return {}
Mike Nichols90f7c152019-04-09 15:14:08 -0600360
361
Mike Nichols137e82d2019-05-15 18:40:34 -0600362def FindDuplicates(symbols, status_url, api_key, timeout=DEDUPE_TIMEOUT):
Alex Klein1699fab2022-09-08 08:46:06 -0600363 """Check whether the symbol files have already been uploaded.
Mike Nichols649e6a82019-04-23 11:44:48 -0600364
Alex Klein1699fab2022-09-08 08:46:06 -0600365 Args:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000366 symbols: A iterable of SymbolFiles to be uploaded
367 status_url: The crash URL to validate the file existence.
368 api_key: Authentication key.
369 timeout: HTTP timeout for request.
Mike Nichols649e6a82019-04-23 11:44:48 -0600370
Alex Klein1699fab2022-09-08 08:46:06 -0600371 Yields:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000372 All SymbolFiles from symbols, but duplicates have status updated to
373 DUPLICATE.
Alex Klein1699fab2022-09-08 08:46:06 -0600374 """
375 for batch in BatchGenerator(symbols, DEDUPE_LIMIT):
376 items = []
377 result = {}
378 for x in batch:
379 items.append(
380 {
381 "debug_file": x.header.name,
382 "debug_id": x.header.id.replace("-", ""),
383 }
384 )
385 symbol_data = {"symbol_ids": items}
386 try:
387 result = ExecRequest(
388 "post",
389 "%s/symbols:checkStatuses" % status_url,
390 timeout,
391 api_key=api_key,
392 data=json.dumps(symbol_data),
393 )
394 except requests.exceptions.RequestException as e:
395 logging.warning("could not identify duplicates: HTTP error: %s", e)
396 for b in batch:
397 b.status = SymbolFile.INITIAL
398 set_match = {
399 "debugId": b.header.id.replace("-", ""),
400 "debugFile": b.header.name,
401 }
402 for cs_result in result.get("pairs", []):
403 if set_match == cs_result.get("symbolId"):
404 if cs_result.get("status") == "FOUND":
405 logging.debug("Found duplicate: %s", b.display_name)
406 b.status = SymbolFile.DUPLICATE
407 break
408 yield b
Mike Nichols137e82d2019-05-15 18:40:34 -0600409
Mike Nichols649e6a82019-04-23 11:44:48 -0600410
Mike Nichols90f7c152019-04-09 15:14:08 -0600411def UploadSymbolFile(upload_url, symbol, api_key):
Alex Klein1699fab2022-09-08 08:46:06 -0600412 """Upload a symbol file to the crash server, returning the status result.
Don Garretta28be6d2016-06-16 18:09:35 -0700413
Alex Klein1699fab2022-09-08 08:46:06 -0600414 Args:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000415 upload_url: The crash URL to POST the |sym_file| to
416 symbol: A SymbolFile instance.
417 api_key: Authentication key
Alex Klein1699fab2022-09-08 08:46:06 -0600418 """
419 timeout = GetUploadTimeout(symbol)
420 logging.debug("Executing post to uploads:create: %s", symbol.display_name)
421 upload = ExecRequest(
422 "post", "%s/uploads:create" % upload_url, timeout, api_key
423 )
Mike Nichols649e6a82019-04-23 11:44:48 -0600424
Alex Klein1699fab2022-09-08 08:46:06 -0600425 if upload and "uploadUrl" in upload.keys():
426 symbol_data = {
427 "symbol_id": {
428 "debug_file": symbol.header.name,
429 "debug_id": symbol.header.id.replace("-", ""),
430 }
431 }
Mike Frysinger31fdddd2023-02-24 15:50:55 -0500432 with open(symbol.file_name, "r", encoding="utf-8") as fp:
Alex Klein1699fab2022-09-08 08:46:06 -0600433 logging.debug("Executing put to uploadUrl: %s", symbol.display_name)
434 ExecRequest(
435 "put",
436 upload["uploadUrl"],
437 timeout,
438 api_key=api_key,
439 data=fp.read(),
440 )
441 logging.debug(
442 "Executing post to uploads/complete: %s", symbol.display_name
443 )
444 ExecRequest(
445 "post",
446 "%s/uploads/%s:complete" % (upload_url, upload["uploadKey"]),
447 timeout,
448 api_key=api_key,
449 # TODO(mikenichols): Validate product_name once it is added
450 # to the proto; currently unsupported.
451 data=json.dumps(symbol_data),
452 )
453 else:
454 raise requests.exceptions.HTTPError
Don Garretta28be6d2016-06-16 18:09:35 -0700455
456
Mike Nichols90f7c152019-04-09 15:14:08 -0600457def PerformSymbolsFileUpload(symbols, upload_url, api_key):
Alex Klein1699fab2022-09-08 08:46:06 -0600458 """Upload the symbols to the crash server
Don Garretta28be6d2016-06-16 18:09:35 -0700459
Alex Klein1699fab2022-09-08 08:46:06 -0600460 Args:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000461 symbols: An iterable of SymbolFiles to be uploaded.
462 upload_url: URL of crash server to upload too.
463 api_key: Authentication key.
464 failures: Tracker for total upload failures.
Don Garretta28be6d2016-06-16 18:09:35 -0700465
Alex Klein1699fab2022-09-08 08:46:06 -0600466 Yields:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000467 Each symbol from symbols, perhaps modified.
Alex Klein1699fab2022-09-08 08:46:06 -0600468 """
469 failures = 0
470 # Failures are figured per request, therefore each HTTP failure
471 # counts against the max. This means that the process will exit
472 # at the point when HTTP errors hit the defined limit.
473 for s in symbols:
474 if failures < MAX_TOTAL_ERRORS_FOR_RETRY and s.status in (
475 SymbolFile.INITIAL,
476 SymbolFile.ERROR,
477 ):
478 # Keeps us from DoS-ing the symbol server.
479 time.sleep(SLEEP_DELAY)
480 logging.info("Uploading symbol_file: %s", s.display_path)
481 try:
Trent Apted4a0812b2023-05-15 15:33:55 +1000482 # This command retries the upload multiple times with growing
483 # delays. We only consider the upload a failure if these retries
484 # fail.
Alex Klein1699fab2022-09-08 08:46:06 -0600485 def ShouldRetryUpload(exception):
486 if isinstance(
487 exception,
488 (
489 requests.exceptions.RequestException,
490 IOError,
491 http.client.HTTPException,
492 socket.error,
493 ),
494 ):
495 logging.info("Request failed, retrying: %s", exception)
496 return True
497 return False
Don Garrette1f47e92016-10-13 16:04:56 -0700498
Alex Klein1699fab2022-09-08 08:46:06 -0600499 with timer.Timer() as t:
500 retry_stats.RetryWithStats(
501 UPLOAD_STATS,
502 ShouldRetryUpload,
503 MAX_RETRIES,
504 UploadSymbolFile,
505 upload_url,
506 s,
507 api_key,
508 sleep=INITIAL_RETRY_DELAY,
509 log_all_retries=True,
510 )
511 if s.status != SymbolFile.DUPLICATE:
512 logging.info(
513 "upload of %s with size %10i bytes took %s",
514 s.display_name,
515 s.FileSize(),
516 t,
517 )
518 s.status = SymbolFile.UPLOADED
519 except requests.exceptions.RequestException as e:
520 logging.warning(
521 "could not upload: %s: HTTP error: %s", s.display_name, e
522 )
523 s.status = SymbolFile.ERROR
524 failures += 1
525 except (http.client.HTTPException, OSError) as e:
526 logging.warning(
527 "could not upload: %s: %s %s",
528 s.display_name,
529 type(e).__name__,
530 e,
531 )
532 s.status = SymbolFile.ERROR
533 failures += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700534
Alex Klein1699fab2022-09-08 08:46:06 -0600535 # We pass the symbol along, on both success and failure.
536 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700537
538
Don Garrettdeb2e032016-07-06 16:44:14 -0700539def ReportResults(symbols, failed_list):
Alex Klein1699fab2022-09-08 08:46:06 -0600540 """Log a summary of the symbol uploading.
Don Garretta28be6d2016-06-16 18:09:35 -0700541
Alex Klein1699fab2022-09-08 08:46:06 -0600542 This has the side effect of fully consuming the symbols iterator.
Don Garretta28be6d2016-06-16 18:09:35 -0700543
Alex Klein1699fab2022-09-08 08:46:06 -0600544 Args:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000545 symbols: An iterator of SymbolFiles to be uploaded.
546 failed_list: A filename at which to write out a list of our failed
547 uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700548
Alex Klein1699fab2022-09-08 08:46:06 -0600549 Returns:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000550 The number of symbols not uploaded.
Alex Klein1699fab2022-09-08 08:46:06 -0600551 """
552 upload_failures = []
553 result_counts = {
554 SymbolFile.INITIAL: 0,
555 SymbolFile.UPLOADED: 0,
556 SymbolFile.DUPLICATE: 0,
557 SymbolFile.ERROR: 0,
558 }
Don Garretta28be6d2016-06-16 18:09:35 -0700559
Alex Klein1699fab2022-09-08 08:46:06 -0600560 for s in symbols:
561 result_counts[s.status] += 1
562 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
563 upload_failures.append(s)
Don Garretta28be6d2016-06-16 18:09:35 -0700564
Alex Klein1699fab2022-09-08 08:46:06 -0600565 # Report retry numbers.
566 _, _, retries = retry_stats.CategoryStats(UPLOAD_STATS)
567 if retries:
568 logging.warning("%d upload retries performed.", retries)
Don Garrette1f47e92016-10-13 16:04:56 -0700569
Alex Klein1699fab2022-09-08 08:46:06 -0600570 logging.info(
571 "Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.",
572 result_counts,
573 )
Don Garretta28be6d2016-06-16 18:09:35 -0700574
Alex Klein1699fab2022-09-08 08:46:06 -0600575 if result_counts[SymbolFile.ERROR]:
576 cbuildbot_alerts.PrintBuildbotStepWarnings()
577 logging.warning(
578 "%d non-recoverable upload errors", result_counts[SymbolFile.ERROR]
579 )
Chris Ching91908032016-09-27 16:55:33 -0600580
Alex Klein1699fab2022-09-08 08:46:06 -0600581 if result_counts[SymbolFile.INITIAL]:
582 cbuildbot_alerts.PrintBuildbotStepWarnings()
583 logging.warning(
584 "%d upload(s) were skipped because of excessive errors",
585 result_counts[SymbolFile.INITIAL],
586 )
Don Garretta28be6d2016-06-16 18:09:35 -0700587
Alex Klein1699fab2022-09-08 08:46:06 -0600588 if failed_list is not None:
Mike Frysinger31fdddd2023-02-24 15:50:55 -0500589 with open(failed_list, "w", encoding="utf-8") as fl:
Alex Klein1699fab2022-09-08 08:46:06 -0600590 for s in upload_failures:
591 fl.write("%s\n" % s.display_path)
Don Garretta28be6d2016-06-16 18:09:35 -0700592
Alex Klein1699fab2022-09-08 08:46:06 -0600593 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
Don Garrettdeb2e032016-07-06 16:44:14 -0700594
Don Garretta28be6d2016-06-16 18:09:35 -0700595
Alex Klein1699fab2022-09-08 08:46:06 -0600596def UploadSymbols(
597 sym_paths,
598 upload_url,
599 failed_list=None,
600 upload_limit=None,
601 strip_cfi=None,
602 timeout=None,
603 api_key=None,
604):
605 """Upload all the generated symbols for |board| to the crash server
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400606
Alex Klein1699fab2022-09-08 08:46:06 -0600607 Args:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000608 sym_paths: Specific symbol files (or dirs of sym files) to upload,
609 otherwise search |breakpad_dir|
610 upload_url: URL of crash server to upload too.
611 failed_list: A filename at which to write out a list of our failed
612 uploads.
613 upload_limit: Integer listing how many files to upload. None for no
614 limit.
615 strip_cfi: File size at which we strip out CFI data. None for no limit.
616 timeout: HTTP timeout for request.
617 api_key: A string based authentication key
Mike Frysinger1a736a82013-12-12 01:50:59 -0500618
Alex Klein1699fab2022-09-08 08:46:06 -0600619 Returns:
Trent Aptedc20bb6d2023-05-10 15:00:03 +1000620 The number of errors that were encountered.
Alex Klein1699fab2022-09-08 08:46:06 -0600621 """
622 retry_stats.SetupStats()
Don Garrette1f47e92016-10-13 16:04:56 -0700623
Alex Klein1699fab2022-09-08 08:46:06 -0600624 # Note: This method looks like each step of processing is performed
Trent Apted4a0812b2023-05-15 15:33:55 +1000625 # sequentially for all SymbolFiles, but instead each step is a generator
626 # that produces the next iteration only when it's read. This means that
627 # (except for some batching) each SymbolFile goes through all of these steps
628 # before the next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400629
Alex Klein1699fab2022-09-08 08:46:06 -0600630 # This is used to hold striped
631 with osutils.TempDir(prefix="upload_symbols.") as tempdir:
632 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500633
Trent Apted4a0812b2023-05-15 15:33:55 +1000634 # Sort all of our symbols so the largest ones (probably the most
635 # important) are processed first.
Alex Klein1699fab2022-09-08 08:46:06 -0600636 symbols = list(symbols)
637 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
Don Garrett1bc1e102016-07-06 17:06:10 -0700638
Alex Klein1699fab2022-09-08 08:46:06 -0600639 if upload_limit is not None:
640 # Restrict symbols processed to the limit.
641 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400642
Alex Klein1699fab2022-09-08 08:46:06 -0600643 # Strip CFI, if needed.
644 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500645
Alex Klein1699fab2022-09-08 08:46:06 -0600646 # Find duplicates via batch API
647 symbols = FindDuplicates(symbols, upload_url, api_key, timeout)
Mike Nichols137e82d2019-05-15 18:40:34 -0600648
Alex Klein1699fab2022-09-08 08:46:06 -0600649 # Perform uploads
650 symbols = PerformSymbolsFileUpload(symbols, upload_url, api_key)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400651
Alex Klein1699fab2022-09-08 08:46:06 -0600652 # Log the final results, and consume the symbols generator fully.
653 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500654
Alex Klein1699fab2022-09-08 08:46:06 -0600655 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400656
657
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400658def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600659 parser = commandline.ArgumentParser(description=__doc__)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400660
Alex Klein1699fab2022-09-08 08:46:06 -0600661 # TODO: Make sym_paths, breakpad_root, and root exclusive.
Don Garretta28be6d2016-06-16 18:09:35 -0700662
Alex Klein1699fab2022-09-08 08:46:06 -0600663 parser.add_argument(
664 "sym_paths",
665 type="path_or_uri",
666 nargs="*",
667 default=None,
668 help="symbol file or directory or URL or tarball",
669 )
670 parser.add_argument(
671 "--board", default=None, help="Used to find default breakpad_root."
672 )
673 parser.add_argument(
674 "--breakpad_root",
675 type="path",
676 default=None,
677 help="full path to the breakpad symbol directory",
678 )
679 parser.add_argument(
680 "--root", type="path", default=None, help="full path to the chroot dir"
681 )
682 parser.add_argument(
683 "--official_build",
684 action="store_true",
685 default=False,
686 help="point to official symbol server",
687 )
688 parser.add_argument(
689 "--server", type=str, default=None, help="URI for custom symbol server"
690 )
691 parser.add_argument(
692 "--regenerate",
693 action="store_true",
694 default=False,
695 help="regenerate all symbols",
696 )
697 parser.add_argument(
698 "--upload-limit", type=int, help="only upload # number of symbols"
699 )
700 parser.add_argument(
701 "--strip_cfi",
702 type=int,
703 default=DEFAULT_FILE_LIMIT,
704 help="strip CFI data for files above this size",
705 )
706 parser.add_argument(
707 "--failed-list",
708 type="path",
709 help="where to save a list of failed symbols",
710 )
711 parser.add_argument(
712 "--dedupe",
713 action="store_true",
714 default=False,
715 help="use the swarming service to avoid re-uploading",
716 )
717 parser.add_argument(
718 "--yes",
719 action="store_true",
720 default=False,
721 help="answer yes to all prompts",
722 )
723 parser.add_argument(
724 "--product_name",
725 type=str,
726 default="ChromeOS",
727 help="Produce Name for breakpad stats.",
728 )
729 parser.add_argument(
730 "--api_key",
731 type=str,
Mike Frysinger9c9fa1a2023-08-04 23:39:43 -0400732 default=CRASH_API_KEY,
Alex Klein1699fab2022-09-08 08:46:06 -0600733 help="full path to the API key file",
734 )
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400735
Alex Klein1699fab2022-09-08 08:46:06 -0600736 opts = parser.parse_args(argv)
737 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400738
Alex Klein1699fab2022-09-08 08:46:06 -0600739 # Figure out the symbol files/directories to upload.
740 if opts.sym_paths:
741 sym_paths = opts.sym_paths
742 elif opts.breakpad_root:
743 sym_paths = [opts.breakpad_root]
744 elif opts.root:
745 if not opts.board:
746 cros_build_lib.Die("--board must be set if --root is used.")
747 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(
748 opts.board
749 )
750 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip("/"))]
Don Garretta28be6d2016-06-16 18:09:35 -0700751 else:
Alex Klein1699fab2022-09-08 08:46:06 -0600752 cros_build_lib.Die(
753 "--sym_paths, --breakpad_root, or --root must be set."
754 )
Don Garretta28be6d2016-06-16 18:09:35 -0700755
Alex Klein1699fab2022-09-08 08:46:06 -0600756 if opts.sym_paths or opts.breakpad_root:
757 if opts.regenerate:
758 cros_build_lib.Die(
759 "--regenerate may not be used with specific files, "
760 "or breakpad_root"
761 )
762 else:
763 if opts.board is None:
764 cros_build_lib.Die("--board is required")
Mike Nichols90f7c152019-04-09 15:14:08 -0600765
Alex Klein1699fab2022-09-08 08:46:06 -0600766 # Figure out which crash server to upload too.
767 upload_url = opts.server
768 if not upload_url:
769 if opts.official_build:
770 upload_url = OFFICIAL_UPLOAD_URL
771 else:
772 logging.warning("unofficial builds upload to the staging server")
773 upload_url = STAGING_UPLOAD_URL
Mike Nichols90f7c152019-04-09 15:14:08 -0600774
Alex Klein1699fab2022-09-08 08:46:06 -0600775 # Set up the API key needed to authenticate to Crash server.
776 # Allow for a local key file for testing purposes.
Mike Frysinger9c9fa1a2023-08-04 23:39:43 -0400777 api_key = osutils.ReadFile(opts.api_key)
Alex Klein1699fab2022-09-08 08:46:06 -0600778
779 # Confirm we really want the long upload.
780 if not opts.yes:
781 prolog = "\n".join(
782 textwrap.wrap(
783 textwrap.dedent(
784 """
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400785 Uploading symbols for an entire Chromium OS build is really only
786 necessary for release builds and in a few cases for developers
787 to debug problems. It will take considerable time to run. For
788 developer debugging purposes, consider instead passing specific
789 files to upload.
Alex Klein1699fab2022-09-08 08:46:06 -0600790 """
791 ),
792 80,
793 )
794 ).strip()
795 if not cros_build_lib.BooleanPrompt(
796 prompt="Are you sure you want to upload all build symbols",
797 default=False,
798 prolog=prolog,
799 ):
800 cros_build_lib.Die("better safe than sorry")
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400801
Alex Klein1699fab2022-09-08 08:46:06 -0600802 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700803
Alex Klein1699fab2022-09-08 08:46:06 -0600804 # Regenerate symbols from binaries.
805 if opts.regenerate:
806 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
807 opts.board, breakpad_dir=opts.breakpad_root
808 )
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400809
Alex Klein1699fab2022-09-08 08:46:06 -0600810 # Do the upload.
811 ret += UploadSymbols(
812 sym_paths=sym_paths,
813 upload_url=upload_url,
814 failed_list=opts.failed_list,
815 upload_limit=opts.upload_limit,
816 strip_cfi=opts.strip_cfi,
817 api_key=api_key,
818 )
Don Garretta28be6d2016-06-16 18:09:35 -0700819
Alex Klein1699fab2022-09-08 08:46:06 -0600820 if ret:
821 logging.error("encountered %i problem(s)", ret)
Trent Apted4a0812b2023-05-15 15:33:55 +1000822 # Since exit(status) gets masked, clamp it to 1 so we don't
823 # inadvertently return 0 in case we are a multiple of the mask.
Alex Klein1699fab2022-09-08 08:46:06 -0600824 return 1