blob: 2a912a2f10f7d71d1bf6bbd7db67593a8db90605 [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
Fang Dengba680462015-08-16 20:34:11 -070014import collections
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040015import ctypes
Mike Frysinger8ec8c502014-02-10 00:19:13 -050016import datetime
Mike Frysingerdad2a032014-05-11 23:05:11 -040017import errno
Mike Frysinger02e92402013-11-22 16:22:02 -050018import functools
Mike Frysinger0c0efa22014-02-09 23:32:23 -050019import hashlib
Mike Frysingera4fa1e82014-01-15 01:45:56 -050020import httplib
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040021import multiprocessing
22import os
Mike Frysinger094a2172013-08-14 12:54:35 -040023import poster
Mike Frysinger66e51e92014-05-03 16:52:00 -040024try:
25 import Queue
26except ImportError:
27 # Python-3 renamed to "queue". We still use Queue to avoid collisions
28 # with naming variables as "queue". Maybe we'll transition at some point.
29 # pylint: disable=F0401
30 import queue as Queue
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040031import random
Mike Frysinger66e51e92014-05-03 16:52:00 -040032import signal
Mike Frysingerfd355652014-01-23 02:57:48 -050033import socket
Mike Frysingerc5597f22014-11-27 15:39:15 -050034import sys
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040035import textwrap
36import tempfile
37import time
Mike Frysinger094a2172013-08-14 12:54:35 -040038import urllib2
Mike Frysingerd41938e2014-02-10 06:37:55 -050039import urlparse
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040040
Don Garrett88b8d782014-05-13 17:30:55 -070041from chromite.cbuildbot import constants
Mike Frysingerd41938e2014-02-10 06:37:55 -050042from chromite.lib import cache
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040043from chromite.lib import commandline
44from chromite.lib import cros_build_lib
Ralph Nathan5a582ff2015-03-20 18:18:30 -070045from chromite.lib import cros_logging as logging
Mike Frysingerd41938e2014-02-10 06:37:55 -050046from chromite.lib import gs
47from chromite.lib import osutils
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040048from chromite.lib import parallel
Gilad Arnold83233ed2015-05-08 12:12:13 -070049from chromite.lib import path_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080050from chromite.lib import retry_util
Mike Frysinger66e51e92014-05-03 16:52:00 -040051from chromite.lib import signals
Mike Frysinger0c0efa22014-02-09 23:32:23 -050052from chromite.lib import timeout_util
Mike Frysinger69cb41d2013-08-11 20:08:19 -040053from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040054
Mike Frysinger0c0efa22014-02-09 23:32:23 -050055# Needs to be after chromite imports.
Mike Frysingerc5597f22014-11-27 15:39:15 -050056# We don't want to import the general keyring module as that will implicitly
57# try to import & connect to a dbus server. That's a waste of time.
58sys.modules['keyring'] = None
59import isolateserver
Mike Frysinger0c0efa22014-02-09 23:32:23 -050060
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040061
62# URLs used for uploading symbols.
63OFFICIAL_UPLOAD_URL = 'http://clients2.google.com/cr/symbol'
64STAGING_UPLOAD_URL = 'http://clients2.google.com/cr/staging_symbol'
65
66
67# The crash server rejects files that are this big.
68CRASH_SERVER_FILE_LIMIT = 350 * 1024 * 1024
69# Give ourselves a little breathing room from what the server expects.
70DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
71
72
Mike Frysinger0c0efa22014-02-09 23:32:23 -050073# The batch limit when talking to the dedup server. We avoid sending one at a
74# time as the round trip overhead will dominate. Conversely, we avoid sending
75# all at once so we can start uploading symbols asap -- the symbol server is a
76# bit slow and will take longer than anything else.
77# TODO: A better algorithm would be adaptive. If we have more than one symbol
78# in the upload queue waiting, we could send more symbols to the dedupe server
79# at a time.
80DEDUPE_LIMIT = 100
81
82# How long to wait for the server to respond with the results. Note that the
83# larger the limit above, the larger this will need to be. So we give it ~1
84# second per item max.
85DEDUPE_TIMEOUT = DEDUPE_LIMIT
86
Mike Frysinger4dd462e2014-04-30 16:21:51 -040087# How long to wait for the notification to finish (in minutes). If it takes
88# longer than this, we'll stop notifiying, but that's not a big deal as we
89# will be able to recover in later runs.
90DEDUPE_NOTIFY_TIMEOUT = 20
91
Mike Frysinger0c0efa22014-02-09 23:32:23 -050092# The unique namespace in the dedupe server that only we use. Helps avoid
93# collisions with all the hashed values and unrelated content.
94OFFICIAL_DEDUPE_NAMESPACE = 'chromium-os-upload-symbols'
95STAGING_DEDUPE_NAMESPACE = '%s-staging' % OFFICIAL_DEDUPE_NAMESPACE
96
97
Mike Frysinger71046662014-09-12 18:15:15 -070098# The minimum average rate (in bytes per second) that we expect to maintain
99# when uploading symbols. This has to allow for symbols that are up to
100# CRASH_SERVER_FILE_LIMIT in size.
101UPLOAD_MIN_RATE = CRASH_SERVER_FILE_LIMIT / (30 * 60)
102
103# The lowest timeout (in seconds) we'll allow. If the server is overloaded,
104# then there might be a delay in setting up the connection, not just with the
105# transfer. So even a small file might need a larger value.
106UPLOAD_MIN_TIMEOUT = 2 * 60
Mike Frysingercd78a082013-06-26 17:13:04 -0400107
108
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400109# Sleep for 200ms in between uploads to avoid DoS'ing symbol server.
110DEFAULT_SLEEP_DELAY = 0.2
111
112
113# Number of seconds to wait before retrying an upload. The delay will double
114# for each subsequent retry of the same symbol file.
115INITIAL_RETRY_DELAY = 1
116
117# Allow up to 7 attempts to upload a symbol file (total delay may be
118# 1+2+4+8+16+32=63 seconds).
119MAX_RETRIES = 6
120
Mike Frysingereb753bf2013-11-22 16:05:35 -0500121# Number of total errors, before uploads are no longer attempted.
122# This is used to avoid lots of errors causing unreasonable delays.
123# See the related, but independent, error values below.
124MAX_TOTAL_ERRORS_FOR_RETRY = 30
125
126# A watermark of transient errors which we allow recovery from. If we hit
127# errors infrequently, overall we're probably doing fine. For example, if
128# we have one failure every 100 passes, then we probably don't want to fail
129# right away. But if we hit a string of failures in a row, we want to abort.
130#
131# The watermark starts at 0 (and can never go below that). When this error
132# level is exceeded, we stop uploading. When a failure happens, we add the
133# fail adjustment, and when an upload succeeds, we add the pass adjustment.
134# We want to penalize failures more so that we ramp up when there is a string
135# of them, but then slowly back off as things start working.
136#
137# A quick example:
138# 0.0: Starting point.
139# 0.0: Upload works, so add -0.5, and then clamp to 0.
140# 1.0: Upload fails, so add 1.0.
141# 2.0: Upload fails, so add 1.0.
142# 1.5: Upload works, so add -0.5.
143# 1.0: Upload works, so add -0.5.
144ERROR_WATERMARK = 3.0
145ERROR_ADJUST_FAIL = 1.0
146ERROR_ADJUST_PASS = -0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400147
148
Fang Dengba680462015-08-16 20:34:11 -0700149# A named tuple which hold a SymbolItem object and
150# a isolateserver._IsolateServerPushState item.
151SymbolElement = collections.namedtuple(
152 'SymbolElement', ('symbol_item', 'opaque_push_state'))
153
154
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700155def GetUploadTimeout(path):
156 """How long to wait for a specific file to upload to the crash server.
157
158 This is a function largely to make unittesting easier.
159
160 Args:
161 path: The path to the file to calculate the timeout for
162
163 Returns:
164 Timeout length (in seconds)
165 """
166 # Scale the timeout based on the filesize.
167 return max(os.path.getsize(path) / UPLOAD_MIN_RATE, UPLOAD_MIN_TIMEOUT)
168
169
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500170def SymUpload(upload_url, sym_item):
Mike Frysinger094a2172013-08-14 12:54:35 -0400171 """Upload a symbol file to a HTTP server
172
173 The upload is a multipart/form-data POST with the following parameters:
174 code_file: the basename of the module, e.g. "app"
175 code_identifier: the module file's identifier
176 debug_file: the basename of the debugging file, e.g. "app"
177 debug_identifier: the debug file's identifier, usually consisting of
178 the guid and age embedded in the pdb, e.g.
179 "11111111BBBB3333DDDD555555555555F"
180 version: the file version of the module, e.g. "1.2.3.4"
181 product: HTTP-friendly product name
182 os: the operating system that the module was built for
183 cpu: the CPU that the module was built for
184 symbol_file: the contents of the breakpad-format symbol file
185
186 Args:
Mike Frysinger094a2172013-08-14 12:54:35 -0400187 upload_url: The crash URL to POST the |sym_file| to
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500188 sym_item: A SymbolItem containing the path to the breakpad symbol to upload
Mike Frysinger094a2172013-08-14 12:54:35 -0400189 """
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500190 sym_header = sym_item.sym_header
191 sym_file = sym_item.sym_file
Mike Frysinger094a2172013-08-14 12:54:35 -0400192
193 fields = (
194 ('code_file', sym_header.name),
195 ('debug_file', sym_header.name),
196 ('debug_identifier', sym_header.id.replace('-', '')),
Mike Frysingerb8a966b2014-03-19 17:36:18 -0400197 # The product/version fields are used by the server only for statistic
198 # purposes. They do not impact symbolization, so they're safe to set
199 # to any value all the time.
200 # In this case, we use it to help see the load our build system is
201 # placing on the server.
202 # Not sure what to set for the version. Maybe the git sha1 of this file.
203 # Note: the server restricts this to 30 chars.
Mike Frysinger094a2172013-08-14 12:54:35 -0400204 #('version', None),
Mike Frysingerb8a966b2014-03-19 17:36:18 -0400205 ('product', 'ChromeOS'),
Mike Frysinger094a2172013-08-14 12:54:35 -0400206 ('os', sym_header.os),
207 ('cpu', sym_header.cpu),
208 poster.encode.MultipartParam.from_file('symbol_file', sym_file),
209 )
210
211 data, headers = poster.encode.multipart_encode(fields)
212 request = urllib2.Request(upload_url, data, headers)
213 request.add_header('User-agent', 'chromite.upload_symbols')
Mike Frysinger0a2fd922014-09-12 20:23:42 -0700214 urllib2.urlopen(request, timeout=GetUploadTimeout(sym_file))
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400215
216
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500217def TestingSymUpload(upload_url, sym_item):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400218 """A stub version of SymUpload for --testing usage"""
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500219 cmd = ['sym_upload', sym_item.sym_file, upload_url]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400220 # Randomly fail 80% of the time (the retry logic makes this 80%/3 per file).
221 returncode = random.randint(1, 100) <= 80
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700222 logging.debug('would run (and return %i): %s', returncode,
223 cros_build_lib.CmdToStr(cmd))
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400224 if returncode:
225 output = 'Failed to send the symbol file.'
226 else:
227 output = 'Successfully sent the symbol file.'
228 result = cros_build_lib.CommandResult(cmd=cmd, error=None, output=output,
229 returncode=returncode)
230 if returncode:
Mike Frysingera4fa1e82014-01-15 01:45:56 -0500231 exceptions = (
Mike Frysingerfd355652014-01-23 02:57:48 -0500232 socket.error('[socket.error] forced test fail'),
Mike Frysingera4fa1e82014-01-15 01:45:56 -0500233 httplib.BadStatusLine('[BadStatusLine] forced test fail'),
234 urllib2.HTTPError(upload_url, 400, '[HTTPError] forced test fail',
235 {}, None),
236 urllib2.URLError('[URLError] forced test fail'),
237 )
238 raise random.choice(exceptions)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400239 else:
240 return result
241
242
Mike Frysingereb753bf2013-11-22 16:05:35 -0500243def ErrorLimitHit(num_errors, watermark_errors):
244 """See if our error limit has been hit
245
246 Args:
247 num_errors: A multiprocessing.Value of the raw number of failures.
248 watermark_errors: A multiprocessing.Value of the current rate of failures.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500249
Mike Frysingereb753bf2013-11-22 16:05:35 -0500250 Returns:
251 True if our error limits have been exceeded.
252 """
253 return ((num_errors is not None and
254 num_errors.value > MAX_TOTAL_ERRORS_FOR_RETRY) or
255 (watermark_errors is not None and
256 watermark_errors.value > ERROR_WATERMARK))
257
258
259def _UpdateCounter(counter, adj):
260 """Update |counter| by |adj|
261
262 Handle atomic updates of |counter|. Also make sure it does not
263 fall below 0.
264
265 Args:
266 counter: A multiprocessing.Value to update
267 adj: The value to add to |counter|
268 """
269 def _Update():
270 clamp = 0 if type(adj) is int else 0.0
271 counter.value = max(clamp, counter.value + adj)
272
273 if hasattr(counter, 'get_lock'):
274 with counter.get_lock():
275 _Update()
276 elif counter is not None:
277 _Update()
278
279
Fang Dengba680462015-08-16 20:34:11 -0700280def UploadSymbol(upload_url, symbol_element, file_limit=DEFAULT_FILE_LIMIT,
Mike Frysinger02e92402013-11-22 16:22:02 -0500281 sleep=0, num_errors=None, watermark_errors=None,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500282 failed_queue=None, passed_queue=None):
Fang Dengba680462015-08-16 20:34:11 -0700283 """Upload |sym_element.symbol_item| to |upload_url|
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400284
285 Args:
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400286 upload_url: The crash server to upload things to
Fang Dengba680462015-08-16 20:34:11 -0700287 symbol_element: A SymbolElement tuple. symbol_element.symbol_item is a
288 SymbolItem object containing the path to the breakpad symbol
289 to upload. symbol_element.opaque_push_state is an object of
290 _IsolateServerPushState or None if the item doesn't have
291 a push state.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400292 file_limit: The max file size of a symbol file before we try to strip it
293 sleep: Number of seconds to sleep before running
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400294 num_errors: An object to update with the error count (needs a .value member)
Mike Frysingereb753bf2013-11-22 16:05:35 -0500295 watermark_errors: An object to track current error behavior (needs a .value)
Mike Frysinger02e92402013-11-22 16:22:02 -0500296 failed_queue: When a symbol fails, add it to this queue
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500297 passed_queue: When a symbol passes, add it to this queue
Mike Frysinger1a736a82013-12-12 01:50:59 -0500298
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400299 Returns:
300 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400301 """
Fang Dengba680462015-08-16 20:34:11 -0700302 sym_file = symbol_element.symbol_item.sym_file
303 upload_item = symbol_element.symbol_item
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500304
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400305 if num_errors is None:
306 num_errors = ctypes.c_int()
Mike Frysingereb753bf2013-11-22 16:05:35 -0500307 if ErrorLimitHit(num_errors, watermark_errors):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400308 # Abandon ship! It's on fire! NOoooooooooooOOOoooooo.
Mike Frysinger7f9be142014-01-15 02:16:42 -0500309 if failed_queue:
310 failed_queue.put(sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400311 return 0
312
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400313 if sleep:
314 # Keeps us from DoS-ing the symbol server.
315 time.sleep(sleep)
316
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700317 logging.debug('uploading %s' % sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400318
319 # Ideally there'd be a tempfile.SpooledNamedTemporaryFile that we could use.
320 with tempfile.NamedTemporaryFile(prefix='upload_symbols',
321 bufsize=0) as temp_sym_file:
322 if file_limit:
323 # If the symbols size is too big, strip out the call frame info. The CFI
324 # is unnecessary for 32bit x86 targets where the frame pointer is used (as
325 # all of ours have) and it accounts for over half the size of the symbols
326 # uploaded.
327 file_size = os.path.getsize(sym_file)
328 if file_size > file_limit:
Ralph Nathan446aee92015-03-23 14:44:56 -0700329 logging.warning('stripping CFI from %s due to size %s > %s', sym_file,
330 file_size, file_limit)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400331 temp_sym_file.writelines([x for x in open(sym_file, 'rb').readlines()
332 if not x.startswith('STACK CFI')])
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500333
334 upload_item = FakeItem(sym_file=temp_sym_file.name,
Fang Dengba680462015-08-16 20:34:11 -0700335 sym_header=symbol_element.symbol_item.sym_header)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400336
337 # Hopefully the crash server will let it through. But it probably won't.
338 # Not sure what the best answer is in this case.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500339 file_size = os.path.getsize(upload_item.sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400340 if file_size > CRASH_SERVER_FILE_LIMIT:
Prathmesh Prabhu17f07422015-07-17 11:40:40 -0700341 logging.PrintBuildbotStepWarnings()
Ralph Nathan446aee92015-03-23 14:44:56 -0700342 logging.warning('upload file %s is awfully large, risking rejection by '
343 'the symbol server (%s > %s)', sym_file, file_size,
344 CRASH_SERVER_FILE_LIMIT)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400345
346 # Upload the symbol file.
Mike Frysingereb753bf2013-11-22 16:05:35 -0500347 success = False
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400348 try:
Mike Frysinger9adcfd22013-10-24 12:01:40 -0400349 cros_build_lib.TimedCommand(
David Jamesc93e6a4d2014-01-13 11:37:36 -0800350 retry_util.RetryException,
Mike Frysinger9adcfd22013-10-24 12:01:40 -0400351 (urllib2.HTTPError, urllib2.URLError), MAX_RETRIES, SymUpload,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500352 upload_url, upload_item, sleep=INITIAL_RETRY_DELAY,
Mike Frysingerf50187c2014-11-27 22:01:29 -0500353 timed_log_msg=('upload of %10i bytes took %%(delta)s: %s' %
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500354 (file_size, os.path.basename(sym_file))))
Mike Frysingereb753bf2013-11-22 16:05:35 -0500355 success = True
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500356
357 if passed_queue:
Fang Dengba680462015-08-16 20:34:11 -0700358 passed_queue.put(symbol_element)
Mike Frysinger094a2172013-08-14 12:54:35 -0400359 except urllib2.HTTPError as e:
Ralph Nathan446aee92015-03-23 14:44:56 -0700360 logging.warning('could not upload: %s: HTTP %s: %s',
361 os.path.basename(sym_file), e.code, e.reason)
Mike Frysingerfd355652014-01-23 02:57:48 -0500362 except (urllib2.URLError, httplib.HTTPException, socket.error) as e:
Ralph Nathan446aee92015-03-23 14:44:56 -0700363 logging.warning('could not upload: %s: %s', os.path.basename(sym_file), e)
Mike Frysingereb753bf2013-11-22 16:05:35 -0500364 finally:
365 if success:
366 _UpdateCounter(watermark_errors, ERROR_ADJUST_PASS)
367 else:
368 _UpdateCounter(num_errors, 1)
369 _UpdateCounter(watermark_errors, ERROR_ADJUST_FAIL)
Mike Frysinger02e92402013-11-22 16:22:02 -0500370 if failed_queue:
371 failed_queue.put(sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400372
373 return num_errors.value
374
375
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500376# A dummy class that allows for stubbing in tests and SymUpload.
377FakeItem = cros_build_lib.Collection(
378 'FakeItem', sym_file=None, sym_header=None, content=lambda x: '')
379
380
Mike Frysingerc5597f22014-11-27 15:39:15 -0500381class SymbolItem(isolateserver.BufferItem):
382 """Turn a sym_file into an isolateserver.Item"""
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500383
Mike Frysingerc5597f22014-11-27 15:39:15 -0500384 ALGO = hashlib.sha1
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500385
Mike Frysingerc5597f22014-11-27 15:39:15 -0500386 def __init__(self, sym_file):
387 sym_header = cros_generate_breakpad_symbols.ReadSymsHeader(sym_file)
388 super(SymbolItem, self).__init__(str(sym_header), self.ALGO)
389 self.sym_header = sym_header
390 self.sym_file = sym_file
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500391
392
393def SymbolDeduplicatorNotify(dedupe_namespace, dedupe_queue):
394 """Send a symbol file to the swarming service
395
396 Notify the swarming service of a successful upload. If the notification fails
397 for any reason, we ignore it. We don't care as it just means we'll upload it
398 again later on, and the symbol server will handle that graciously.
399
400 This func runs in a different process from the main one, so we cannot share
401 the storage object. Instead, we create our own. This func stays alive for
402 the life of the process, so we only create one here overall.
403
404 Args:
405 dedupe_namespace: The isolateserver namespace to dedupe uploaded symbols.
Fang Dengba680462015-08-16 20:34:11 -0700406 dedupe_queue: The queue to read SymbolElements from
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500407 """
408 if dedupe_queue is None:
409 return
410
Fang Dengba680462015-08-16 20:34:11 -0700411 sym_file = ''
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500412 try:
Mike Frysinger650e6722014-04-28 18:29:15 -0400413 with timeout_util.Timeout(DEDUPE_TIMEOUT):
414 storage = isolateserver.get_storage_api(constants.ISOLATESERVER,
415 dedupe_namespace)
Fang Dengba680462015-08-16 20:34:11 -0700416 for symbol_element in iter(dedupe_queue.get, None):
417 if not symbol_element or not symbol_element.symbol_item:
418 continue
419 symbol_item = symbol_element.symbol_item
420 push_state = symbol_element.opaque_push_state
421 sym_file = symbol_item.sym_file if symbol_item.sym_file else ''
422 if push_state is not None:
423 with timeout_util.Timeout(DEDUPE_TIMEOUT):
424 logging.debug('sending %s to dedupe server', sym_file)
425 symbol_item.prepare(SymbolItem.ALGO)
426 storage.push(symbol_item, push_state, symbol_item.content())
427 logging.debug('sent %s', sym_file)
Ralph Nathan03047282015-03-23 11:09:32 -0700428 logging.info('dedupe notification finished; exiting')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500429 except Exception:
Ralph Nathan446aee92015-03-23 14:44:56 -0700430 logging.warning('posting %s to dedupe server failed',
431 os.path.basename(sym_file), exc_info=True)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500432
Mike Frysinger58312e92014-03-18 04:18:36 -0400433 # Keep draining the queue though so it doesn't fill up.
434 while dedupe_queue.get() is not None:
435 continue
436
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500437
438def SymbolDeduplicator(storage, sym_paths):
439 """Filter out symbol files that we've already uploaded
440
441 Using the swarming service, ask it to tell us which symbol files we've already
442 uploaded in previous runs and/or by other bots. If the query fails for any
443 reason, we'll just upload all symbols. This is fine as the symbol server will
444 do the right thing and this phase is purely an optimization.
445
446 This code runs in the main thread which is why we can re-use the existing
447 storage object. Saves us from having to recreate one all the time.
448
449 Args:
450 storage: An isolateserver.StorageApi object
451 sym_paths: List of symbol files to check against the dedupe server
452
453 Returns:
Fang Dengba680462015-08-16 20:34:11 -0700454 List of SymbolElement objects that have not been uploaded before
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500455 """
456 if not sym_paths:
457 return sym_paths
458
459 items = [SymbolItem(x) for x in sym_paths]
Fang Dengba680462015-08-16 20:34:11 -0700460 for item in items:
461 item.prepare(SymbolItem.ALGO)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500462 if storage:
463 try:
464 with timeout_util.Timeout(DEDUPE_TIMEOUT):
465 items = storage.contains(items)
Fang Dengba680462015-08-16 20:34:11 -0700466 return [SymbolElement(symbol_item=item, opaque_push_state=push_state)
467 for (item, push_state) in items.iteritems()]
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500468 except Exception:
Ralph Nathan446aee92015-03-23 14:44:56 -0700469 logging.warning('talking to dedupe server failed', exc_info=True)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500470
Fang Dengba680462015-08-16 20:34:11 -0700471 return [SymbolElement(symbol_item=item, opaque_push_state=None)
472 for item in items]
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500473
474
Mike Frysingerd41938e2014-02-10 06:37:55 -0500475def IsTarball(path):
476 """Guess if this is a tarball based on the filename."""
477 parts = path.split('.')
478 if len(parts) <= 1:
479 return False
480
481 if parts[-1] == 'tar':
482 return True
483
484 if parts[-2] == 'tar':
485 return parts[-1] in ('bz2', 'gz', 'xz')
486
487 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
488
489
490def SymbolFinder(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500491 """Locate symbol files in |paths|
492
493 Args:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500494 tempdir: Path to use for temporary files (caller will clean up).
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500495 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500496 Dirs are searched for files that end in ".sym". Urls are fetched and then
497 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500498
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500499 Returns:
500 Yield every viable sym file.
501 """
Gilad Arnold83233ed2015-05-08 12:12:13 -0700502 cache_dir = path_util.GetCacheDir()
Mike Frysingere847efd2015-01-08 03:57:24 -0500503 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
504 tar_cache = cache.TarballCache(common_path)
505
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500506 for p in paths:
Don Garrett25f309a2014-03-19 14:02:12 -0700507 # Pylint is confused about members of ParseResult.
Don Garrettf8bf7842014-03-20 17:03:42 -0700508
Mike Frysingerd41938e2014-02-10 06:37:55 -0500509 o = urlparse.urlparse(p)
Don Garrettf8bf7842014-03-20 17:03:42 -0700510 if o.scheme: # pylint: disable=E1101
Mike Frysingerd41938e2014-02-10 06:37:55 -0500511 # Support globs of filenames.
512 ctx = gs.GSContext()
513 for p in ctx.LS(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700514 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500515 o = urlparse.urlparse(p)
Don Garrettf8bf7842014-03-20 17:03:42 -0700516 key = ('%s%s' % (o.netloc, o.path)).split('/') # pylint: disable=E1101
Mike Frysingerd41938e2014-02-10 06:37:55 -0500517 # The common cache will not be LRU, removing the need to hold a read
518 # lock on the cached gsutil.
519 ref = tar_cache.Lookup(key)
520 try:
521 ref.SetDefault(p)
522 except cros_build_lib.RunCommandError as e:
Ralph Nathan446aee92015-03-23 14:44:56 -0700523 logging.warning('ignoring %s\n%s', p, e)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500524 continue
525 for p in SymbolFinder(tempdir, [ref.path]):
526 yield p
527
528 elif os.path.isdir(p):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500529 for root, _, files in os.walk(p):
530 for f in files:
531 if f.endswith('.sym'):
532 yield os.path.join(root, f)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500533
534 elif IsTarball(p):
Ralph Nathan03047282015-03-23 11:09:32 -0700535 logging.info('processing files inside %s', p)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500536 tardir = tempfile.mkdtemp(dir=tempdir)
537 cache.Untar(os.path.realpath(p), tardir)
538 for p in SymbolFinder(tardir, [tardir]):
539 yield p
540
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500541 else:
542 yield p
543
544
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500545def WriteQueueToFile(listing, queue, relpath=None):
546 """Write all the items in |queue| to the |listing|.
547
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500548 Note: The queue must have a sentinel None appended to the end.
549
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500550 Args:
551 listing: Where to write out the list of files.
552 queue: The queue of paths to drain.
553 relpath: If set, write out paths relative to this one.
554 """
555 if not listing:
Mike Frysingera0ddac62014-03-14 10:30:25 -0400556 # Still drain the queue so we make sure the producer has finished
557 # before we return. Otherwise, the queue might get destroyed too
558 # quickly which will trigger a traceback in the producer.
559 while queue.get() is not None:
560 continue
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500561 return
562
563 with cros_build_lib.Open(listing, 'wb+') as f:
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500564 while True:
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500565 path = queue.get()
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500566 if path is None:
567 return
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500568 if relpath:
569 path = os.path.relpath(path, relpath)
570 f.write('%s\n' % path)
571
572
Mike Frysinger38647542014-09-12 18:15:39 -0700573def UploadSymbols(board=None, official=False, server=None, breakpad_dir=None,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400574 file_limit=DEFAULT_FILE_LIMIT, sleep=DEFAULT_SLEEP_DELAY,
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500575 upload_limit=None, sym_paths=None, failed_list=None,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500576 root=None, retry=True, dedupe_namespace=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400577 """Upload all the generated symbols for |board| to the crash server
578
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400579 You can use in a few ways:
580 * pass |board| to locate all of its symbols
581 * pass |breakpad_dir| to upload all the symbols in there
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500582 * pass |sym_paths| to upload specific symbols (or dirs of symbols)
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400583
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400584 Args:
585 board: The board whose symbols we wish to upload
586 official: Use the official symbol server rather than the staging one
Mike Frysinger38647542014-09-12 18:15:39 -0700587 server: Explicit server to post symbols to
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400588 breakpad_dir: The full path to the breakpad directory where symbols live
589 file_limit: The max file size of a symbol file before we try to strip it
590 sleep: How long to sleep in between uploads
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500591 upload_limit: If set, only upload this many symbols (meant for testing)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500592 sym_paths: Specific symbol files (or dirs of sym files) to upload,
593 otherwise search |breakpad_dir|
Mike Frysinger7f9be142014-01-15 02:16:42 -0500594 failed_list: Write the names of all sym files we did not upload; can be a
595 filename or file-like object.
Mike Frysinger118d2502013-08-19 03:36:56 -0400596 root: The tree to prefix to |breakpad_dir| (if |breakpad_dir| is not set)
Mike Frysinger02e92402013-11-22 16:22:02 -0500597 retry: Whether we should retry failures.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500598 dedupe_namespace: The isolateserver namespace to dedupe uploaded symbols.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500599
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400600 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400601 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400602 """
Mike Frysinger38647542014-09-12 18:15:39 -0700603 if server is None:
604 if official:
605 upload_url = OFFICIAL_UPLOAD_URL
606 else:
Ralph Nathan446aee92015-03-23 14:44:56 -0700607 logging.warning('unofficial builds upload to the staging server')
Mike Frysinger38647542014-09-12 18:15:39 -0700608 upload_url = STAGING_UPLOAD_URL
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400609 else:
Mike Frysinger38647542014-09-12 18:15:39 -0700610 upload_url = server
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400611
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500612 if sym_paths:
Ralph Nathan03047282015-03-23 11:09:32 -0700613 logging.info('uploading specified symbols to %s', upload_url)
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400614 else:
615 if breakpad_dir is None:
Mike Frysingerc5597f22014-11-27 15:39:15 -0500616 if root is None:
617 raise ValueError('breakpad_dir requires root to be set')
Mike Frysinger118d2502013-08-19 03:36:56 -0400618 breakpad_dir = os.path.join(
619 root,
620 cros_generate_breakpad_symbols.FindBreakpadDir(board).lstrip('/'))
Ralph Nathan03047282015-03-23 11:09:32 -0700621 logging.info('uploading all symbols to %s from %s', upload_url,
622 breakpad_dir)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500623 sym_paths = [breakpad_dir]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400624
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500625 # We use storage_query to ask the server about existing symbols. The
626 # storage_notify_proc process is used to post updates to the server. We
627 # cannot safely share the storage object between threads/processes, but
628 # we also want to minimize creating new ones as each object has to init
629 # new state (like server connections).
Mike Frysinger650e6722014-04-28 18:29:15 -0400630 storage_query = None
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500631 if dedupe_namespace:
632 dedupe_limit = DEDUPE_LIMIT
633 dedupe_queue = multiprocessing.Queue()
Mike Frysinger650e6722014-04-28 18:29:15 -0400634 try:
635 with timeout_util.Timeout(DEDUPE_TIMEOUT):
636 storage_query = isolateserver.get_storage_api(constants.ISOLATESERVER,
637 dedupe_namespace)
638 except Exception:
Ralph Nathan446aee92015-03-23 14:44:56 -0700639 logging.warning('initializing dedupe server connection failed',
640 exc_info=True)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500641 else:
642 dedupe_limit = 1
Mike Frysinger650e6722014-04-28 18:29:15 -0400643 dedupe_queue = None
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500644 # Can't use parallel.BackgroundTaskRunner because that'll create multiple
645 # processes and we want only one the whole time (see comment above).
646 storage_notify_proc = multiprocessing.Process(
647 target=SymbolDeduplicatorNotify, args=(dedupe_namespace, dedupe_queue))
648
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400649 bg_errors = multiprocessing.Value('i')
Mike Frysingereb753bf2013-11-22 16:05:35 -0500650 watermark_errors = multiprocessing.Value('f')
Mike Frysinger02e92402013-11-22 16:22:02 -0500651 failed_queue = multiprocessing.Queue()
652 uploader = functools.partial(
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500653 UploadSymbol, upload_url, file_limit=file_limit, sleep=sleep,
654 num_errors=bg_errors, watermark_errors=watermark_errors,
655 failed_queue=failed_queue, passed_queue=dedupe_queue)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400656
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500657 start_time = datetime.datetime.now()
658 Counters = cros_build_lib.Collection(
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500659 'Counters', upload_limit=upload_limit, uploaded_count=0, deduped_count=0)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500660 counters = Counters()
661
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500662 def _Upload(queue, counters, files):
663 if not files:
664 return
665
666 missing_count = 0
667 for item in SymbolDeduplicator(storage_query, files):
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500668 missing_count += 1
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400669
670 if counters.upload_limit == 0:
671 continue
672
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500673 queue.put((item,))
674 counters.uploaded_count += 1
675 if counters.upload_limit is not None:
676 counters.upload_limit -= 1
677
678 counters.deduped_count += (len(files) - missing_count)
679
Mike Frysinger13870082014-03-14 10:41:20 -0400680 try:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500681 storage_notify_proc.start()
Mike Frysinger02e92402013-11-22 16:22:02 -0500682
Mike Frysinger13870082014-03-14 10:41:20 -0400683 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
684 # For the first run, we collect the symbols that failed. If the
685 # overall failure rate was low, we'll retry them on the second run.
686 for retry in (retry, False):
687 # We need to limit ourselves to one upload at a time to avoid the server
688 # kicking in DoS protection. See these bugs for more details:
689 # http://crbug.com/209442
690 # http://crbug.com/212496
691 with parallel.BackgroundTaskRunner(uploader, processes=1) as queue:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500692 dedupe_list = []
Mike Frysinger13870082014-03-14 10:41:20 -0400693 for sym_file in SymbolFinder(tempdir, sym_paths):
694 dedupe_list.append(sym_file)
695 dedupe_len = len(dedupe_list)
696 if dedupe_len < dedupe_limit:
697 if (counters.upload_limit is None or
698 dedupe_len < counters.upload_limit):
699 continue
Mike Frysinger02e92402013-11-22 16:22:02 -0500700
Mike Frysinger1010a892014-03-14 11:24:17 -0400701 # We check the counter before _Upload so that we don't keep talking
702 # to the dedupe server. Otherwise, we end up sending one symbol at
703 # a time to it and that slows things down a lot.
704 if counters.upload_limit == 0:
705 break
706
Mike Frysinger13870082014-03-14 10:41:20 -0400707 _Upload(queue, counters, dedupe_list)
708 dedupe_list = []
709 _Upload(queue, counters, dedupe_list)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500710
Mike Frysinger13870082014-03-14 10:41:20 -0400711 # See if we need to retry, and if we haven't failed too many times yet.
712 if not retry or ErrorLimitHit(bg_errors, watermark_errors):
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500713 break
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500714
Mike Frysinger13870082014-03-14 10:41:20 -0400715 sym_paths = []
716 failed_queue.put(None)
717 while True:
718 sym_path = failed_queue.get()
719 if sym_path is None:
720 break
721 sym_paths.append(sym_path)
Mike Frysinger02e92402013-11-22 16:22:02 -0500722
Mike Frysinger13870082014-03-14 10:41:20 -0400723 if sym_paths:
Ralph Nathan446aee92015-03-23 14:44:56 -0700724 logging.warning('retrying %i symbols', len(sym_paths))
Mike Frysinger13870082014-03-14 10:41:20 -0400725 if counters.upload_limit is not None:
726 counters.upload_limit += len(sym_paths)
727 # Decrement the error count in case we recover in the second pass.
728 assert bg_errors.value >= len(sym_paths), \
729 'more failed files than errors?'
730 bg_errors.value -= len(sym_paths)
731 else:
732 # No failed symbols, so just return now.
733 break
Mike Frysinger7f9be142014-01-15 02:16:42 -0500734
Mike Frysinger13870082014-03-14 10:41:20 -0400735 # If the user has requested it, save all the symbol files that we failed to
736 # upload to a listing file. This should help with recovery efforts later.
737 failed_queue.put(None)
738 WriteQueueToFile(failed_list, failed_queue, breakpad_dir)
739
740 finally:
Ralph Nathan03047282015-03-23 11:09:32 -0700741 logging.info('finished uploading; joining background process')
Mike Frysinger13870082014-03-14 10:41:20 -0400742 if dedupe_queue:
743 dedupe_queue.put(None)
Mike Frysinger4dd462e2014-04-30 16:21:51 -0400744
745 # The notification might be slow going, so give it some time to finish.
746 # We have to poll here as the process monitor is watching for output and
747 # will kill us if we go silent for too long.
748 wait_minutes = DEDUPE_NOTIFY_TIMEOUT
749 while storage_notify_proc.is_alive() and wait_minutes > 0:
Aviv Keshetd1f04632014-05-09 11:33:46 -0700750 if dedupe_queue:
751 qsize = str(dedupe_queue.qsize())
752 else:
753 qsize = '[None]'
Ralph Nathan03047282015-03-23 11:09:32 -0700754 logging.info('waiting up to %i minutes for ~%s notifications',
755 wait_minutes, qsize)
Mike Frysinger4dd462e2014-04-30 16:21:51 -0400756 storage_notify_proc.join(60)
757 wait_minutes -= 1
758
759 # The process is taking too long, so kill it and complain.
760 if storage_notify_proc.is_alive():
Ralph Nathan446aee92015-03-23 14:44:56 -0700761 logging.warning('notification process took too long')
Prathmesh Prabhu17f07422015-07-17 11:40:40 -0700762 logging.PrintBuildbotStepWarnings()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500763
Mike Frysinger66e51e92014-05-03 16:52:00 -0400764 # Kill it gracefully first (traceback) before tacking it down harder.
765 pid = storage_notify_proc.pid
766 for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGKILL):
Ralph Nathan446aee92015-03-23 14:44:56 -0700767 logging.warning('sending %s to %i', signals.StrSignal(sig), pid)
Mike Frysingerdad2a032014-05-11 23:05:11 -0400768 # The process might have exited between the last check and the
769 # actual kill below, so ignore ESRCH errors.
770 try:
771 os.kill(pid, sig)
772 except OSError as e:
773 if e.errno == errno.ESRCH:
774 break
775 else:
776 raise
Mike Frysinger66e51e92014-05-03 16:52:00 -0400777 time.sleep(5)
Mike Frysingerdad2a032014-05-11 23:05:11 -0400778 if not storage_notify_proc.is_alive():
Mike Frysinger66e51e92014-05-03 16:52:00 -0400779 break
780
781 # Drain the queue so we don't hang when we finish.
782 try:
Ralph Nathan446aee92015-03-23 14:44:56 -0700783 logging.warning('draining the notify queue manually')
Mike Frysinger4f5ea832014-05-12 00:54:28 -0400784 with timeout_util.Timeout(60):
785 try:
786 while dedupe_queue.get_nowait():
787 pass
788 except Queue.Empty:
789 pass
790 except timeout_util.TimeoutError:
Ralph Nathan446aee92015-03-23 14:44:56 -0700791 logging.warning('draining the notify queue failed; trashing it')
Mike Frysinger4f5ea832014-05-12 00:54:28 -0400792 dedupe_queue.cancel_join_thread()
Mike Frysinger66e51e92014-05-03 16:52:00 -0400793
Ralph Nathan03047282015-03-23 11:09:32 -0700794 logging.info('uploaded %i symbols (%i were deduped) which took: %s',
795 counters.uploaded_count, counters.deduped_count,
796 datetime.datetime.now() - start_time)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500797
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500798 return bg_errors.value
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400799
800
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400801def main(argv):
802 parser = commandline.ArgumentParser(description=__doc__)
803
Mike Frysingerd41938e2014-02-10 06:37:55 -0500804 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
805 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400806 parser.add_argument('--board', default=None,
807 help='board to build packages for')
808 parser.add_argument('--breakpad_root', type='path', default=None,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500809 help='full path to the breakpad symbol directory')
810 parser.add_argument('--root', type='path', default=None,
811 help='full path to the chroot dir')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400812 parser.add_argument('--official_build', action='store_true', default=False,
813 help='point to official symbol server')
Mike Frysinger38647542014-09-12 18:15:39 -0700814 parser.add_argument('--server', type=str, default=None,
815 help='URI for custom symbol server')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400816 parser.add_argument('--regenerate', action='store_true', default=False,
817 help='regenerate all symbols')
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500818 parser.add_argument('--upload-limit', type=int, default=None,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400819 help='only upload # number of symbols')
820 parser.add_argument('--strip_cfi', type=int,
821 default=CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024),
822 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500823 parser.add_argument('--failed-list', type='path',
824 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500825 parser.add_argument('--dedupe', action='store_true', default=False,
826 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400827 parser.add_argument('--testing', action='store_true', default=False,
828 help='run in testing mode')
829 parser.add_argument('--yes', action='store_true', default=False,
830 help='answer yes to all prompts')
831
832 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500833 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400834
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500835 if opts.sym_paths:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400836 if opts.regenerate:
837 cros_build_lib.Die('--regenerate may not be used with specific files')
838 else:
839 if opts.board is None:
840 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400841
842 if opts.breakpad_root and opts.regenerate:
843 cros_build_lib.Die('--regenerate may not be used with --breakpad_root')
844
845 if opts.testing:
846 # TODO(build): Kill off --testing mode once unittests are up-to-snuff.
Ralph Nathan03047282015-03-23 11:09:32 -0700847 logging.info('running in testing mode')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400848 # pylint: disable=W0601,W0603
849 global INITIAL_RETRY_DELAY, SymUpload, DEFAULT_SLEEP_DELAY
850 INITIAL_RETRY_DELAY = DEFAULT_SLEEP_DELAY = 0
851 SymUpload = TestingSymUpload
852
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500853 dedupe_namespace = None
854 if opts.dedupe:
855 if opts.official_build and not opts.testing:
856 dedupe_namespace = OFFICIAL_DEDUPE_NAMESPACE
857 else:
858 dedupe_namespace = STAGING_DEDUPE_NAMESPACE
859
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400860 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500861 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400862 Uploading symbols for an entire Chromium OS build is really only
863 necessary for release builds and in a few cases for developers
864 to debug problems. It will take considerable time to run. For
865 developer debugging purposes, consider instead passing specific
866 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500867 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400868 if not cros_build_lib.BooleanPrompt(
869 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500870 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400871 cros_build_lib.Die('better safe than sorry')
872
873 ret = 0
874 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400875 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
876 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400877
878 ret += UploadSymbols(opts.board, official=opts.official_build,
Mike Frysinger38647542014-09-12 18:15:39 -0700879 server=opts.server, breakpad_dir=opts.breakpad_root,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400880 file_limit=opts.strip_cfi, sleep=DEFAULT_SLEEP_DELAY,
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500881 upload_limit=opts.upload_limit, sym_paths=opts.sym_paths,
Mike Frysingerc5597f22014-11-27 15:39:15 -0500882 failed_list=opts.failed_list, root=opts.root,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500883 dedupe_namespace=dedupe_namespace)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400884 if ret:
Ralph Nathan59900422015-03-24 10:41:17 -0700885 logging.error('encountered %i problem(s)', ret)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400886 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
887 # return 0 in case we are a multiple of the mask.
888 ret = 1
889
890 return ret
Mike Frysinger094a2172013-08-14 12:54:35 -0400891
892
893# We need this to run once per process. Do it at module import time as that
894# will let us avoid doing it inline at function call time (see SymUpload) as
895# that func might be called by the multiprocessing module which means we'll
896# do the opener logic multiple times overall. Plus, if you're importing this
897# module, it's a pretty good chance that you're going to need this.
898poster.streaminghttp.register_openers()