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-8706] Capture app memory stats #1435

Merged
merged 1 commit into from
Jul 15, 2022
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
10 changes: 10 additions & 0 deletions Bugsnag.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,10 @@
0126F7BF25DD512B008483C2 /* BSGEventUploadKSCrashReportOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 0126F7BA25DD512B008483C2 /* BSGEventUploadKSCrashReportOperation.m */; };
0126F7C025DD512B008483C2 /* BSGEventUploadKSCrashReportOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 0126F7BA25DD512B008483C2 /* BSGEventUploadKSCrashReportOperation.m */; };
0126F7C125DD512B008483C2 /* BSGEventUploadKSCrashReportOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 0126F7BA25DD512B008483C2 /* BSGEventUploadKSCrashReportOperation.m */; };
0130DEF92880203A00E5953F /* BSGRunContextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0130DEF82880203A00E5953F /* BSGRunContextTests.m */; };
0130DEFA2880203A00E5953F /* BSGRunContextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0130DEF82880203A00E5953F /* BSGRunContextTests.m */; };
0130DEFB2880203A00E5953F /* BSGRunContextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0130DEF82880203A00E5953F /* BSGRunContextTests.m */; };
0130DEFC2880203A00E5953F /* BSGRunContextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0130DEF82880203A00E5953F /* BSGRunContextTests.m */; };
013D9CD126C5262F0077F0AD /* UISceneStub.h in Headers */ = {isa = PBXBuildFile; fileRef = 013D9CCF26C5262F0077F0AD /* UISceneStub.h */; };
013D9CD226C5262F0077F0AD /* UISceneStub.h in Headers */ = {isa = PBXBuildFile; fileRef = 013D9CCF26C5262F0077F0AD /* UISceneStub.h */; };
013D9CD326C5262F0077F0AD /* UISceneStub.h in Headers */ = {isa = PBXBuildFile; fileRef = 013D9CCF26C5262F0077F0AD /* UISceneStub.h */; };
Expand Down Expand Up @@ -1577,6 +1581,7 @@
0126F7AA25DD5118008483C2 /* BSGEventUploadFileOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSGEventUploadFileOperation.m; sourceTree = "<group>"; };
0126F7B925DD512B008483C2 /* BSGEventUploadKSCrashReportOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BSGEventUploadKSCrashReportOperation.h; sourceTree = "<group>"; };
0126F7BA25DD512B008483C2 /* BSGEventUploadKSCrashReportOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSGEventUploadKSCrashReportOperation.m; sourceTree = "<group>"; };
0130DEF82880203A00E5953F /* BSGRunContextTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSGRunContextTests.m; sourceTree = "<group>"; };
0134524A256BCF7C0088C548 /* BugsnagError+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagError+Private.h"; sourceTree = "<group>"; };
0134524B256BD00A0088C548 /* BugsnagThread+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagThread+Private.h"; sourceTree = "<group>"; };
013D9CCF26C5262F0077F0AD /* UISceneStub.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UISceneStub.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2045,6 +2050,7 @@
CBCF77AA250142E0004AF22A /* BSGJSONSerializationTests.m */,
0163BF5825823D8D008DC28B /* BSGNotificationBreadcrumbsTests.m */,
008966C82486D43600DC48C2 /* BSGOutOfMemoryTests.m */,
0130DEF82880203A00E5953F /* BSGRunContextTests.m */,
CB6419AA25A73E8C00613D25 /* BSGStorageMigratorTests.m */,
017DCF9A287422BB000ECB22 /* BSGTelemetryTests.m */,
01DE903B26CEAF9E00455213 /* BSGUtilsTests.m */,
Expand Down Expand Up @@ -3204,6 +3210,7 @@
008966EB2486D43700DC48C2 /* BugsnagDeviceTest.m in Sources */,
CBCF77AB250142E0004AF22A /* BSGJSONSerializationTests.m in Sources */,
008967A22486D43700DC48C2 /* KSCrashSentry_Signal_Tests.m in Sources */,
0130DEF92880203A00E5953F /* BSGRunContextTests.m in Sources */,
008967272486D43700DC48C2 /* BugsnagStackframeTest.m in Sources */,
008967302486D43700DC48C2 /* BSGClientObserverTests.m in Sources */,
E701FAAB2490EFD9008D842F /* EventApiValidationTest.m in Sources */,
Expand Down Expand Up @@ -3419,6 +3426,7 @@
008966EF2486D43700DC48C2 /* BugsnagClientPayloadInfoTest.m in Sources */,
CBA2249C251E429C00B87416 /* TestSupport.m in Sources */,
E701FAB02490EFE8008D842F /* ConfigurationApiValidationTest.m in Sources */,
0130DEFA2880203A00E5953F /* BSGRunContextTests.m in Sources */,
008967282486D43700DC48C2 /* BugsnagStackframeTest.m in Sources */,
008967942486D43700DC48C2 /* KSSignalInfo_Tests.m in Sources */,
004E35402487B3BE007FBAE4 /* BugsnagSwiftConfigurationTests.swift in Sources */,
Expand Down Expand Up @@ -3618,6 +3626,7 @@
01447605256684500018AB94 /* BugsnagApiClientTest.m in Sources */,
0187D464255BD7B800C503D9 /* BugsnagApiClientTest.m in Sources */,
0089676E2486D43700DC48C2 /* BugsnagTestsDummyClass.m in Sources */,
0130DEFB2880203A00E5953F /* BSGRunContextTests.m in Sources */,
008967412486D43700DC48C2 /* BugsnagAppTest.m in Sources */,
017DCF9D287422BB000ECB22 /* BSGTelemetryTests.m in Sources */,
008967052486D43700DC48C2 /* BugsnagThreadSerializationTest.m in Sources */,
Expand Down Expand Up @@ -3887,6 +3896,7 @@
CB28F0CF282A4A2E003AB200 /* BugsnagClientTests.m in Sources */,
CB28F0E0282A4BEE003AB200 /* BugsnagOnBreadcrumbTest.m in Sources */,
CB28F0D1282A4B91003AB200 /* BugsnagEnabledBreadcrumbTest.m in Sources */,
0130DEFC2880203A00E5953F /* BSGRunContextTests.m in Sources */,
CB28F125282A7DAB003AB200 /* BugsnagUserTest.m in Sources */,
CB28F09F28294D44003AB200 /* BSG_KSMachTests.m in Sources */,
);
Expand Down
54 changes: 27 additions & 27 deletions Bugsnag/Client/BugsnagClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
*
* @param writer report writer which will receive updated metadata
*/
void BSSerializeDataCrashHandler(const BSG_KSCrashReportWriter *writer) {
static void BSSerializeDataCrashHandler(const BSG_KSCrashReportWriter *writer) {
BOOL isCrash = YES;
BSGSessionWriteCrashReport(writer);

Expand All @@ -101,6 +101,17 @@ void BSSerializeDataCrashHandler(const BSG_KSCrashReportWriter *writer) {
writer->addJSONElement(writer, "metaData", bsg_g_bugsnag_data.metadataJSON);
writer->addJSONElement(writer, "state", bsg_g_bugsnag_data.stateJSON);

writer->beginObject(writer, "app"); {
if (bsg_runContext->memoryLimit) {
writer->addUIntegerElement(writer, "freeMemory", bsg_runContext->memoryAvailable);
writer->addUIntegerElement(writer, "memoryLimit", bsg_runContext->memoryLimit);
}
if (bsg_runContext->memoryFootprint) {
writer->addUIntegerElement(writer, "memoryUsage", bsg_runContext->memoryFootprint);
}
}
writer->endContainer(writer);

#if BSG_HAVE_BATTERY
if (BSGIsBatteryStateKnown(bsg_runContext->batteryState)) {
writer->addFloatingPointElement(writer, "batteryLevel", bsg_runContext->batteryLevel);
Expand Down Expand Up @@ -699,18 +710,13 @@ - (void)notifyInternal:(BugsnagEvent *_Nonnull)event
return;
}

// Device information that isn't part of `event.device`
NSMutableDictionary *deviceMetadata = [NSMutableDictionary dictionary];
#if BSG_HAVE_BATTERY
if (BSGIsBatteryStateKnown(BSGGetDevice().batteryState)) {
deviceMetadata[BSGKeyBatteryLevel] = @(BSGGetDevice().batteryLevel);
deviceMetadata[BSGKeyCharging] = BSGIsBatteryCharging(BSGGetDevice().batteryState) ? @YES : @NO;
}
#if TARGET_OS_WATCH
// Update BSGRunContext because we cannot observe battery level or state on watchOS :-(
bsg_runContext->batteryLevel = BSGGetDevice().batteryLevel;
bsg_runContext->batteryState = BSGGetDevice().batteryState;
#endif
if (@available(iOS 11.0, tvOS 11.0, watchOS 4.0, *)) {
deviceMetadata[BSGKeyThermalState] = BSGStringFromThermalState(bsg_runContext->thermalState);
}
[event.metadata addMetadata:deviceMetadata toSection:BSGKeyDevice];
[event.metadata addMetadata:BSGAppMetadataFromRunContext(bsg_runContext) toSection:BSGKeyApp];
[event.metadata addMetadata:BSGDeviceMetadataFromRunContext(bsg_runContext) toSection:BSGKeyDevice];

// App hang events will already contain feature flags
if (!event.featureFlagStore.count) {
Expand Down Expand Up @@ -1155,27 +1161,21 @@ - (nullable BugsnagEvent *)generateEventForLastLaunchWithError:(BugsnagError *)e
if (bsg_lastRunContext->timestamp > 0) {
device.time = [NSDate dateWithTimeIntervalSinceReferenceDate:bsg_lastRunContext->timestamp];
}
device.freeMemory = @(bsg_lastRunContext->hostMemoryFree);

NSDictionary *metadataDict = BSGJSONDictionaryFromFile(BSGFileLocations.current.metadata, 0, nil);
BugsnagMetadata *metadata = [[BugsnagMetadata alloc] initWithDictionary:metadataDict ?: @{}];

// Device information that isn't part of `event.device`
NSMutableDictionary *deviceMetadata = [NSMutableDictionary dictionary];
#if BSG_HAVE_BATTERY
if (BSGIsBatteryStateKnown(bsg_lastRunContext->batteryState)) {
deviceMetadata[BSGKeyBatteryLevel] = @(bsg_lastRunContext->batteryLevel);
// Our intepretation of "charging" really means "plugged in"
deviceMetadata[BSGKeyCharging] = BSGIsBatteryCharging(bsg_lastRunContext->batteryState) ? @YES : @NO;
}
#endif
[metadata addMetadata:BSGAppMetadataFromRunContext((const struct BSGRunContext *_Nonnull)bsg_lastRunContext) toSection:BSGKeyApp];
[metadata addMetadata:BSGDeviceMetadataFromRunContext((const struct BSGRunContext *_Nonnull)bsg_lastRunContext) toSection:BSGKeyDevice];

#if BSG_HAVE_OOM_DETECTION
// Don't set to @NO because server may interpret any non-nil value as meaning true
deviceMetadata[BSGKeyLowMemoryWarning] = BSGRunContextWasMemoryWarning() ? @YES : nil;
#endif
if (@available(iOS 11.0, tvOS 11.0, watchOS 4.0, *)) {
deviceMetadata[BSGKeyThermalState] = BSGStringFromThermalState(bsg_lastRunContext->thermalState);
if (BSGRunContextWasMemoryWarning()) {
[metadata addMetadata:@YES
withKey:BSGKeyLowMemoryWarning
toSection:BSGKeyDevice];
}
[metadata addMetadata:deviceMetadata toSection:BSGKeyDevice];
#endif

NSDictionary *userDict = stateDict[BSGKeyUser];
BugsnagUser *user = [[BugsnagUser alloc] initWithDictionary:userDict];
Expand Down
3 changes: 3 additions & 0 deletions Bugsnag/Helpers/BSGKeys.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ static BSGKey const BSGKeyExtraRuntimeInfo = @"extraRuntimeInfo";
static BSGKey const BSGKeyFeatureFlag = @"featureFlag";
static BSGKey const BSGKeyFeatureFlags = @"featureFlags";
static BSGKey const BSGKeyFrameAddress = @"frameAddress";
static BSGKey const BSGKeyFreeMemory = @"freeMemory";
static BSGKey const BSGKeyGroupingHash = @"groupingHash";
static BSGKey const BSGKeyHandled = @"handled";
static BSGKey const BSGKeyHandledCount = @"handledCount";
Expand All @@ -72,6 +73,8 @@ static BSGKey const BSGKeyMaxBreadcrumbs = @"maxBreadcrumbs";
static BSGKey const BSGKeyMaxPersistedEvents = @"maxPersistedEvents";
static BSGKey const BSGKeyMaxPersistedSessions = @"maxPersistedSessions";
static BSGKey const BSGKeyMessage = @"message";
static BSGKey const BSGKeyMemoryLimit = @"memoryLimit";
static BSGKey const BSGKeyMemoryUsage = @"memoryUsage";
static BSGKey const BSGKeyMetadata = @"metaData";
static BSGKey const BSGKeyMethod = @"method";
static BSGKey const BSGKeyName = @"name";
Expand Down
11 changes: 9 additions & 2 deletions Bugsnag/Helpers/BSGRunContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
// During development this is not strictly necessary since last run's data will
// not be loaded if the struct's size has changed.
//
#define BSGRUNCONTEXT_VERSION 3
#define BSGRUNCONTEXT_VERSION 4

struct BSGRunContext {
long structVersion;
Expand All @@ -46,7 +46,10 @@ struct BSGRunContext {
dispatch_source_memorypressure_flags_t memoryPressure;
#endif
double timestamp __attribute__((aligned(8)));
size_t availableMemory;
unsigned long long hostMemoryFree;
unsigned long long memoryAvailable;
unsigned long long memoryFootprint;
unsigned long long memoryLimit;
};

/// Information about the current run of the app / process.
Expand All @@ -61,10 +64,14 @@ extern const struct BSGRunContext *_Nullable bsg_lastRunContext;

#pragma mark -

#ifdef FOUNDATION_EXTERN
void BSGRunContextInit(NSString *_Nonnull path);
#endif

#pragma mark -

BSG_PRIVATE void BSGRunContextUpdateMemory(void);

BSG_PRIVATE void BSGRunContextUpdateTimestamp(void);

#pragma mark -
Expand Down
67 changes: 52 additions & 15 deletions Bugsnag/Helpers/BSGRunContext.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@
#import <sys/stat.h>
#import <sys/sysctl.h>

#if __has_include(<os/proc.h>) && TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST
#include <os/proc.h>
#endif


// Fields which may be updated from arbitrary threads simultaneously should be
// updated using this macro to avoid data races (which are detected by TSan.)
Expand All @@ -45,7 +41,6 @@
static UIApplication * GetUIApplication(void);
#endif
static void InstallTimer(void);
static void UpdateAvailableMemory(void);


#pragma mark - Initial setup
Expand Down Expand Up @@ -82,7 +77,10 @@ static void InitRunContext() {
BSGRunContextUpdateTimestamp();
InstallTimer();

UpdateAvailableMemory();
BSGRunContextUpdateMemory();
if (!bsg_runContext->memoryLimit) {
bsg_log_debug(@"Cannot query `memoryLimit` on this device");
}

// Set `structVersion` last so that BSGRunContextLoadLast() will reject data
// that is not fully initialised.
Expand Down Expand Up @@ -192,13 +190,13 @@ static void InstallTimer() {

timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 0),
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW,
/* interval */ NSEC_PER_SEC / 2,
/* leeway */ NSEC_PER_SEC / 4);

dispatch_source_set_event_handler(timer, ^{
BSGRunContextUpdateTimestamp();
UpdateAvailableMemory();
BSGRunContextUpdateMemory();
});

dispatch_resume(timer);
Expand Down Expand Up @@ -284,7 +282,7 @@ static void ObserveMemoryPressure() {
dispatch_source_set_event_handler(source, ^{
bsg_runContext->memoryPressure = dispatch_source_get_data(source);
BSGRunContextUpdateTimestamp();
UpdateAvailableMemory();
BSGRunContextUpdateMemory();
});
dispatch_resume(source);
}
Expand Down Expand Up @@ -343,16 +341,55 @@ void BSGRunContextUpdateTimestamp() {
ATOMIC_SET(bsg_runContext->timestamp, CFAbsoluteTimeGetCurrent());
}

static void UpdateAvailableMemory() {
// Deliberately avoids use of bsg_ksmachfreeMemory() because that falls back
// to a much more expensive (~5x) system call on earlier releases.
#if __has_include(<os/proc.h>) && TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST
if (__builtin_available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
ATOMIC_SET(bsg_runContext->availableMemory, os_proc_available_memory());
static void UpdateHostMemory() {
static mach_port_t host;
if (!host) {
host = mach_host_self();
}

vm_statistics_data_t host_vm;
mach_msg_type_number_t count = HOST_VM_INFO_COUNT;
kern_return_t kr = host_statistics(host, HOST_VM_INFO,
(host_info_t)&host_vm, &count);
if (kr != KERN_SUCCESS) {
bsg_log_debug(@"host_statistics: %d", kr);
return;
}

size_t hostMemoryFree = host_vm.free_count * vm_kernel_page_size;
ATOMIC_SET(bsg_runContext->hostMemoryFree, hostMemoryFree);
}

static void UpdateTaskMemory() {
task_vm_info_data_t task_vm = {0};
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
kern_return_t kr = task_info(current_task(), TASK_VM_INFO,
(task_info_t)&task_vm, &count);
if (kr != KERN_SUCCESS) {
bsg_log_debug(@"task_info: %d", kr);
return;
}

unsigned long long footprint = task_vm.phys_footprint;
ATOMIC_SET(bsg_runContext->memoryFootprint, footprint);

// Since limit_bytes_remaining was added in iOS 13 (xnu-6153)
// this code must be compiled out when building with older SDKs.
#ifdef TASK_VM_INFO_REV4_COUNT
if (task_vm.limit_bytes_remaining) {
unsigned long long available = task_vm.limit_bytes_remaining;
unsigned long long limit = footprint + available;
ATOMIC_SET(bsg_runContext->memoryAvailable, available);
ATOMIC_SET(bsg_runContext->memoryLimit, limit);
}
#endif
}

void BSGRunContextUpdateMemory() {
UpdateTaskMemory();
UpdateHostMemory();
}


#pragma mark - Kill detection

Expand Down
4 changes: 3 additions & 1 deletion Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashReport.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
#include "BSG_KSCrashSentry.h"
#include "BSG_Symbolicate.h"
#include "BSGDefines.h"
#include "BSGRunContext.h"

#include <mach-o/loader.h>
#include <sys/time.h>
Expand Down Expand Up @@ -1065,7 +1066,7 @@ void bsg_kscrw_i_writeMemoryInfo(const BSG_KSCrashReportWriter *const writer,
writer->beginObject(writer, key);
{
writer->addUIntegerElement(writer, BSG_KSCrashField_Free,
bsg_ksmachfreeMemory());
bsg_runContext->hostMemoryFree);
}
writer->endContainer(writer);
}
Expand Down Expand Up @@ -1502,6 +1503,7 @@ void bsg_kscrashreport_writeKSCrashFields(BSG_KSCrash_Context *crashContext,

writer->beginObject(writer, BSG_KSCrashField_SystemAtCrash);
{
BSGRunContextUpdateMemory();
bsg_kscrw_i_writeMemoryInfo(writer, BSG_KSCrashField_Memory);
bsg_kscrw_i_writeAppStats(writer, BSG_KSCrashField_AppStats,
&crashContext->state);
Expand Down
5 changes: 3 additions & 2 deletions Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#import "BSG_KSSystemInfo.h"

#import "BSGKeys.h"
#import "BSGRunContext.h"
#import "BSG_Jailbreak.h"
#import "BSG_KSCrashC.h"
#import "BSG_KSCrashReportFields.h"
Expand Down Expand Up @@ -435,8 +436,8 @@ + (NSDictionary *)systemInfo {
sysInfo[@BSG_KSSystemField_Jailbroken] = @(is_jailbroken());
sysInfo[@BSG_KSSystemField_TimeZone] = [[NSTimeZone localTimeZone] abbreviation];
sysInfo[@BSG_KSSystemField_Memory] = @{
@BSG_KSCrashField_Free: @(bsg_ksmachfreeMemory()),
@BSG_KSSystemField_Size: [self int64Sysctl:@"hw.memsize"]
@BSG_KSCrashField_Free: @(bsg_runContext->hostMemoryFree),
@BSG_KSCrashField_Size: @(NSProcessInfo.processInfo.physicalMemory)
};

NSString *dir = NSSearchPathForDirectoriesInDomains(
Expand Down
Loading