Skip to content

Commit

Permalink
Feature flags performance improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Robert committed Dec 17, 2024
1 parent 615a965 commit 2f44b7b
Show file tree
Hide file tree
Showing 27 changed files with 897 additions and 184 deletions.
152 changes: 129 additions & 23 deletions Bugsnag.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

16 changes: 13 additions & 3 deletions Bugsnag/BugsnagInternals.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,18 @@

#import "BugsnagHandledState.h"
#import "BugsnagNotifier.h"
#import "FeatureFlags/BSGFeatureFlagStore.h"

@interface BSGFeatureFlagStore : NSObject <NSCopying>
@interface BSGMemoryFeatureFlagStore : NSObject <BSGFeatureFlagStore>
@end

@interface BSGPersistentFeatureFlagStore : NSObject <BSGFeatureFlagStore>
@end

@interface BSGAtomicFeatureFlagStore : NSObject <BSGFeatureFlagStore>
@end

@interface BSGCompositeFeatureFlagStore : NSObject <BSGFeatureFlagStore>
@end

NS_ASSUME_NONNULL_BEGIN
Expand Down Expand Up @@ -79,7 +89,7 @@ typedef void (^ BSGClientObserver)(BSGClientObserverEvent event, _Nullable id va

@property (retain, nonatomic) BugsnagConfiguration *configuration;

@property (readonly, nonatomic) BSGFeatureFlagStore *featureFlagStore;
@property (readonly, nonatomic) BSGCompositeFeatureFlagStore *featureFlagStore;

@property (strong, nonatomic) BugsnagMetadata *metadata;

Expand Down Expand Up @@ -161,7 +171,7 @@ typedef void (^ BSGClientObserver)(BSGClientObserverEvent event, _Nullable id va

- (NSDictionary *)toJsonWithRedactedKeys:(nullable NSSet *)redactedKeys;

@property (readwrite, strong, nonnull, nonatomic) BSGFeatureFlagStore *featureFlagStore;
@property (readwrite, strong, nonnull, nonatomic) id<BSGFeatureFlagStore> featureFlagStore;

@end

Expand Down
1 change: 1 addition & 0 deletions Bugsnag/Client/BugsnagClient+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
@class BugsnagNotifier;
@class BugsnagSessionTracker;
@class BugsnagSystemState;
@class BSGPersistentFeatureFlagStore;

NS_ASSUME_NONNULL_BEGIN

Expand Down
38 changes: 19 additions & 19 deletions Bugsnag/Client/BugsnagClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@
#import "BugsnagUser+Private.h"
#import "BSGPersistentDeviceID.h"
#import "BugsnagCocoaPerformanceFromBugsnagCocoa.h"
#import "BSGPersistentFeatureFlagStore.h"
#import "BSGAtomicFeatureFlagStore.h"
#import "BSGCompositeFeatureFlagStore.h"

static struct {
// Contains the user-specified metadata, including the user tab from config.
Expand Down Expand Up @@ -141,6 +144,7 @@ static void BSSerializeDataCrashHandler(const BSG_KSCrashReportWriter *writer, b
writer->addIntegerElement(writer, "thermalState", bsg_runContext->thermalState);

BugsnagBreadcrumbsWriteCrashReport(writer, requiresAsyncSafety);
BugsnagFeatureFlagsWriteCrashReport(writer, requiresAsyncSafety);

// Create a file to indicate that the crash has been handled by
// the library. This exists in case the subsequent `onCrash` handler
Expand Down Expand Up @@ -211,13 +215,10 @@ - (instancetype)initWithConfiguration:(BugsnagConfiguration *)configuration {
withEmail:_configuration.user.email
andName:_configuration.user.name];
}

_featureFlagStore = [configuration.featureFlagStore copy];

_state = [[BugsnagMetadata alloc] initWithDictionary:@{
BSGKeyClient: @{
BSGKeyContext: _configuration.context ?: [NSNull null],
BSGKeyFeatureFlags: BSGFeatureFlagStoreToJSON(_featureFlagStore),
},
BSGKeyUser: [_configuration.user toJson] ?: @{}
}];
Expand All @@ -236,6 +237,11 @@ - (instancetype)initWithConfiguration:(BugsnagConfiguration *)configuration {
bsg_g_bugsnag_data.onCrash = (void (*)(const BSG_KSCrashReportWriter *))self.configuration.onCrashHandler;

_breadcrumbStore = [[BugsnagBreadcrumbs alloc] initWithConfiguration:self.configuration];

id<BSGFeatureFlagStore> persistentFeatureFlagsStore = [[BSGPersistentFeatureFlagStore alloc] initWithStorageDirectory:fileLocations.featureFlags];
_featureFlagStore = [BSGCompositeFeatureFlagStore storeWithMemoryStore:[configuration.featureFlagStore copy]
persistentStore:persistentFeatureFlagsStore
atomicStore:[BSGAtomicFeatureFlagStore store]];

// Start with a copy of the configuration metadata
self.metadata = [[_configuration metadata] copy];
Expand Down Expand Up @@ -285,6 +291,7 @@ - (void)start {
[self.metadata setStorageBuffer:&bsg_g_bugsnag_data.metadataJSON file:BSGFileLocations.current.metadata];
[self.state setStorageBuffer:&bsg_g_bugsnag_data.stateJSON file:BSGFileLocations.current.state];
[self.breadcrumbStore removeAllBreadcrumbs];
[self.featureFlagStore synchronizeFlagsWithMemoryStore];

#if BSG_HAVE_REACHABILITY
[self setupConnectivityListener];
Expand Down Expand Up @@ -796,9 +803,9 @@ - (void)notifyInternal:(BugsnagEvent *_Nonnull)event
[event.metadata addMetadata:BSGDeviceMetadataFromRunContext(bsg_runContext) toSection:BSGKeyDevice];

// App hang events will already contain feature flags
if (!event.featureFlagStore.count) {
if (event.featureFlagStore.isEmpty) {
@synchronized (self.featureFlagStore) {
event.featureFlagStore = [self.featureFlagStore copy];
event.featureFlagStore = [self.featureFlagStore copyMemoryStore];
}
}

Expand Down Expand Up @@ -882,8 +889,7 @@ - (void)dealloc {

- (void)addFeatureFlagWithName:(NSString *)name variant:(nullable NSString *)variant {
@synchronized (self.featureFlagStore) {
BSGFeatureFlagStoreAddFeatureFlag(self.featureFlagStore, name, variant);
[self.state addMetadata:BSGFeatureFlagStoreToJSON(self.featureFlagStore) withKey:BSGKeyFeatureFlags toSection:BSGKeyClient];
[self.featureFlagStore addFeatureFlag:name withVariant:variant];
}
if (self.observer) {
self.observer(BSGClientObserverAddFeatureFlag, [BugsnagFeatureFlag flagWithName:name variant:variant]);
Expand All @@ -892,8 +898,7 @@ - (void)addFeatureFlagWithName:(NSString *)name variant:(nullable NSString *)var

- (void)addFeatureFlagWithName:(NSString *)name {
@synchronized (self.featureFlagStore) {
BSGFeatureFlagStoreAddFeatureFlag(self.featureFlagStore, name, nil);
[self.state addMetadata:BSGFeatureFlagStoreToJSON(self.featureFlagStore) withKey:BSGKeyFeatureFlags toSection:BSGKeyClient];
[self.featureFlagStore addFeatureFlag:name withVariant:nil];
}
if (self.observer) {
self.observer(BSGClientObserverAddFeatureFlag, [BugsnagFeatureFlag flagWithName:name]);
Expand All @@ -902,8 +907,7 @@ - (void)addFeatureFlagWithName:(NSString *)name {

- (void)addFeatureFlags:(NSArray<BugsnagFeatureFlag *> *)featureFlags {
@synchronized (self.featureFlagStore) {
BSGFeatureFlagStoreAddFeatureFlags(self.featureFlagStore, featureFlags);
[self.state addMetadata:BSGFeatureFlagStoreToJSON(self.featureFlagStore) withKey:BSGKeyFeatureFlags toSection:BSGKeyClient];
[self.featureFlagStore addFeatureFlags:featureFlags];
}
if (self.observer) {
for (BugsnagFeatureFlag *featureFlag in featureFlags) {
Expand All @@ -914,8 +918,7 @@ - (void)addFeatureFlags:(NSArray<BugsnagFeatureFlag *> *)featureFlags {

- (void)clearFeatureFlagWithName:(NSString *)name {
@synchronized (self.featureFlagStore) {
BSGFeatureFlagStoreClear(self.featureFlagStore, name);
[self.state addMetadata:BSGFeatureFlagStoreToJSON(self.featureFlagStore) withKey:BSGKeyFeatureFlags toSection:BSGKeyClient];
[self.featureFlagStore clear:name];
}
if (self.observer) {
self.observer(BSGClientObserverClearFeatureFlag, name);
Expand All @@ -924,8 +927,7 @@ - (void)clearFeatureFlagWithName:(NSString *)name {

- (void)clearFeatureFlags {
@synchronized (self.featureFlagStore) {
BSGFeatureFlagStoreClear(self.featureFlagStore, nil);
[self.state addMetadata:BSGFeatureFlagStoreToJSON(self.featureFlagStore) withKey:BSGKeyFeatureFlags toSection:BSGKeyClient];
[self.featureFlagStore clear];
}
if (self.observer) {
self.observer(BSGClientObserverClearFeatureFlag, nil);
Expand Down Expand Up @@ -1076,7 +1078,7 @@ - (void)appHangDetectedAtDate:(NSDate *)date withThreads:(NSArray<BugsnagThread
self.appHangEvent.context = self.context;

@synchronized (self.featureFlagStore) {
self.appHangEvent.featureFlagStore = [self.featureFlagStore copy];
self.appHangEvent.featureFlagStore = [self.featureFlagStore copyMemoryStore];
}

[self.appHangEvent symbolicateIfNeeded];
Expand Down Expand Up @@ -1225,9 +1227,7 @@ - (nullable BugsnagEvent *)generateEventForLastLaunchWithError:(BugsnagError *)e
session:session];

event.context = stateDict[BSGKeyClient][BSGKeyContext];

id featureFlags = stateDict[BSGKeyClient][BSGKeyFeatureFlags];
event.featureFlagStore = BSGFeatureFlagStoreFromJSON(featureFlags);
event.featureFlagStore = BSGFeatureFlagStoreWithFlags([self.featureFlagStore persistedFlags]);

return event;
}
Expand Down
2 changes: 1 addition & 1 deletion Bugsnag/Configuration/BugsnagConfiguration+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ BSG_OBJC_DIRECT_MEMBERS

@property (readonly, nonatomic) NSDictionary<NSString *, id> *dictionaryRepresentation;

@property (nonatomic) BSGFeatureFlagStore *featureFlagStore;
@property (nonatomic) BSGMemoryFeatureFlagStore *featureFlagStore;

@property (copy, nonatomic) BugsnagMetadata *metadata;

Expand Down
16 changes: 8 additions & 8 deletions Bugsnag/Configuration/BugsnagConfiguration.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

#import "BSGConfigurationBuilder.h"
#import "BSGDefines.h"
#import "BSGFeatureFlagStore.h"
#import "BSGMemoryFeatureFlagStore.h"
#import "BSGKeys.h"
#import "BugsnagApiClient.h"
#import "BugsnagEndpointConfiguration.h"
Expand Down Expand Up @@ -166,7 +166,7 @@ - (instancetype)initWithApiKey:(NSString *)apiKey {
if (apiKey) {
[self setApiKey:apiKey];
}
_featureFlagStore = [[BSGFeatureFlagStore alloc] init];
_featureFlagStore = [[BSGMemoryFeatureFlagStore alloc] init];
_metadata = [[BugsnagMetadata alloc] init];
_endpoints = [BugsnagEndpointConfiguration new];
_autoDetectErrors = YES;
Expand Down Expand Up @@ -241,7 +241,7 @@ - (instancetype)initWithDictionaryRepresentation:(NSDictionary<NSString *, id> *
_bundleVersion = dictionaryRepresentation[BSGKeyBundleVersion];
_context = dictionaryRepresentation[BSGKeyContext];
_enabledReleaseStages = dictionaryRepresentation[BSGKeyEnabledReleaseStages];
_featureFlagStore = [[BSGFeatureFlagStore alloc] init];
_featureFlagStore = [[BSGMemoryFeatureFlagStore alloc] init];
_releaseStage = dictionaryRepresentation[BSGKeyReleaseStage];
return self;
}
Expand Down Expand Up @@ -508,23 +508,23 @@ - (void)addPlugin:(id<BugsnagPlugin> _Nonnull)plugin {
// MARK: - <BugsnagFeatureFlagStore>

- (void)addFeatureFlagWithName:(NSString *)name variant:(nullable NSString *)variant {
BSGFeatureFlagStoreAddFeatureFlag(self.featureFlagStore, name, variant);
[self.featureFlagStore addFeatureFlag:name withVariant:variant];
}

- (void)addFeatureFlagWithName:(NSString *)name {
BSGFeatureFlagStoreAddFeatureFlag(self.featureFlagStore, name, nil);
[self.featureFlagStore addFeatureFlag:name withVariant:nil];
}

- (void)addFeatureFlags:(NSArray<BugsnagFeatureFlag *> *)featureFlags {
BSGFeatureFlagStoreAddFeatureFlags(self.featureFlagStore, featureFlags);
[self.featureFlagStore addFeatureFlags:featureFlags];
}

- (void)clearFeatureFlagWithName:(NSString *)name {
BSGFeatureFlagStoreClear(self.featureFlagStore, name);
[self.featureFlagStore clear:name];
}

- (void)clearFeatureFlags {
BSGFeatureFlagStoreClear(self.featureFlagStore, nil);
[self.featureFlagStore clear];
}

// MARK: - <MetadataStore>
Expand Down
2 changes: 1 addition & 1 deletion Bugsnag/Delivery/BSGEventUploadKSCrashReportOperation.m
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
return nil;
}
NSMutableArray *keys = [NSMutableArray array];
NSString *pattern = @"\"(report|process|system|system_atcrash|binary_images|crash|threads|error|user_atcrash|config|metaData|state|breadcrumbs)\":";
NSString *pattern = @"\"(report|process|system|system_atcrash|binary_images|crash|threads|error|user_atcrash|config|metaData|state|breadcrumbs|featureFlags)\":";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil];
for (NSTextCheckingResult *result in [regex matchesInString:string options:0 range:NSMakeRange(0, string.length)]) {
if ([result numberOfRanges] == 2) {
Expand Down
31 changes: 31 additions & 0 deletions Bugsnag/FeatureFlags/BSGAtomicFeatureFlagStore.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// BSGAtomicFeatureFlagStore.h
// Bugsnag
//
// Created by Robert B on 16/12/2024.
// Copyright © 2024 Bugsnag Inc. All rights reserved.
//

#import "BugsnagInternals.h"
#import "BSGDefines.h"
#import "BSGFeatureFlagStore.h"

NS_ASSUME_NONNULL_BEGIN


BSG_OBJC_DIRECT_MEMBERS
@interface BSGAtomicFeatureFlagStore ()

+ (instancetype)store;

@end

#pragma mark - Crash reporting

/**
* Inserts the current feature flags into a crash report.
*
*/
void BugsnagFeatureFlagsWriteCrashReport(const BSG_KSCrashReportWriter * _Nonnull writer,
bool requiresAsyncSafety);
NS_ASSUME_NONNULL_END
Loading

0 comments on commit 2f44b7b

Please sign in to comment.