Skip to content

Commit 37bbb15

Browse files
NR-199912: Background Instrumentation Proof of Concept
1 parent 8f04f0b commit 37bbb15

File tree

10 files changed

+251
-148
lines changed

10 files changed

+251
-148
lines changed

Agent/General/NewRelicAgentInternal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#import "NRMAUserActionFacade.h"
1414
#import "NRMAURLTransformer.h"
1515

16+
#import <BackgroundTasks/BackgroundTasks.h>
17+
1618
// Keys used for harvester data request.
1719
#define NEW_RELIC_APP_VERSION_HEADER_KEY @"X-NewRelic-App-Version"
1820
#define NEW_RELIC_OS_NAME_HEADER_KEY @"X-NewRelic-OS-Name"

Agent/General/NewRelicAgentInternal.m

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,18 @@ - (id) initWithCollectorAddress:(NSString*)collectorHost
149149

150150
self = [super init];
151151
if (self) {
152+
153+
154+
// TODO: Check if this is the right place for this code?
155+
if (@available(iOS 13.0, *)) {
156+
[[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:@"com.newrelic.NRApp.bitcode" usingQueue:nil launchHandler:^(__kindof BGTask * _Nonnull task) {
157+
[self handleAppRefreshTask: task];
158+
}];
159+
} else {
160+
// Fallback on earlier versions
161+
}
162+
163+
152164
self.appWillTerminate = NO;
153165
[NRMACPUVitals setAppStartCPUTime];
154166
if ([UIApplication sharedApplication].applicationState != UIApplicationStateBackground) {
@@ -186,6 +198,9 @@ - (id) initWithCollectorAddress:(NSString*)collectorHost
186198
selector:@selector(applicationWillTerminate)
187199
name:UIApplicationWillTerminateNotification
188200
object:[UIApplication sharedApplication]];
201+
202+
// TODO: Is this the right place to put this?
203+
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
189204

190205
NRLOG_INFO(@"Agent enabled");
191206

@@ -644,7 +659,7 @@ - (void) applicationDidEnterBackground {
644659
if ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)] &&
645660
[[UIDevice currentDevice] isMultitaskingSupported]) {
646661

647-
662+
// Current One time background task is dispatched upon going to the background.
648663
UIApplication* application = [UIApplication sharedApplication];
649664

650665
// Mark the start of our background task.
@@ -692,6 +707,8 @@ - (void) applicationDidEnterBackground {
692707
if (self.appWillTerminate) {
693708
return;
694709
}
710+
711+
// Currently this is where the actual harvest occurs when we go to background
695712
NRLOG_VERBOSE(@"Harvesting data in background");
696713
[[[NRMAHarvestController harvestController] harvester] execute];
697714
#ifndef DISABLE_NRMA_EXCEPTION_WRAPPER
@@ -700,7 +717,13 @@ - (void) applicationDidEnterBackground {
700717
class:NSStringFromClass([NRMAHarvester class])
701718
selector:@"execute"];
702719
} @finally {
703-
[self agentShutdown];
720+
721+
722+
// What would happen if we didn't call agentShutdown?
723+
724+
NRLOG_VERBOSE(@"used to agentShutdown.");
725+
726+
// [self agentShutdown];
704727
}
705728
#endif
706729

@@ -709,6 +732,11 @@ - (void) applicationDidEnterBackground {
709732
[application endBackgroundTask:background_task];
710733
// Invalidate the background_task.
711734
background_task = UIBackgroundTaskInvalid;
735+
736+
737+
738+
// Schedule the next background harvest.
739+
[self scheduleHeartbeatTask];
712740
}
713741
});
714742
} else {
@@ -742,6 +770,59 @@ - (void) agentShutdown {
742770
[NRMALastActivityTraceController clearLastActivityStamp];
743771
}
744772

773+
// We only support background featch in iOS 13+
774+
- (void) scheduleHeartbeatTask {
775+
if (@available(iOS 13.0, *)) {
776+
// TODO: Pass instrumented app bundle id
777+
BGAppRefreshTaskRequest *request = [[BGAppRefreshTaskRequest alloc] initWithIdentifier:@"com.newrelic.NRApp.bitcode"];
778+
request.earliestBeginDate = [NSDate dateWithTimeIntervalSinceNow:5 * 60];
779+
780+
NSError *error = nil;
781+
782+
[[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error];
783+
784+
if (error) {
785+
NRLOG_ERROR(@"Error schedule heartbeat: %@", error);
786+
}
787+
else {
788+
NRLOG_VERBOSE(@"Scheduled heartbeat");
789+
790+
}
791+
} else {
792+
// Fallback on earlier versions
793+
}
794+
795+
}
796+
797+
- (void) handleAppRefreshTask:(BGAppRefreshTask *)task {
798+
NRLOG_VERBOSE(@"handleAppRefreshTask BGAppRefreshTask");
799+
if (@available(iOS 13.0, *)) {
800+
801+
[task setExpirationHandler:^{
802+
__weak BGTask *weakTask = task;
803+
if (weakTask) {
804+
[weakTask setTaskCompletedWithSuccess:false];
805+
}
806+
//TODO: Invalidate and cancel the harvest request
807+
// PokeManager.urlSession.invalidateAndCancel()
808+
809+
}];
810+
811+
[[[NRMAHarvestController harvestController] harvester] execute];
812+
813+
// TODO: Make sure this is the right place to call this.
814+
[task setTaskCompletedWithSuccess:true];
815+
816+
// We always reschedule the heartbeat task.
817+
[self scheduleHeartbeatTask];
818+
}
819+
else {
820+
NRLOG_VERBOSE(@"No background tasks pre iOS 13");
821+
822+
}
823+
824+
}
825+
745826
+ (BOOL) harvestNow {
746827
return [NRMAHarvestController harvestNow];
747828
}

Agent/HandledException/NRMAHexBackgroundUploader.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
#import <Foundation/Foundation.h>
77
#import "NRMAConnection.h"
88

9-
@interface NRMAHexBackgroundUploader : NRMAConnection<NSURLSessionDelegate>
10-
@property(strong) NSString* hexHost;
11-
12-
- (instancetype) initWithHexHost:(NSString*)hexHost;
13-
14-
- (void) sendData:(NSData*)data;
15-
@end
9+
//@interface NRMAHexBackgroundUploader : NRMAConnection<NSURLSessionDelegate>
10+
//@property(strong) NSString* hexHost;
11+
//
12+
//- (instancetype) initWithHexHost:(NSString*)hexHost;
13+
//
14+
//- (void) sendData:(NSData*)data;
15+
//@end

Agent/HandledException/NRMAHexBackgroundUploader.mm

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,46 @@
33
// Copyright © 2023 New Relic. All rights reserved.
44
//
55

6-
#import "NRMAHexBackgroundUploader.h"
7-
#import "NRMASessionIdentifierManager.h"
8-
9-
@interface NRMAHexBackgroundUploader ()
10-
@property(strong) NRMASessionIdentifierManager* sessionIdManager;
11-
@property(strong) NSURLSession* session;
12-
@end
13-
14-
@implementation NRMAHexBackgroundUploader
15-
16-
- (instancetype) initWithHexHost:(NSString*)hexHost {
17-
self = [super init];
18-
if (self) {
19-
self.sessionIdManager = [[[NRMASessionIdentifierManager alloc] init] autorelease];
20-
NSURLSessionConfiguration* backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[self.sessionIdManager sessionIdentifier]];
21-
self.session = [NSURLSession sessionWithConfiguration:backgroundConfiguration
22-
delegate:self
23-
delegateQueue:[NSOperationQueue currentQueue]];
24-
self.hexHost = hexHost;
25-
}
26-
return self;
27-
}
28-
29-
- (void) sendData:(NSData*)data {
30-
31-
NSMutableURLRequest* request = [self newPostWithURI:self.hexHost];
32-
request.HTTPMethod = @"POST";
33-
request.HTTPBody = data;
34-
[request setValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"];
35-
NSURLSessionUploadTask* uploadTask = [self.session uploadTaskWithStreamedRequest:request];
36-
[uploadTask resume];
37-
}
38-
39-
- (void) dealloc {
40-
[self.session invalidateAndCancel];
41-
[self.session release];
42-
[self.hexHost release];
43-
[self.sessionIdManager release];
44-
45-
[super dealloc];
46-
}
47-
48-
@end
6+
//#import "NRMAHexBackgroundUploader.h"
7+
//#import "NRMASessionIdentifierManager.h"
8+
//
9+
//@interface NRMAHexBackgroundUploader ()
10+
//@property(strong) NRMASessionIdentifierManager* sessionIdManager;
11+
//@property(strong) NSURLSession* session;
12+
//@end
13+
//
14+
//@implementation NRMAHexBackgroundUploader
15+
//
16+
//- (instancetype) initWithHexHost:(NSString*)hexHost {
17+
// self = [super init];
18+
// if (self) {
19+
// self.sessionIdManager = [[[NRMASessionIdentifierManager alloc] init] autorelease];
20+
// NSURLSessionConfiguration* backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[self.sessionIdManager sessionIdentifier]];
21+
// self.session = [NSURLSession sessionWithConfiguration:backgroundConfiguration
22+
// delegate:self
23+
// delegateQueue:[NSOperationQueue currentQueue]];
24+
// self.hexHost = hexHost;
25+
// }
26+
// return self;
27+
//}
28+
//
29+
//- (void) sendData:(NSData*)data {
30+
//
31+
// NSMutableURLRequest* request = [self newPostWithURI:self.hexHost];
32+
// request.HTTPMethod = @"POST";
33+
// request.HTTPBody = data;
34+
// [request setValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"];
35+
// NSURLSessionUploadTask* uploadTask = [self.session uploadTaskWithStreamedRequest:request];
36+
// [uploadTask resume];
37+
//}
38+
//
39+
//- (void) dealloc {
40+
// [self.session invalidateAndCancel];
41+
// [self.session release];
42+
// [self.hexHost release];
43+
// [self.sessionIdManager release];
44+
//
45+
// [super dealloc];
46+
//}
47+
//
48+
//@end

Agent/HandledException/NRMASessionIdentifierManager.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
// Copyright © 2023 New Relic. All rights reserved.
44
//
55

6-
#import <Foundation/Foundation.h>
7-
8-
@interface NRMASessionIdentifierManager : NSObject
9-
- (NSString*) sessionIdentifier;
10-
@end
6+
//#import <Foundation/Foundation.h>
7+
//
8+
//@interface NRMASessionIdentifierManager : NSObject
9+
//- (NSString*) sessionIdentifier;
10+
//@end

Agent/HandledException/NRMASessionIdentifierManager.mm

Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,40 @@
33
// Copyright © 2023 New Relic. All rights reserved.
44
//
55

6-
#import "NRMASessionIdentifierManager.h"
7-
8-
#define kNRMA_SESSION_ID_KEY @"NewRelicSessionIdentifier"
9-
10-
@interface NRMASessionIdentifierManager ()
11-
@property(strong) NSString* identifier;
12-
@end
13-
14-
@implementation NRMASessionIdentifierManager
15-
16-
- (NSString*) sessionIdentifier {
17-
if (self.identifier) return self.identifier;
18-
19-
self.identifier = [[NSUserDefaults standardUserDefaults] objectForKey:kNRMA_SESSION_ID_KEY];
20-
21-
if(self.identifier) return self.identifier;
22-
23-
self.identifier = [[NSUUID UUID] UUIDString];
24-
25-
[self storeIdentifier:self.identifier];
26-
27-
return self.identifier;
28-
}
29-
30-
- (void) storeIdentifier:(NSString*)uuid {
31-
[[NSUserDefaults standardUserDefaults] setObject:uuid
32-
forKey:kNRMA_SESSION_ID_KEY];
33-
[[NSUserDefaults standardUserDefaults] synchronize];
34-
}
35-
36-
// Function included for testing.
37-
+ (void) purge {
38-
[[NSUserDefaults standardUserDefaults] setObject:nil
39-
forKey:kNRMA_SESSION_ID_KEY];
40-
[[NSUserDefaults standardUserDefaults] synchronize];
41-
}
42-
43-
@end
6+
//#import "NRMASessionIdentifierManager.h"
7+
//
8+
//#define kNRMA_SESSION_ID_KEY @"NewRelicSessionIdentifier"
9+
//
10+
//@interface NRMASessionIdentifierManager ()
11+
//@property(strong) NSString* identifier;
12+
//@end
13+
//
14+
//@implementation NRMASessionIdentifierManager
15+
//
16+
//- (NSString*) sessionIdentifier {
17+
// if (self.identifier) return self.identifier;
18+
//
19+
// self.identifier = [[NSUserDefaults standardUserDefaults] objectForKey:kNRMA_SESSION_ID_KEY];
20+
//
21+
// if(self.identifier) return self.identifier;
22+
//
23+
// self.identifier = [[NSUUID UUID] UUIDString];
24+
//
25+
// [self storeIdentifier:self.identifier];
26+
//
27+
// return self.identifier;
28+
//}
29+
//
30+
//- (void) storeIdentifier:(NSString*)uuid {
31+
// [[NSUserDefaults standardUserDefaults] setObject:uuid
32+
// forKey:kNRMA_SESSION_ID_KEY];
33+
// [[NSUserDefaults standardUserDefaults] synchronize];
34+
//}
35+
//
36+
//// Function included for testing.
37+
//+ (void) purge {
38+
// [[NSUserDefaults standardUserDefaults] setObject:nil
39+
// forKey:kNRMA_SESSION_ID_KEY];
40+
// [[NSUserDefaults standardUserDefaults] synchronize];
41+
//}
42+
//@end

Test Harness/NRTestApp/NRTestApp/AppDelegate.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
8787
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
8888
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
8989
}
90+
91+
92+
// Background fetch handling\
93+
94+
// TODO: Must add this and then we will capture in NewRelicAgentInternal
95+
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
96+
97+
NewRelic.logVerbose("performFetchWithCompletionHandler called")
98+
completionHandler(.newData)
99+
}
90100
}

Test Harness/NRTestApp/NRTestApp/Info.plist

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plist version="1.0">
44
<dict>
5+
<key>BGTaskSchedulerPermittedIdentifiers</key>
6+
<array>
7+
<string>com.newrelic.NRApp.bitcode</string>
8+
</array>
59
<key>ITSAppUsesNonExemptEncryption</key>
610
<false/>
711
<key>NSAppTransportSecurity</key>
@@ -26,5 +30,10 @@
2630
</array>
2731
</dict>
2832
</dict>
33+
<key>UIBackgroundModes</key>
34+
<array>
35+
<string>fetch</string>
36+
<string>processing</string>
37+
</array>
2938
</dict>
3039
</plist>

Tests/Unit-Tests/NewRelicAgentTests/Exception-Handler-Tests/NRMACrashReportTest.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ - (void) testCrashReport {
154154
XCTAssertNoThrow([report JSONObject]);
155155
}
156156

157+
// TODO: Debug occasional crash on CFUUIDCreateString(NULL, report.uuidRef)
158+
// NRMACrashDataWriter
157159
-(void) testCrashDataWriter {
158160

159161
NSError *error;

0 commit comments

Comments
 (0)