blob: ab89e3e9d16ce4d88a5d2c136077ff20cab09049 [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -04002# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Upload all debug symbols required for crash reporting purposes.
7
8This script need only be used to upload release builds symbols or to debug
9crashes on non-release builds (in which case try to only upload the symbols
Mike Frysinger02e1e072013-11-10 22:11:34 -050010for those executables involved).
11"""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040012
Mike Frysingera4fa1e82014-01-15 01:45:56 -050013from __future__ import print_function
14
Mike Frysinger0c0efa22014-02-09 23:32:23 -050015import hashlib
Mike Frysingera4fa1e82014-01-15 01:45:56 -050016import httplib
Don Garretta28be6d2016-06-16 18:09:35 -070017import itertools
Mike Nichols90f7c152019-04-09 15:14:08 -060018import json
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040019import os
Mike Nichols90f7c152019-04-09 15:14:08 -060020import requests
Mike Frysingerfd355652014-01-23 02:57:48 -050021import socket
Mike Frysingerc5597f22014-11-27 15:39:15 -050022import sys
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040023import textwrap
24import tempfile
25import time
Mike Frysinger094a2172013-08-14 12:54:35 -040026import urllib2
Mike Frysingerd41938e2014-02-10 06:37:55 -050027import urlparse
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040028
Aviv Keshetb7519e12016-10-04 00:50:00 -070029from chromite.lib import constants
Mike Frysingerbbd1f112016-09-08 18:25:11 -040030
31# The isolateserver includes a bunch of third_party python packages that clash
32# with chromite's bundled third_party python packages (like oauth2client).
33# Since upload_symbols is not imported in to other parts of chromite, and there
34# are no deps in third_party we care about, purge the chromite copy. This way
35# we can use isolateserver for deduping.
36# TODO: If we ever sort out third_party/ handling and make it per-script opt-in,
37# we can purge this logic.
Mike Frysinger3fff4ff2018-07-24 19:24:58 -040038# pylint: disable=ungrouped-imports
Mike Frysingerbbd1f112016-09-08 18:25:11 -040039third_party = os.path.join(constants.CHROMITE_DIR, 'third_party')
40while True:
41 try:
42 sys.path.remove(third_party)
43 except ValueError:
44 break
45sys.path.insert(0, os.path.join(third_party, 'swarming.client'))
46sys.path.insert(0, os.path.join(third_party, 'upload_symbols'))
47del third_party
48
49# Has to be after sys.path manipulation above.
50# And our sys.path muckery confuses pylint.
51import poster # pylint: disable=import-error
52
Mike Frysingerd41938e2014-02-10 06:37:55 -050053from chromite.lib import cache
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040054from chromite.lib import commandline
55from chromite.lib import cros_build_lib
Ralph Nathan5a582ff2015-03-20 18:18:30 -070056from chromite.lib import cros_logging as logging
Mike Frysingerd41938e2014-02-10 06:37:55 -050057from chromite.lib import gs
58from chromite.lib import osutils
Gilad Arnold83233ed2015-05-08 12:12:13 -070059from chromite.lib import path_util
Don Garrette1f47e92016-10-13 16:04:56 -070060from chromite.lib import retry_stats
Mike Frysinger0c0efa22014-02-09 23:32:23 -050061from chromite.lib import timeout_util
Mike Frysinger69cb41d2013-08-11 20:08:19 -040062from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040063
Mike Frysinger0c0efa22014-02-09 23:32:23 -050064# Needs to be after chromite imports.
Mike Frysingerc5597f22014-11-27 15:39:15 -050065# We don't want to import the general keyring module as that will implicitly
66# try to import & connect to a dbus server. That's a waste of time.
67sys.modules['keyring'] = None
Mike Frysingerbbd1f112016-09-08 18:25:11 -040068# And our sys.path muckery confuses pylint.
Xixuan Wu3e840592018-05-30 15:27:03 -070069import isolate_storage # pylint: disable=import-error
Mike Frysingerbbd1f112016-09-08 18:25:11 -040070import isolateserver # pylint: disable=import-error
Mike Frysinger0c0efa22014-02-09 23:32:23 -050071
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040072
Don Garretta28be6d2016-06-16 18:09:35 -070073# We need this to run once per process. Do it at module import time as that
74# will let us avoid doing it inline at function call time (see UploadSymbolFile)
75# as that func might be called by the multiprocessing module which means we'll
76# do the opener logic multiple times overall. Plus, if you're importing this
77# module, it's a pretty good chance that you're going to need this.
78poster.streaminghttp.register_openers()
79
80
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040081# URLs used for uploading symbols.
Mike Nichols90f7c152019-04-09 15:14:08 -060082OFFICIAL_UPLOAD_URL = 'https://prod-crashsymbolcollector-pa.googleapis.com/v1'
83STAGING_UPLOAD_URL = 'https://staging-crashsymbolcollector-pa.googleapis.com/v1'
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040084
85# The crash server rejects files that are this big.
Ryo Hashimotof864c0e2017-02-14 12:46:08 +090086CRASH_SERVER_FILE_LIMIT = 700 * 1024 * 1024
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040087# Give ourselves a little breathing room from what the server expects.
88DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
89
90
Mike Frysinger0c0efa22014-02-09 23:32:23 -050091# The batch limit when talking to the dedup server. We avoid sending one at a
92# time as the round trip overhead will dominate. Conversely, we avoid sending
93# all at once so we can start uploading symbols asap -- the symbol server is a
94# bit slow and will take longer than anything else.
Mike Frysinger0c0efa22014-02-09 23:32:23 -050095DEDUPE_LIMIT = 100
96
97# How long to wait for the server to respond with the results. Note that the
98# larger the limit above, the larger this will need to be. So we give it ~1
99# second per item max.
100DEDUPE_TIMEOUT = DEDUPE_LIMIT
101
Don Garretta28be6d2016-06-16 18:09:35 -0700102# How long to wait for the notification to finish (in seconds).
103DEDUPE_NOTIFY_TIMEOUT = 240
Mike Frysinger4dd462e2014-04-30 16:21:51 -0400104
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500105# The unique namespace in the dedupe server that only we use. Helps avoid
106# collisions with all the hashed values and unrelated content.
Don Garrett747cc4b2015-10-07 14:48:48 -0700107OFFICIAL_DEDUPE_NAMESPACE_TMPL = '%s-upload-symbols'
108STAGING_DEDUPE_NAMESPACE_TMPL = '%s-staging' % OFFICIAL_DEDUPE_NAMESPACE_TMPL
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500109
110
Mike Frysinger71046662014-09-12 18:15:15 -0700111# The minimum average rate (in bytes per second) that we expect to maintain
112# when uploading symbols. This has to allow for symbols that are up to
113# CRASH_SERVER_FILE_LIMIT in size.
114UPLOAD_MIN_RATE = CRASH_SERVER_FILE_LIMIT / (30 * 60)
115
116# The lowest timeout (in seconds) we'll allow. If the server is overloaded,
117# then there might be a delay in setting up the connection, not just with the
118# transfer. So even a small file might need a larger value.
Ryo Hashimotoc0049372017-02-16 18:50:00 +0900119UPLOAD_MIN_TIMEOUT = 5 * 60
Mike Frysingercd78a082013-06-26 17:13:04 -0400120
121
Don Garrett7a793092016-07-06 16:50:27 -0700122# Sleep for 500ms in between uploads to avoid DoS'ing symbol server.
123SLEEP_DELAY = 0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400124
125
126# Number of seconds to wait before retrying an upload. The delay will double
127# for each subsequent retry of the same symbol file.
128INITIAL_RETRY_DELAY = 1
129
130# Allow up to 7 attempts to upload a symbol file (total delay may be
131# 1+2+4+8+16+32=63 seconds).
132MAX_RETRIES = 6
133
Mike Frysingereb753bf2013-11-22 16:05:35 -0500134# Number of total errors, before uploads are no longer attempted.
135# This is used to avoid lots of errors causing unreasonable delays.
Mike Frysingereb753bf2013-11-22 16:05:35 -0500136MAX_TOTAL_ERRORS_FOR_RETRY = 30
137
Don Garrette1f47e92016-10-13 16:04:56 -0700138# Category to use for collection upload retry stats.
139UPLOAD_STATS = 'UPLOAD'
140
Don Garretta28be6d2016-06-16 18:09:35 -0700141
Don Garretta28be6d2016-06-16 18:09:35 -0700142def BatchGenerator(iterator, batch_size):
143 """Given an iterator, break into lists of size batch_size.
Fang Dengba680462015-08-16 20:34:11 -0700144
Don Garretta28be6d2016-06-16 18:09:35 -0700145 The result is a generator, that will only read in as many inputs as needed for
146 the current batch. The final result can be smaller than batch_size.
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700147 """
Don Garretta28be6d2016-06-16 18:09:35 -0700148 batch = []
149 for i in iterator:
150 batch.append(i)
151 if len(batch) >= batch_size:
152 yield batch
153 batch = []
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700154
Don Garretta28be6d2016-06-16 18:09:35 -0700155 if batch:
156 # if there was anything left in the final batch, yield it.
157 yield batch
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500158
159
Mike Frysingerd41938e2014-02-10 06:37:55 -0500160def IsTarball(path):
161 """Guess if this is a tarball based on the filename."""
162 parts = path.split('.')
163 if len(parts) <= 1:
164 return False
165
166 if parts[-1] == 'tar':
167 return True
168
169 if parts[-2] == 'tar':
170 return parts[-1] in ('bz2', 'gz', 'xz')
171
172 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
173
174
Don Garretta28be6d2016-06-16 18:09:35 -0700175class SymbolFile(object):
176 """This class represents the state of a symbol file during processing.
177
178 Properties:
179 display_path: Name of symbol file that should be consistent between builds.
180 file_name: Transient path of the symbol file.
181 header: ReadSymsHeader output. Dict with assorted meta-data.
182 status: INITIAL, DUPLICATE, or UPLOADED based on status of processing.
183 dedupe_item: None or instance of DedupeItem for this symbol file.
184 dedupe_push_state: Opaque value to return to dedupe code for file.
185 display_name: Read only friendly (short) file name for logging.
186 file_size: Read only size of the symbol file.
187 """
188 INITIAL = 'initial'
189 DUPLICATE = 'duplicate'
190 UPLOADED = 'uploaded'
Don Garrettdeb2e032016-07-06 16:44:14 -0700191 ERROR = 'error'
Don Garretta28be6d2016-06-16 18:09:35 -0700192
193 def __init__(self, display_path, file_name):
194 """An instance of this class represents a symbol file over time.
195
196 Args:
197 display_path: A unique/persistent between builds name to present to the
198 crash server. It is the file name, relative to where it
199 came from (tarball, breakpad dir, etc).
200 file_name: A the current location of the symbol file.
201 """
202 self.display_path = display_path
203 self.file_name = file_name
204 self.header = cros_generate_breakpad_symbols.ReadSymsHeader(file_name)
205 self.status = SymbolFile.INITIAL
206 self.dedupe_item = None
207 self.dedupe_push_state = None
208
209 @property
210 def display_name(self):
211 return os.path.basename(self.display_path)
212
213 def FileSize(self):
214 return os.path.getsize(self.file_name)
215
216
217class DedupeItem(isolateserver.BufferItem):
218 """Turn a SymbolFile into an isolateserver.Item"""
219
220 ALGO = hashlib.sha1
221
222 def __init__(self, symbol):
Mike Frysingerbbd1f112016-09-08 18:25:11 -0400223 isolateserver.BufferItem.__init__(self, str(symbol.header), self.ALGO)
Don Garretta28be6d2016-06-16 18:09:35 -0700224 self.symbol = symbol
225
226
227def FindSymbolFiles(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500228 """Locate symbol files in |paths|
229
Don Garretta28be6d2016-06-16 18:09:35 -0700230 This returns SymbolFile objects that contain file references which are valid
231 after this exits. Those files may exist externally, or be created in the
232 tempdir (say, when expanding tarballs). The caller must not consider
233 SymbolFile's valid after tempdir is cleaned up.
234
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500235 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700236 tempdir: Path to use for temporary files.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500237 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500238 Dirs are searched for files that end in ".sym". Urls are fetched and then
239 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500240
Don Garretta28be6d2016-06-16 18:09:35 -0700241 Yields:
242 A SymbolFile for every symbol file found in paths.
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500243 """
Gilad Arnold83233ed2015-05-08 12:12:13 -0700244 cache_dir = path_util.GetCacheDir()
Mike Frysingere847efd2015-01-08 03:57:24 -0500245 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
246 tar_cache = cache.TarballCache(common_path)
247
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500248 for p in paths:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500249 o = urlparse.urlparse(p)
Mike Frysinger27e21b72018-07-12 14:20:21 -0400250 if o.scheme:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500251 # Support globs of filenames.
252 ctx = gs.GSContext()
253 for p in ctx.LS(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700254 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500255 o = urlparse.urlparse(p)
Mike Frysinger27e21b72018-07-12 14:20:21 -0400256 key = ('%s%s' % (o.netloc, o.path)).split('/')
Mike Frysingerd41938e2014-02-10 06:37:55 -0500257 # The common cache will not be LRU, removing the need to hold a read
258 # lock on the cached gsutil.
259 ref = tar_cache.Lookup(key)
260 try:
261 ref.SetDefault(p)
262 except cros_build_lib.RunCommandError as e:
Ralph Nathan446aee92015-03-23 14:44:56 -0700263 logging.warning('ignoring %s\n%s', p, e)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500264 continue
Don Garretta28be6d2016-06-16 18:09:35 -0700265 for p in FindSymbolFiles(tempdir, [ref.path]):
Mike Frysingerd41938e2014-02-10 06:37:55 -0500266 yield p
267
268 elif os.path.isdir(p):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500269 for root, _, files in os.walk(p):
270 for f in files:
271 if f.endswith('.sym'):
Don Garretta28be6d2016-06-16 18:09:35 -0700272 # If p is '/tmp/foo' and filename is '/tmp/foo/bar/bar.sym',
273 # display_path = 'bar/bar.sym'
274 filename = os.path.join(root, f)
275 yield SymbolFile(display_path=filename[len(p):].lstrip('/'),
276 file_name=filename)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500277
278 elif IsTarball(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700279 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500280 tardir = tempfile.mkdtemp(dir=tempdir)
281 cache.Untar(os.path.realpath(p), tardir)
Don Garretta28be6d2016-06-16 18:09:35 -0700282 for p in FindSymbolFiles(tardir, [tardir]):
Mike Frysingerd41938e2014-02-10 06:37:55 -0500283 yield p
284
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500285 else:
Don Garretta28be6d2016-06-16 18:09:35 -0700286 yield SymbolFile(display_path=p, file_name=p)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500287
288
Don Garretta28be6d2016-06-16 18:09:35 -0700289def AdjustSymbolFileSize(symbol, tempdir, file_limit):
290 """Examine symbols files for size problems, and reduce if needed.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500291
Don Garretta28be6d2016-06-16 18:09:35 -0700292 If the symbols size is too big, strip out the call frame info. The CFI
293 is unnecessary for 32bit x86 targets where the frame pointer is used (as
294 all of ours have) and it accounts for over half the size of the symbols
295 uploaded.
296
297 Stripped files will be created inside tempdir, and will be the callers
298 responsibility to clean up.
299
300 We also warn, if a symbols file is still too large after stripping.
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500301
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500302 Args:
Don Garretta28be6d2016-06-16 18:09:35 -0700303 symbol: SymbolFile instance to be examined and modified as needed..
304 tempdir: A temporary directory we can create files in that the caller will
305 clean up.
306 file_limit: We only strip files which are larger than this limit.
307
308 Returns:
309 SymbolFile instance (original or modified as needed)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500310 """
Don Garretta28be6d2016-06-16 18:09:35 -0700311 file_size = symbol.FileSize()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500312
Don Garretta28be6d2016-06-16 18:09:35 -0700313 if file_limit and symbol.FileSize() > file_limit:
314 with tempfile.NamedTemporaryFile(
315 prefix='upload_symbols', bufsize=0,
316 dir=tempdir, delete=False) as temp_sym_file:
317
318 temp_sym_file.writelines(
319 [x for x in open(symbol.file_name, 'rb').readlines()
320 if not x.startswith('STACK CFI')]
321 )
322
323 original_file_size = file_size
324 symbol.file_name = temp_sym_file.name
325 file_size = symbol.FileSize()
326
327 logging.warning('stripped CFI for %s reducing size %s > %s',
328 symbol.display_name, original_file_size, file_size)
329
330 # Hopefully the crash server will let it through. But it probably won't.
331 # Not sure what the best answer is in this case.
332 if file_size >= CRASH_SERVER_FILE_LIMIT:
333 logging.PrintBuildbotStepWarnings()
334 logging.warning('upload file %s is awfully large, risking rejection by '
335 'the symbol server (%s > %s)', symbol.display_path,
336 file_size, CRASH_SERVER_FILE_LIMIT)
337
338 return symbol
339
340def OpenDeduplicateConnection(dedupe_namespace):
341 """Open a connection to the isolate server for Dedupe use.
342
343 Args:
344 dedupe_namespace: String id for the comparison namespace.
345
346 Returns:
347 Connection proxy, or None on failure.
348 """
349 try:
350 with timeout_util.Timeout(DEDUPE_TIMEOUT):
Xixuan Wu3e840592018-05-30 15:27:03 -0700351 return isolate_storage.get_storage_api(constants.ISOLATESERVER,
352 dedupe_namespace)
Don Garretta28be6d2016-06-16 18:09:35 -0700353 except Exception:
354 logging.warning('initializing isolate server connection failed',
355 exc_info=True)
356 return None
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500357
358
Don Garretta28be6d2016-06-16 18:09:35 -0700359def FindDuplicates(symbols, dedupe_namespace):
360 """Mark symbol files we've already uploaded as duplicates.
361
362 Using the swarming service, ask it to tell us which symbol files we've already
363 uploaded in previous runs and/or by other bots. If the query fails for any
364 reason, we'll just upload all symbols. This is fine as the symbol server will
365 do the right thing and this phase is purely an optimization.
366
367 Args:
368 symbols: An iterable of SymbolFiles to be uploaded.
369 dedupe_namespace: String id for the comparison namespace.
370
371 Yields:
372 All SymbolFiles from symbols, but duplicates have status updated to
373 DUPLICATE.
374 """
375 storage_query = OpenDeduplicateConnection(dedupe_namespace)
376
377 # We query isolate in batches, to reduce the number of network queries.
378 for batch in BatchGenerator(symbols, DEDUPE_LIMIT):
379 query_results = None
380
381 if storage_query:
382 # Convert SymbolFiles into DedupeItems.
383 items = [DedupeItem(x) for x in batch]
Don Garretta28be6d2016-06-16 18:09:35 -0700384
385 # Look for duplicates.
386 try:
387 with timeout_util.Timeout(DEDUPE_TIMEOUT):
388 query_results = storage_query.contains(items)
389 except Exception:
390 logging.warning('talking to dedupe server failed', exc_info=True)
391 storage_query = None
392
393 if query_results is not None:
394 for b in batch:
395 b.status = SymbolFile.DUPLICATE
396
397 # Only the non-duplicates appear in the query_results.
398 for item, push_state in query_results.iteritems():
399 # Remember the dedupe state, so we can mark the symbol as uploaded
400 # later on.
401 item.symbol.status = SymbolFile.INITIAL
402 item.symbol.dedupe_item = item
403 item.symbol.dedupe_push_state = push_state
404
405 # Yield all symbols we haven't shown to be duplicates.
406 for b in batch:
407 if b.status == SymbolFile.DUPLICATE:
408 logging.debug('Found duplicate: %s', b.display_name)
409 yield b
410
411
412def PostForDeduplication(symbols, dedupe_namespace):
413 """Send a symbol file to the swarming service
414
415 Notify the isolate service of a successful upload. If the notification fails
416 for any reason, we ignore it. We don't care as it just means we'll upload it
417 again later on, and the symbol server will handle that graciously.
418
419 Args:
420 symbols: An iterable of SymbolFiles to be uploaded.
421 dedupe_namespace: String id for the comparison namespace.
422
423 Yields:
424 Each symbol from symbols, unmodified.
425 """
426 storage_query = OpenDeduplicateConnection(dedupe_namespace)
427
428 for s in symbols:
429 # If we can talk to isolate, and we uploaded this symbol, and we
430 # queried for it's presence before, upload to isolate now.
431
432 if storage_query and s.status == SymbolFile.UPLOADED and s.dedupe_item:
Don Garretta28be6d2016-06-16 18:09:35 -0700433 try:
434 with timeout_util.Timeout(DEDUPE_NOTIFY_TIMEOUT):
435 storage_query.push(s.dedupe_item, s.dedupe_push_state,
436 s.dedupe_item.content())
437 logging.info('sent %s', s.display_name)
438 except Exception:
439 logging.warning('posting %s to dedupe server failed',
440 os.path.basename(s.display_path), exc_info=True)
441 storage_query = None
442
443 yield s
444
445
446def GetUploadTimeout(symbol):
447 """How long to wait for a specific file to upload to the crash server.
448
449 This is a function largely to make unittesting easier.
450
451 Args:
452 symbol: A SymbolFile instance.
453
454 Returns:
455 Timeout length (in seconds)
456 """
457 # Scale the timeout based on the filesize.
458 return max(symbol.FileSize() / UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
459
460
Mike Nichols90f7c152019-04-09 15:14:08 -0600461def ExecRequest(operator, url, timeout, api_key, **kwargs):
462 """Makes a web request with default timeout, returning the json result.
Don Garretta28be6d2016-06-16 18:09:35 -0700463
Mike Nichols90f7c152019-04-09 15:14:08 -0600464 This method will raise a requests.exceptions.HTTPError if the status
465 code is not 4XX, 5XX
466
467 Note: If you are using verbose logging it is entirely possible that the
468 subsystem will write your api key to the logs!
469
470 Args:
471 operator: HTTP method
472 url: Endpoint URL
473 timeout: HTTP timeout for request
474 api_key: Authentication key
475
476 Returns:
477 HTTP response content
478 """
479 resp = requests.request(operator, url,
480 params={'key': api_key},
481 headers={'User-agent': 'chromite.upload_symbols'},
482 timeout=timeout, **kwargs)
483 # Make sure we don't leak secret keys by accident.
484 if resp.status_code > 399:
485 resp.url = resp.url.replace(urllib2.quote(api_key), 'XX-HIDDEN-XX')
486 logging.error('Url: %s, Status: %s, response: "%s", in: %s',
487 resp.url, resp.status_code, resp.text, resp.elapsed)
488 resp.raise_for_status()
489 if resp.content:
490 return resp.json()
491 return {}
492
493
494def UploadSymbolFile(upload_url, symbol, api_key):
495 '''Upload a symbol file to the crash server, returning the status result.
Don Garretta28be6d2016-06-16 18:09:35 -0700496
497 Args:
498 upload_url: The crash URL to POST the |sym_file| to
499 symbol: A SymbolFile instance.
Mike Nichols90f7c152019-04-09 15:14:08 -0600500 api_key: Authentication key
501 '''
502 timeout = GetUploadTimeout(symbol)
503 upload = ExecRequest('post',
504 '{}/uploads:create'.format(upload_url), timeout, api_key)
505 print("API Key: ", api_key)
506 print("Upload: ", upload)
Don Garretta28be6d2016-06-16 18:09:35 -0700507
Mike Nichols90f7c152019-04-09 15:14:08 -0600508 if upload and 'uploadUrl' in upload.keys():
509 symbol_data = {'symbol_id':
510 {'debug_file': symbol.header.name,
511 'debug_id': symbol.header.id.replace('-', '')}
512 }
513 ExecRequest('put',
514 upload['uploadUrl'], timeout,
515 api_key=api_key,
516 data=open(symbol.file_name, 'r'))
517 ExecRequest('post',
518 '%s/uploads/%s:complete' % (
519 upload_url, upload['uploadKey']),
520 timeout, api_key=api_key,
521 # TODO(mikenichols): Validate product_name once it is added
522 # to the proto; currently unsupported.
523 data=json.dumps(symbol_data))
524 else:
525 raise requests.exceptions.HTTPError
Don Garretta28be6d2016-06-16 18:09:35 -0700526
527
Mike Nichols90f7c152019-04-09 15:14:08 -0600528def PerformSymbolsFileUpload(symbols, upload_url, api_key):
Don Garretta28be6d2016-06-16 18:09:35 -0700529 """Upload the symbols to the crash server
530
531 Args:
Don Garrettdeb2e032016-07-06 16:44:14 -0700532 symbols: An iterable of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700533 upload_url: URL of crash server to upload too.
Mike Nichols90f7c152019-04-09 15:14:08 -0600534 api_key: Authentication key.
Don Garretta28be6d2016-06-16 18:09:35 -0700535 failures: Tracker for total upload failures.
Don Garretta28be6d2016-06-16 18:09:35 -0700536
Don Garrettdeb2e032016-07-06 16:44:14 -0700537 Yields:
538 Each symbol from symbols, perhaps modified.
Don Garretta28be6d2016-06-16 18:09:35 -0700539 """
Don Garrettdeb2e032016-07-06 16:44:14 -0700540 failures = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700541
Don Garrettdeb2e032016-07-06 16:44:14 -0700542 for s in symbols:
543 if (failures < MAX_TOTAL_ERRORS_FOR_RETRY and
544 s.status in (SymbolFile.INITIAL, SymbolFile.ERROR)):
545 # Keeps us from DoS-ing the symbol server.
546 time.sleep(SLEEP_DELAY)
547 logging.info('Uploading symbol_file: %s', s.display_path)
548 try:
549 # This command retries the upload multiple times with growing delays. We
550 # only consider the upload a failure if these retries fail.
Don Garrette1f47e92016-10-13 16:04:56 -0700551 def ShouldRetryUpload(exception):
Mike Nichols90f7c152019-04-09 15:14:08 -0600552 return isinstance(exception, (requests.exceptions.HTTPError,
553 requests.exceptions.RequestException,
554 urllib2.URLError,
Ryo Hashimoto4ddf3f02017-03-22 18:25:27 +0900555 httplib.HTTPException, socket.error))
Don Garrette1f47e92016-10-13 16:04:56 -0700556
Don Garrett440944e2016-10-03 16:33:31 -0700557 with cros_build_lib.TimedSection() as timer:
Don Garrette1f47e92016-10-13 16:04:56 -0700558 retry_stats.RetryWithStats(
559 UPLOAD_STATS, ShouldRetryUpload, MAX_RETRIES,
Don Garrett440944e2016-10-03 16:33:31 -0700560 UploadSymbolFile,
Mike Nichols90f7c152019-04-09 15:14:08 -0600561 upload_url, s, api_key,
Luigi Semenzato5104f3c2016-10-12 12:37:42 -0700562 sleep=INITIAL_RETRY_DELAY,
563 log_all_retries=True)
Don Garrett440944e2016-10-03 16:33:31 -0700564 logging.info('upload of %10i bytes took %s', s.FileSize(), timer.delta)
Don Garrettdeb2e032016-07-06 16:44:14 -0700565 s.status = SymbolFile.UPLOADED
Mike Nichols90f7c152019-04-09 15:14:08 -0600566 except (requests.exceptions.HTTPError,
567 requests.exceptions.Timeout,
568 requests.exceptions.RequestException) as e:
569 logging.warning('could not upload: %s: HTTP error: %s',
570 s.display_name, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700571 s.status = SymbolFile.ERROR
572 failures += 1
Mike Nichols90f7c152019-04-09 15:14:08 -0600573 except (httplib.HTTPException, urllib2.URLError, socket.error) as e:
Ryo Hashimoto45273f02017-03-16 17:45:56 +0900574 logging.warning('could not upload: %s: %s %s', s.display_name,
575 type(e).__name__, e)
Don Garrettdeb2e032016-07-06 16:44:14 -0700576 s.status = SymbolFile.ERROR
577 failures += 1
578
579 # We pass the symbol along, on both success and failure.
580 yield s
Don Garretta28be6d2016-06-16 18:09:35 -0700581
582
Don Garrettdeb2e032016-07-06 16:44:14 -0700583def ReportResults(symbols, failed_list):
Don Garretta28be6d2016-06-16 18:09:35 -0700584 """Log a summary of the symbol uploading.
585
586 This has the side effect of fully consuming the symbols iterator.
587
588 Args:
589 symbols: An iterator of SymbolFiles to be uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700590 failed_list: A filename at which to write out a list of our failed uploads.
Don Garrettdeb2e032016-07-06 16:44:14 -0700591
592 Returns:
593 The number of symbols not uploaded.
Don Garretta28be6d2016-06-16 18:09:35 -0700594 """
595 upload_failures = []
596 result_counts = {
597 SymbolFile.INITIAL: 0,
598 SymbolFile.UPLOADED: 0,
599 SymbolFile.DUPLICATE: 0,
Don Garrettdeb2e032016-07-06 16:44:14 -0700600 SymbolFile.ERROR: 0,
Don Garretta28be6d2016-06-16 18:09:35 -0700601 }
602
603 for s in symbols:
604 result_counts[s.status] += 1
Don Garrettdeb2e032016-07-06 16:44:14 -0700605 if s.status in [SymbolFile.INITIAL, SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700606 upload_failures.append(s)
607
Don Garrette1f47e92016-10-13 16:04:56 -0700608 # Report retry numbers.
609 _, _, retries = retry_stats.CategoryStats(UPLOAD_STATS)
610 if retries:
611 logging.warning('%d upload retries performed.', retries)
612
Don Garretta28be6d2016-06-16 18:09:35 -0700613 logging.info('Uploaded %(uploaded)d, Skipped %(duplicate)d duplicates.',
614 result_counts)
615
Chris Ching91908032016-09-27 16:55:33 -0600616 if result_counts[SymbolFile.ERROR]:
Don Garretta28be6d2016-06-16 18:09:35 -0700617 logging.PrintBuildbotStepWarnings()
Chris Ching91908032016-09-27 16:55:33 -0600618 logging.warning('%d non-recoverable upload errors',
619 result_counts[SymbolFile.ERROR])
620
621 if result_counts[SymbolFile.INITIAL]:
622 logging.PrintBuildbotStepWarnings()
623 logging.warning('%d upload(s) were skipped because of excessive errors',
Don Garrettdeb2e032016-07-06 16:44:14 -0700624 result_counts[SymbolFile.INITIAL])
Don Garretta28be6d2016-06-16 18:09:35 -0700625
626 if failed_list is not None:
627 with open(failed_list, 'w') as fl:
628 for s in upload_failures:
629 fl.write('%s\n' % s.display_path)
630
Don Garrettdeb2e032016-07-06 16:44:14 -0700631 return result_counts[SymbolFile.INITIAL] + result_counts[SymbolFile.ERROR]
632
Don Garretta28be6d2016-06-16 18:09:35 -0700633
Mike Nichols90f7c152019-04-09 15:14:08 -0600634def UploadSymbols(sym_paths, upload_url, dedupe_namespace=None,
635 failed_list=None, upload_limit=None, strip_cfi=None,
636 api_key=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400637 """Upload all the generated symbols for |board| to the crash server
638
639 Args:
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500640 sym_paths: Specific symbol files (or dirs of sym files) to upload,
641 otherwise search |breakpad_dir|
Don Garretta28be6d2016-06-16 18:09:35 -0700642 upload_url: URL of crash server to upload too.
Don Garretta28be6d2016-06-16 18:09:35 -0700643 dedupe_namespace: None for no deduping, or string namespace in isolate.
644 failed_list: A filename at which to write out a list of our failed uploads.
645 upload_limit: Integer listing how many files to upload. None for no limit.
646 strip_cfi: File size at which we strip out CFI data. None for no limit.
Mike Nichols90f7c152019-04-09 15:14:08 -0600647 api_key: A string based authentication key
Mike Frysinger1a736a82013-12-12 01:50:59 -0500648
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400649 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400650 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400651 """
Don Garrette1f47e92016-10-13 16:04:56 -0700652 retry_stats.SetupStats()
653
Don Garretta28be6d2016-06-16 18:09:35 -0700654 # Note: This method looks like each step of processing is performed
655 # sequentially for all SymbolFiles, but instead each step is a generator that
656 # produces the next iteration only when it's read. This means that (except for
657 # some batching) each SymbolFile goes through all of these steps before the
658 # next one is processed at all.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400659
Don Garretta28be6d2016-06-16 18:09:35 -0700660 # This is used to hold striped
661 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
662 symbols = FindSymbolFiles(tempdir, sym_paths)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500663
Don Garrett1bc1e102016-07-06 17:06:10 -0700664 # Sort all of our symbols so the largest ones (probably the most important)
665 # are processed first.
666 symbols = list(symbols)
667 symbols.sort(key=lambda s: s.FileSize(), reverse=True)
668
Don Garretta28be6d2016-06-16 18:09:35 -0700669 if upload_limit is not None:
670 # Restrict symbols processed to the limit.
671 symbols = itertools.islice(symbols, None, upload_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400672
Don Garretta28be6d2016-06-16 18:09:35 -0700673 # Strip CFI, if needed.
674 symbols = (AdjustSymbolFileSize(s, tempdir, strip_cfi) for s in symbols)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500675
Don Garretta28be6d2016-06-16 18:09:35 -0700676 # Skip duplicates.
677 if dedupe_namespace:
678 symbols = FindDuplicates(symbols, dedupe_namespace)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500679
Don Garretta28be6d2016-06-16 18:09:35 -0700680 # Perform uploads
Mike Nichols90f7c152019-04-09 15:14:08 -0600681 symbols = PerformSymbolsFileUpload(symbols, upload_url, api_key)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400682
Don Garretta28be6d2016-06-16 18:09:35 -0700683 # Record for future deduping.
684 if dedupe_namespace:
685 symbols = PostForDeduplication(symbols, dedupe_namespace)
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400686
Don Garretta28be6d2016-06-16 18:09:35 -0700687 # Log the final results, and consume the symbols generator fully.
Don Garrettdeb2e032016-07-06 16:44:14 -0700688 failures = ReportResults(symbols, failed_list)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500689
Don Garrettdeb2e032016-07-06 16:44:14 -0700690 return failures
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400691
692
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400693def main(argv):
694 parser = commandline.ArgumentParser(description=__doc__)
695
Don Garretta28be6d2016-06-16 18:09:35 -0700696 # TODO: Make sym_paths, breakpad_root, and root exclusive.
697
Mike Frysingerd41938e2014-02-10 06:37:55 -0500698 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
699 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400700 parser.add_argument('--board', default=None,
Don Garrett747cc4b2015-10-07 14:48:48 -0700701 help='Used to find default breakpad_root.')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400702 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500703 help='full path to the breakpad symbol directory')
704 parser.add_argument('--root', type='path', default=None,
705 help='full path to the chroot dir')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400706 parser.add_argument('--official_build', action='store_true', default=False,
707 help='point to official symbol server')
Mike Frysinger38647542014-09-12 18:15:39 -0700708 parser.add_argument('--server', type=str, default=None,
709 help='URI for custom symbol server')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400710 parser.add_argument('--regenerate', action='store_true', default=False,
711 help='regenerate all symbols')
Don Garretta28be6d2016-06-16 18:09:35 -0700712 parser.add_argument('--upload-limit', type=int,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400713 help='only upload # number of symbols')
714 parser.add_argument('--strip_cfi', type=int,
Don Garretta28be6d2016-06-16 18:09:35 -0700715 default=DEFAULT_FILE_LIMIT,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400716 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500717 parser.add_argument('--failed-list', type='path',
718 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500719 parser.add_argument('--dedupe', action='store_true', default=False,
720 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400721 parser.add_argument('--yes', action='store_true', default=False,
722 help='answer yes to all prompts')
Don Garrett747cc4b2015-10-07 14:48:48 -0700723 parser.add_argument('--product_name', type=str, default='ChromeOS',
724 help='Produce Name for breakpad stats.')
Mike Nichols90f7c152019-04-09 15:14:08 -0600725 parser.add_argument('--api_key', type=str, default=None,
726 help='full path to the API key file')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400727
728 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500729 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400730
Don Garretta28be6d2016-06-16 18:09:35 -0700731 # Figure out the symbol files/directories to upload.
732 if opts.sym_paths:
733 sym_paths = opts.sym_paths
734 elif opts.breakpad_root:
735 sym_paths = [opts.breakpad_root]
736 elif opts.root:
737 if not opts.board:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400738 cros_build_lib.Die('--board must be set if --root is used.')
Don Garretta28be6d2016-06-16 18:09:35 -0700739 breakpad_dir = cros_generate_breakpad_symbols.FindBreakpadDir(opts.board)
740 sym_paths = [os.path.join(opts.root, breakpad_dir.lstrip('/'))]
741 else:
Mike Frysinger8390dec2017-08-11 15:11:38 -0400742 cros_build_lib.Die('--sym_paths, --breakpad_root, or --root must be set.')
Don Garretta28be6d2016-06-16 18:09:35 -0700743
Don Garrett747cc4b2015-10-07 14:48:48 -0700744 if opts.sym_paths or opts.breakpad_root:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400745 if opts.regenerate:
Don Garrett747cc4b2015-10-07 14:48:48 -0700746 cros_build_lib.Die('--regenerate may not be used with specific files, '
747 'or breakpad_root')
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400748 else:
749 if opts.board is None:
750 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400751
Don Garretta28be6d2016-06-16 18:09:35 -0700752 # Figure out the dedupe namespace.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500753 dedupe_namespace = None
754 if opts.dedupe:
Don Garretta28be6d2016-06-16 18:09:35 -0700755 if opts.official_build:
Don Garrett747cc4b2015-10-07 14:48:48 -0700756 dedupe_namespace = OFFICIAL_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500757 else:
Don Garrett747cc4b2015-10-07 14:48:48 -0700758 dedupe_namespace = STAGING_DEDUPE_NAMESPACE_TMPL % opts.product_name
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500759
Don Garretta28be6d2016-06-16 18:09:35 -0700760 # Figure out which crash server to upload too.
761 upload_url = opts.server
762 if not upload_url:
763 if opts.official_build:
764 upload_url = OFFICIAL_UPLOAD_URL
765 else:
766 logging.warning('unofficial builds upload to the staging server')
767 upload_url = STAGING_UPLOAD_URL
768
Mike Nichols90f7c152019-04-09 15:14:08 -0600769 # Set up the API key needed to authenticate to Crash server.
770 # Allow for a local key file for testing purposes.
771 if opts.api_key:
772 api_key_file = opts.api_key
773 else:
774 api_key_file = constants.CRASH_API_KEY
775
776 api_key = osutils.ReadFile(api_key_file)
777
Don Garretta28be6d2016-06-16 18:09:35 -0700778 # Confirm we really want the long upload.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400779 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500780 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400781 Uploading symbols for an entire Chromium OS build is really only
782 necessary for release builds and in a few cases for developers
783 to debug problems. It will take considerable time to run. For
784 developer debugging purposes, consider instead passing specific
785 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500786 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400787 if not cros_build_lib.BooleanPrompt(
788 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500789 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400790 cros_build_lib.Die('better safe than sorry')
791
792 ret = 0
Don Garretta28be6d2016-06-16 18:09:35 -0700793
794 # Regenerate symbols from binaries.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400795 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400796 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
797 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400798
Don Garretta28be6d2016-06-16 18:09:35 -0700799 # Do the upload.
800 ret += UploadSymbols(
801 sym_paths=sym_paths,
802 upload_url=upload_url,
Don Garretta28be6d2016-06-16 18:09:35 -0700803 dedupe_namespace=dedupe_namespace,
804 failed_list=opts.failed_list,
805 upload_limit=opts.upload_limit,
Mike Nichols90f7c152019-04-09 15:14:08 -0600806 strip_cfi=opts.strip_cfi,
807 api_key=api_key)
Don Garretta28be6d2016-06-16 18:09:35 -0700808
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400809 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700810 logging.error('encountered %i problem(s)', ret)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400811 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
812 # return 0 in case we are a multiple of the mask.
Don Garretta28be6d2016-06-16 18:09:35 -0700813 return 1