1 |
efrain |
1 |
// Copyright 2021 Google LLC
|
|
|
2 |
//
|
|
|
3 |
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
4 |
// you may not use this file except in compliance with the License.
|
|
|
5 |
// You may obtain a copy of the License at
|
|
|
6 |
//
|
|
|
7 |
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
8 |
//
|
|
|
9 |
// Unless required by applicable law or agreed to in writing, software
|
|
|
10 |
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
11 |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
12 |
// See the License for the specific language governing permissions and
|
|
|
13 |
// limitations under the License.
|
|
|
14 |
|
|
|
15 |
#import <Foundation/Foundation.h>
|
|
|
16 |
|
|
|
17 |
#import "Crashlytics/Crashlytics/Controllers/FIRCLSMetricKitManager.h"
|
|
|
18 |
|
|
|
19 |
#if CLS_METRICKIT_SUPPORTED
|
|
|
20 |
|
|
|
21 |
#import "Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h"
|
|
|
22 |
#include "Crashlytics/Crashlytics/Handlers/FIRCLSMachException.h"
|
|
|
23 |
#include "Crashlytics/Crashlytics/Handlers/FIRCLSSignal.h"
|
|
|
24 |
#import "Crashlytics/Crashlytics/Helpers/FIRCLSCallStackTree.h"
|
|
|
25 |
#import "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
|
|
|
26 |
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
|
|
|
27 |
#import "Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.h"
|
|
|
28 |
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
|
|
|
29 |
#import "Crashlytics/Crashlytics/Public/FirebaseCrashlytics/FIRCrashlytics.h"
|
|
|
30 |
#import "Crashlytics/Crashlytics/Public/FirebaseCrashlytics/FIRCrashlyticsReport.h"
|
|
|
31 |
|
|
|
32 |
@interface FIRCLSMetricKitManager ()
|
|
|
33 |
|
|
|
34 |
@property FBLPromise *metricKitDataAvailable;
|
|
|
35 |
@property FIRCLSExistingReportManager *existingReportManager;
|
|
|
36 |
@property FIRCLSFileManager *fileManager;
|
|
|
37 |
@property FIRCLSManagerData *managerData;
|
|
|
38 |
@property BOOL metricKitPromiseFulfilled;
|
|
|
39 |
|
|
|
40 |
@end
|
|
|
41 |
|
|
|
42 |
@implementation FIRCLSMetricKitManager
|
|
|
43 |
|
|
|
44 |
- (instancetype)initWithManagerData:(FIRCLSManagerData *)managerData
|
|
|
45 |
existingReportManager:(FIRCLSExistingReportManager *)existingReportManager
|
|
|
46 |
fileManager:(FIRCLSFileManager *)fileManager {
|
|
|
47 |
_existingReportManager = existingReportManager;
|
|
|
48 |
_fileManager = fileManager;
|
|
|
49 |
_managerData = managerData;
|
|
|
50 |
_metricKitPromiseFulfilled = NO;
|
|
|
51 |
return self;
|
|
|
52 |
}
|
|
|
53 |
|
|
|
54 |
/*
|
|
|
55 |
* Registers the MetricKit manager to receive MetricKit reports by adding self to the
|
|
|
56 |
* MXMetricManager subscribers. Also initializes the promise that we'll use to ensure that any
|
|
|
57 |
* MetricKit report files are included in Crashylytics fatal reports. If no crash occurred on the
|
|
|
58 |
* last run of the app, this promise is immediately resolved so that the upload of any nonfatal
|
|
|
59 |
* events can proceed.
|
|
|
60 |
*/
|
|
|
61 |
- (void)registerMetricKitManager API_AVAILABLE(ios(14)) {
|
|
|
62 |
[[MXMetricManager sharedManager] addSubscriber:self];
|
|
|
63 |
self.metricKitDataAvailable = [FBLPromise pendingPromise];
|
|
|
64 |
|
|
|
65 |
// If there was no crash on the last run of the app or there's no diagnostic report in the
|
|
|
66 |
// MetricKit directory, then we aren't expecting a MetricKit diagnostic report and should resolve
|
|
|
67 |
// the promise immediately. If MetricKit captured a fatal event and Crashlytics did not, then
|
|
|
68 |
// we'll still process the MetricKit crash but won't upload it until the app restarts again.
|
|
|
69 |
if (![self.fileManager didCrashOnPreviousExecution] ||
|
|
|
70 |
![self.fileManager metricKitDiagnosticFileExists]) {
|
|
|
71 |
@synchronized(self) {
|
|
|
72 |
[self fulfillMetricKitPromise];
|
|
|
73 |
}
|
|
|
74 |
}
|
|
|
75 |
|
|
|
76 |
// If we haven't resolved this promise within three seconds, resolve it now so that we're not
|
|
|
77 |
// waiting indefinitely for MetricKit payloads that won't arrive.
|
|
|
78 |
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), self.managerData.dispatchQueue,
|
|
|
79 |
^{
|
|
|
80 |
@synchronized(self) {
|
|
|
81 |
if (!self.metricKitPromiseFulfilled) {
|
|
|
82 |
FIRCLSDebugLog(@"Resolving MetricKit promise after three seconds");
|
|
|
83 |
[self fulfillMetricKitPromise];
|
|
|
84 |
}
|
|
|
85 |
}
|
|
|
86 |
});
|
|
|
87 |
|
|
|
88 |
FIRCLSDebugLog(@"Finished registering metrickit manager");
|
|
|
89 |
}
|
|
|
90 |
|
|
|
91 |
/*
|
|
|
92 |
* This method receives diagnostic payloads from MetricKit whenever a fatal or nonfatal MetricKit
|
|
|
93 |
* event occurs. If a fatal event, this method will be called when the app restarts. Since we're
|
|
|
94 |
* including a MetricKit report file in the Crashlytics report to be sent to the backend, we need
|
|
|
95 |
* to make sure that we process the payloads and write the included information to file before
|
|
|
96 |
* the report is sent up. If this method is called due to a nonfatal event, it will be called
|
|
|
97 |
* immediately after the event. Since we send nonfatal events on the next run of the app, we can
|
|
|
98 |
* write out the information but won't need to resolve the promise.
|
|
|
99 |
*/
|
|
|
100 |
- (void)didReceiveDiagnosticPayloads:(NSArray<MXDiagnosticPayload *> *)payloads
|
|
|
101 |
API_AVAILABLE(ios(14)) {
|
|
|
102 |
BOOL processedFatalPayload = NO;
|
|
|
103 |
for (MXDiagnosticPayload *diagnosticPayload in payloads) {
|
|
|
104 |
if (!diagnosticPayload) {
|
|
|
105 |
continue;
|
|
|
106 |
}
|
|
|
107 |
|
|
|
108 |
BOOL processedPayload = [self processMetricKitPayload:diagnosticPayload
|
|
|
109 |
skipCrashEvent:processedFatalPayload];
|
|
|
110 |
if (processedPayload && ([diagnosticPayload.crashDiagnostics count] > 0)) {
|
|
|
111 |
processedFatalPayload = YES;
|
|
|
112 |
}
|
|
|
113 |
}
|
|
|
114 |
// Once we've processed all the payloads, resolve the promise so that reporting uploading
|
|
|
115 |
// continues. If there was not a crash on the previous run of the app, the promise will already
|
|
|
116 |
// have been resolved.
|
|
|
117 |
@synchronized(self) {
|
|
|
118 |
[self fulfillMetricKitPromise];
|
|
|
119 |
}
|
|
|
120 |
}
|
|
|
121 |
|
|
|
122 |
// Helper method to write a MetricKit payload's data to file.
|
|
|
123 |
- (BOOL)processMetricKitPayload:(MXDiagnosticPayload *)diagnosticPayload
|
|
|
124 |
skipCrashEvent:(BOOL)skipCrashEvent API_AVAILABLE(ios(14)) {
|
|
|
125 |
BOOL writeFailed = NO;
|
|
|
126 |
|
|
|
127 |
// Write out each type of diagnostic if it exists in the report
|
|
|
128 |
BOOL hasCrash = [diagnosticPayload.crashDiagnostics count] > 0;
|
|
|
129 |
BOOL hasHang = [diagnosticPayload.hangDiagnostics count] > 0;
|
|
|
130 |
BOOL hasCPUException = [diagnosticPayload.cpuExceptionDiagnostics count] > 0;
|
|
|
131 |
BOOL hasDiskWriteException = [diagnosticPayload.diskWriteExceptionDiagnostics count] > 0;
|
|
|
132 |
|
|
|
133 |
// If there are no diagnostics in the report, return before writing out any files.
|
|
|
134 |
if (!hasCrash && !hasHang && !hasCPUException && !hasDiskWriteException) {
|
|
|
135 |
return false;
|
|
|
136 |
}
|
|
|
137 |
|
|
|
138 |
// MXDiagnosticPayload have both a timeStampBegin and timeStampEnd. Now that these events are
|
|
|
139 |
// real-time, both refer to the same time - record both values anyway.
|
|
|
140 |
NSTimeInterval beginSecondsSince1970 = [diagnosticPayload.timeStampBegin timeIntervalSince1970];
|
|
|
141 |
NSTimeInterval endSecondsSince1970 = [diagnosticPayload.timeStampEnd timeIntervalSince1970];
|
|
|
142 |
|
|
|
143 |
// Get file path for the active reports directory.
|
|
|
144 |
NSString *activePath = [[self.fileManager activePath] stringByAppendingString:@"/"];
|
|
|
145 |
|
|
|
146 |
// If there is a crash diagnostic in the payload, then this method was called for a fatal event.
|
|
|
147 |
// Also ensure that there is a report from the last run of the app that we can write to.
|
|
|
148 |
NSString *metricKitFatalReportFile;
|
|
|
149 |
NSString *metricKitNonfatalReportFile;
|
|
|
150 |
|
|
|
151 |
NSString *newestUnsentReportID =
|
|
|
152 |
self.existingReportManager.newestUnsentReport.reportID
|
|
|
153 |
? [self.existingReportManager.newestUnsentReport.reportID stringByAppendingString:@"/"]
|
|
|
154 |
: nil;
|
|
|
155 |
NSString *currentReportID =
|
|
|
156 |
[_managerData.executionIDModel.executionID stringByAppendingString:@"/"];
|
|
|
157 |
BOOL crashlyticsFatalReported =
|
|
|
158 |
([diagnosticPayload.crashDiagnostics count] > 0) && (newestUnsentReportID != nil) &&
|
|
|
159 |
([self.fileManager
|
|
|
160 |
fileExistsAtPath:[activePath stringByAppendingString:newestUnsentReportID]]);
|
|
|
161 |
|
|
|
162 |
// Set the MetricKit fatal path appropriately depending on whether we also captured a Crashlytics
|
|
|
163 |
// fatal event and whether the diagnostic report came from a fatal or nonfatal event.
|
|
|
164 |
if (crashlyticsFatalReported) {
|
|
|
165 |
metricKitFatalReportFile = [[activePath stringByAppendingString:newestUnsentReportID]
|
|
|
166 |
stringByAppendingString:FIRCLSMetricKitFatalReportFile];
|
|
|
167 |
} else {
|
|
|
168 |
metricKitFatalReportFile = [[activePath stringByAppendingString:currentReportID]
|
|
|
169 |
stringByAppendingString:FIRCLSMetricKitFatalReportFile];
|
|
|
170 |
}
|
|
|
171 |
metricKitNonfatalReportFile = [[activePath stringByAppendingString:currentReportID]
|
|
|
172 |
stringByAppendingString:FIRCLSMetricKitNonfatalReportFile];
|
|
|
173 |
|
|
|
174 |
if (!metricKitFatalReportFile || !metricKitNonfatalReportFile) {
|
|
|
175 |
FIRCLSDebugLog(@"Error finding MetricKit files");
|
|
|
176 |
return NO;
|
|
|
177 |
}
|
|
|
178 |
|
|
|
179 |
FIRCLSDebugLog(@"File paths for MetricKit report: %@, %@", metricKitFatalReportFile,
|
|
|
180 |
metricKitNonfatalReportFile);
|
|
|
181 |
if (hasCrash && ![_fileManager fileExistsAtPath:metricKitFatalReportFile]) {
|
|
|
182 |
[_fileManager createFileAtPath:metricKitFatalReportFile contents:nil attributes:nil];
|
|
|
183 |
}
|
|
|
184 |
if ((hasHang | hasCPUException | hasDiskWriteException) &&
|
|
|
185 |
![_fileManager fileExistsAtPath:metricKitNonfatalReportFile]) {
|
|
|
186 |
[_fileManager createFileAtPath:metricKitNonfatalReportFile contents:nil attributes:nil];
|
|
|
187 |
}
|
|
|
188 |
NSFileHandle *nonfatalFile =
|
|
|
189 |
[NSFileHandle fileHandleForUpdatingAtPath:metricKitNonfatalReportFile];
|
|
|
190 |
if ((hasHang | hasCPUException | hasDiskWriteException) && nonfatalFile == nil) {
|
|
|
191 |
FIRCLSDebugLog(@"Unable to create or open nonfatal MetricKit file.");
|
|
|
192 |
return false;
|
|
|
193 |
}
|
|
|
194 |
NSFileHandle *fatalFile = [NSFileHandle fileHandleForUpdatingAtPath:metricKitFatalReportFile];
|
|
|
195 |
if (hasCrash && fatalFile == nil) {
|
|
|
196 |
FIRCLSDebugLog(@"Unable to create or open fatal MetricKit file.");
|
|
|
197 |
return false;
|
|
|
198 |
}
|
|
|
199 |
|
|
|
200 |
NSData *newLineData = [@"\n" dataUsingEncoding:NSUTF8StringEncoding];
|
|
|
201 |
|
|
|
202 |
// For each diagnostic type, write out a section in the MetricKit report file. This section will
|
|
|
203 |
// have subsections for threads, metadata, and event specific metadata.
|
|
|
204 |
if (hasCrash && !skipCrashEvent) {
|
|
|
205 |
// Write out time information to the MetricKit report file. Time needs to be a value for
|
|
|
206 |
// backend serialization, so we write out end_time separately.
|
|
|
207 |
|
|
|
208 |
MXCrashDiagnostic *crashDiagnostic = [diagnosticPayload.crashDiagnostics objectAtIndex:0];
|
|
|
209 |
|
|
|
210 |
NSArray *threadArray = [self convertThreadsToArray:crashDiagnostic.callStackTree];
|
|
|
211 |
NSDictionary *metadataDict = [self convertMetadataToDictionary:crashDiagnostic.metaData];
|
|
|
212 |
|
|
|
213 |
NSString *nilString = @"";
|
|
|
214 |
|
|
|
215 |
// On the backend, we process name, code name, and address into the subtitle of an issue.
|
|
|
216 |
// Mach exception name and code should be preferred over signal name and code if available.
|
|
|
217 |
const char *signalName = NULL;
|
|
|
218 |
const char *signalCodeName = NULL;
|
|
|
219 |
FIRCLSSignalNameLookup([crashDiagnostic.signal intValue], 0, &signalName, &signalCodeName);
|
|
|
220 |
// signalName is the default name, so should never be NULL
|
|
|
221 |
if (signalName == NULL) {
|
|
|
222 |
signalName = "UNKNOWN";
|
|
|
223 |
}
|
|
|
224 |
if (signalCodeName == NULL) {
|
|
|
225 |
signalCodeName = "";
|
|
|
226 |
}
|
|
|
227 |
|
|
|
228 |
const char *machExceptionName = NULL;
|
|
|
229 |
const char *machExceptionCodeName = NULL;
|
|
|
230 |
#if CLS_MACH_EXCEPTION_SUPPORTED
|
|
|
231 |
FIRCLSMachExceptionNameLookup(
|
|
|
232 |
[crashDiagnostic.exceptionType intValue],
|
|
|
233 |
(mach_exception_data_type_t)[crashDiagnostic.exceptionCode intValue], &machExceptionName,
|
|
|
234 |
&machExceptionCodeName);
|
|
|
235 |
#endif
|
|
|
236 |
if (machExceptionCodeName == NULL) {
|
|
|
237 |
machExceptionCodeName = "";
|
|
|
238 |
}
|
|
|
239 |
|
|
|
240 |
NSString *name = machExceptionName != NULL ? [NSString stringWithUTF8String:machExceptionName]
|
|
|
241 |
: [NSString stringWithUTF8String:signalName];
|
|
|
242 |
NSString *codeName = machExceptionName != NULL
|
|
|
243 |
? [NSString stringWithUTF8String:machExceptionCodeName]
|
|
|
244 |
: [NSString stringWithUTF8String:signalCodeName];
|
|
|
245 |
|
|
|
246 |
NSDictionary *crashDictionary = @{
|
|
|
247 |
@"metric_kit_fatal" : @{
|
|
|
248 |
@"time" : [NSNumber numberWithLong:beginSecondsSince1970],
|
|
|
249 |
@"end_time" : [NSNumber numberWithLong:endSecondsSince1970],
|
|
|
250 |
@"metadata" : metadataDict,
|
|
|
251 |
@"termination_reason" :
|
|
|
252 |
(crashDiagnostic.terminationReason) ? crashDiagnostic.terminationReason : nilString,
|
|
|
253 |
@"virtual_memory_region_info" : (crashDiagnostic.virtualMemoryRegionInfo)
|
|
|
254 |
? crashDiagnostic.virtualMemoryRegionInfo
|
|
|
255 |
: nilString,
|
|
|
256 |
@"exception_type" : crashDiagnostic.exceptionType,
|
|
|
257 |
@"exception_code" : crashDiagnostic.exceptionCode,
|
|
|
258 |
@"signal" : crashDiagnostic.signal,
|
|
|
259 |
@"app_version" : crashDiagnostic.applicationVersion,
|
|
|
260 |
@"code_name" : codeName,
|
|
|
261 |
@"name" : name
|
|
|
262 |
}
|
|
|
263 |
};
|
|
|
264 |
writeFailed = ![self writeDictionaryToFile:crashDictionary
|
|
|
265 |
file:fatalFile
|
|
|
266 |
newLineData:newLineData];
|
|
|
267 |
writeFailed = writeFailed | ![self writeDictionaryToFile:@{@"threads" : threadArray}
|
|
|
268 |
file:fatalFile
|
|
|
269 |
newLineData:newLineData];
|
|
|
270 |
}
|
|
|
271 |
|
|
|
272 |
if (hasHang) {
|
|
|
273 |
MXHangDiagnostic *hangDiagnostic = [diagnosticPayload.hangDiagnostics objectAtIndex:0];
|
|
|
274 |
|
|
|
275 |
NSArray *threadArray = [self convertThreadsToArray:hangDiagnostic.callStackTree];
|
|
|
276 |
NSDictionary *metadataDict = [self convertMetadataToDictionary:hangDiagnostic.metaData];
|
|
|
277 |
|
|
|
278 |
NSDictionary *hangDictionary = @{
|
|
|
279 |
@"exception" : @{
|
|
|
280 |
@"type" : @"metrickit_nonfatal",
|
|
|
281 |
@"name" : @"hang_event",
|
|
|
282 |
@"time" : [NSNumber numberWithLong:beginSecondsSince1970],
|
|
|
283 |
@"end_time" : [NSNumber numberWithLong:endSecondsSince1970],
|
|
|
284 |
@"threads" : threadArray,
|
|
|
285 |
@"metadata" : metadataDict,
|
|
|
286 |
@"hang_duration" : [NSNumber numberWithDouble:[hangDiagnostic.hangDuration doubleValue]],
|
|
|
287 |
@"app_version" : hangDiagnostic.applicationVersion
|
|
|
288 |
}
|
|
|
289 |
};
|
|
|
290 |
|
|
|
291 |
writeFailed = ![self writeDictionaryToFile:hangDictionary
|
|
|
292 |
file:nonfatalFile
|
|
|
293 |
newLineData:newLineData];
|
|
|
294 |
}
|
|
|
295 |
|
|
|
296 |
if (hasCPUException) {
|
|
|
297 |
MXCPUExceptionDiagnostic *cpuExceptionDiagnostic =
|
|
|
298 |
[diagnosticPayload.cpuExceptionDiagnostics objectAtIndex:0];
|
|
|
299 |
|
|
|
300 |
NSArray *threadArray = [self convertThreadsToArray:cpuExceptionDiagnostic.callStackTree];
|
|
|
301 |
NSDictionary *metadataDict = [self convertMetadataToDictionary:cpuExceptionDiagnostic.metaData];
|
|
|
302 |
|
|
|
303 |
NSDictionary *cpuDictionary = @{
|
|
|
304 |
@"exception" : @{
|
|
|
305 |
@"type" : @"metrickit_nonfatal",
|
|
|
306 |
@"name" : @"cpu_exception_event",
|
|
|
307 |
@"time" : [NSNumber numberWithLong:beginSecondsSince1970],
|
|
|
308 |
@"end_time" : [NSNumber numberWithLong:endSecondsSince1970],
|
|
|
309 |
@"threads" : threadArray,
|
|
|
310 |
@"metadata" : metadataDict,
|
|
|
311 |
@"total_cpu_time" :
|
|
|
312 |
[NSNumber numberWithDouble:[cpuExceptionDiagnostic.totalCPUTime doubleValue]],
|
|
|
313 |
@"total_sampled_time" :
|
|
|
314 |
[NSNumber numberWithDouble:[cpuExceptionDiagnostic.totalSampledTime doubleValue]],
|
|
|
315 |
@"app_version" : cpuExceptionDiagnostic.applicationVersion
|
|
|
316 |
}
|
|
|
317 |
};
|
|
|
318 |
writeFailed = ![self writeDictionaryToFile:cpuDictionary
|
|
|
319 |
file:nonfatalFile
|
|
|
320 |
newLineData:newLineData];
|
|
|
321 |
}
|
|
|
322 |
|
|
|
323 |
if (hasDiskWriteException) {
|
|
|
324 |
MXDiskWriteExceptionDiagnostic *diskWriteExceptionDiagnostic =
|
|
|
325 |
[diagnosticPayload.diskWriteExceptionDiagnostics objectAtIndex:0];
|
|
|
326 |
|
|
|
327 |
NSArray *threadArray = [self convertThreadsToArray:diskWriteExceptionDiagnostic.callStackTree];
|
|
|
328 |
NSDictionary *metadataDict =
|
|
|
329 |
[self convertMetadataToDictionary:diskWriteExceptionDiagnostic.metaData];
|
|
|
330 |
|
|
|
331 |
NSDictionary *diskWriteDictionary = @{
|
|
|
332 |
@"exception" : @{
|
|
|
333 |
@"type" : @"metrickit_nonfatal",
|
|
|
334 |
@"name" : @"disk_write_exception_event",
|
|
|
335 |
@"time" : [NSNumber numberWithLong:beginSecondsSince1970],
|
|
|
336 |
@"end_time" : [NSNumber numberWithLong:endSecondsSince1970],
|
|
|
337 |
@"threads" : threadArray,
|
|
|
338 |
@"metadata" : metadataDict,
|
|
|
339 |
@"app_version" : diskWriteExceptionDiagnostic.applicationVersion,
|
|
|
340 |
@"total_writes_caused" :
|
|
|
341 |
[NSNumber numberWithDouble:[diskWriteExceptionDiagnostic.totalWritesCaused doubleValue]]
|
|
|
342 |
}
|
|
|
343 |
};
|
|
|
344 |
writeFailed = ![self writeDictionaryToFile:diskWriteDictionary
|
|
|
345 |
file:nonfatalFile
|
|
|
346 |
newLineData:newLineData];
|
|
|
347 |
}
|
|
|
348 |
|
|
|
349 |
return !writeFailed;
|
|
|
350 |
}
|
|
|
351 |
/*
|
|
|
352 |
* Required for MXMetricManager subscribers. Since we aren't currently collecting any MetricKit
|
|
|
353 |
* metrics, this method is left empty.
|
|
|
354 |
*/
|
|
|
355 |
- (void)didReceiveMetricPayloads:(NSArray<MXMetricPayload *> *)payloads API_AVAILABLE(ios(13)) {
|
|
|
356 |
}
|
|
|
357 |
|
|
|
358 |
- (FBLPromise *)waitForMetricKitDataAvailable {
|
|
|
359 |
FBLPromise *result = nil;
|
|
|
360 |
@synchronized(self) {
|
|
|
361 |
result = self.metricKitDataAvailable;
|
|
|
362 |
}
|
|
|
363 |
return result;
|
|
|
364 |
}
|
|
|
365 |
|
|
|
366 |
/*
|
|
|
367 |
* Helper method to convert threads for a MetricKit fatal diagnostic event to an array of threads.
|
|
|
368 |
*/
|
|
|
369 |
- (NSArray *)convertThreadsToArray:(MXCallStackTree *)mxCallStackTree API_AVAILABLE(ios(14)) {
|
|
|
370 |
FIRCLSCallStackTree *tree = [[FIRCLSCallStackTree alloc] initWithMXCallStackTree:mxCallStackTree];
|
|
|
371 |
return [tree getArrayRepresentation];
|
|
|
372 |
}
|
|
|
373 |
|
|
|
374 |
/*
|
|
|
375 |
* Helper method to convert threads for a MetricKit nonfatal diagnostic event to an array of frames.
|
|
|
376 |
*/
|
|
|
377 |
- (NSArray *)convertThreadsToArrayForNonfatal:(MXCallStackTree *)mxCallStackTree
|
|
|
378 |
API_AVAILABLE(ios(14)) {
|
|
|
379 |
FIRCLSCallStackTree *tree = [[FIRCLSCallStackTree alloc] initWithMXCallStackTree:mxCallStackTree];
|
|
|
380 |
return [tree getFramesOfBlamedThread];
|
|
|
381 |
}
|
|
|
382 |
|
|
|
383 |
/*
|
|
|
384 |
* Helper method to convert metadata for a MetricKit diagnostic event to a dictionary. MXMetadata
|
|
|
385 |
* has a dictionaryRepresentation method but it is deprecated.
|
|
|
386 |
*/
|
|
|
387 |
- (NSDictionary *)convertMetadataToDictionary:(MXMetaData *)metadata API_AVAILABLE(ios(14)) {
|
|
|
388 |
NSError *error = nil;
|
|
|
389 |
NSDictionary *metadataDictionary =
|
|
|
390 |
[NSJSONSerialization JSONObjectWithData:[metadata JSONRepresentation] options:0 error:&error];
|
|
|
391 |
return metadataDictionary;
|
|
|
392 |
}
|
|
|
393 |
|
|
|
394 |
/*
|
|
|
395 |
* Helper method to fulfill the metricKitDataAvailable promise and track that it has been fulfilled.
|
|
|
396 |
*/
|
|
|
397 |
- (void)fulfillMetricKitPromise {
|
|
|
398 |
if (self.metricKitPromiseFulfilled) return;
|
|
|
399 |
|
|
|
400 |
[self.metricKitDataAvailable fulfill:nil];
|
|
|
401 |
self.metricKitPromiseFulfilled = YES;
|
|
|
402 |
}
|
|
|
403 |
|
|
|
404 |
/*
|
|
|
405 |
* Helper method to write a dictionary of event information to file. Returns whether it succeeded.
|
|
|
406 |
*/
|
|
|
407 |
- (BOOL)writeDictionaryToFile:(NSDictionary *)dictionary
|
|
|
408 |
file:(NSFileHandle *)file
|
|
|
409 |
newLineData:(NSData *)newLineData {
|
|
|
410 |
NSError *dataError = nil;
|
|
|
411 |
NSData *data = [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:&dataError];
|
|
|
412 |
if (dataError) {
|
|
|
413 |
FIRCLSDebugLog(@"Unable to write out dictionary.");
|
|
|
414 |
return NO;
|
|
|
415 |
}
|
|
|
416 |
|
|
|
417 |
[file seekToEndOfFile];
|
|
|
418 |
[file writeData:data];
|
|
|
419 |
[file writeData:newLineData];
|
|
|
420 |
|
|
|
421 |
return YES;
|
|
|
422 |
}
|
|
|
423 |
|
|
|
424 |
- (NSString *)getSignalName:(NSNumber *)signalCode {
|
|
|
425 |
int signal = [signalCode intValue];
|
|
|
426 |
switch (signal) {
|
|
|
427 |
case SIGABRT:
|
|
|
428 |
return @"SIGABRT";
|
|
|
429 |
case SIGBUS:
|
|
|
430 |
return @"SIGBUS";
|
|
|
431 |
case SIGFPE:
|
|
|
432 |
return @"SIGFPE";
|
|
|
433 |
case SIGILL:
|
|
|
434 |
return @"SIGILL";
|
|
|
435 |
case SIGSEGV:
|
|
|
436 |
return @"SIGSEGV";
|
|
|
437 |
case SIGSYS:
|
|
|
438 |
return @"SIGSYS";
|
|
|
439 |
case SIGTRAP:
|
|
|
440 |
return @"SIGTRAP";
|
|
|
441 |
default:
|
|
|
442 |
return @"UNKNOWN";
|
|
|
443 |
}
|
|
|
444 |
return @"UNKNOWN";
|
|
|
445 |
}
|
|
|
446 |
|
|
|
447 |
@end
|
|
|
448 |
|
|
|
449 |
#endif // CLS_METRICKIT_SUPPORTED
|