blob: 8b1811b310311efa38e1096b250923da8988ca63 [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('-', '')),
Mike Frysingerb8a966b2014-03-19 17:36:18 -0400155 # The product/version fields are used by the server only for statistic
156 # purposes. They do not impact symbolization, so they're safe to set
157 # to any value all the time.
158 # In this case, we use it to help see the load our build system is
159 # placing on the server.
160 # Not sure what to set for the version. Maybe the git sha1 of this file.
161 # Note: the server restricts this to 30 chars.
Mike Frysinger094a2172013-08-14 12:54:35 -0400162 #('version', None),
Mike Frysingerb8a966b2014-03-19 17:36:18 -0400163 ('product', 'ChromeOS'),
Mike Frysinger094a2172013-08-14 12:54:35 -0400164 ('os', sym_header.os),
165 ('cpu', sym_header.cpu),
166 poster.encode.MultipartParam.from_file('symbol_file', sym_file),
167 )
168
169 data, headers = poster.encode.multipart_encode(fields)
170 request = urllib2.Request(upload_url, data, headers)
171 request.add_header('User-agent', 'chromite.upload_symbols')
172 urllib2.urlopen(request, timeout=UPLOAD_TIMEOUT)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400173
174
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500175def TestingSymUpload(upload_url, sym_item):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400176 """A stub version of SymUpload for --testing usage"""
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500177 cmd = ['sym_upload', sym_item.sym_file, upload_url]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400178 # Randomly fail 80% of the time (the retry logic makes this 80%/3 per file).
179 returncode = random.randint(1, 100) <= 80
180 cros_build_lib.Debug('would run (and return %i): %s', returncode,
Matt Tennant7feda352013-12-20 14:03:40 -0800181 cros_build_lib.CmdToStr(cmd))
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400182 if returncode:
183 output = 'Failed to send the symbol file.'
184 else:
185 output = 'Successfully sent the symbol file.'
186 result = cros_build_lib.CommandResult(cmd=cmd, error=None, output=output,
187 returncode=returncode)
188 if returncode:
Mike Frysingera4fa1e82014-01-15 01:45:56 -0500189 exceptions = (
Mike Frysingerfd355652014-01-23 02:57:48 -0500190 socket.error('[socket.error] forced test fail'),
Mike Frysingera4fa1e82014-01-15 01:45:56 -0500191 httplib.BadStatusLine('[BadStatusLine] forced test fail'),
192 urllib2.HTTPError(upload_url, 400, '[HTTPError] forced test fail',
193 {}, None),
194 urllib2.URLError('[URLError] forced test fail'),
195 )
196 raise random.choice(exceptions)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400197 else:
198 return result
199
200
Mike Frysingereb753bf2013-11-22 16:05:35 -0500201def ErrorLimitHit(num_errors, watermark_errors):
202 """See if our error limit has been hit
203
204 Args:
205 num_errors: A multiprocessing.Value of the raw number of failures.
206 watermark_errors: A multiprocessing.Value of the current rate of failures.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500207
Mike Frysingereb753bf2013-11-22 16:05:35 -0500208 Returns:
209 True if our error limits have been exceeded.
210 """
211 return ((num_errors is not None and
212 num_errors.value > MAX_TOTAL_ERRORS_FOR_RETRY) or
213 (watermark_errors is not None and
214 watermark_errors.value > ERROR_WATERMARK))
215
216
217def _UpdateCounter(counter, adj):
218 """Update |counter| by |adj|
219
220 Handle atomic updates of |counter|. Also make sure it does not
221 fall below 0.
222
223 Args:
224 counter: A multiprocessing.Value to update
225 adj: The value to add to |counter|
226 """
227 def _Update():
228 clamp = 0 if type(adj) is int else 0.0
229 counter.value = max(clamp, counter.value + adj)
230
231 if hasattr(counter, 'get_lock'):
232 with counter.get_lock():
233 _Update()
234 elif counter is not None:
235 _Update()
236
237
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500238def UploadSymbol(upload_url, sym_item, file_limit=DEFAULT_FILE_LIMIT,
Mike Frysinger02e92402013-11-22 16:22:02 -0500239 sleep=0, num_errors=None, watermark_errors=None,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500240 failed_queue=None, passed_queue=None):
241 """Upload |sym_item| to |upload_url|
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400242
243 Args:
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400244 upload_url: The crash server to upload things to
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500245 sym_item: A SymbolItem containing the path to the breakpad symbol to upload
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400246 file_limit: The max file size of a symbol file before we try to strip it
247 sleep: Number of seconds to sleep before running
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400248 num_errors: An object to update with the error count (needs a .value member)
Mike Frysingereb753bf2013-11-22 16:05:35 -0500249 watermark_errors: An object to track current error behavior (needs a .value)
Mike Frysinger02e92402013-11-22 16:22:02 -0500250 failed_queue: When a symbol fails, add it to this queue
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500251 passed_queue: When a symbol passes, add it to this queue
Mike Frysinger1a736a82013-12-12 01:50:59 -0500252
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400253 Returns:
254 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400255 """
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500256 sym_file = sym_item.sym_file
257 upload_item = sym_item
258
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400259 if num_errors is None:
260 num_errors = ctypes.c_int()
Mike Frysingereb753bf2013-11-22 16:05:35 -0500261 if ErrorLimitHit(num_errors, watermark_errors):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400262 # Abandon ship! It's on fire! NOoooooooooooOOOoooooo.
Mike Frysinger7f9be142014-01-15 02:16:42 -0500263 if failed_queue:
264 failed_queue.put(sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400265 return 0
266
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400267 if sleep:
268 # Keeps us from DoS-ing the symbol server.
269 time.sleep(sleep)
270
271 cros_build_lib.Debug('uploading %s' % sym_file)
272
273 # Ideally there'd be a tempfile.SpooledNamedTemporaryFile that we could use.
274 with tempfile.NamedTemporaryFile(prefix='upload_symbols',
275 bufsize=0) as temp_sym_file:
276 if file_limit:
277 # If the symbols size is too big, strip out the call frame info. The CFI
278 # is unnecessary for 32bit x86 targets where the frame pointer is used (as
279 # all of ours have) and it accounts for over half the size of the symbols
280 # uploaded.
281 file_size = os.path.getsize(sym_file)
282 if file_size > file_limit:
283 cros_build_lib.Warning('stripping CFI from %s due to size %s > %s',
284 sym_file, file_size, file_limit)
285 temp_sym_file.writelines([x for x in open(sym_file, 'rb').readlines()
286 if not x.startswith('STACK CFI')])
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500287
288 upload_item = FakeItem(sym_file=temp_sym_file.name,
289 sym_header=sym_item.sym_header)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400290
291 # Hopefully the crash server will let it through. But it probably won't.
292 # Not sure what the best answer is in this case.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500293 file_size = os.path.getsize(upload_item.sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400294 if file_size > CRASH_SERVER_FILE_LIMIT:
295 cros_build_lib.PrintBuildbotStepWarnings()
Mike Frysinger02e92402013-11-22 16:22:02 -0500296 cros_build_lib.Warning('upload file %s is awfully large, risking '
297 'rejection by the symbol server (%s > %s)',
298 sym_file, file_size, CRASH_SERVER_FILE_LIMIT)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400299
300 # Upload the symbol file.
Mike Frysingereb753bf2013-11-22 16:05:35 -0500301 success = False
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400302 try:
Mike Frysinger9adcfd22013-10-24 12:01:40 -0400303 cros_build_lib.TimedCommand(
David Jamesc93e6a4d2014-01-13 11:37:36 -0800304 retry_util.RetryException,
Mike Frysinger9adcfd22013-10-24 12:01:40 -0400305 (urllib2.HTTPError, urllib2.URLError), MAX_RETRIES, SymUpload,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500306 upload_url, upload_item, sleep=INITIAL_RETRY_DELAY,
Mike Frysinger9adcfd22013-10-24 12:01:40 -0400307 timed_log_msg='upload of %10i bytes took %%s: %s' %
308 (file_size, os.path.basename(sym_file)))
Mike Frysingereb753bf2013-11-22 16:05:35 -0500309 success = True
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500310
311 if passed_queue:
312 passed_queue.put(sym_item)
Mike Frysinger094a2172013-08-14 12:54:35 -0400313 except urllib2.HTTPError as e:
314 cros_build_lib.Warning('could not upload: %s: HTTP %s: %s',
315 os.path.basename(sym_file), e.code, e.reason)
Mike Frysingerfd355652014-01-23 02:57:48 -0500316 except (urllib2.URLError, httplib.HTTPException, socket.error) as e:
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400317 cros_build_lib.Warning('could not upload: %s: %s',
318 os.path.basename(sym_file), e)
Mike Frysingereb753bf2013-11-22 16:05:35 -0500319 finally:
320 if success:
321 _UpdateCounter(watermark_errors, ERROR_ADJUST_PASS)
322 else:
323 _UpdateCounter(num_errors, 1)
324 _UpdateCounter(watermark_errors, ERROR_ADJUST_FAIL)
Mike Frysinger02e92402013-11-22 16:22:02 -0500325 if failed_queue:
326 failed_queue.put(sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400327
328 return num_errors.value
329
330
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500331# A dummy class that allows for stubbing in tests and SymUpload.
332FakeItem = cros_build_lib.Collection(
333 'FakeItem', sym_file=None, sym_header=None, content=lambda x: '')
334
335
336# TODO(build): Delete this if check. http://crbug.com/341152
337if isolateserver:
338 class SymbolItem(isolateserver.BufferItem):
339 """Turn a sym_file into an isolateserver.Item"""
340
341 ALGO = hashlib.sha1
342
343 def __init__(self, sym_file):
344 sym_header = cros_generate_breakpad_symbols.ReadSymsHeader(sym_file)
345 super(SymbolItem, self).__init__(str(sym_header), self.ALGO)
346 self.sym_header = sym_header
347 self.sym_file = sym_file
348
349
350def SymbolDeduplicatorNotify(dedupe_namespace, dedupe_queue):
351 """Send a symbol file to the swarming service
352
353 Notify the swarming service of a successful upload. If the notification fails
354 for any reason, we ignore it. We don't care as it just means we'll upload it
355 again later on, and the symbol server will handle that graciously.
356
357 This func runs in a different process from the main one, so we cannot share
358 the storage object. Instead, we create our own. This func stays alive for
359 the life of the process, so we only create one here overall.
360
361 Args:
362 dedupe_namespace: The isolateserver namespace to dedupe uploaded symbols.
363 dedupe_queue: The queue to read SymbolItems from
364 """
365 if dedupe_queue is None:
366 return
367
368 item = None
369 try:
Mike Frysinger650e6722014-04-28 18:29:15 -0400370 with timeout_util.Timeout(DEDUPE_TIMEOUT):
371 storage = isolateserver.get_storage_api(constants.ISOLATESERVER,
372 dedupe_namespace)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500373 for item in iter(dedupe_queue.get, None):
374 with timeout_util.Timeout(DEDUPE_TIMEOUT):
Mike Frysingerefef3672014-04-20 10:06:45 -0400375 cros_build_lib.Debug('sending %s to dedupe server', item.sym_file)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500376 storage.push(item, item.content(0))
Mike Frysingerae298452014-03-24 22:45:23 -0400377 cros_build_lib.Info('dedupe notification finished; exiting')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500378 except Exception:
379 sym_file = item.sym_file if (item and item.sym_file) else ''
380 cros_build_lib.Warning('posting %s to dedupe server failed',
381 os.path.basename(sym_file), exc_info=True)
382
Mike Frysinger58312e92014-03-18 04:18:36 -0400383 # Keep draining the queue though so it doesn't fill up.
384 while dedupe_queue.get() is not None:
385 continue
386
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500387
388def SymbolDeduplicator(storage, sym_paths):
389 """Filter out symbol files that we've already uploaded
390
391 Using the swarming service, ask it to tell us which symbol files we've already
392 uploaded in previous runs and/or by other bots. If the query fails for any
393 reason, we'll just upload all symbols. This is fine as the symbol server will
394 do the right thing and this phase is purely an optimization.
395
396 This code runs in the main thread which is why we can re-use the existing
397 storage object. Saves us from having to recreate one all the time.
398
399 Args:
400 storage: An isolateserver.StorageApi object
401 sym_paths: List of symbol files to check against the dedupe server
402
403 Returns:
404 List of symbol files that have not been uploaded before
405 """
406 if not sym_paths:
407 return sym_paths
408
409 items = [SymbolItem(x) for x in sym_paths]
410 if storage:
411 try:
412 with timeout_util.Timeout(DEDUPE_TIMEOUT):
413 items = storage.contains(items)
414 except Exception:
415 cros_build_lib.Warning('talking to dedupe server failed', exc_info=True)
416
417 return items
418
419
Mike Frysingerd41938e2014-02-10 06:37:55 -0500420def IsTarball(path):
421 """Guess if this is a tarball based on the filename."""
422 parts = path.split('.')
423 if len(parts) <= 1:
424 return False
425
426 if parts[-1] == 'tar':
427 return True
428
429 if parts[-2] == 'tar':
430 return parts[-1] in ('bz2', 'gz', 'xz')
431
432 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
433
434
435def SymbolFinder(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500436 """Locate symbol files in |paths|
437
438 Args:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500439 tempdir: Path to use for temporary files (caller will clean up).
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500440 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500441 Dirs are searched for files that end in ".sym". Urls are fetched and then
442 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500443
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500444 Returns:
445 Yield every viable sym file.
446 """
447 for p in paths:
Don Garrett25f309a2014-03-19 14:02:12 -0700448 # Pylint is confused about members of ParseResult.
Don Garrettf8bf7842014-03-20 17:03:42 -0700449
Mike Frysingerd41938e2014-02-10 06:37:55 -0500450 o = urlparse.urlparse(p)
Don Garrettf8bf7842014-03-20 17:03:42 -0700451 if o.scheme: # pylint: disable=E1101
Mike Frysingerd41938e2014-02-10 06:37:55 -0500452 # Support globs of filenames.
453 ctx = gs.GSContext()
454 for p in ctx.LS(p):
455 cros_build_lib.Info('processing files inside %s', p)
456 o = urlparse.urlparse(p)
457 cache_dir = commandline.GetCacheDir()
458 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
459 tar_cache = cache.TarballCache(common_path)
Don Garrettf8bf7842014-03-20 17:03:42 -0700460 key = ('%s%s' % (o.netloc, o.path)).split('/') # pylint: disable=E1101
Mike Frysingerd41938e2014-02-10 06:37:55 -0500461 # The common cache will not be LRU, removing the need to hold a read
462 # lock on the cached gsutil.
463 ref = tar_cache.Lookup(key)
464 try:
465 ref.SetDefault(p)
466 except cros_build_lib.RunCommandError as e:
467 cros_build_lib.Warning('ignoring %s\n%s', p, e)
468 continue
469 for p in SymbolFinder(tempdir, [ref.path]):
470 yield p
471
472 elif os.path.isdir(p):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500473 for root, _, files in os.walk(p):
474 for f in files:
475 if f.endswith('.sym'):
476 yield os.path.join(root, f)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500477
478 elif IsTarball(p):
479 cros_build_lib.Info('processing files inside %s', p)
480 tardir = tempfile.mkdtemp(dir=tempdir)
481 cache.Untar(os.path.realpath(p), tardir)
482 for p in SymbolFinder(tardir, [tardir]):
483 yield p
484
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500485 else:
486 yield p
487
488
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500489def WriteQueueToFile(listing, queue, relpath=None):
490 """Write all the items in |queue| to the |listing|.
491
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500492 Note: The queue must have a sentinel None appended to the end.
493
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500494 Args:
495 listing: Where to write out the list of files.
496 queue: The queue of paths to drain.
497 relpath: If set, write out paths relative to this one.
498 """
499 if not listing:
Mike Frysingera0ddac62014-03-14 10:30:25 -0400500 # Still drain the queue so we make sure the producer has finished
501 # before we return. Otherwise, the queue might get destroyed too
502 # quickly which will trigger a traceback in the producer.
503 while queue.get() is not None:
504 continue
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500505 return
506
507 with cros_build_lib.Open(listing, 'wb+') as f:
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500508 while True:
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500509 path = queue.get()
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500510 if path is None:
511 return
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500512 if relpath:
513 path = os.path.relpath(path, relpath)
514 f.write('%s\n' % path)
515
516
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400517def UploadSymbols(board=None, official=False, breakpad_dir=None,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400518 file_limit=DEFAULT_FILE_LIMIT, sleep=DEFAULT_SLEEP_DELAY,
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500519 upload_limit=None, sym_paths=None, failed_list=None,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500520 root=None, retry=True, dedupe_namespace=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400521 """Upload all the generated symbols for |board| to the crash server
522
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400523 You can use in a few ways:
524 * pass |board| to locate all of its symbols
525 * pass |breakpad_dir| to upload all the symbols in there
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500526 * pass |sym_paths| to upload specific symbols (or dirs of symbols)
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400527
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400528 Args:
529 board: The board whose symbols we wish to upload
530 official: Use the official symbol server rather than the staging one
531 breakpad_dir: The full path to the breakpad directory where symbols live
532 file_limit: The max file size of a symbol file before we try to strip it
533 sleep: How long to sleep in between uploads
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500534 upload_limit: If set, only upload this many symbols (meant for testing)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500535 sym_paths: Specific symbol files (or dirs of sym files) to upload,
536 otherwise search |breakpad_dir|
Mike Frysinger7f9be142014-01-15 02:16:42 -0500537 failed_list: Write the names of all sym files we did not upload; can be a
538 filename or file-like object.
Mike Frysinger118d2502013-08-19 03:36:56 -0400539 root: The tree to prefix to |breakpad_dir| (if |breakpad_dir| is not set)
Mike Frysinger02e92402013-11-22 16:22:02 -0500540 retry: Whether we should retry failures.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500541 dedupe_namespace: The isolateserver namespace to dedupe uploaded symbols.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500542
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400543 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400544 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400545 """
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500546 # TODO(build): Delete this assert.
547 assert isolateserver, 'Missing isolateserver import http://crbug.com/341152'
548
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400549 if official:
550 upload_url = OFFICIAL_UPLOAD_URL
551 else:
552 cros_build_lib.Warning('unofficial builds upload to the staging server')
553 upload_url = STAGING_UPLOAD_URL
554
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500555 if sym_paths:
556 cros_build_lib.Info('uploading specified symbols to %s', upload_url)
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400557 else:
558 if breakpad_dir is None:
Mike Frysinger118d2502013-08-19 03:36:56 -0400559 breakpad_dir = os.path.join(
560 root,
561 cros_generate_breakpad_symbols.FindBreakpadDir(board).lstrip('/'))
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400562 cros_build_lib.Info('uploading all symbols to %s from %s', upload_url,
563 breakpad_dir)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500564 sym_paths = [breakpad_dir]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400565
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500566 # We use storage_query to ask the server about existing symbols. The
567 # storage_notify_proc process is used to post updates to the server. We
568 # cannot safely share the storage object between threads/processes, but
569 # we also want to minimize creating new ones as each object has to init
570 # new state (like server connections).
Mike Frysinger650e6722014-04-28 18:29:15 -0400571 storage_query = None
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500572 if dedupe_namespace:
573 dedupe_limit = DEDUPE_LIMIT
574 dedupe_queue = multiprocessing.Queue()
Mike Frysinger650e6722014-04-28 18:29:15 -0400575 try:
576 with timeout_util.Timeout(DEDUPE_TIMEOUT):
577 storage_query = isolateserver.get_storage_api(constants.ISOLATESERVER,
578 dedupe_namespace)
579 except Exception:
580 cros_build_lib.Warning('initializing dedupe server connection failed',
581 exc_info=True)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500582 else:
583 dedupe_limit = 1
Mike Frysinger650e6722014-04-28 18:29:15 -0400584 dedupe_queue = None
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500585 # Can't use parallel.BackgroundTaskRunner because that'll create multiple
586 # processes and we want only one the whole time (see comment above).
587 storage_notify_proc = multiprocessing.Process(
588 target=SymbolDeduplicatorNotify, args=(dedupe_namespace, dedupe_queue))
589
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400590 bg_errors = multiprocessing.Value('i')
Mike Frysingereb753bf2013-11-22 16:05:35 -0500591 watermark_errors = multiprocessing.Value('f')
Mike Frysinger02e92402013-11-22 16:22:02 -0500592 failed_queue = multiprocessing.Queue()
593 uploader = functools.partial(
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500594 UploadSymbol, upload_url, file_limit=file_limit, sleep=sleep,
595 num_errors=bg_errors, watermark_errors=watermark_errors,
596 failed_queue=failed_queue, passed_queue=dedupe_queue)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400597
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500598 start_time = datetime.datetime.now()
599 Counters = cros_build_lib.Collection(
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500600 'Counters', upload_limit=upload_limit, uploaded_count=0, deduped_count=0)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500601 counters = Counters()
602
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500603 def _Upload(queue, counters, files):
604 if not files:
605 return
606
607 missing_count = 0
608 for item in SymbolDeduplicator(storage_query, files):
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500609 missing_count += 1
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400610
611 if counters.upload_limit == 0:
612 continue
613
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500614 queue.put((item,))
615 counters.uploaded_count += 1
616 if counters.upload_limit is not None:
617 counters.upload_limit -= 1
618
619 counters.deduped_count += (len(files) - missing_count)
620
Mike Frysinger13870082014-03-14 10:41:20 -0400621 try:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500622 storage_notify_proc.start()
Mike Frysinger02e92402013-11-22 16:22:02 -0500623
Mike Frysinger13870082014-03-14 10:41:20 -0400624 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
625 # For the first run, we collect the symbols that failed. If the
626 # overall failure rate was low, we'll retry them on the second run.
627 for retry in (retry, False):
628 # We need to limit ourselves to one upload at a time to avoid the server
629 # kicking in DoS protection. See these bugs for more details:
630 # http://crbug.com/209442
631 # http://crbug.com/212496
632 with parallel.BackgroundTaskRunner(uploader, processes=1) as queue:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500633 dedupe_list = []
Mike Frysinger13870082014-03-14 10:41:20 -0400634 for sym_file in SymbolFinder(tempdir, sym_paths):
635 dedupe_list.append(sym_file)
636 dedupe_len = len(dedupe_list)
637 if dedupe_len < dedupe_limit:
638 if (counters.upload_limit is None or
639 dedupe_len < counters.upload_limit):
640 continue
Mike Frysinger02e92402013-11-22 16:22:02 -0500641
Mike Frysinger1010a892014-03-14 11:24:17 -0400642 # We check the counter before _Upload so that we don't keep talking
643 # to the dedupe server. Otherwise, we end up sending one symbol at
644 # a time to it and that slows things down a lot.
645 if counters.upload_limit == 0:
646 break
647
Mike Frysinger13870082014-03-14 10:41:20 -0400648 _Upload(queue, counters, dedupe_list)
649 dedupe_list = []
650 _Upload(queue, counters, dedupe_list)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500651
Mike Frysinger13870082014-03-14 10:41:20 -0400652 # See if we need to retry, and if we haven't failed too many times yet.
653 if not retry or ErrorLimitHit(bg_errors, watermark_errors):
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500654 break
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500655
Mike Frysinger13870082014-03-14 10:41:20 -0400656 sym_paths = []
657 failed_queue.put(None)
658 while True:
659 sym_path = failed_queue.get()
660 if sym_path is None:
661 break
662 sym_paths.append(sym_path)
Mike Frysinger02e92402013-11-22 16:22:02 -0500663
Mike Frysinger13870082014-03-14 10:41:20 -0400664 if sym_paths:
665 cros_build_lib.Warning('retrying %i symbols', len(sym_paths))
666 if counters.upload_limit is not None:
667 counters.upload_limit += len(sym_paths)
668 # Decrement the error count in case we recover in the second pass.
669 assert bg_errors.value >= len(sym_paths), \
670 'more failed files than errors?'
671 bg_errors.value -= len(sym_paths)
672 else:
673 # No failed symbols, so just return now.
674 break
Mike Frysinger7f9be142014-01-15 02:16:42 -0500675
Mike Frysinger13870082014-03-14 10:41:20 -0400676 # If the user has requested it, save all the symbol files that we failed to
677 # upload to a listing file. This should help with recovery efforts later.
678 failed_queue.put(None)
679 WriteQueueToFile(failed_list, failed_queue, breakpad_dir)
680
681 finally:
Mike Frysingerae298452014-03-24 22:45:23 -0400682 cros_build_lib.Info('finished uploading; joining background process')
Mike Frysinger13870082014-03-14 10:41:20 -0400683 if dedupe_queue:
684 dedupe_queue.put(None)
685 storage_notify_proc.join()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500686
687 cros_build_lib.Info('uploaded %i symbols (%i were deduped) which took: %s',
688 counters.uploaded_count, counters.deduped_count,
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500689 datetime.datetime.now() - start_time)
690
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500691 return bg_errors.value
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400692
693
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400694def main(argv):
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500695 # TODO(build): Delete this assert.
696 assert isolateserver, 'Missing isolateserver import http://crbug.com/341152'
697
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400698 parser = commandline.ArgumentParser(description=__doc__)
699
Mike Frysingerd41938e2014-02-10 06:37:55 -0500700 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
701 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400702 parser.add_argument('--board', default=None,
703 help='board to build packages for')
704 parser.add_argument('--breakpad_root', type='path', default=None,
705 help='root directory for breakpad symbols')
706 parser.add_argument('--official_build', action='store_true', default=False,
707 help='point to official symbol server')
708 parser.add_argument('--regenerate', action='store_true', default=False,
709 help='regenerate all symbols')
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500710 parser.add_argument('--upload-limit', type=int, default=None,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400711 help='only upload # number of symbols')
712 parser.add_argument('--strip_cfi', type=int,
713 default=CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024),
714 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500715 parser.add_argument('--failed-list', type='path',
716 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500717 parser.add_argument('--dedupe', action='store_true', default=False,
718 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400719 parser.add_argument('--testing', action='store_true', default=False,
720 help='run in testing mode')
721 parser.add_argument('--yes', action='store_true', default=False,
722 help='answer yes to all prompts')
723
724 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500725 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400726
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500727 if opts.sym_paths:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400728 if opts.regenerate:
729 cros_build_lib.Die('--regenerate may not be used with specific files')
730 else:
731 if opts.board is None:
732 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400733
734 if opts.breakpad_root and opts.regenerate:
735 cros_build_lib.Die('--regenerate may not be used with --breakpad_root')
736
737 if opts.testing:
738 # TODO(build): Kill off --testing mode once unittests are up-to-snuff.
739 cros_build_lib.Info('running in testing mode')
740 # pylint: disable=W0601,W0603
741 global INITIAL_RETRY_DELAY, SymUpload, DEFAULT_SLEEP_DELAY
742 INITIAL_RETRY_DELAY = DEFAULT_SLEEP_DELAY = 0
743 SymUpload = TestingSymUpload
744
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500745 dedupe_namespace = None
746 if opts.dedupe:
747 if opts.official_build and not opts.testing:
748 dedupe_namespace = OFFICIAL_DEDUPE_NAMESPACE
749 else:
750 dedupe_namespace = STAGING_DEDUPE_NAMESPACE
751
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400752 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500753 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400754 Uploading symbols for an entire Chromium OS build is really only
755 necessary for release builds and in a few cases for developers
756 to debug problems. It will take considerable time to run. For
757 developer debugging purposes, consider instead passing specific
758 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500759 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400760 if not cros_build_lib.BooleanPrompt(
761 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500762 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400763 cros_build_lib.Die('better safe than sorry')
764
765 ret = 0
766 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400767 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
768 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400769
770 ret += UploadSymbols(opts.board, official=opts.official_build,
771 breakpad_dir=opts.breakpad_root,
772 file_limit=opts.strip_cfi, sleep=DEFAULT_SLEEP_DELAY,
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500773 upload_limit=opts.upload_limit, sym_paths=opts.sym_paths,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500774 failed_list=opts.failed_list,
775 dedupe_namespace=dedupe_namespace)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400776 if ret:
777 cros_build_lib.Error('encountered %i problem(s)', ret)
778 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
779 # return 0 in case we are a multiple of the mask.
780 ret = 1
781
782 return ret
Mike Frysinger094a2172013-08-14 12:54:35 -0400783
784
785# We need this to run once per process. Do it at module import time as that
786# will let us avoid doing it inline at function call time (see SymUpload) as
787# that func might be called by the multiprocessing module which means we'll
788# do the opener logic multiple times overall. Plus, if you're importing this
789# module, it's a pretty good chance that you're going to need this.
790poster.streaminghttp.register_openers()