blob: 96257ed0fb2b6123e14cae9ee764ae5c5b8ae95b [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
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800163 self.header = cros_generate_breakpad_symbols.ReadSymsHeader(
164 file_name, file_name
165 )
Alex Klein1699fab2022-09-08 08:46:06 -0600166 self.status = SymbolFile.INITIAL
167
168 @property
169 def display_name(self):
170 return os.path.basename(self.display_path)
171
172 def FileSize(self):
173 return os.path.getsize(self.file_name)
Don Garretta28be6d2016-06-16 18:09:35 -0700174
175
Don Garretta28be6d2016-06-16 18:09:35 -0700176def FindSymbolFiles(tempdir, paths):
Alex Klein1699fab2022-09-08 08:46:06 -0600177 """Locate symbol files in |paths|
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500178
Alex Klein1699fab2022-09-08 08:46:06 -0600179 This returns SymbolFile objects that contain file references which are valid
180 after this exits. Those files may exist externally, or be created in the
181 tempdir (say, when expanding tarballs). The caller must not consider
182 SymbolFile's valid after tempdir is cleaned up.
Don Garretta28be6d2016-06-16 18:09:35 -0700183
Alex Klein1699fab2022-09-08 08:46:06 -0600184 Args:
185 tempdir: Path to use for temporary files.
186 paths: A list of input paths to walk. Files are returned w/out any checks.
187 Dirs are searched for files that end in ".sym". Urls are fetched and then
188 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500189
Alex Klein1699fab2022-09-08 08:46:06 -0600190 Yields:
191 A SymbolFile for every symbol file found in paths.
192 """
193 cache_dir = path_util.GetCacheDir()
194 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
195 tar_cache = cache.TarballCache(common_path)
Mike Frysingere847efd2015-01-08 03:57:24 -0500196
Alex Klein1699fab2022-09-08 08:46:06 -0600197 for p in paths:
198 o = urllib.parse.urlparse(p)
199 if o.scheme:
200 # Support globs of filenames.
201 ctx = gs.GSContext()
202 for gspath in ctx.LS(p):
203 logging.info("processing files inside %s", gspath)
204 o = urllib.parse.urlparse(gspath)
205 key = ("%s%s" % (o.netloc, o.path)).split("/")
206 # The common cache will not be LRU, removing the need to hold a read
207 # lock on the cached gsutil.
208 ref = tar_cache.Lookup(key)
209 try:
210 ref.SetDefault(gspath)
211 except cros_build_lib.RunCommandError as e:
212 logging.warning("ignoring %s\n%s", gspath, e)
213 continue
214 for sym in FindSymbolFiles(tempdir, [ref.path]):
215 yield sym
Mike Frysingerd41938e2014-02-10 06:37:55 -0500216
Alex Klein1699fab2022-09-08 08:46:06 -0600217 elif os.path.isdir(p):
218 for root, _, files in os.walk(p):
219 for f in files:
220 if f.endswith(".sym"):
221 # If p is '/tmp/foo' and filename is '/tmp/foo/bar/bar.sym',
222 # display_path = 'bar/bar.sym'
223 filename = os.path.join(root, f)
224 yield SymbolFile(
225 display_path=filename[len(p) :].lstrip("/"),
226 file_name=filename,
227 )
Mike Frysingerd41938e2014-02-10 06:37:55 -0500228
Alex Klein1699fab2022-09-08 08:46:06 -0600229 elif IsTarball(p):
230 logging.info("processing files inside %s", p)
231 tardir = tempfile.mkdtemp(dir=tempdir)
232 cache.Untar(os.path.realpath(p), tardir)
233 for sym in FindSymbolFiles(tardir, [tardir]):
234 yield sym
Mike Frysingerd41938e2014-02-10 06:37:55 -0500235
Alex Klein1699fab2022-09-08 08:46:06 -0600236 else:
237 yield SymbolFile(display_path=p, file_name=p)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500238
239
Don Garretta28be6d2016-06-16 18:09:35 -0700240def AdjustSymbolFileSize(symbol, tempdir, file_limit):
Alex Klein1699fab2022-09-08 08:46:06 -0600241 """Examine symbols files for size problems, and reduce if needed.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500242
Alex Klein1699fab2022-09-08 08:46:06 -0600243 If the symbols size is too big, strip out the call frame info. The CFI
244 is unnecessary for 32bit x86 targets where the frame pointer is used (as
245 all of ours have) and it accounts for over half the size of the symbols
246 uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700247
Alex Klein1699fab2022-09-08 08:46:06 -0600248 Stripped files will be created inside tempdir, and will be the callers
249 responsibility to clean up.
Don Garretta28be6d2016-06-16 18:09:35 -0700250
Alex Klein1699fab2022-09-08 08:46:06 -0600251 We also warn, if a symbols file is still too large after stripping.
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500252
Alex Klein1699fab2022-09-08 08:46:06 -0600253 Args:
254 symbol: SymbolFile instance to be examined and modified as needed..
255 tempdir: A temporary directory we can create files in that the caller will
256 clean up.
257 file_limit: We only strip files which are larger than this limit.
Don Garretta28be6d2016-06-16 18:09:35 -0700258
Alex Klein1699fab2022-09-08 08:46:06 -0600259 Returns:
260 SymbolFile instance (original or modified as needed)
261 """
262 file_size = symbol.FileSize()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500263
Alex Klein1699fab2022-09-08 08:46:06 -0600264 if file_limit and symbol.FileSize() > file_limit:
265 with cros_build_lib.UnbufferedNamedTemporaryFile(
266 prefix="upload_symbols", dir=tempdir, delete=False
267 ) as temp_sym_file:
Alex Klein1699fab2022-09-08 08:46:06 -0600268 with open(symbol.file_name, "rb") as fp:
269 temp_sym_file.writelines(
270 x for x in fp.readlines() if not x.startswith(b"STACK CFI")
271 )
272
273 original_file_size = file_size
274 symbol.file_name = temp_sym_file.name
275 file_size = symbol.FileSize()
276
277 logging.warning(
278 "stripped CFI for %s reducing size %s > %s",
279 symbol.display_name,
280 original_file_size,
281 file_size,
282 )
283
284 # Hopefully the crash server will let it through. But it probably won't.
285 # Not sure what the best answer is in this case.
286 if file_size >= CRASH_SERVER_FILE_LIMIT:
287 cbuildbot_alerts.PrintBuildbotStepWarnings()
288 logging.warning(
289 "upload file %s is awfully large, risking rejection by "
290 "the symbol server (%s > %s)",
291 symbol.display_path,
292 file_size,
293 CRASH_SERVER_FILE_LIMIT,
Mike Frysingerac75f602019-11-14 23:18:51 -0500294 )
Don Garretta28be6d2016-06-16 18:09:35 -0700295
Alex Klein1699fab2022-09-08 08:46:06 -0600296 return symbol
Don Garretta28be6d2016-06-16 18:09:35 -0700297
Don Garretta28be6d2016-06-16 18:09:35 -0700298
299def GetUploadTimeout(symbol):
Alex Klein1699fab2022-09-08 08:46:06 -0600300 """How long to wait for a specific file to upload to the crash server.
Don Garretta28be6d2016-06-16 18:09:35 -0700301
Alex Klein1699fab2022-09-08 08:46:06 -0600302 This is a function largely to make unittesting easier.
Don Garretta28be6d2016-06-16 18:09:35 -0700303
Alex Klein1699fab2022-09-08 08:46:06 -0600304 Args:
305 symbol: A SymbolFile instance.
Don Garretta28be6d2016-06-16 18:09:35 -0700306
Alex Klein1699fab2022-09-08 08:46:06 -0600307 Returns:
308 Timeout length (in seconds)
309 """
310 # Scale the timeout based on the filesize.
311 return max(symbol.FileSize() // UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
Don Garretta28be6d2016-06-16 18:09:35 -0700312
313
Mike Nichols90f7c152019-04-09 15:14:08 -0600314def ExecRequest(operator, url, timeout, api_key, **kwargs):
Alex Klein1699fab2022-09-08 08:46:06 -0600315 """Makes a web request with default timeout, returning the json result.
Don Garretta28be6d2016-06-16 18:09:35 -0700316
Alex Klein1699fab2022-09-08 08:46:06 -0600317 This method will raise a requests.exceptions.HTTPError if the status
318 code is not 4XX, 5XX
Mike Nichols90f7c152019-04-09 15:14:08 -0600319
Alex Klein1699fab2022-09-08 08:46:06 -0600320 Note: If you are using verbose logging it is entirely possible that the
321 subsystem will write your api key to the logs!
Mike Nichols90f7c152019-04-09 15:14:08 -0600322
Alex Klein1699fab2022-09-08 08:46:06 -0600323 Args:
324 operator: HTTP method.
325 url: Endpoint URL.
326 timeout: HTTP timeout for request.
327 api_key: Authentication key.
Mike Nichols90f7c152019-04-09 15:14:08 -0600328
Alex Klein1699fab2022-09-08 08:46:06 -0600329 Returns:
330 HTTP response content
331 """
332 resp = requests.request(
333 operator,
334 url,
335 params={"key": api_key},
336 headers={"User-agent": "chromite.upload_symbols"},
337 timeout=timeout,
338 **kwargs,
339 )
340 # Make sure we don't leak secret keys by accident.
341 if resp.status_code > 399:
342 resp.url = resp.url.replace(urllib.parse.quote(api_key), "XX-HIDDEN-XX")
343 logging.warning(
344 'Url: %s, Status: %s, response: "%s", in: %s',
345 resp.url,
346 resp.status_code,
347 resp.text,
348 resp.elapsed,
349 )
350 elif resp.content:
351 return resp.json()
Mike Nicholscf5b7a92019-04-25 12:01:28 -0600352
Alex Klein1699fab2022-09-08 08:46:06 -0600353 return {}
Mike Nichols90f7c152019-04-09 15:14:08 -0600354
355
Mike Nichols137e82d2019-05-15 18:40:34 -0600356def FindDuplicates(symbols, status_url, api_key, timeout=DEDUPE_TIMEOUT):
Alex Klein1699fab2022-09-08 08:46:06 -0600357 """Check whether the symbol files have already been uploaded.
Mike Nichols649e6a82019-04-23 11:44:48 -0600358
Alex Klein1699fab2022-09-08 08:46:06 -0600359 Args:
360 symbols: A iterable of SymbolFiles to be uploaded
361 status_url: The crash URL to validate the file existence.
362 api_key: Authentication key.
363 timeout: HTTP timeout for request.
Mike Nichols649e6a82019-04-23 11:44:48 -0600364
Alex Klein1699fab2022-09-08 08:46:06 -0600365 Yields:
366 All SymbolFiles from symbols, but duplicates have status updated to
367 DUPLICATE.
368 """
369 for batch in BatchGenerator(symbols, DEDUPE_LIMIT):
370 items = []
371 result = {}
372 for x in batch:
373 items.append(
374 {
375 "debug_file": x.header.name,
376 "debug_id": x.header.id.replace("-", ""),
377 }
378 )
379 symbol_data = {"symbol_ids": items}
380 try:
381 result = ExecRequest(
382 "post",
383 "%s/symbols:checkStatuses" % status_url,
384 timeout,
385 api_key=api_key,
386 data=json.dumps(symbol_data),
387 )
388 except requests.exceptions.RequestException as e:
389 logging.warning("could not identify duplicates: HTTP error: %s", e)
390 for b in batch:
391 b.status = SymbolFile.INITIAL
392 set_match = {
393 "debugId": b.header.id.replace("-", ""),
394 "debugFile": b.header.name,
395 }
396 for cs_result in result.get("pairs", []):
397 if set_match == cs_result.get("symbolId"):
398 if cs_result.get("status") == "FOUND":
399 logging.debug("Found duplicate: %s", b.display_name)
400 b.status = SymbolFile.DUPLICATE
401 break
402 yield b
Mike Nichols137e82d2019-05-15 18:40:34 -0600403
Mike Nichols649e6a82019-04-23 11:44:48 -0600404
Mike Nichols90f7c152019-04-09 15:14:08 -0600405def UploadSymbolFile(upload_url, symbol, api_key):
Alex Klein1699fab2022-09-08 08:46:06 -0600406 """Upload a symbol file to the crash server, returning the status result.
Don Garretta28be6d2016-06-16 18:09:35 -0700407
Alex Klein1699fab2022-09-08 08:46:06 -0600408 Args:
409 upload_url: The crash URL to POST the |sym_file| to
410 symbol: A SymbolFile instance.
411 api_key: Authentication key
412 """
413 timeout = GetUploadTimeout(symbol)
414 logging.debug("Executing post to uploads:create: %s", symbol.display_name)
415 upload = ExecRequest(
416 "post", "%s/uploads:create" % upload_url, timeout, api_key
417 )
Mike Nichols649e6a82019-04-23 11:44:48 -0600418
Alex Klein1699fab2022-09-08 08:46:06 -0600419 if upload and "uploadUrl" in upload.keys():
420 symbol_data = {
421 "symbol_id": {
422 "debug_file": symbol.header.name,
423 "debug_id": symbol.header.id.replace("-", ""),
424 }
425 }
Mike Frysinger31fdddd2023-02-24 15:50:55 -0500426 with open(symbol.file_name, "r", encoding="utf-8") as fp:
Alex Klein1699fab2022-09-08 08:46:06 -0600427 logging.debug("Executing put to uploadUrl: %s", symbol.display_name)
428 ExecRequest(
429 "put",
430 upload["uploadUrl"],
431 timeout,
432 api_key=api_key,
433 data=fp.read(),
434 )
435 logging.debug(
436 "Executing post to uploads/complete: %s", symbol.display_name
437 )
438 ExecRequest(
439 "post",
440 "%s/uploads/%s:complete" % (upload_url, upload["uploadKey"]),
441 timeout,
442 api_key=api_key,
443 # TODO(mikenichols): Validate product_name once it is added
444 # to the proto; currently unsupported.
445 data=json.dumps(symbol_data),
446 )
447 else:
448 raise requests.exceptions.HTTPError
Don Garretta28be6d2016-06-16 18:09:35 -0700449
450
Mike Nichols90f7c152019-04-09 15:14:08 -0600451def PerformSymbolsFileUpload(symbols, upload_url, api_key):
Alex Klein1699fab2022-09-08 08:46:06 -0600452 """Upload the symbols to the crash server
Don Garretta28be6d2016-06-16 18:09:35 -0700453
Alex Klein1699fab2022-09-08 08:46:06 -0600454 Args:
455 symbols: An iterable of SymbolFiles to be uploaded.
456 upload_url: URL of crash server to upload too.
457 api_key: Authentication key.
458 failures: Tracker for total upload failures.
Don Garretta28be6d2016-06-16 18:09:35 -0700459
Alex Klein1699fab2022-09-08 08:46:06 -0600460 Yields:
461 Each symbol from symbols, perhaps modified.
462 """
463 failures = 0
464 # Failures are figured per request, therefore each HTTP failure
465 # counts against the max. This means that the process will exit
466 # at the point when HTTP errors hit the defined limit.
467 for s in symbols:
468 if failures < MAX_TOTAL_ERRORS_FOR_RETRY and s.status in (
469 SymbolFile.INITIAL,
470 SymbolFile.ERROR,
471 ):
472 # Keeps us from DoS-ing the symbol server.
473 time.sleep(SLEEP_DELAY)
474 logging.info("Uploading symbol_file: %s", s.display_path)
475 try:
476 # This command retries the upload multiple times with growing delays. We
477 # only consider the upload a failure if these retries fail.
478 def ShouldRetryUpload(exception):
479 if isinstance(
480 exception,
481 (
482 requests.exceptions.RequestException,
483 IOError,
484 http.client.HTTPException,
485 socket.error,
486 ),
487 ):
488 logging.info("Request failed, retrying: %s", exception)
489 return True
490 return False
Don Garrette1f47e92016-10-13 16:04:56 -0700491
Alex Klein1699fab2022-09-08 08:46:06 -0600492 with timer.Timer() as t:
493 retry_stats.RetryWithStats(
494 UPLOAD_STATS,
495 ShouldRetryUpload,
496 MAX_RETRIES,
497 UploadSymbolFile,
498 upload_url,
499 s,
500 api_key,
501 sleep=INITIAL_RETRY_DELAY,
502 log_all_retries=True,
503 )
504 if s.status != SymbolFile.DUPLICATE:
505 logging.info(
506 "upload of %s with size %10i bytes took %s",
507 s.display_name,
508 s.FileSize(),
509 t,
510 )
511 s.status = SymbolFile.UPLOADED
512 except requests.exceptions.RequestException as e:
513 logging.warning(
514 "could not upload: %s: HTTP error: %s", s.display_name, e
515 )
516 s.status = SymbolFile.ERROR
517 failures += 1
518 except (http.client.HTTPException, OSError) as e:
519 logging.warning(
520 "could not upload: %s: %s %s",
521 s.display_name,
522 type(e).__name__,
523 e,
524 )
525 s.status = SymbolFile.ERROR
526 failures += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700527
Alex Klein1699fab2022-09-08 08:46:06 -0600528 # We pass the symbol along, on both success and failure.
529 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700530
531
Don Garrettdeb2e032016-07-06 16:44:14 -0700532def ReportResults(symbols, failed_list):
Alex Klein1699fab2022-09-08 08:46:06 -0600533 """Log a summary of the symbol uploading.
Don Garretta28be6d2016-06-16 18:09:35 -0700534
Alex Klein1699fab2022-09-08 08:46:06 -0600535 This has the side effect of fully consuming the symbols iterator.
Don Garretta28be6d2016-06-16 18:09:35 -0700536
Alex Klein1699fab2022-09-08 08:46:06 -0600537 Args:
538 symbols: An iterator of SymbolFiles to be uploaded.
539 failed_list: A filename at which to write out a list of our failed uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700540
Alex Klein1699fab2022-09-08 08:46:06 -0600541 Returns:
542 The number of symbols not uploaded.
543 """
544 upload_failures = []
545 result_counts = {
546 SymbolFile.INITIAL: 0,
547 SymbolFile.UPLOADED: 0,
548 SymbolFile.DUPLICATE: 0,
549 SymbolFile.ERROR: 0,
550 }
Don Garretta28be6d2016-06-16 18:09:35 -0700551
Alex Klein1699fab2022-09-08 08:46:06 -0600552 for s in symbols:
553 result_counts[s.status] += 1
554 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
555 upload_failures.append(s)
Don Garretta28be6d2016-06-16 18:09:35 -0700556
Alex Klein1699fab2022-09-08 08:46:06 -0600557 # Report retry numbers.
558 _, _, retries = retry_stats.CategoryStats(UPLOAD_STATS)
559 if retries:
560 logging.warning("%d upload retries performed.", retries)
Don Garrette1f47e92016-10-13 16:04:56 -0700561
Alex Klein1699fab2022-09-08 08:46:06 -0600562 logging.info(
563 "Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.",
564 result_counts,
565 )
Don Garretta28be6d2016-06-16 18:09:35 -0700566
Alex Klein1699fab2022-09-08 08:46:06 -0600567 if result_counts[SymbolFile.ERROR]:
568 cbuildbot_alerts.PrintBuildbotStepWarnings()
569 logging.warning(
570 "%d non-recoverable upload errors", result_counts[SymbolFile.ERROR]
571 )
Chris Ching91908032016-09-27 16:55:33 -0600572
Alex Klein1699fab2022-09-08 08:46:06 -0600573 if result_counts[SymbolFile.INITIAL]:
574 cbuildbot_alerts.PrintBuildbotStepWarnings()
575 logging.warning(
576 "%d upload(s) were skipped because of excessive errors",
577 result_counts[SymbolFile.INITIAL],
578 )
Don Garretta28be6d2016-06-16 18:09:35 -0700579
Alex Klein1699fab2022-09-08 08:46:06 -0600580 if failed_list is not None:
Mike Frysinger31fdddd2023-02-24 15:50:55 -0500581 with open(failed_list, "w", encoding="utf-8") as fl:
Alex Klein1699fab2022-09-08 08:46:06 -0600582 for s in upload_failures:
583 fl.write("%s\n" % s.display_path)
Don Garretta28be6d2016-06-16 18:09:35 -0700584
Alex Klein1699fab2022-09-08 08:46:06 -0600585 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
Don Garrettdeb2e032016-07-06 16:44:14 -0700586
Don Garretta28be6d2016-06-16 18:09:35 -0700587
Alex Klein1699fab2022-09-08 08:46:06 -0600588def UploadSymbols(
589 sym_paths,
590 upload_url,
591 failed_list=None,
592 upload_limit=None,
593 strip_cfi=None,
594 timeout=None,
595 api_key=None,
596):
597 """Upload all the generated symbols for |board| to the crash server
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400598
Alex Klein1699fab2022-09-08 08:46:06 -0600599 Args:
600 sym_paths: Specific symbol files (or dirs of sym files) to upload,
601 otherwise search |breakpad_dir|
602 upload_url: URL of crash server to upload too.
603 failed_list: A filename at which to write out a list of our failed uploads.
604 upload_limit: Integer listing how many files to upload. None for no limit.
605 strip_cfi: File size at which we strip out CFI data. None for no limit.
606 timeout: HTTP timeout for request.
607 api_key: A string based authentication key
Mike Frysinger1a736a82013-12-12 01:50:59 -0500608
Alex Klein1699fab2022-09-08 08:46:06 -0600609 Returns:
610 The number of errors that were encountered.
611 """
612 retry_stats.SetupStats()
Don Garrette1f47e92016-10-13 16:04:56 -0700613
Alex Klein1699fab2022-09-08 08:46:06 -0600614 # Note: This method looks like each step of processing is performed
615 # sequentially for all SymbolFiles, but instead each step is a generator that
616 # produces the next iteration only when it's read. This means that (except for
617 # some batching) each SymbolFile goes through all of these steps before the
618 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400619
Alex Klein1699fab2022-09-08 08:46:06 -0600620 # This is used to hold striped
621 with osutils.TempDir(prefix="upload_symbols.") as tempdir:
622 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500623
Alex Klein1699fab2022-09-08 08:46:06 -0600624 # Sort all of our symbols so the largest ones (probably the most important)
625 # are processed first.
626 symbols = list(symbols)
627 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
Don Garrett1bc1e102016-07-06 17:06:10 -0700628
Alex Klein1699fab2022-09-08 08:46:06 -0600629 if upload_limit is not None:
630 # Restrict symbols processed to the limit.
631 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400632
Alex Klein1699fab2022-09-08 08:46:06 -0600633 # Strip CFI, if needed.
634 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500635
Alex Klein1699fab2022-09-08 08:46:06 -0600636 # Find duplicates via batch API
637 symbols = FindDuplicates(symbols, upload_url, api_key, timeout)
Mike Nichols137e82d2019-05-15 18:40:34 -0600638
Alex Klein1699fab2022-09-08 08:46:06 -0600639 # Perform uploads
640 symbols = PerformSymbolsFileUpload(symbols, upload_url, api_key)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400641
Alex Klein1699fab2022-09-08 08:46:06 -0600642 # Log the final results, and consume the symbols generator fully.
643 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500644
Alex Klein1699fab2022-09-08 08:46:06 -0600645 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400646
647
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400648def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600649 parser = commandline.ArgumentParser(description=__doc__)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400650
Alex Klein1699fab2022-09-08 08:46:06 -0600651 # TODO: Make sym_paths, breakpad_root, and root exclusive.
Don Garretta28be6d2016-06-16 18:09:35 -0700652
Alex Klein1699fab2022-09-08 08:46:06 -0600653 parser.add_argument(
654 "sym_paths",
655 type="path_or_uri",
656 nargs="*",
657 default=None,
658 help="symbol file or directory or URL or tarball",
659 )
660 parser.add_argument(
661 "--board", default=None, help="Used to find default breakpad_root."
662 )
663 parser.add_argument(
664 "--breakpad_root",
665 type="path",
666 default=None,
667 help="full path to the breakpad symbol directory",
668 )
669 parser.add_argument(
670 "--root", type="path", default=None, help="full path to the chroot dir"
671 )
672 parser.add_argument(
673 "--official_build",
674 action="store_true",
675 default=False,
676 help="point to official symbol server",
677 )
678 parser.add_argument(
679 "--server", type=str, default=None, help="URI for custom symbol server"
680 )
681 parser.add_argument(
682 "--regenerate",
683 action="store_true",
684 default=False,
685 help="regenerate all symbols",
686 )
687 parser.add_argument(
688 "--upload-limit", type=int, help="only upload # number of symbols"
689 )
690 parser.add_argument(
691 "--strip_cfi",
692 type=int,
693 default=DEFAULT_FILE_LIMIT,
694 help="strip CFI data for files above this size",
695 )
696 parser.add_argument(
697 "--failed-list",
698 type="path",
699 help="where to save a list of failed symbols",
700 )
701 parser.add_argument(
702 "--dedupe",
703 action="store_true",
704 default=False,
705 help="use the swarming service to avoid re-uploading",
706 )
707 parser.add_argument(
708 "--yes",
709 action="store_true",
710 default=False,
711 help="answer yes to all prompts",
712 )
713 parser.add_argument(
714 "--product_name",
715 type=str,
716 default="ChromeOS",
717 help="Produce Name for breakpad stats.",
718 )
719 parser.add_argument(
720 "--api_key",
721 type=str,
722 default=None,
723 help="full path to the API key file",
724 )
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400725
Alex Klein1699fab2022-09-08 08:46:06 -0600726 opts = parser.parse_args(argv)
727 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400728
Alex Klein1699fab2022-09-08 08:46:06 -0600729 # Figure out the symbol files/directories to upload.
730 if opts.sym_paths:
731 sym_paths = opts.sym_paths
732 elif opts.breakpad_root:
733 sym_paths = [opts.breakpad_root]
734 elif opts.root:
735 if not opts.board:
736 cros_build_lib.Die("--board must be set if --root is used.")
737 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(
738 opts.board
739 )
740 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip("/"))]
Don Garretta28be6d2016-06-16 18:09:35 -0700741 else:
Alex Klein1699fab2022-09-08 08:46:06 -0600742 cros_build_lib.Die(
743 "--sym_paths, --breakpad_root, or --root must be set."
744 )
Don Garretta28be6d2016-06-16 18:09:35 -0700745
Alex Klein1699fab2022-09-08 08:46:06 -0600746 if opts.sym_paths or opts.breakpad_root:
747 if opts.regenerate:
748 cros_build_lib.Die(
749 "--regenerate may not be used with specific files, "
750 "or breakpad_root"
751 )
752 else:
753 if opts.board is None:
754 cros_build_lib.Die("--board is required")
Mike Nichols90f7c152019-04-09 15:14:08 -0600755
Alex Klein1699fab2022-09-08 08:46:06 -0600756 # Figure out which crash server to upload too.
757 upload_url = opts.server
758 if not upload_url:
759 if opts.official_build:
760 upload_url = OFFICIAL_UPLOAD_URL
761 else:
762 logging.warning("unofficial builds upload to the staging server")
763 upload_url = STAGING_UPLOAD_URL
Mike Nichols90f7c152019-04-09 15:14:08 -0600764
Alex Klein1699fab2022-09-08 08:46:06 -0600765 # Set up the API key needed to authenticate to Crash server.
766 # Allow for a local key file for testing purposes.
767 if opts.api_key:
768 api_key_file = opts.api_key
769 else:
770 api_key_file = constants.CRASH_API_KEY
771
772 api_key = osutils.ReadFile(api_key_file)
773
774 # Confirm we really want the long upload.
775 if not opts.yes:
776 prolog = "\n".join(
777 textwrap.wrap(
778 textwrap.dedent(
779 """
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400780 Uploading symbols for an entire Chromium OS build is really only
781 necessary for release builds and in a few cases for developers
782 to debug problems. It will take considerable time to run. For
783 developer debugging purposes, consider instead passing specific
784 files to upload.
Alex Klein1699fab2022-09-08 08:46:06 -0600785 """
786 ),
787 80,
788 )
789 ).strip()
790 if not cros_build_lib.BooleanPrompt(
791 prompt="Are you sure you want to upload all build symbols",
792 default=False,
793 prolog=prolog,
794 ):
795 cros_build_lib.Die("better safe than sorry")
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400796
Alex Klein1699fab2022-09-08 08:46:06 -0600797 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700798
Alex Klein1699fab2022-09-08 08:46:06 -0600799 # Regenerate symbols from binaries.
800 if opts.regenerate:
801 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
802 opts.board, breakpad_dir=opts.breakpad_root
803 )
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400804
Alex Klein1699fab2022-09-08 08:46:06 -0600805 # Do the upload.
806 ret += UploadSymbols(
807 sym_paths=sym_paths,
808 upload_url=upload_url,
809 failed_list=opts.failed_list,
810 upload_limit=opts.upload_limit,
811 strip_cfi=opts.strip_cfi,
812 api_key=api_key,
813 )
Don Garretta28be6d2016-06-16 18:09:35 -0700814
Alex Klein1699fab2022-09-08 08:46:06 -0600815 if ret:
816 logging.error("encountered %i problem(s)", ret)
817 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
818 # return 0 in case we are a multiple of the mask.
819 return 1