blob: ba1bb4d3073c09f0c4b2bd252538d0cbf1da18be [file] [log] [blame]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -04001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# 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 Frysingera4fa1e82014-01-15 01:45:56 -050012from __future__ import print_function
13
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040014import ctypes
Mike Frysinger8ec8c502014-02-10 00:19:13 -050015import datetime
Mike Frysinger02e92402013-11-22 16:22:02 -050016import functools
Mike Frysinger0c0efa22014-02-09 23:32:23 -050017import hashlib
Mike Frysingera4fa1e82014-01-15 01:45:56 -050018import httplib
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040019import multiprocessing
20import os
Mike Frysinger094a2172013-08-14 12:54:35 -040021import poster
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040022import random
Mike Frysingerfd355652014-01-23 02:57:48 -050023import socket
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040024import textwrap
25import tempfile
26import time
Mike Frysinger094a2172013-08-14 12:54:35 -040027import urllib2
Mike Frysingerd41938e2014-02-10 06:37:55 -050028import urlparse
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040029
Mike Frysinger0c0efa22014-02-09 23:32:23 -050030from chromite.buildbot import constants
Mike Frysingerd41938e2014-02-10 06:37:55 -050031from chromite.lib import cache
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040032from chromite.lib import commandline
33from chromite.lib import cros_build_lib
Mike Frysingerd41938e2014-02-10 06:37:55 -050034from chromite.lib import gs
35from chromite.lib import osutils
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040036from chromite.lib import parallel
David Jamesc93e6a4d2014-01-13 11:37:36 -080037from chromite.lib import retry_util
Mike Frysinger0c0efa22014-02-09 23:32:23 -050038from chromite.lib import timeout_util
Mike Frysinger69cb41d2013-08-11 20:08:19 -040039from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040040
Mike Frysinger0c0efa22014-02-09 23:32:23 -050041# Needs to be after chromite imports.
42# TODO(build): When doing the initial buildbot bootstrap, we won't have any
43# other repos available. So ignore isolateserver imports. But buildbot will
44# re-exec itself once it has done a full repo sync and then the module will
45# be available -- it isn't needed that early. http://crbug.com/341152
46try:
47 import isolateserver
48except ImportError:
49 isolateserver = None
50
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040051
52# URLs used for uploading symbols.
53OFFICIAL_UPLOAD_URL = 'http://clients2.google.com/cr/symbol'
54STAGING_UPLOAD_URL = 'http://clients2.google.com/cr/staging_symbol'
55
56
57# The crash server rejects files that are this big.
58CRASH_SERVER_FILE_LIMIT = 350 * 1024 * 1024
59# Give ourselves a little breathing room from what the server expects.
60DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
61
62
Mike Frysinger0c0efa22014-02-09 23:32:23 -050063# The batch limit when talking to the dedup server. We avoid sending one at a
64# time as the round trip overhead will dominate. Conversely, we avoid sending
65# all at once so we can start uploading symbols asap -- the symbol server is a
66# bit slow and will take longer than anything else.
67# TODO: A better algorithm would be adaptive. If we have more than one symbol
68# in the upload queue waiting, we could send more symbols to the dedupe server
69# at a time.
70DEDUPE_LIMIT = 100
71
72# How long to wait for the server to respond with the results. Note that the
73# larger the limit above, the larger this will need to be. So we give it ~1
74# second per item max.
75DEDUPE_TIMEOUT = DEDUPE_LIMIT
76
77# The unique namespace in the dedupe server that only we use. Helps avoid
78# collisions with all the hashed values and unrelated content.
79OFFICIAL_DEDUPE_NAMESPACE = 'chromium-os-upload-symbols'
80STAGING_DEDUPE_NAMESPACE = '%s-staging' % OFFICIAL_DEDUPE_NAMESPACE
81
82
Mike Frysingercd78a082013-06-26 17:13:04 -040083# How long to wait (in seconds) for a single upload to complete. This has
84# to allow for symbols that are up to CRASH_SERVER_FILE_LIMIT in size.
85UPLOAD_TIMEOUT = 30 * 60
86
87
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040088# Sleep for 200ms in between uploads to avoid DoS'ing symbol server.
89DEFAULT_SLEEP_DELAY = 0.2
90
91
92# Number of seconds to wait before retrying an upload. The delay will double
93# for each subsequent retry of the same symbol file.
94INITIAL_RETRY_DELAY = 1
95
96# Allow up to 7 attempts to upload a symbol file (total delay may be
97# 1+2+4+8+16+32=63 seconds).
98MAX_RETRIES = 6
99
Mike Frysingereb753bf2013-11-22 16:05:35 -0500100# Number of total errors, before uploads are no longer attempted.
101# This is used to avoid lots of errors causing unreasonable delays.
102# See the related, but independent, error values below.
103MAX_TOTAL_ERRORS_FOR_RETRY = 30
104
105# A watermark of transient errors which we allow recovery from. If we hit
106# errors infrequently, overall we're probably doing fine. For example, if
107# we have one failure every 100 passes, then we probably don't want to fail
108# right away. But if we hit a string of failures in a row, we want to abort.
109#
110# The watermark starts at 0 (and can never go below that). When this error
111# level is exceeded, we stop uploading. When a failure happens, we add the
112# fail adjustment, and when an upload succeeds, we add the pass adjustment.
113# We want to penalize failures more so that we ramp up when there is a string
114# of them, but then slowly back off as things start working.
115#
116# A quick example:
117# 0.0: Starting point.
118# 0.0: Upload works, so add -0.5, and then clamp to 0.
119# 1.0: Upload fails, so add 1.0.
120# 2.0: Upload fails, so add 1.0.
121# 1.5: Upload works, so add -0.5.
122# 1.0: Upload works, so add -0.5.
123ERROR_WATERMARK = 3.0
124ERROR_ADJUST_FAIL = 1.0
125ERROR_ADJUST_PASS = -0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400126
127
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500128def SymUpload(upload_url, sym_item):
Mike Frysinger094a2172013-08-14 12:54:35 -0400129 """Upload a symbol file to a HTTP server
130
131 The upload is a multipart/form-data POST with the following parameters:
132 code_file: the basename of the module, e.g. "app"
133 code_identifier: the module file's identifier
134 debug_file: the basename of the debugging file, e.g. "app"
135 debug_identifier: the debug file's identifier, usually consisting of
136 the guid and age embedded in the pdb, e.g.
137 "11111111BBBB3333DDDD555555555555F"
138 version: the file version of the module, e.g. "1.2.3.4"
139 product: HTTP-friendly product name
140 os: the operating system that the module was built for
141 cpu: the CPU that the module was built for
142 symbol_file: the contents of the breakpad-format symbol file
143
144 Args:
Mike Frysinger094a2172013-08-14 12:54:35 -0400145 upload_url: The crash URL to POST the |sym_file| to
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500146 sym_item: A SymbolItem containing the path to the breakpad symbol to upload
Mike Frysinger094a2172013-08-14 12:54:35 -0400147 """
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500148 sym_header = sym_item.sym_header
149 sym_file = sym_item.sym_file
Mike Frysinger094a2172013-08-14 12:54:35 -0400150
151 fields = (
152 ('code_file', sym_header.name),
153 ('debug_file', sym_header.name),
154 ('debug_identifier', sym_header.id.replace('-', '')),
155 # Should we set these fields? They aren't critical, but it might be nice?
156 # We'd have to figure out what file this symbol is coming from and what
157 # package provides it ...
158 #('version', None),
159 #('product', 'ChromeOS'),
160 ('os', sym_header.os),
161 ('cpu', sym_header.cpu),
162 poster.encode.MultipartParam.from_file('symbol_file', sym_file),
163 )
164
165 data, headers = poster.encode.multipart_encode(fields)
166 request = urllib2.Request(upload_url, data, headers)
167 request.add_header('User-agent', 'chromite.upload_symbols')
168 urllib2.urlopen(request, timeout=UPLOAD_TIMEOUT)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400169
170
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500171def TestingSymUpload(upload_url, sym_item):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400172 """A stub version of SymUpload for --testing usage"""
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500173 cmd = ['sym_upload', sym_item.sym_file, upload_url]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400174 # Randomly fail 80% of the time (the retry logic makes this 80%/3 per file).
175 returncode = random.randint(1, 100) <= 80
176 cros_build_lib.Debug('would run (and return %i): %s', returncode,
Matt Tennant7feda352013-12-20 14:03:40 -0800177 cros_build_lib.CmdToStr(cmd))
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400178 if returncode:
179 output = 'Failed to send the symbol file.'
180 else:
181 output = 'Successfully sent the symbol file.'
182 result = cros_build_lib.CommandResult(cmd=cmd, error=None, output=output,
183 returncode=returncode)
184 if returncode:
Mike Frysingera4fa1e82014-01-15 01:45:56 -0500185 exceptions = (
Mike Frysingerfd355652014-01-23 02:57:48 -0500186 socket.error('[socket.error] forced test fail'),
Mike Frysingera4fa1e82014-01-15 01:45:56 -0500187 httplib.BadStatusLine('[BadStatusLine] forced test fail'),
188 urllib2.HTTPError(upload_url, 400, '[HTTPError] forced test fail',
189 {}, None),
190 urllib2.URLError('[URLError] forced test fail'),
191 )
192 raise random.choice(exceptions)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400193 else:
194 return result
195
196
Mike Frysingereb753bf2013-11-22 16:05:35 -0500197def ErrorLimitHit(num_errors, watermark_errors):
198 """See if our error limit has been hit
199
200 Args:
201 num_errors: A multiprocessing.Value of the raw number of failures.
202 watermark_errors: A multiprocessing.Value of the current rate of failures.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500203
Mike Frysingereb753bf2013-11-22 16:05:35 -0500204 Returns:
205 True if our error limits have been exceeded.
206 """
207 return ((num_errors is not None and
208 num_errors.value > MAX_TOTAL_ERRORS_FOR_RETRY) or
209 (watermark_errors is not None and
210 watermark_errors.value > ERROR_WATERMARK))
211
212
213def _UpdateCounter(counter, adj):
214 """Update |counter| by |adj|
215
216 Handle atomic updates of |counter|. Also make sure it does not
217 fall below 0.
218
219 Args:
220 counter: A multiprocessing.Value to update
221 adj: The value to add to |counter|
222 """
223 def _Update():
224 clamp = 0 if type(adj) is int else 0.0
225 counter.value = max(clamp, counter.value + adj)
226
227 if hasattr(counter, 'get_lock'):
228 with counter.get_lock():
229 _Update()
230 elif counter is not None:
231 _Update()
232
233
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500234def UploadSymbol(upload_url, sym_item, file_limit=DEFAULT_FILE_LIMIT,
Mike Frysinger02e92402013-11-22 16:22:02 -0500235 sleep=0, num_errors=None, watermark_errors=None,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500236 failed_queue=None, passed_queue=None):
237 """Upload |sym_item| to |upload_url|
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400238
239 Args:
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400240 upload_url: The crash server to upload things to
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500241 sym_item: A SymbolItem containing the path to the breakpad symbol to upload
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400242 file_limit: The max file size of a symbol file before we try to strip it
243 sleep: Number of seconds to sleep before running
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400244 num_errors: An object to update with the error count (needs a .value member)
Mike Frysingereb753bf2013-11-22 16:05:35 -0500245 watermark_errors: An object to track current error behavior (needs a .value)
Mike Frysinger02e92402013-11-22 16:22:02 -0500246 failed_queue: When a symbol fails, add it to this queue
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500247 passed_queue: When a symbol passes, add it to this queue
Mike Frysinger1a736a82013-12-12 01:50:59 -0500248
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400249 Returns:
250 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400251 """
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500252 sym_file = sym_item.sym_file
253 upload_item = sym_item
254
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400255 if num_errors is None:
256 num_errors = ctypes.c_int()
Mike Frysingereb753bf2013-11-22 16:05:35 -0500257 if ErrorLimitHit(num_errors, watermark_errors):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400258 # Abandon ship! It's on fire! NOoooooooooooOOOoooooo.
Mike Frysinger7f9be142014-01-15 02:16:42 -0500259 if failed_queue:
260 failed_queue.put(sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400261 return 0
262
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400263 if sleep:
264 # Keeps us from DoS-ing the symbol server.
265 time.sleep(sleep)
266
267 cros_build_lib.Debug('uploading %s' % sym_file)
268
269 # Ideally there'd be a tempfile.SpooledNamedTemporaryFile that we could use.
270 with tempfile.NamedTemporaryFile(prefix='upload_symbols',
271 bufsize=0) as temp_sym_file:
272 if file_limit:
273 # If the symbols size is too big, strip out the call frame info. The CFI
274 # is unnecessary for 32bit x86 targets where the frame pointer is used (as
275 # all of ours have) and it accounts for over half the size of the symbols
276 # uploaded.
277 file_size = os.path.getsize(sym_file)
278 if file_size > file_limit:
279 cros_build_lib.Warning('stripping CFI from %s due to size %s > %s',
280 sym_file, file_size, file_limit)
281 temp_sym_file.writelines([x for x in open(sym_file, 'rb').readlines()
282 if not x.startswith('STACK CFI')])
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500283
284 upload_item = FakeItem(sym_file=temp_sym_file.name,
285 sym_header=sym_item.sym_header)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400286
287 # Hopefully the crash server will let it through. But it probably won't.
288 # Not sure what the best answer is in this case.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500289 file_size = os.path.getsize(upload_item.sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400290 if file_size > CRASH_SERVER_FILE_LIMIT:
291 cros_build_lib.PrintBuildbotStepWarnings()
Mike Frysinger02e92402013-11-22 16:22:02 -0500292 cros_build_lib.Warning('upload file %s is awfully large, risking '
293 'rejection by the symbol server (%s > %s)',
294 sym_file, file_size, CRASH_SERVER_FILE_LIMIT)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400295
296 # Upload the symbol file.
Mike Frysingereb753bf2013-11-22 16:05:35 -0500297 success = False
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400298 try:
Mike Frysinger9adcfd22013-10-24 12:01:40 -0400299 cros_build_lib.TimedCommand(
David Jamesc93e6a4d2014-01-13 11:37:36 -0800300 retry_util.RetryException,
Mike Frysinger9adcfd22013-10-24 12:01:40 -0400301 (urllib2.HTTPError, urllib2.URLError), MAX_RETRIES, SymUpload,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500302 upload_url, upload_item, sleep=INITIAL_RETRY_DELAY,
Mike Frysinger9adcfd22013-10-24 12:01:40 -0400303 timed_log_msg='upload of %10i bytes took %%s: %s' %
304 (file_size, os.path.basename(sym_file)))
Mike Frysingereb753bf2013-11-22 16:05:35 -0500305 success = True
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500306
307 if passed_queue:
308 passed_queue.put(sym_item)
Mike Frysinger094a2172013-08-14 12:54:35 -0400309 except urllib2.HTTPError as e:
310 cros_build_lib.Warning('could not upload: %s: HTTP %s: %s',
311 os.path.basename(sym_file), e.code, e.reason)
Mike Frysingerfd355652014-01-23 02:57:48 -0500312 except (urllib2.URLError, httplib.HTTPException, socket.error) as e:
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400313 cros_build_lib.Warning('could not upload: %s: %s',
314 os.path.basename(sym_file), e)
Mike Frysingereb753bf2013-11-22 16:05:35 -0500315 finally:
316 if success:
317 _UpdateCounter(watermark_errors, ERROR_ADJUST_PASS)
318 else:
319 _UpdateCounter(num_errors, 1)
320 _UpdateCounter(watermark_errors, ERROR_ADJUST_FAIL)
Mike Frysinger02e92402013-11-22 16:22:02 -0500321 if failed_queue:
322 failed_queue.put(sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400323
324 return num_errors.value
325
326
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500327# A dummy class that allows for stubbing in tests and SymUpload.
328FakeItem = cros_build_lib.Collection(
329 'FakeItem', sym_file=None, sym_header=None, content=lambda x: '')
330
331
332# TODO(build): Delete this if check. http://crbug.com/341152
333if isolateserver:
334 class SymbolItem(isolateserver.BufferItem):
335 """Turn a sym_file into an isolateserver.Item"""
336
337 ALGO = hashlib.sha1
338
339 def __init__(self, sym_file):
340 sym_header = cros_generate_breakpad_symbols.ReadSymsHeader(sym_file)
341 super(SymbolItem, self).__init__(str(sym_header), self.ALGO)
342 self.sym_header = sym_header
343 self.sym_file = sym_file
344
345
346def SymbolDeduplicatorNotify(dedupe_namespace, dedupe_queue):
347 """Send a symbol file to the swarming service
348
349 Notify the swarming service of a successful upload. If the notification fails
350 for any reason, we ignore it. We don't care as it just means we'll upload it
351 again later on, and the symbol server will handle that graciously.
352
353 This func runs in a different process from the main one, so we cannot share
354 the storage object. Instead, we create our own. This func stays alive for
355 the life of the process, so we only create one here overall.
356
357 Args:
358 dedupe_namespace: The isolateserver namespace to dedupe uploaded symbols.
359 dedupe_queue: The queue to read SymbolItems from
360 """
361 if dedupe_queue is None:
362 return
363
364 item = None
365 try:
366 storage = isolateserver.get_storage_api(constants.ISOLATESERVER,
367 dedupe_namespace)
368 for item in iter(dedupe_queue.get, None):
369 with timeout_util.Timeout(DEDUPE_TIMEOUT):
370 storage.push(item, item.content(0))
371 except Exception:
372 sym_file = item.sym_file if (item and item.sym_file) else ''
373 cros_build_lib.Warning('posting %s to dedupe server failed',
374 os.path.basename(sym_file), exc_info=True)
375
Mike Frysinger58312e92014-03-18 04:18:36 -0400376 # Keep draining the queue though so it doesn't fill up.
377 while dedupe_queue.get() is not None:
378 continue
379
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500380
381def SymbolDeduplicator(storage, sym_paths):
382 """Filter out symbol files that we've already uploaded
383
384 Using the swarming service, ask it to tell us which symbol files we've already
385 uploaded in previous runs and/or by other bots. If the query fails for any
386 reason, we'll just upload all symbols. This is fine as the symbol server will
387 do the right thing and this phase is purely an optimization.
388
389 This code runs in the main thread which is why we can re-use the existing
390 storage object. Saves us from having to recreate one all the time.
391
392 Args:
393 storage: An isolateserver.StorageApi object
394 sym_paths: List of symbol files to check against the dedupe server
395
396 Returns:
397 List of symbol files that have not been uploaded before
398 """
399 if not sym_paths:
400 return sym_paths
401
402 items = [SymbolItem(x) for x in sym_paths]
403 if storage:
404 try:
405 with timeout_util.Timeout(DEDUPE_TIMEOUT):
406 items = storage.contains(items)
407 except Exception:
408 cros_build_lib.Warning('talking to dedupe server failed', exc_info=True)
409
410 return items
411
412
Mike Frysingerd41938e2014-02-10 06:37:55 -0500413def IsTarball(path):
414 """Guess if this is a tarball based on the filename."""
415 parts = path.split('.')
416 if len(parts) <= 1:
417 return False
418
419 if parts[-1] == 'tar':
420 return True
421
422 if parts[-2] == 'tar':
423 return parts[-1] in ('bz2', 'gz', 'xz')
424
425 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
426
427
428def SymbolFinder(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500429 """Locate symbol files in |paths|
430
431 Args:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500432 tempdir: Path to use for temporary files (caller will clean up).
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500433 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500434 Dirs are searched for files that end in ".sym". Urls are fetched and then
435 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500436
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500437 Returns:
438 Yield every viable sym file.
439 """
440 for p in paths:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500441 o = urlparse.urlparse(p)
442 if o.scheme:
443 # Support globs of filenames.
444 ctx = gs.GSContext()
445 for p in ctx.LS(p):
446 cros_build_lib.Info('processing files inside %s', p)
447 o = urlparse.urlparse(p)
448 cache_dir = commandline.GetCacheDir()
449 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
450 tar_cache = cache.TarballCache(common_path)
451 key = ('%s%s' % (o.netloc, o.path)).split('/')
452 # The common cache will not be LRU, removing the need to hold a read
453 # lock on the cached gsutil.
454 ref = tar_cache.Lookup(key)
455 try:
456 ref.SetDefault(p)
457 except cros_build_lib.RunCommandError as e:
458 cros_build_lib.Warning('ignoring %s\n%s', p, e)
459 continue
460 for p in SymbolFinder(tempdir, [ref.path]):
461 yield p
462
463 elif os.path.isdir(p):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500464 for root, _, files in os.walk(p):
465 for f in files:
466 if f.endswith('.sym'):
467 yield os.path.join(root, f)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500468
469 elif IsTarball(p):
470 cros_build_lib.Info('processing files inside %s', p)
471 tardir = tempfile.mkdtemp(dir=tempdir)
472 cache.Untar(os.path.realpath(p), tardir)
473 for p in SymbolFinder(tardir, [tardir]):
474 yield p
475
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500476 else:
477 yield p
478
479
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500480def WriteQueueToFile(listing, queue, relpath=None):
481 """Write all the items in |queue| to the |listing|.
482
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500483 Note: The queue must have a sentinel None appended to the end.
484
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500485 Args:
486 listing: Where to write out the list of files.
487 queue: The queue of paths to drain.
488 relpath: If set, write out paths relative to this one.
489 """
490 if not listing:
Mike Frysingera0ddac62014-03-14 10:30:25 -0400491 # Still drain the queue so we make sure the producer has finished
492 # before we return. Otherwise, the queue might get destroyed too
493 # quickly which will trigger a traceback in the producer.
494 while queue.get() is not None:
495 continue
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500496 return
497
498 with cros_build_lib.Open(listing, 'wb+') as f:
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500499 while True:
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500500 path = queue.get()
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500501 if path is None:
502 return
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500503 if relpath:
504 path = os.path.relpath(path, relpath)
505 f.write('%s\n' % path)
506
507
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400508def UploadSymbols(board=None, official=False, breakpad_dir=None,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400509 file_limit=DEFAULT_FILE_LIMIT, sleep=DEFAULT_SLEEP_DELAY,
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500510 upload_limit=None, sym_paths=None, failed_list=None,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500511 root=None, retry=True, dedupe_namespace=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400512 """Upload all the generated symbols for |board| to the crash server
513
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400514 You can use in a few ways:
515 * pass |board| to locate all of its symbols
516 * pass |breakpad_dir| to upload all the symbols in there
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500517 * pass |sym_paths| to upload specific symbols (or dirs of symbols)
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400518
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400519 Args:
520 board: The board whose symbols we wish to upload
521 official: Use the official symbol server rather than the staging one
522 breakpad_dir: The full path to the breakpad directory where symbols live
523 file_limit: The max file size of a symbol file before we try to strip it
524 sleep: How long to sleep in between uploads
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500525 upload_limit: If set, only upload this many symbols (meant for testing)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500526 sym_paths: Specific symbol files (or dirs of sym files) to upload,
527 otherwise search |breakpad_dir|
Mike Frysinger7f9be142014-01-15 02:16:42 -0500528 failed_list: Write the names of all sym files we did not upload; can be a
529 filename or file-like object.
Mike Frysinger118d2502013-08-19 03:36:56 -0400530 root: The tree to prefix to |breakpad_dir| (if |breakpad_dir| is not set)
Mike Frysinger02e92402013-11-22 16:22:02 -0500531 retry: Whether we should retry failures.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500532 dedupe_namespace: The isolateserver namespace to dedupe uploaded symbols.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500533
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400534 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400535 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400536 """
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500537 # TODO(build): Delete this assert.
538 assert isolateserver, 'Missing isolateserver import http://crbug.com/341152'
539
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400540 if official:
541 upload_url = OFFICIAL_UPLOAD_URL
542 else:
543 cros_build_lib.Warning('unofficial builds upload to the staging server')
544 upload_url = STAGING_UPLOAD_URL
545
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500546 if sym_paths:
547 cros_build_lib.Info('uploading specified symbols to %s', upload_url)
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400548 else:
549 if breakpad_dir is None:
Mike Frysinger118d2502013-08-19 03:36:56 -0400550 breakpad_dir = os.path.join(
551 root,
552 cros_generate_breakpad_symbols.FindBreakpadDir(board).lstrip('/'))
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400553 cros_build_lib.Info('uploading all symbols to %s from %s', upload_url,
554 breakpad_dir)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500555 sym_paths = [breakpad_dir]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400556
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500557 # We use storage_query to ask the server about existing symbols. The
558 # storage_notify_proc process is used to post updates to the server. We
559 # cannot safely share the storage object between threads/processes, but
560 # we also want to minimize creating new ones as each object has to init
561 # new state (like server connections).
562 if dedupe_namespace:
563 dedupe_limit = DEDUPE_LIMIT
564 dedupe_queue = multiprocessing.Queue()
565 storage_query = isolateserver.get_storage_api(constants.ISOLATESERVER,
566 dedupe_namespace)
567 else:
568 dedupe_limit = 1
569 dedupe_queue = storage_query = None
570 # Can't use parallel.BackgroundTaskRunner because that'll create multiple
571 # processes and we want only one the whole time (see comment above).
572 storage_notify_proc = multiprocessing.Process(
573 target=SymbolDeduplicatorNotify, args=(dedupe_namespace, dedupe_queue))
574
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400575 bg_errors = multiprocessing.Value('i')
Mike Frysingereb753bf2013-11-22 16:05:35 -0500576 watermark_errors = multiprocessing.Value('f')
Mike Frysinger02e92402013-11-22 16:22:02 -0500577 failed_queue = multiprocessing.Queue()
578 uploader = functools.partial(
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500579 UploadSymbol, upload_url, file_limit=file_limit, sleep=sleep,
580 num_errors=bg_errors, watermark_errors=watermark_errors,
581 failed_queue=failed_queue, passed_queue=dedupe_queue)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400582
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500583 start_time = datetime.datetime.now()
584 Counters = cros_build_lib.Collection(
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500585 'Counters', upload_limit=upload_limit, uploaded_count=0, deduped_count=0)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500586 counters = Counters()
587
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500588 def _Upload(queue, counters, files):
589 if not files:
590 return
591
592 missing_count = 0
593 for item in SymbolDeduplicator(storage_query, files):
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500594 missing_count += 1
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400595
596 if counters.upload_limit == 0:
597 continue
598
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500599 queue.put((item,))
600 counters.uploaded_count += 1
601 if counters.upload_limit is not None:
602 counters.upload_limit -= 1
603
604 counters.deduped_count += (len(files) - missing_count)
605
Mike Frysinger13870082014-03-14 10:41:20 -0400606 try:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500607 storage_notify_proc.start()
Mike Frysinger02e92402013-11-22 16:22:02 -0500608
Mike Frysinger13870082014-03-14 10:41:20 -0400609 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
610 # For the first run, we collect the symbols that failed. If the
611 # overall failure rate was low, we'll retry them on the second run.
612 for retry in (retry, False):
613 # We need to limit ourselves to one upload at a time to avoid the server
614 # kicking in DoS protection. See these bugs for more details:
615 # http://crbug.com/209442
616 # http://crbug.com/212496
617 with parallel.BackgroundTaskRunner(uploader, processes=1) as queue:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500618 dedupe_list = []
Mike Frysinger13870082014-03-14 10:41:20 -0400619 for sym_file in SymbolFinder(tempdir, sym_paths):
620 dedupe_list.append(sym_file)
621 dedupe_len = len(dedupe_list)
622 if dedupe_len < dedupe_limit:
623 if (counters.upload_limit is None or
624 dedupe_len < counters.upload_limit):
625 continue
Mike Frysinger02e92402013-11-22 16:22:02 -0500626
Mike Frysinger1010a892014-03-14 11:24:17 -0400627 # We check the counter before _Upload so that we don't keep talking
628 # to the dedupe server. Otherwise, we end up sending one symbol at
629 # a time to it and that slows things down a lot.
630 if counters.upload_limit == 0:
631 break
632
Mike Frysinger13870082014-03-14 10:41:20 -0400633 _Upload(queue, counters, dedupe_list)
634 dedupe_list = []
635 _Upload(queue, counters, dedupe_list)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500636
Mike Frysinger13870082014-03-14 10:41:20 -0400637 # See if we need to retry, and if we haven't failed too many times yet.
638 if not retry or ErrorLimitHit(bg_errors, watermark_errors):
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500639 break
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500640
Mike Frysinger13870082014-03-14 10:41:20 -0400641 sym_paths = []
642 failed_queue.put(None)
643 while True:
644 sym_path = failed_queue.get()
645 if sym_path is None:
646 break
647 sym_paths.append(sym_path)
Mike Frysinger02e92402013-11-22 16:22:02 -0500648
Mike Frysinger13870082014-03-14 10:41:20 -0400649 if sym_paths:
650 cros_build_lib.Warning('retrying %i symbols', len(sym_paths))
651 if counters.upload_limit is not None:
652 counters.upload_limit += len(sym_paths)
653 # Decrement the error count in case we recover in the second pass.
654 assert bg_errors.value >= len(sym_paths), \
655 'more failed files than errors?'
656 bg_errors.value -= len(sym_paths)
657 else:
658 # No failed symbols, so just return now.
659 break
Mike Frysinger7f9be142014-01-15 02:16:42 -0500660
Mike Frysinger13870082014-03-14 10:41:20 -0400661 # If the user has requested it, save all the symbol files that we failed to
662 # upload to a listing file. This should help with recovery efforts later.
663 failed_queue.put(None)
664 WriteQueueToFile(failed_list, failed_queue, breakpad_dir)
665
666 finally:
667 if dedupe_queue:
668 dedupe_queue.put(None)
669 storage_notify_proc.join()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500670
671 cros_build_lib.Info('uploaded %i symbols (%i were deduped) which took: %s',
672 counters.uploaded_count, counters.deduped_count,
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500673 datetime.datetime.now() - start_time)
674
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500675 return bg_errors.value
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400676
677
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400678def main(argv):
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500679 # TODO(build): Delete this assert.
680 assert isolateserver, 'Missing isolateserver import http://crbug.com/341152'
681
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400682 parser = commandline.ArgumentParser(description=__doc__)
683
Mike Frysingerd41938e2014-02-10 06:37:55 -0500684 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
685 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400686 parser.add_argument('--board', default=None,
687 help='board to build packages for')
688 parser.add_argument('--breakpad_root', type='path', default=None,
689 help='root directory for breakpad symbols')
690 parser.add_argument('--official_build', action='store_true', default=False,
691 help='point to official symbol server')
692 parser.add_argument('--regenerate', action='store_true', default=False,
693 help='regenerate all symbols')
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500694 parser.add_argument('--upload-limit', type=int, default=None,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400695 help='only upload # number of symbols')
696 parser.add_argument('--strip_cfi', type=int,
697 default=CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024),
698 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500699 parser.add_argument('--failed-list', type='path',
700 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500701 parser.add_argument('--dedupe', action='store_true', default=False,
702 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400703 parser.add_argument('--testing', action='store_true', default=False,
704 help='run in testing mode')
705 parser.add_argument('--yes', action='store_true', default=False,
706 help='answer yes to all prompts')
707
708 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500709 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400710
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500711 if opts.sym_paths:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400712 if opts.regenerate:
713 cros_build_lib.Die('--regenerate may not be used with specific files')
714 else:
715 if opts.board is None:
716 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400717
718 if opts.breakpad_root and opts.regenerate:
719 cros_build_lib.Die('--regenerate may not be used with --breakpad_root')
720
721 if opts.testing:
722 # TODO(build): Kill off --testing mode once unittests are up-to-snuff.
723 cros_build_lib.Info('running in testing mode')
724 # pylint: disable=W0601,W0603
725 global INITIAL_RETRY_DELAY, SymUpload, DEFAULT_SLEEP_DELAY
726 INITIAL_RETRY_DELAY = DEFAULT_SLEEP_DELAY = 0
727 SymUpload = TestingSymUpload
728
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500729 dedupe_namespace = None
730 if opts.dedupe:
731 if opts.official_build and not opts.testing:
732 dedupe_namespace = OFFICIAL_DEDUPE_NAMESPACE
733 else:
734 dedupe_namespace = STAGING_DEDUPE_NAMESPACE
735
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400736 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500737 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400738 Uploading symbols for an entire Chromium OS build is really only
739 necessary for release builds and in a few cases for developers
740 to debug problems. It will take considerable time to run. For
741 developer debugging purposes, consider instead passing specific
742 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500743 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400744 if not cros_build_lib.BooleanPrompt(
745 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500746 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400747 cros_build_lib.Die('better safe than sorry')
748
749 ret = 0
750 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400751 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
752 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400753
754 ret += UploadSymbols(opts.board, official=opts.official_build,
755 breakpad_dir=opts.breakpad_root,
756 file_limit=opts.strip_cfi, sleep=DEFAULT_SLEEP_DELAY,
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500757 upload_limit=opts.upload_limit, sym_paths=opts.sym_paths,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500758 failed_list=opts.failed_list,
759 dedupe_namespace=dedupe_namespace)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400760 if ret:
761 cros_build_lib.Error('encountered %i problem(s)', ret)
762 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
763 # return 0 in case we are a multiple of the mask.
764 ret = 1
765
766 return ret
Mike Frysinger094a2172013-08-14 12:54:35 -0400767
768
769# We need this to run once per process. Do it at module import time as that
770# will let us avoid doing it inline at function call time (see SymUpload) as
771# that func might be called by the multiprocessing module which means we'll
772# do the opener logic multiple times overall. Plus, if you're importing this
773# module, it's a pretty good chance that you're going to need this.
774poster.streaminghttp.register_openers()