toolkit/crashreporter/google-breakpad/src/client/ios/BreakpadController.mm

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:7282853c0134
1 // Copyright (c) 2012, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
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
13 // distribution.
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.
17 //
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.
29
30 #import "BreakpadController.h"
31
32 #import <UIKit/UIKit.h>
33 #include <asl.h>
34 #include <execinfo.h>
35 #include <signal.h>
36 #include <unistd.h>
37 #include <sys/sysctl.h>
38
39 #include <common/scoped_ptr.h>
40
41 #pragma mark -
42 #pragma mark Private Methods
43
44 @interface BreakpadController ()
45
46 // Init the singleton instance.
47 - (id)initSingleton;
48
49 // Load a crash report and send it to the server.
50 - (void)sendStoredCrashReports;
51
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.
55 - (int)sendDelay;
56
57 // Notifies that a report will be sent, and update the last sending time
58 // accordingly.
59 - (void)reportWillBeSent;
60
61 @end
62
63 #pragma mark -
64 #pragma mark Anonymous namespace
65
66 namespace {
67
68 // The name of the user defaults key for the last submission to the crash
69 // server.
70 NSString* const kLastSubmission = @"com.google.Breakpad.LastSubmission";
71
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";
76
77 NSString* result = nil;
78
79 size_t size = 0;
80 if (sysctlbyname(kHwMachineSysctlName, NULL, &size, NULL, 0) || size == 0)
81 return nil;
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()];
85 return result;
86 }
87
88 } // namespace
89
90 #pragma mark -
91 #pragma mark BreakpadController Implementation
92
93 @implementation BreakpadController
94
95 + (BreakpadController*)sharedInstance {
96 @synchronized(self) {
97 static BreakpadController* sharedInstance_ =
98 [[BreakpadController alloc] initSingleton];
99 return sharedInstance_;
100 }
101 }
102
103 - (id)init {
104 return nil;
105 }
106
107 - (id)initSingleton {
108 self = [super init];
109 if (self) {
110 queue_ = dispatch_queue_create("com.google.BreakpadQueue", NULL);
111 configuration_ = [[[NSBundle mainBundle] infoDictionary] mutableCopy];
112 enableUploads_ = NO;
113 started_ = NO;
114 NSString* uploadInterval =
115 [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL];
116 [self setUploadInterval:[uploadInterval intValue]];
117 }
118 return self;
119 }
120
121 // Since this class is a singleton, this method is not expected to be called.
122 - (void)dealloc {
123 assert(!breakpadRef_);
124 dispatch_release(queue_);
125 [configuration_ release];
126 [super dealloc];
127 }
128
129 #pragma mark -
130
131 - (void)start:(BOOL)onCurrentThread {
132 if (started_)
133 return;
134 started_ = YES;
135 void(^startBlock)() = ^{
136 assert(!breakpadRef_);
137 breakpadRef_ = BreakpadCreate(configuration_);
138 if (breakpadRef_) {
139 BreakpadAddUploadParameter(breakpadRef_, @"platform", GetPlatform());
140 }
141 };
142 if (onCurrentThread)
143 startBlock();
144 else
145 dispatch_async(queue_, startBlock);
146 }
147
148 - (void)stop {
149 if (!started_)
150 return;
151 started_ = NO;
152 dispatch_sync(queue_, ^{
153 if (breakpadRef_) {
154 BreakpadRelease(breakpadRef_);
155 breakpadRef_ = NULL;
156 }
157 });
158 }
159
160 - (void)setUploadingEnabled:(BOOL)enabled {
161 NSAssert(started_,
162 @"The controller must be started before setUploadingEnabled is called");
163 dispatch_async(queue_, ^{
164 if (enabled == enableUploads_)
165 return;
166 if (enabled) {
167 // Set this before calling doSendStoredCrashReport, because that
168 // calls sendDelay, which in turn checks this flag.
169 enableUploads_ = YES;
170 [self sendStoredCrashReports];
171 } else {
172 enableUploads_ = NO;
173 [NSObject cancelPreviousPerformRequestsWithTarget:self
174 selector:@selector(sendStoredCrashReports)
175 object:nil];
176 }
177 });
178 }
179
180 - (void)updateConfiguration:(NSDictionary*)configuration {
181 NSAssert(!started_,
182 @"The controller must not be started when updateConfiguration is called");
183 [configuration_ addEntriesFromDictionary:configuration];
184 NSString* uploadInterval =
185 [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL];
186 if (uploadInterval)
187 [self setUploadInterval:[uploadInterval intValue]];
188 }
189
190 - (void)setUploadingURL:(NSString*)url {
191 NSAssert(!started_,
192 @"The controller must not be started when setUploadingURL is called");
193 [configuration_ setValue:url forKey:@BREAKPAD_URL];
194 }
195
196 - (void)setUploadInterval:(int)intervalInSeconds {
197 NSAssert(!started_,
198 @"The controller must not be started when setUploadInterval is called");
199 [configuration_ removeObjectForKey:@BREAKPAD_REPORT_INTERVAL];
200 uploadIntervalInSeconds_ = intervalInSeconds;
201 if (uploadIntervalInSeconds_ < 0)
202 uploadIntervalInSeconds_ = 0;
203 }
204
205 - (void)addUploadParameter:(NSString*)value forKey:(NSString*)key {
206 NSAssert(started_,
207 @"The controller must be started before addUploadParameter is called");
208 dispatch_async(queue_, ^{
209 if (breakpadRef_)
210 BreakpadAddUploadParameter(breakpadRef_, key, value);
211 });
212 }
213
214 - (void)removeUploadParameterForKey:(NSString*)key {
215 NSAssert(started_, @"The controller must be started before "
216 "removeUploadParameterForKey is called");
217 dispatch_async(queue_, ^{
218 if (breakpadRef_)
219 BreakpadRemoveUploadParameter(breakpadRef_, key);
220 });
221 }
222
223 - (void)withBreakpadRef:(void(^)(BreakpadRef))callback {
224 NSAssert(started_,
225 @"The controller must be started before withBreakpadRef is called");
226 dispatch_async(queue_, ^{
227 callback(breakpadRef_);
228 });
229 }
230
231 - (void)hasReportToUpload:(void(^)(BOOL))callback {
232 NSAssert(started_, @"The controller must be started before "
233 "hasReportToUpload is called");
234 dispatch_async(queue_, ^{
235 callback(breakpadRef_ && BreakpadHasCrashReportToUpload(breakpadRef_));
236 });
237 }
238
239 #pragma mark -
240
241 - (int)sendDelay {
242 if (!breakpadRef_ || uploadIntervalInSeconds_ <= 0 || !enableUploads_)
243 return -1;
244
245 // To prevent overloading the crash server, crashes are not sent than one
246 // report every |uploadIntervalInSeconds_|. A value in the user defaults is
247 // used to keep the time of the last upload.
248 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
249 NSNumber *lastTimeNum = [userDefaults objectForKey:kLastSubmission];
250 NSTimeInterval lastTime = lastTimeNum ? [lastTimeNum floatValue] : 0;
251 NSTimeInterval spanSeconds = CFAbsoluteTimeGetCurrent() - lastTime;
252
253 if (spanSeconds >= uploadIntervalInSeconds_)
254 return 0;
255 return uploadIntervalInSeconds_ - static_cast<int>(spanSeconds);
256 }
257
258 - (void)reportWillBeSent {
259 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
260 [userDefaults setObject:[NSNumber numberWithDouble:CFAbsoluteTimeGetCurrent()]
261 forKey:kLastSubmission];
262 [userDefaults synchronize];
263 }
264
265 - (void)sendStoredCrashReports {
266 dispatch_async(queue_, ^{
267 if (!BreakpadHasCrashReportToUpload(breakpadRef_))
268 return;
269
270 int timeToWait = [self sendDelay];
271
272 // Unable to ever send report.
273 if (timeToWait == -1)
274 return;
275
276 // A report can be sent now.
277 if (timeToWait == 0) {
278 [self reportWillBeSent];
279 BreakpadUploadNextReport(breakpadRef_);
280
281 // If more reports must be sent, make sure this method is called again.
282 if (BreakpadHasCrashReportToUpload(breakpadRef_))
283 timeToWait = uploadIntervalInSeconds_;
284 }
285
286 // A report must be sent later.
287 if (timeToWait > 0)
288 [self performSelector:@selector(sendStoredCrashReports)
289 withObject:nil
290 afterDelay:timeToWait];
291 });
292 }
293
294 @end

mercurial