Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PLAT-5450] Add missing app and device data to OOM events #908

Merged
merged 5 commits into from
Nov 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Bugsnag/BugsnagSystemState.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,16 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithConfiguration:(BugsnagConfiguration *)config;

- (void)recordAppUUID;

- (void)setBatteryCharging:(BOOL)charging;

- (void)setBatteryLevel:(NSNumber *)batteryLevel;

- (void)setCodeBundleID:(NSString*)codeBundleID;

- (void)setOrientation:(NSString *)orientation;

/**
* Purge all stored system state.
*/
Expand Down
61 changes: 50 additions & 11 deletions Bugsnag/BugsnagSystemState.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
#define STATE_DIR @"bugsnag/state"
#define STATE_FILE @"system_state.json"

#if BSG_PLATFORM_IOS
NSString *BSGOrientationNameFromEnum(UIDeviceOrientation deviceOrientation);
#endif

static NSDictionary* loadPreviousState(BugsnagKVStore *kvstore, NSString *jsonPath) {
NSData *data = [NSData dataWithContentsOfFile:jsonPath];
if(data == nil) {
Expand Down Expand Up @@ -117,11 +121,22 @@ id blankIfNil(id value) {
device[@"modelNumber"] = systemInfo[@ BSG_KSSystemField_Model];
device[@"wordSize"] = @(PLATFORM_WORD_SIZE);
device[@"locale"] = [[NSLocale currentLocale] localeIdentifier];
device[@"runtimeVersions"] = @{
@"clangVersion": systemInfo[@BSG_KSSystemField_ClangVersion] ?: @"",
@"osBuild": systemInfo[@BSG_KSSystemField_OSVersion] ?: @""
};
#if BSG_PLATFORM_SIMULATOR
device[@"simulator"] = @YES;
#else
device[@"simulator"] = @NO;
#endif
device[@"totalMemory"] = systemInfo[@BSG_KSSystemField_Memory][@"usable"];
#if BSG_PLATFORM_IOS
device[BSGKeyBatteryLevel] = @([UIDevice currentDevice].batteryLevel);
UIDeviceBatteryState batteryState = [UIDevice currentDevice].batteryState;
device[BSGKeyCharging] = @(batteryState == UIDeviceBatteryStateCharging || batteryState == UIDeviceBatteryStateFull);
device[BSGKeyOrientation] = BSGOrientationNameFromEnum([UIDevice currentDevice].orientation);
#endif

NSMutableDictionary *state = [NSMutableDictionary new];
state[BSGKeyApp] = app;
Expand Down Expand Up @@ -167,11 +182,11 @@ - (instancetype)initWithConfiguration:(BugsnagConfiguration *)config {
// MacOS "active" serves the same purpose as "foreground" in iOS
[center addObserverForName:NSApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
[weakSelf.kvStore setBoolean:YES forKey:SYSTEMSTATE_APP_IS_IN_FOREGROUND];
[weakSelf bgSetAppValue:@YES forKey:SYSTEMSTATE_APP_IS_IN_FOREGROUND];
[weakSelf setValue:@YES forAppKey:SYSTEMSTATE_APP_IS_IN_FOREGROUND];
}];
[center addObserverForName:NSApplicationDidResignActiveNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
[weakSelf.kvStore setBoolean:NO forKey:SYSTEMSTATE_APP_IS_IN_FOREGROUND];
[weakSelf bgSetAppValue:@NO forKey:SYSTEMSTATE_APP_IS_IN_FOREGROUND];
[weakSelf setValue:@NO forAppKey:SYSTEMSTATE_APP_IS_IN_FOREGROUND];
}];
#else
[center addObserverForName:UIApplicationWillTerminateNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
Expand All @@ -180,47 +195,71 @@ - (instancetype)initWithConfiguration:(BugsnagConfiguration *)config {
}];
[center addObserverForName:UIApplicationWillEnterForegroundNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
[weakSelf.kvStore setBoolean:YES forKey:SYSTEMSTATE_APP_IS_IN_FOREGROUND];
[weakSelf bgSetAppValue:@YES forKey:SYSTEMSTATE_APP_IS_IN_FOREGROUND];
[weakSelf setValue:@YES forAppKey:SYSTEMSTATE_APP_IS_IN_FOREGROUND];
}];
[center addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
[weakSelf.kvStore setBoolean:NO forKey:SYSTEMSTATE_APP_IS_IN_FOREGROUND];
[weakSelf bgSetAppValue:@NO forKey:SYSTEMSTATE_APP_IS_IN_FOREGROUND];
[weakSelf setValue:@NO forAppKey:SYSTEMSTATE_APP_IS_IN_FOREGROUND];
}];
[center addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
[weakSelf.kvStore setBoolean:YES forKey:SYSTEMSTATE_APP_IS_ACTIVE];
[weakSelf bgSetAppValue:@YES forKey:SYSTEMSTATE_APP_IS_ACTIVE];
[weakSelf setValue:@YES forAppKey:SYSTEMSTATE_APP_IS_ACTIVE];
}];
[center addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
[weakSelf.kvStore setBoolean:NO forKey:SYSTEMSTATE_APP_IS_ACTIVE];
[weakSelf bgSetAppValue:@NO forKey:SYSTEMSTATE_APP_IS_ACTIVE];
[weakSelf setValue:@NO forAppKey:SYSTEMSTATE_APP_IS_ACTIVE];
}];
[center addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
NSString *date = [BSG_RFC3339DateTool stringFromDate:[NSDate date]];
[weakSelf.kvStore setString:date forKey:SYSTEMSTATE_APP_LAST_LOW_MEMORY_WARNING];
[weakSelf bgSetAppValue:date forKey:SYSTEMSTATE_APP_LAST_LOW_MEMORY_WARNING];
[weakSelf setValue:date forAppKey:SYSTEMSTATE_APP_LAST_LOW_MEMORY_WARNING];
}];
#endif
}
return self;
}

- (void)recordAppUUID {
// [BSG_KSSystemInfo appUUID] returns nil until we have called _dyld_register_func_for_add_image()
[self setValue:[BSG_KSSystemInfo appUUID] forAppKey:BSGKeyMachoUUID];
}

- (void)setBatteryCharging:(BOOL)charging {
[self setValue:@(charging) forDeviceKey:BSGKeyCharging];
}

- (void)setBatteryLevel:(NSNumber *)batteryLevel {
[self setValue:batteryLevel forDeviceKey:BSGKeyBatteryLevel];
}

- (void)setCodeBundleID:(NSString*)codeBundleID {
[self bgSetAppValue:codeBundleID forKey:BSGKeyCodeBundleId];
[self setValue:codeBundleID forAppKey:BSGKeyCodeBundleId];
}

- (void)setOrientation:(NSString *)orientation {
[self setValue:orientation forDeviceKey:BSGKeyOrientation];
}

- (void)setValue:(id)value forAppKey:(NSString *)key {
[self setValue:value forKey:key inSection:SYSTEMSTATE_KEY_APP];
}

- (void)setValue:(id)value forDeviceKey:(NSString *)key {
[self setValue:value forKey:key inSection:SYSTEMSTATE_KEY_DEVICE];
}

- (void)bgSetAppValue:(id)value forKey:(NSString*)key {
- (void)setValue:(id)value forKey:(NSString *)key inSection:(NSString *)section {
// Run on a BG thread so we don't monopolize the notification queue.
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
@synchronized (self) {
self.currentLaunchStateRW[SYSTEMSTATE_KEY_APP][key] = value;
self.currentLaunchStateRW[section][key] = value;
// User-facing state should never mutate from under them.
self.currentLaunchState = copyLaunchState(self.currentLaunchStateRW);
}
[self sync];
});
}


- (void)sync {
NSDictionary *state = self.currentLaunchState;
NSError *error = nil;
Expand Down
8 changes: 8 additions & 0 deletions Bugsnag/Client/BugsnagClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@ - (void)start {
[self.crashSentry install:self.configuration
apiClient:self.errorReportApiClient
onCrash:&BSSerializeDataCrashHandler];
[self.systemState recordAppUUID]; // Needs to be called after crashSentry installed but before -computeDidCrashLastLaunch
[self computeDidCrashLastLaunch];
[self.breadcrumbs removeAllBreadcrumbs];
[self setupConnectivityListener];
Expand Down Expand Up @@ -559,6 +560,7 @@ - (void)start {
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];

[self batteryChanged:nil];
[self orientationChanged:nil];
[self addTerminationObserver:UIApplicationWillTerminateNotification];

#elif BSG_PLATFORM_OSX
Expand Down Expand Up @@ -1173,6 +1175,10 @@ - (void)batteryChanged:(NSNotification *)notification {
[self.state addMetadata:charging ? @YES : @NO
withKey:BSGKeyCharging
toSection:BSGKeyDeviceState];

[self.systemState setBatteryLevel:batteryLevel];

[self.systemState setBatteryCharging:charging];
}

/**
Expand All @@ -1194,6 +1200,8 @@ - (void)orientationChanged:(NSNotification *)notification {
[self.state addMetadata:orientation
withKey:BSGKeyOrientation
toSection:BSGKeyDeviceState];

[self.systemState setOrientation:orientation];

// Short-circuit the exit if we don't have enough info to record a full breadcrumb
// or the orientation hasn't changed (false positive).
Expand Down
7 changes: 7 additions & 0 deletions Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@
*/
+ (NSDictionary *)systemInfo;

/** Get this application's UUID.
*
* @return The UUID.
*/
+ (NSString *)appUUID;

/**
* The build version of the OS
*/
Expand All @@ -99,6 +105,7 @@
* YES if the app is currently shown in the foreground
*/
+ (BOOL)isInForeground:(UIApplicationState)state;

#endif

@end
1 change: 1 addition & 0 deletions Bugsnag/Payload/BugsnagAppWithState.m
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ + (BugsnagAppWithState *)appWithOomData:(NSDictionary *)event
{
BugsnagAppWithState *app = [BugsnagAppWithState new];
app.id = event[@"id"];
app.dsymUuid = event[BSGKeyMachoUUID];
app.releaseStage = event[@"releaseStage"];
app.version = event[@"version"];
app.bundleVersion = event[@"bundleVersion"];
Expand Down
6 changes: 5 additions & 1 deletion Bugsnag/Payload/BugsnagDeviceWithState.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
#import "BugsnagSystemState.h"
#import "Bugsnag.h"

NSDictionary *BSGParseDeviceMetadata(NSDictionary *event) {
NSMutableDictionary *BSGParseDeviceMetadata(NSDictionary *event) {
NSMutableDictionary *device = [NSMutableDictionary new];
NSDictionary *state = [event valueForKeyPath:@"user.state.deviceState"];
[device addEntriesFromDictionary:state];
Expand Down Expand Up @@ -95,9 +95,13 @@ + (BugsnagDeviceWithState *)deviceWithOomData:(NSDictionary *)data {
device.id = data[@"id"];
device.osVersion = data[@"osVersion"];
device.osName = data[@"osName"];
device.manufacturer = @"Apple";
device.model = data[@"model"];
device.modelNumber = data[@"modelNumber"];
device.orientation = data[@"orientation"];
device.locale = data[@"locale"];
device.runtimeVersions = data[@"runtimeVersions"];
device.totalMemory = data[@"totalMemory"];
return device;
}

Expand Down
9 changes: 7 additions & 2 deletions Bugsnag/Payload/BugsnagEvent.m
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@

// MARK: - Accessing hidden methods/properties

NSDictionary *_Nonnull BSGParseDeviceMetadata(NSDictionary *_Nonnull event);
NSMutableDictionary *_Nonnull BSGParseDeviceMetadata(NSDictionary *_Nonnull event);
NSDictionary *_Nonnull BSGParseAppMetadata(NSDictionary *_Nonnull event);

@interface BugsnagAppWithState ()
Expand Down Expand Up @@ -296,7 +296,12 @@ - (instancetype)initWithOOMData:(NSDictionary *)event {
}
BugsnagMetadata *metadata = [BugsnagMetadata new];
// Cocoa-specific, non-spec., device and app data
[metadata addMetadata:BSGParseDeviceMetadata(event) toSection:BSGKeyDevice];
NSMutableDictionary *deviceMetadata = BSGParseDeviceMetadata(event);
// This can be removed once metadata is being persisted to disk
deviceMetadata[BSGKeyBatteryLevel] = [event valueForKeyPath:@"user.state.oom.device.batteryLevel"];
deviceMetadata[BSGKeyCharging] = [event valueForKeyPath:@"user.state.oom.device.charging"];
deviceMetadata[BSGKeyOrientation] = [event valueForKeyPath:@"user.state.oom.device.orientation"];
[metadata addMetadata:deviceMetadata toSection:BSGKeyDevice];
[metadata addMetadata:BSGParseAppMetadata(event) toSection:BSGKeyApp];

BugsnagEvent *obj = [self initWithApp:[BugsnagAppWithState appWithOomData:[event valueForKeyPath:@"user.state.oom.app"]]
Expand Down
8 changes: 8 additions & 0 deletions features/out_of_memory.feature
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ Feature: Out of memory errors

# Ensure the basic data from OOMs are present
And the event "device.jailbroken" is false
And the event "metaData.device.batteryLevel" is not null
And the event "metaData.device.charging" is not null
And the event "metaData.device.orientation" is not null
And the event "metaData.device.timezone" is not null
And the event "metaData.device.simulator" is false
And the event "metaData.device.wordSize" is not null
Expand All @@ -32,7 +35,12 @@ Feature: Out of memory errors
And the event "app.inForeground" is true
And the event "app.type" equals "iOS"
And the event "app.bundleVersion" is not null
And the event "app.dsymUUIDs" is not null
And the event "app.version" is not null
And the event "device.manufacturer" equals "Apple"
And the event "device.orientation" is not null
And the event "device.runtimeVersions" is not null
And the event "device.totalMemory" is not null

# Ensure breadcrumbs are carried over
And the event has a "manual" breadcrumb named "OOMLoadScenarioBreadcrumb"
Expand Down