blob: 421f9a4215eb725b604b8cdb54c021ed6fd8daa8 [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 Frysingerd5fcb3a2013-05-30 21:10:50 -040028
Mike Frysinger0c0efa22014-02-09 23:32:23 -050029from chromite.buildbot import constants
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040030from chromite.lib import commandline
31from chromite.lib import cros_build_lib
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040032from chromite.lib import parallel
David Jamesc93e6a4d2014-01-13 11:37:36 -080033from chromite.lib import retry_util
Mike Frysinger0c0efa22014-02-09 23:32:23 -050034from chromite.lib import timeout_util
Mike Frysinger69cb41d2013-08-11 20:08:19 -040035from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040036
Mike Frysinger0c0efa22014-02-09 23:32:23 -050037# Needs to be after chromite imports.
38# TODO(build): When doing the initial buildbot bootstrap, we won't have any
39# other repos available. So ignore isolateserver imports. But buildbot will
40# re-exec itself once it has done a full repo sync and then the module will
41# be available -- it isn't needed that early. http://crbug.com/341152
42try:
43 import isolateserver
44except ImportError:
45 isolateserver = None
46
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040047
48# URLs used for uploading symbols.
49OFFICIAL_UPLOAD_URL = 'http://clients2.google.com/cr/symbol'
50STAGING_UPLOAD_URL = 'http://clients2.google.com/cr/staging_symbol'
51
52
53# The crash server rejects files that are this big.
54CRASH_SERVER_FILE_LIMIT = 350 * 1024 * 1024
55# Give ourselves a little breathing room from what the server expects.
56DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
57
58
Mike Frysinger0c0efa22014-02-09 23:32:23 -050059# The batch limit when talking to the dedup server. We avoid sending one at a
60# time as the round trip overhead will dominate. Conversely, we avoid sending
61# all at once so we can start uploading symbols asap -- the symbol server is a
62# bit slow and will take longer than anything else.
63# TODO: A better algorithm would be adaptive. If we have more than one symbol
64# in the upload queue waiting, we could send more symbols to the dedupe server
65# at a time.
66DEDUPE_LIMIT = 100
67
68# How long to wait for the server to respond with the results. Note that the
69# larger the limit above, the larger this will need to be. So we give it ~1
70# second per item max.
71DEDUPE_TIMEOUT = DEDUPE_LIMIT
72
73# The unique namespace in the dedupe server that only we use. Helps avoid
74# collisions with all the hashed values and unrelated content.
75OFFICIAL_DEDUPE_NAMESPACE = 'chromium-os-upload-symbols'
76STAGING_DEDUPE_NAMESPACE = '%s-staging' % OFFICIAL_DEDUPE_NAMESPACE
77
78
Mike Frysingercd78a082013-06-26 17:13:04 -040079# How long to wait (in seconds) for a single upload to complete. This has
80# to allow for symbols that are up to CRASH_SERVER_FILE_LIMIT in size.
81UPLOAD_TIMEOUT = 30 * 60
82
83
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040084# Sleep for 200ms in between uploads to avoid DoS'ing symbol server.
85DEFAULT_SLEEP_DELAY = 0.2
86
87
88# Number of seconds to wait before retrying an upload. The delay will double
89# for each subsequent retry of the same symbol file.
90INITIAL_RETRY_DELAY = 1
91
92# Allow up to 7 attempts to upload a symbol file (total delay may be
93# 1+2+4+8+16+32=63 seconds).
94MAX_RETRIES = 6
95
Mike Frysingereb753bf2013-11-22 16:05:35 -050096# Number of total errors, before uploads are no longer attempted.
97# This is used to avoid lots of errors causing unreasonable delays.
98# See the related, but independent, error values below.
99MAX_TOTAL_ERRORS_FOR_RETRY = 30
100
101# A watermark of transient errors which we allow recovery from. If we hit
102# errors infrequently, overall we're probably doing fine. For example, if
103# we have one failure every 100 passes, then we probably don't want to fail
104# right away. But if we hit a string of failures in a row, we want to abort.
105#
106# The watermark starts at 0 (and can never go below that). When this error
107# level is exceeded, we stop uploading. When a failure happens, we add the
108# fail adjustment, and when an upload succeeds, we add the pass adjustment.
109# We want to penalize failures more so that we ramp up when there is a string
110# of them, but then slowly back off as things start working.
111#
112# A quick example:
113# 0.0: Starting point.
114# 0.0: Upload works, so add -0.5, and then clamp to 0.
115# 1.0: Upload fails, so add 1.0.
116# 2.0: Upload fails, so add 1.0.
117# 1.5: Upload works, so add -0.5.
118# 1.0: Upload works, so add -0.5.
119ERROR_WATERMARK = 3.0
120ERROR_ADJUST_FAIL = 1.0
121ERROR_ADJUST_PASS = -0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400122
123
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500124def SymUpload(upload_url, sym_item):
Mike Frysinger094a2172013-08-14 12:54:35 -0400125 """Upload a symbol file to a HTTP server
126
127 The upload is a multipart/form-data POST with the following parameters:
128 code_file: the basename of the module, e.g. "app"
129 code_identifier: the module file's identifier
130 debug_file: the basename of the debugging file, e.g. "app"
131 debug_identifier: the debug file's identifier, usually consisting of
132 the guid and age embedded in the pdb, e.g.
133 "11111111BBBB3333DDDD555555555555F"
134 version: the file version of the module, e.g. "1.2.3.4"
135 product: HTTP-friendly product name
136 os: the operating system that the module was built for
137 cpu: the CPU that the module was built for
138 symbol_file: the contents of the breakpad-format symbol file
139
140 Args:
Mike Frysinger094a2172013-08-14 12:54:35 -0400141 upload_url: The crash URL to POST the |sym_file| to
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500142 sym_item: A SymbolItem containing the path to the breakpad symbol to upload
Mike Frysinger094a2172013-08-14 12:54:35 -0400143 """
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500144 sym_header = sym_item.sym_header
145 sym_file = sym_item.sym_file
Mike Frysinger094a2172013-08-14 12:54:35 -0400146
147 fields = (
148 ('code_file', sym_header.name),
149 ('debug_file', sym_header.name),
150 ('debug_identifier', sym_header.id.replace('-', '')),
151 # Should we set these fields? They aren't critical, but it might be nice?
152 # We'd have to figure out what file this symbol is coming from and what
153 # package provides it ...
154 #('version', None),
155 #('product', 'ChromeOS'),
156 ('os', sym_header.os),
157 ('cpu', sym_header.cpu),
158 poster.encode.MultipartParam.from_file('symbol_file', sym_file),
159 )
160
161 data, headers = poster.encode.multipart_encode(fields)
162 request = urllib2.Request(upload_url, data, headers)
163 request.add_header('User-agent', 'chromite.upload_symbols')
164 urllib2.urlopen(request, timeout=UPLOAD_TIMEOUT)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400165
166
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500167def TestingSymUpload(upload_url, sym_item):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400168 """A stub version of SymUpload for --testing usage"""
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500169 cmd = ['sym_upload', sym_item.sym_file, upload_url]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400170 # Randomly fail 80% of the time (the retry logic makes this 80%/3 per file).
171 returncode = random.randint(1, 100) <= 80
172 cros_build_lib.Debug('would run (and return %i): %s', returncode,
Matt Tennant7feda352013-12-20 14:03:40 -0800173 cros_build_lib.CmdToStr(cmd))
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400174 if returncode:
175 output = 'Failed to send the symbol file.'
176 else:
177 output = 'Successfully sent the symbol file.'
178 result = cros_build_lib.CommandResult(cmd=cmd, error=None, output=output,
179 returncode=returncode)
180 if returncode:
Mike Frysingera4fa1e82014-01-15 01:45:56 -0500181 exceptions = (
Mike Frysingerfd355652014-01-23 02:57:48 -0500182 socket.error('[socket.error] forced test fail'),
Mike Frysingera4fa1e82014-01-15 01:45:56 -0500183 httplib.BadStatusLine('[BadStatusLine] forced test fail'),
184 urllib2.HTTPError(upload_url, 400, '[HTTPError] forced test fail',
185 {}, None),
186 urllib2.URLError('[URLError] forced test fail'),
187 )
188 raise random.choice(exceptions)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400189 else:
190 return result
191
192
Mike Frysingereb753bf2013-11-22 16:05:35 -0500193def ErrorLimitHit(num_errors, watermark_errors):
194 """See if our error limit has been hit
195
196 Args:
197 num_errors: A multiprocessing.Value of the raw number of failures.
198 watermark_errors: A multiprocessing.Value of the current rate of failures.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500199
Mike Frysingereb753bf2013-11-22 16:05:35 -0500200 Returns:
201 True if our error limits have been exceeded.
202 """
203 return ((num_errors is not None and
204 num_errors.value > MAX_TOTAL_ERRORS_FOR_RETRY) or
205 (watermark_errors is not None and
206 watermark_errors.value > ERROR_WATERMARK))
207
208
209def _UpdateCounter(counter, adj):
210 """Update |counter| by |adj|
211
212 Handle atomic updates of |counter|. Also make sure it does not
213 fall below 0.
214
215 Args:
216 counter: A multiprocessing.Value to update
217 adj: The value to add to |counter|
218 """
219 def _Update():
220 clamp = 0 if type(adj) is int else 0.0
221 counter.value = max(clamp, counter.value + adj)
222
223 if hasattr(counter, 'get_lock'):
224 with counter.get_lock():
225 _Update()
226 elif counter is not None:
227 _Update()
228
229
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500230def UploadSymbol(upload_url, sym_item, file_limit=DEFAULT_FILE_LIMIT,
Mike Frysinger02e92402013-11-22 16:22:02 -0500231 sleep=0, num_errors=None, watermark_errors=None,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500232 failed_queue=None, passed_queue=None):
233 """Upload |sym_item| to |upload_url|
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400234
235 Args:
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400236 upload_url: The crash server to upload things to
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500237 sym_item: A SymbolItem containing the path to the breakpad symbol to upload
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400238 file_limit: The max file size of a symbol file before we try to strip it
239 sleep: Number of seconds to sleep before running
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400240 num_errors: An object to update with the error count (needs a .value member)
Mike Frysingereb753bf2013-11-22 16:05:35 -0500241 watermark_errors: An object to track current error behavior (needs a .value)
Mike Frysinger02e92402013-11-22 16:22:02 -0500242 failed_queue: When a symbol fails, add it to this queue
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500243 passed_queue: When a symbol passes, add it to this queue
Mike Frysinger1a736a82013-12-12 01:50:59 -0500244
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400245 Returns:
246 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400247 """
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500248 sym_file = sym_item.sym_file
249 upload_item = sym_item
250
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400251 if num_errors is None:
252 num_errors = ctypes.c_int()
Mike Frysingereb753bf2013-11-22 16:05:35 -0500253 if ErrorLimitHit(num_errors, watermark_errors):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400254 # Abandon ship! It's on fire! NOoooooooooooOOOoooooo.
Mike Frysinger7f9be142014-01-15 02:16:42 -0500255 if failed_queue:
256 failed_queue.put(sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400257 return 0
258
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400259 if sleep:
260 # Keeps us from DoS-ing the symbol server.
261 time.sleep(sleep)
262
263 cros_build_lib.Debug('uploading %s' % sym_file)
264
265 # Ideally there'd be a tempfile.SpooledNamedTemporaryFile that we could use.
266 with tempfile.NamedTemporaryFile(prefix='upload_symbols',
267 bufsize=0) as temp_sym_file:
268 if file_limit:
269 # If the symbols size is too big, strip out the call frame info. The CFI
270 # is unnecessary for 32bit x86 targets where the frame pointer is used (as
271 # all of ours have) and it accounts for over half the size of the symbols
272 # uploaded.
273 file_size = os.path.getsize(sym_file)
274 if file_size > file_limit:
275 cros_build_lib.Warning('stripping CFI from %s due to size %s > %s',
276 sym_file, file_size, file_limit)
277 temp_sym_file.writelines([x for x in open(sym_file, 'rb').readlines()
278 if not x.startswith('STACK CFI')])
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500279
280 upload_item = FakeItem(sym_file=temp_sym_file.name,
281 sym_header=sym_item.sym_header)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400282
283 # Hopefully the crash server will let it through. But it probably won't.
284 # Not sure what the best answer is in this case.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500285 file_size = os.path.getsize(upload_item.sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400286 if file_size > CRASH_SERVER_FILE_LIMIT:
287 cros_build_lib.PrintBuildbotStepWarnings()
Mike Frysinger02e92402013-11-22 16:22:02 -0500288 cros_build_lib.Warning('upload file %s is awfully large, risking '
289 'rejection by the symbol server (%s > %s)',
290 sym_file, file_size, CRASH_SERVER_FILE_LIMIT)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400291
292 # Upload the symbol file.
Mike Frysingereb753bf2013-11-22 16:05:35 -0500293 success = False
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400294 try:
Mike Frysinger9adcfd22013-10-24 12:01:40 -0400295 cros_build_lib.TimedCommand(
David Jamesc93e6a4d2014-01-13 11:37:36 -0800296 retry_util.RetryException,
Mike Frysinger9adcfd22013-10-24 12:01:40 -0400297 (urllib2.HTTPError, urllib2.URLError), MAX_RETRIES, SymUpload,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500298 upload_url, upload_item, sleep=INITIAL_RETRY_DELAY,
Mike Frysinger9adcfd22013-10-24 12:01:40 -0400299 timed_log_msg='upload of %10i bytes took %%s: %s' %
300 (file_size, os.path.basename(sym_file)))
Mike Frysingereb753bf2013-11-22 16:05:35 -0500301 success = True
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500302
303 if passed_queue:
304 passed_queue.put(sym_item)
Mike Frysinger094a2172013-08-14 12:54:35 -0400305 except urllib2.HTTPError as e:
306 cros_build_lib.Warning('could not upload: %s: HTTP %s: %s',
307 os.path.basename(sym_file), e.code, e.reason)
Mike Frysingerfd355652014-01-23 02:57:48 -0500308 except (urllib2.URLError, httplib.HTTPException, socket.error) as e:
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400309 cros_build_lib.Warning('could not upload: %s: %s',
310 os.path.basename(sym_file), e)
Mike Frysingereb753bf2013-11-22 16:05:35 -0500311 finally:
312 if success:
313 _UpdateCounter(watermark_errors, ERROR_ADJUST_PASS)
314 else:
315 _UpdateCounter(num_errors, 1)
316 _UpdateCounter(watermark_errors, ERROR_ADJUST_FAIL)
Mike Frysinger02e92402013-11-22 16:22:02 -0500317 if failed_queue:
318 failed_queue.put(sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400319
320 return num_errors.value
321
322
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500323# A dummy class that allows for stubbing in tests and SymUpload.
324FakeItem = cros_build_lib.Collection(
325 'FakeItem', sym_file=None, sym_header=None, content=lambda x: '')
326
327
328# TODO(build): Delete this if check. http://crbug.com/341152
329if isolateserver:
330 class SymbolItem(isolateserver.BufferItem):
331 """Turn a sym_file into an isolateserver.Item"""
332
333 ALGO = hashlib.sha1
334
335 def __init__(self, sym_file):
336 sym_header = cros_generate_breakpad_symbols.ReadSymsHeader(sym_file)
337 super(SymbolItem, self).__init__(str(sym_header), self.ALGO)
338 self.sym_header = sym_header
339 self.sym_file = sym_file
340
341
342def SymbolDeduplicatorNotify(dedupe_namespace, dedupe_queue):
343 """Send a symbol file to the swarming service
344
345 Notify the swarming service of a successful upload. If the notification fails
346 for any reason, we ignore it. We don't care as it just means we'll upload it
347 again later on, and the symbol server will handle that graciously.
348
349 This func runs in a different process from the main one, so we cannot share
350 the storage object. Instead, we create our own. This func stays alive for
351 the life of the process, so we only create one here overall.
352
353 Args:
354 dedupe_namespace: The isolateserver namespace to dedupe uploaded symbols.
355 dedupe_queue: The queue to read SymbolItems from
356 """
357 if dedupe_queue is None:
358 return
359
360 item = None
361 try:
362 storage = isolateserver.get_storage_api(constants.ISOLATESERVER,
363 dedupe_namespace)
364 for item in iter(dedupe_queue.get, None):
365 with timeout_util.Timeout(DEDUPE_TIMEOUT):
366 storage.push(item, item.content(0))
367 except Exception:
368 sym_file = item.sym_file if (item and item.sym_file) else ''
369 cros_build_lib.Warning('posting %s to dedupe server failed',
370 os.path.basename(sym_file), exc_info=True)
371
372
373def SymbolDeduplicator(storage, sym_paths):
374 """Filter out symbol files that we've already uploaded
375
376 Using the swarming service, ask it to tell us which symbol files we've already
377 uploaded in previous runs and/or by other bots. If the query fails for any
378 reason, we'll just upload all symbols. This is fine as the symbol server will
379 do the right thing and this phase is purely an optimization.
380
381 This code runs in the main thread which is why we can re-use the existing
382 storage object. Saves us from having to recreate one all the time.
383
384 Args:
385 storage: An isolateserver.StorageApi object
386 sym_paths: List of symbol files to check against the dedupe server
387
388 Returns:
389 List of symbol files that have not been uploaded before
390 """
391 if not sym_paths:
392 return sym_paths
393
394 items = [SymbolItem(x) for x in sym_paths]
395 if storage:
396 try:
397 with timeout_util.Timeout(DEDUPE_TIMEOUT):
398 items = storage.contains(items)
399 except Exception:
400 cros_build_lib.Warning('talking to dedupe server failed', exc_info=True)
401
402 return items
403
404
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500405def SymbolFinder(paths):
406 """Locate symbol files in |paths|
407
408 Args:
409 paths: A list of input paths to walk. Files are returned w/out any checks.
410 Dirs are searched for files that end in ".sym".
Mike Frysinger1a736a82013-12-12 01:50:59 -0500411
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500412 Returns:
413 Yield every viable sym file.
414 """
415 for p in paths:
416 if os.path.isdir(p):
417 for root, _, files in os.walk(p):
418 for f in files:
419 if f.endswith('.sym'):
420 yield os.path.join(root, f)
421 else:
422 yield p
423
424
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500425def WriteQueueToFile(listing, queue, relpath=None):
426 """Write all the items in |queue| to the |listing|.
427
428 Args:
429 listing: Where to write out the list of files.
430 queue: The queue of paths to drain.
431 relpath: If set, write out paths relative to this one.
432 """
433 if not listing:
434 return
435
436 with cros_build_lib.Open(listing, 'wb+') as f:
437 while not queue.empty():
438 path = queue.get()
439 if relpath:
440 path = os.path.relpath(path, relpath)
441 f.write('%s\n' % path)
442
443
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400444def UploadSymbols(board=None, official=False, breakpad_dir=None,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400445 file_limit=DEFAULT_FILE_LIMIT, sleep=DEFAULT_SLEEP_DELAY,
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500446 upload_limit=None, sym_paths=None, failed_list=None,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500447 root=None, retry=True, dedupe_namespace=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400448 """Upload all the generated symbols for |board| to the crash server
449
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400450 You can use in a few ways:
451 * pass |board| to locate all of its symbols
452 * pass |breakpad_dir| to upload all the symbols in there
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500453 * pass |sym_paths| to upload specific symbols (or dirs of symbols)
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400454
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400455 Args:
456 board: The board whose symbols we wish to upload
457 official: Use the official symbol server rather than the staging one
458 breakpad_dir: The full path to the breakpad directory where symbols live
459 file_limit: The max file size of a symbol file before we try to strip it
460 sleep: How long to sleep in between uploads
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500461 upload_limit: If set, only upload this many symbols (meant for testing)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500462 sym_paths: Specific symbol files (or dirs of sym files) to upload,
463 otherwise search |breakpad_dir|
Mike Frysinger7f9be142014-01-15 02:16:42 -0500464 failed_list: Write the names of all sym files we did not upload; can be a
465 filename or file-like object.
Mike Frysinger118d2502013-08-19 03:36:56 -0400466 root: The tree to prefix to |breakpad_dir| (if |breakpad_dir| is not set)
Mike Frysinger02e92402013-11-22 16:22:02 -0500467 retry: Whether we should retry failures.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500468 dedupe_namespace: The isolateserver namespace to dedupe uploaded symbols.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500469
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400470 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400471 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400472 """
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500473 # TODO(build): Delete this assert.
474 assert isolateserver, 'Missing isolateserver import http://crbug.com/341152'
475
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400476 if official:
477 upload_url = OFFICIAL_UPLOAD_URL
478 else:
479 cros_build_lib.Warning('unofficial builds upload to the staging server')
480 upload_url = STAGING_UPLOAD_URL
481
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500482 if sym_paths:
483 cros_build_lib.Info('uploading specified symbols to %s', upload_url)
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400484 else:
485 if breakpad_dir is None:
Mike Frysinger118d2502013-08-19 03:36:56 -0400486 breakpad_dir = os.path.join(
487 root,
488 cros_generate_breakpad_symbols.FindBreakpadDir(board).lstrip('/'))
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400489 cros_build_lib.Info('uploading all symbols to %s from %s', upload_url,
490 breakpad_dir)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500491 sym_paths = [breakpad_dir]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400492
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500493 # We use storage_query to ask the server about existing symbols. The
494 # storage_notify_proc process is used to post updates to the server. We
495 # cannot safely share the storage object between threads/processes, but
496 # we also want to minimize creating new ones as each object has to init
497 # new state (like server connections).
498 if dedupe_namespace:
499 dedupe_limit = DEDUPE_LIMIT
500 dedupe_queue = multiprocessing.Queue()
501 storage_query = isolateserver.get_storage_api(constants.ISOLATESERVER,
502 dedupe_namespace)
503 else:
504 dedupe_limit = 1
505 dedupe_queue = storage_query = None
506 # Can't use parallel.BackgroundTaskRunner because that'll create multiple
507 # processes and we want only one the whole time (see comment above).
508 storage_notify_proc = multiprocessing.Process(
509 target=SymbolDeduplicatorNotify, args=(dedupe_namespace, dedupe_queue))
510
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400511 bg_errors = multiprocessing.Value('i')
Mike Frysingereb753bf2013-11-22 16:05:35 -0500512 watermark_errors = multiprocessing.Value('f')
Mike Frysinger02e92402013-11-22 16:22:02 -0500513 failed_queue = multiprocessing.Queue()
514 uploader = functools.partial(
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500515 UploadSymbol, upload_url, file_limit=file_limit, sleep=sleep,
516 num_errors=bg_errors, watermark_errors=watermark_errors,
517 failed_queue=failed_queue, passed_queue=dedupe_queue)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400518
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500519 start_time = datetime.datetime.now()
520 Counters = cros_build_lib.Collection(
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500521 'Counters', upload_limit=upload_limit, uploaded_count=0, deduped_count=0)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500522 counters = Counters()
523
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500524 def _Upload(queue, counters, files):
525 if not files:
526 return
527
528 missing_count = 0
529 for item in SymbolDeduplicator(storage_query, files):
530 if counters.upload_limit == 0:
531 break
532
533 missing_count += 1
534 queue.put((item,))
535 counters.uploaded_count += 1
536 if counters.upload_limit is not None:
537 counters.upload_limit -= 1
538
539 counters.deduped_count += (len(files) - missing_count)
540
541 storage_notify_proc.start()
Mike Frysinger02e92402013-11-22 16:22:02 -0500542 # For the first run, we collect the symbols that failed. If the
543 # overall failure rate was low, we'll retry them on the second run.
544 for retry in (retry, False):
545 # We need to limit ourselves to one upload at a time to avoid the server
546 # kicking in DoS protection. See these bugs for more details:
547 # http://crbug.com/209442
548 # http://crbug.com/212496
549 with parallel.BackgroundTaskRunner(uploader, processes=1) as queue:
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500550 dedupe_list = []
Mike Frysinger02e92402013-11-22 16:22:02 -0500551 for sym_file in SymbolFinder(sym_paths):
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500552 dedupe_list.append(sym_file)
553 dedupe_len = len(dedupe_list)
554 if dedupe_len < dedupe_limit:
555 if (counters.upload_limit is None or
556 dedupe_len < counters.upload_limit):
557 continue
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400558
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500559 _Upload(queue, counters, dedupe_list)
560 dedupe_list = []
561 _Upload(queue, counters, dedupe_list)
Mike Frysinger02e92402013-11-22 16:22:02 -0500562
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500563 # See if we need to retry, and if we haven't failed too many times yet.
Mike Frysinger02e92402013-11-22 16:22:02 -0500564 if not retry or ErrorLimitHit(bg_errors, watermark_errors):
565 break
566
567 sym_paths = []
568 while not failed_queue.empty():
569 sym_paths.append(failed_queue.get())
570 if sym_paths:
571 cros_build_lib.Warning('retrying %i symbols', len(sym_paths))
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500572 if counters.upload_limit is not None:
573 counters.upload_limit += len(sym_paths)
Mike Frysinger02e92402013-11-22 16:22:02 -0500574 # Decrement the error count in case we recover in the second pass.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500575 assert bg_errors.value >= len(sym_paths), \
576 'more failed files than errors?'
Mike Frysinger02e92402013-11-22 16:22:02 -0500577 bg_errors.value -= len(sym_paths)
578 else:
579 # No failed symbols, so just return now.
580 break
581
Mike Frysinger7f9be142014-01-15 02:16:42 -0500582 # If the user has requested it, save all the symbol files that we failed to
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500583 # upload to a listing file. This should help with recovery efforts later.
584 WriteQueueToFile(failed_list, failed_queue, breakpad_dir)
Mike Frysinger7f9be142014-01-15 02:16:42 -0500585
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500586 if dedupe_queue:
587 dedupe_queue.put(None)
588 dedupe_queue.close()
589 storage_notify_proc.join()
590
591 cros_build_lib.Info('uploaded %i symbols (%i were deduped) which took: %s',
592 counters.uploaded_count, counters.deduped_count,
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500593 datetime.datetime.now() - start_time)
594
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500595 return bg_errors.value
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400596
597
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400598def main(argv):
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500599 # TODO(build): Delete this assert.
600 assert isolateserver, 'Missing isolateserver import http://crbug.com/341152'
601
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400602 parser = commandline.ArgumentParser(description=__doc__)
603
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500604 parser.add_argument('sym_paths', type='path', nargs='*', default=None)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400605 parser.add_argument('--board', default=None,
606 help='board to build packages for')
607 parser.add_argument('--breakpad_root', type='path', default=None,
608 help='root directory for breakpad symbols')
609 parser.add_argument('--official_build', action='store_true', default=False,
610 help='point to official symbol server')
611 parser.add_argument('--regenerate', action='store_true', default=False,
612 help='regenerate all symbols')
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500613 parser.add_argument('--upload-limit', type=int, default=None,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400614 help='only upload # number of symbols')
615 parser.add_argument('--strip_cfi', type=int,
616 default=CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024),
617 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500618 parser.add_argument('--failed-list', type='path',
619 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500620 parser.add_argument('--dedupe', action='store_true', default=False,
621 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400622 parser.add_argument('--testing', action='store_true', default=False,
623 help='run in testing mode')
624 parser.add_argument('--yes', action='store_true', default=False,
625 help='answer yes to all prompts')
626
627 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500628 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400629
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500630 if opts.sym_paths:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400631 if opts.regenerate:
632 cros_build_lib.Die('--regenerate may not be used with specific files')
633 else:
634 if opts.board is None:
635 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400636
637 if opts.breakpad_root and opts.regenerate:
638 cros_build_lib.Die('--regenerate may not be used with --breakpad_root')
639
640 if opts.testing:
641 # TODO(build): Kill off --testing mode once unittests are up-to-snuff.
642 cros_build_lib.Info('running in testing mode')
643 # pylint: disable=W0601,W0603
644 global INITIAL_RETRY_DELAY, SymUpload, DEFAULT_SLEEP_DELAY
645 INITIAL_RETRY_DELAY = DEFAULT_SLEEP_DELAY = 0
646 SymUpload = TestingSymUpload
647
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500648 dedupe_namespace = None
649 if opts.dedupe:
650 if opts.official_build and not opts.testing:
651 dedupe_namespace = OFFICIAL_DEDUPE_NAMESPACE
652 else:
653 dedupe_namespace = STAGING_DEDUPE_NAMESPACE
654
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400655 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500656 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400657 Uploading symbols for an entire Chromium OS build is really only
658 necessary for release builds and in a few cases for developers
659 to debug problems. It will take considerable time to run. For
660 developer debugging purposes, consider instead passing specific
661 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500662 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400663 if not cros_build_lib.BooleanPrompt(
664 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500665 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400666 cros_build_lib.Die('better safe than sorry')
667
668 ret = 0
669 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400670 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
671 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400672
673 ret += UploadSymbols(opts.board, official=opts.official_build,
674 breakpad_dir=opts.breakpad_root,
675 file_limit=opts.strip_cfi, sleep=DEFAULT_SLEEP_DELAY,
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500676 upload_limit=opts.upload_limit, sym_paths=opts.sym_paths,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500677 failed_list=opts.failed_list,
678 dedupe_namespace=dedupe_namespace)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400679 if ret:
680 cros_build_lib.Error('encountered %i problem(s)', ret)
681 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
682 # return 0 in case we are a multiple of the mask.
683 ret = 1
684
685 return ret
Mike Frysinger094a2172013-08-14 12:54:35 -0400686
687
688# We need this to run once per process. Do it at module import time as that
689# will let us avoid doing it inline at function call time (see SymUpload) as
690# that func might be called by the multiprocessing module which means we'll
691# do the opener logic multiple times overall. Plus, if you're importing this
692# module, it's a pretty good chance that you're going to need this.
693poster.streaminghttp.register_openers()