1 // Copyright (c) 2012, Google Inc.
2 // All rights reserved.
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
14 // * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #import "BreakpadController.h"
32 #import <UIKit/UIKit.h>
37 #include <sys/sysctl.h>
39 #include <common/scoped_ptr.h>
42 #pragma mark Private Methods
44 @interface BreakpadController ()
46 // Init the singleton instance.
49 // Load a crash report and send it to the server.
50 - (void)sendStoredCrashReports;
52 // Returns when a report can be sent. |-1| means never, |0| means that a report
53 // can be sent immediately, a positive number is the number of seconds to wait
54 // before being allowed to upload a report.
57 // Notifies that a report will be sent, and update the last sending time
59 - (void)reportWillBeSent;
64 #pragma mark Anonymous namespace
68 // The name of the user defaults key for the last submission to the crash
70 NSString* const kLastSubmission = @"com.google.Breakpad.LastSubmission";
72 // Returns a NSString describing the current platform.
73 NSString* GetPlatform() {
74 // Name of the system call for getting the platform.
75 static const char kHwMachineSysctlName[] = "hw.machine";
77 NSString* result = nil;
80 if (sysctlbyname(kHwMachineSysctlName, NULL, &size, NULL, 0) || size == 0)
82 google_breakpad::scoped_array<char> machine(new char[size]);
83 if (sysctlbyname(kHwMachineSysctlName, machine.get(), &size, NULL, 0) == 0)
84 result = [NSString stringWithUTF8String:machine.get()];
91 #pragma mark BreakpadController Implementation
93 @implementation BreakpadController
95 + (BreakpadController*)sharedInstance {
97 static BreakpadController* sharedInstance_ =
98 [[BreakpadController alloc] initSingleton];
99 return sharedInstance_;
107 - (id)initSingleton {
110 queue_ = dispatch_queue_create("com.google.BreakpadQueue", NULL);
113 [self resetConfiguration];
118 // Since this class is a singleton, this method is not expected to be called.
120 assert(!breakpadRef_);
121 dispatch_release(queue_);
122 [configuration_ release];
123 [uploadTimeParameters_ release];
129 - (void)start:(BOOL)onCurrentThread {
133 void(^startBlock)() = ^{
134 assert(!breakpadRef_);
135 breakpadRef_ = BreakpadCreate(configuration_);
137 BreakpadAddUploadParameter(breakpadRef_, @"platform", GetPlatform());
143 dispatch_async(queue_, startBlock);
150 dispatch_sync(queue_, ^{
152 BreakpadRelease(breakpadRef_);
158 // This method must be called from the breakpad queue.
159 - (void)threadUnsafeSendReportWithConfiguration:(NSDictionary*)configuration
160 withBreakpadRef:(BreakpadRef)ref {
161 NSAssert(started_, @"The controller must be started before "
162 "threadUnsafeSendReportWithConfiguration is called");
164 BreakpadUploadReportWithParametersAndConfiguration(breakpadRef_,
165 uploadTimeParameters_,
170 - (void)setUploadingEnabled:(BOOL)enabled {
172 @"The controller must be started before setUploadingEnabled is called");
173 dispatch_async(queue_, ^{
174 if (enabled == enableUploads_)
177 // Set this before calling doSendStoredCrashReport, because that
178 // calls sendDelay, which in turn checks this flag.
179 enableUploads_ = YES;
180 [self sendStoredCrashReports];
183 [NSObject cancelPreviousPerformRequestsWithTarget:self
184 selector:@selector(sendStoredCrashReports)
190 - (void)updateConfiguration:(NSDictionary*)configuration {
192 @"The controller must not be started when updateConfiguration is called");
193 [configuration_ addEntriesFromDictionary:configuration];
194 NSString* uploadInterval =
195 [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL];
197 [self setUploadInterval:[uploadInterval intValue]];
200 - (void)resetConfiguration {
202 @"The controller must not be started when resetConfiguration is called");
203 [configuration_ autorelease];
204 configuration_ = [[[NSBundle mainBundle] infoDictionary] mutableCopy];
205 NSString* uploadInterval =
206 [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL];
207 [self setUploadInterval:[uploadInterval intValue]];
208 [self setParametersToAddAtUploadTime:nil];
211 - (void)setUploadingURL:(NSString*)url {
213 @"The controller must not be started when setUploadingURL is called");
214 [configuration_ setValue:url forKey:@BREAKPAD_URL];
217 - (void)setUploadInterval:(int)intervalInSeconds {
219 @"The controller must not be started when setUploadInterval is called");
220 [configuration_ removeObjectForKey:@BREAKPAD_REPORT_INTERVAL];
221 uploadIntervalInSeconds_ = intervalInSeconds;
222 if (uploadIntervalInSeconds_ < 0)
223 uploadIntervalInSeconds_ = 0;
226 - (void)setParametersToAddAtUploadTime:(NSDictionary*)uploadTimeParameters {
227 NSAssert(!started_, @"The controller must not be started when "
228 "setParametersToAddAtUploadTime is called");
229 [uploadTimeParameters_ autorelease];
230 uploadTimeParameters_ = [uploadTimeParameters copy];
233 - (void)addUploadParameter:(NSString*)value forKey:(NSString*)key {
235 @"The controller must be started before addUploadParameter is called");
236 dispatch_async(queue_, ^{
238 BreakpadAddUploadParameter(breakpadRef_, key, value);
242 - (void)removeUploadParameterForKey:(NSString*)key {
243 NSAssert(started_, @"The controller must be started before "
244 "removeUploadParameterForKey is called");
245 dispatch_async(queue_, ^{
247 BreakpadRemoveUploadParameter(breakpadRef_, key);
251 - (void)withBreakpadRef:(void(^)(BreakpadRef))callback {
253 @"The controller must be started before withBreakpadRef is called");
254 dispatch_async(queue_, ^{
255 callback(breakpadRef_);
259 - (void)hasReportToUpload:(void(^)(BOOL))callback {
260 NSAssert(started_, @"The controller must be started before "
261 "hasReportToUpload is called");
262 dispatch_async(queue_, ^{
263 callback(breakpadRef_ && (BreakpadGetCrashReportCount(breakpadRef_) > 0));
267 - (void)getCrashReportCount:(void(^)(int))callback {
268 NSAssert(started_, @"The controller must be started before "
269 "getCrashReportCount is called");
270 dispatch_async(queue_, ^{
271 callback(breakpadRef_ ? BreakpadGetCrashReportCount(breakpadRef_) : 0);
275 - (void)getNextReportConfigurationOrSendDelay:
276 (void(^)(NSDictionary*, int))callback {
277 NSAssert(started_, @"The controller must be started before "
278 "getNextReportConfigurationOrSendDelay is called");
279 dispatch_async(queue_, ^{
284 int delay = [self sendDelay];
286 callback(nil, delay);
289 [self reportWillBeSent];
290 callback(BreakpadGetNextReportConfiguration(breakpadRef_), 0);
297 if (!breakpadRef_ || uploadIntervalInSeconds_ <= 0 || !enableUploads_)
300 // To prevent overloading the crash server, crashes are not sent than one
301 // report every |uploadIntervalInSeconds_|. A value in the user defaults is
302 // used to keep the time of the last upload.
303 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
304 NSNumber *lastTimeNum = [userDefaults objectForKey:kLastSubmission];
305 NSTimeInterval lastTime = lastTimeNum ? [lastTimeNum floatValue] : 0;
306 NSTimeInterval spanSeconds = CFAbsoluteTimeGetCurrent() - lastTime;
308 if (spanSeconds >= uploadIntervalInSeconds_)
310 return uploadIntervalInSeconds_ - static_cast<int>(spanSeconds);
313 - (void)reportWillBeSent {
314 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
315 [userDefaults setObject:[NSNumber numberWithDouble:CFAbsoluteTimeGetCurrent()]
316 forKey:kLastSubmission];
317 [userDefaults synchronize];
320 - (void)sendStoredCrashReports {
321 dispatch_async(queue_, ^{
322 if (BreakpadGetCrashReportCount(breakpadRef_) == 0)
325 int timeToWait = [self sendDelay];
327 // Unable to ever send report.
328 if (timeToWait == -1)
331 // A report can be sent now.
332 if (timeToWait == 0) {
333 [self reportWillBeSent];
334 BreakpadUploadNextReportWithParameters(breakpadRef_,
335 uploadTimeParameters_);
337 // If more reports must be sent, make sure this method is called again.
338 if (BreakpadGetCrashReportCount(breakpadRef_) > 0)
339 timeToWait = uploadIntervalInSeconds_;
342 // A report must be sent later.
343 if (timeToWait > 0) {
344 // performSelector: doesn't work on queue_
345 dispatch_async(dispatch_get_main_queue(), ^{
346 [self performSelector:@selector(sendStoredCrashReports)
348 afterDelay:timeToWait];