Skip to content

Commit

Permalink
NR-199912: Background Instrumentation Proof of Concept
Browse files Browse the repository at this point in the history
  • Loading branch information
cdillard-NewRelic committed Dec 27, 2023
1 parent 8f04f0b commit 37bbb15
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 148 deletions.
2 changes: 2 additions & 0 deletions Agent/General/NewRelicAgentInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#import "NRMAUserActionFacade.h"
#import "NRMAURLTransformer.h"

#import <BackgroundTasks/BackgroundTasks.h>

// Keys used for harvester data request.
#define NEW_RELIC_APP_VERSION_HEADER_KEY @"X-NewRelic-App-Version"
#define NEW_RELIC_OS_NAME_HEADER_KEY @"X-NewRelic-OS-Name"
Expand Down
85 changes: 83 additions & 2 deletions Agent/General/NewRelicAgentInternal.m
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,18 @@ - (id) initWithCollectorAddress:(NSString*)collectorHost

self = [super init];
if (self) {


// TODO: Check if this is the right place for this code?
if (@available(iOS 13.0, *)) {
[[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:@"com.newrelic.NRApp.bitcode" usingQueue:nil launchHandler:^(__kindof BGTask * _Nonnull task) {
[self handleAppRefreshTask: task];
}];
} else {
// Fallback on earlier versions
}


self.appWillTerminate = NO;
[NRMACPUVitals setAppStartCPUTime];
if ([UIApplication sharedApplication].applicationState != UIApplicationStateBackground) {
Expand Down Expand Up @@ -186,6 +198,9 @@ - (id) initWithCollectorAddress:(NSString*)collectorHost
selector:@selector(applicationWillTerminate)
name:UIApplicationWillTerminateNotification
object:[UIApplication sharedApplication]];

// TODO: Is this the right place to put this?
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];

NRLOG_INFO(@"Agent enabled");

Expand Down Expand Up @@ -644,7 +659,7 @@ - (void) applicationDidEnterBackground {
if ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)] &&
[[UIDevice currentDevice] isMultitaskingSupported]) {


// Current One time background task is dispatched upon going to the background.
UIApplication* application = [UIApplication sharedApplication];

// Mark the start of our background task.
Expand Down Expand Up @@ -692,6 +707,8 @@ - (void) applicationDidEnterBackground {
if (self.appWillTerminate) {
return;
}

// Currently this is where the actual harvest occurs when we go to background
NRLOG_VERBOSE(@"Harvesting data in background");
[[[NRMAHarvestController harvestController] harvester] execute];
#ifndef DISABLE_NRMA_EXCEPTION_WRAPPER
Expand All @@ -700,7 +717,13 @@ - (void) applicationDidEnterBackground {
class:NSStringFromClass([NRMAHarvester class])
selector:@"execute"];
} @finally {
[self agentShutdown];


// What would happen if we didn't call agentShutdown?

NRLOG_VERBOSE(@"used to agentShutdown.");

// [self agentShutdown];
}
#endif

Expand All @@ -709,6 +732,11 @@ - (void) applicationDidEnterBackground {
[application endBackgroundTask:background_task];
// Invalidate the background_task.
background_task = UIBackgroundTaskInvalid;



// Schedule the next background harvest.
[self scheduleHeartbeatTask];
}
});
} else {
Expand Down Expand Up @@ -742,6 +770,59 @@ - (void) agentShutdown {
[NRMALastActivityTraceController clearLastActivityStamp];
}

// We only support background featch in iOS 13+
- (void) scheduleHeartbeatTask {
if (@available(iOS 13.0, *)) {
// TODO: Pass instrumented app bundle id
BGAppRefreshTaskRequest *request = [[BGAppRefreshTaskRequest alloc] initWithIdentifier:@"com.newrelic.NRApp.bitcode"];
request.earliestBeginDate = [NSDate dateWithTimeIntervalSinceNow:5 * 60];

NSError *error = nil;

[[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error];

if (error) {
NRLOG_ERROR(@"Error schedule heartbeat: %@", error);
}
else {
NRLOG_VERBOSE(@"Scheduled heartbeat");

}
} else {
// Fallback on earlier versions
}

}

- (void) handleAppRefreshTask:(BGAppRefreshTask *)task {
NRLOG_VERBOSE(@"handleAppRefreshTask BGAppRefreshTask");
if (@available(iOS 13.0, *)) {

[task setExpirationHandler:^{
__weak BGTask *weakTask = task;
if (weakTask) {
[weakTask setTaskCompletedWithSuccess:false];
}
//TODO: Invalidate and cancel the harvest request
// PokeManager.urlSession.invalidateAndCancel()

}];

[[[NRMAHarvestController harvestController] harvester] execute];

// TODO: Make sure this is the right place to call this.
[task setTaskCompletedWithSuccess:true];

// We always reschedule the heartbeat task.
[self scheduleHeartbeatTask];
}
else {
NRLOG_VERBOSE(@"No background tasks pre iOS 13");

}

}

+ (BOOL) harvestNow {
return [NRMAHarvestController harvestNow];
}
Expand Down
14 changes: 7 additions & 7 deletions Agent/HandledException/NRMAHexBackgroundUploader.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
#import <Foundation/Foundation.h>
#import "NRMAConnection.h"

@interface NRMAHexBackgroundUploader : NRMAConnection<NSURLSessionDelegate>
@property(strong) NSString* hexHost;

- (instancetype) initWithHexHost:(NSString*)hexHost;

- (void) sendData:(NSData*)data;
@end
//@interface NRMAHexBackgroundUploader : NRMAConnection<NSURLSessionDelegate>
//@property(strong) NSString* hexHost;
//
//- (instancetype) initWithHexHost:(NSString*)hexHost;
//
//- (void) sendData:(NSData*)data;
//@end
86 changes: 43 additions & 43 deletions Agent/HandledException/NRMAHexBackgroundUploader.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,46 @@
// Copyright © 2023 New Relic. All rights reserved.
//

#import "NRMAHexBackgroundUploader.h"
#import "NRMASessionIdentifierManager.h"

@interface NRMAHexBackgroundUploader ()
@property(strong) NRMASessionIdentifierManager* sessionIdManager;
@property(strong) NSURLSession* session;
@end

@implementation NRMAHexBackgroundUploader

- (instancetype) initWithHexHost:(NSString*)hexHost {
self = [super init];
if (self) {
self.sessionIdManager = [[[NRMASessionIdentifierManager alloc] init] autorelease];
NSURLSessionConfiguration* backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[self.sessionIdManager sessionIdentifier]];
self.session = [NSURLSession sessionWithConfiguration:backgroundConfiguration
delegate:self
delegateQueue:[NSOperationQueue currentQueue]];
self.hexHost = hexHost;
}
return self;
}

- (void) sendData:(NSData*)data {

NSMutableURLRequest* request = [self newPostWithURI:self.hexHost];
request.HTTPMethod = @"POST";
request.HTTPBody = data;
[request setValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"];
NSURLSessionUploadTask* uploadTask = [self.session uploadTaskWithStreamedRequest:request];
[uploadTask resume];
}

- (void) dealloc {
[self.session invalidateAndCancel];
[self.session release];
[self.hexHost release];
[self.sessionIdManager release];

[super dealloc];
}

@end
//#import "NRMAHexBackgroundUploader.h"
//#import "NRMASessionIdentifierManager.h"
//
//@interface NRMAHexBackgroundUploader ()
//@property(strong) NRMASessionIdentifierManager* sessionIdManager;
//@property(strong) NSURLSession* session;
//@end
//
//@implementation NRMAHexBackgroundUploader
//
//- (instancetype) initWithHexHost:(NSString*)hexHost {
// self = [super init];
// if (self) {
// self.sessionIdManager = [[[NRMASessionIdentifierManager alloc] init] autorelease];
// NSURLSessionConfiguration* backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[self.sessionIdManager sessionIdentifier]];
// self.session = [NSURLSession sessionWithConfiguration:backgroundConfiguration
// delegate:self
// delegateQueue:[NSOperationQueue currentQueue]];
// self.hexHost = hexHost;
// }
// return self;
//}
//
//- (void) sendData:(NSData*)data {
//
// NSMutableURLRequest* request = [self newPostWithURI:self.hexHost];
// request.HTTPMethod = @"POST";
// request.HTTPBody = data;
// [request setValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"];
// NSURLSessionUploadTask* uploadTask = [self.session uploadTaskWithStreamedRequest:request];
// [uploadTask resume];
//}
//
//- (void) dealloc {
// [self.session invalidateAndCancel];
// [self.session release];
// [self.hexHost release];
// [self.sessionIdManager release];
//
// [super dealloc];
//}
//
//@end
10 changes: 5 additions & 5 deletions Agent/HandledException/NRMASessionIdentifierManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
// Copyright © 2023 New Relic. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface NRMASessionIdentifierManager : NSObject
- (NSString*) sessionIdentifier;
@end
//#import <Foundation/Foundation.h>
//
//@interface NRMASessionIdentifierManager : NSObject
//- (NSString*) sessionIdentifier;
//@end
75 changes: 37 additions & 38 deletions Agent/HandledException/NRMASessionIdentifierManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,40 @@
// Copyright © 2023 New Relic. All rights reserved.
//

#import "NRMASessionIdentifierManager.h"

#define kNRMA_SESSION_ID_KEY @"NewRelicSessionIdentifier"

@interface NRMASessionIdentifierManager ()
@property(strong) NSString* identifier;
@end

@implementation NRMASessionIdentifierManager

- (NSString*) sessionIdentifier {
if (self.identifier) return self.identifier;

self.identifier = [[NSUserDefaults standardUserDefaults] objectForKey:kNRMA_SESSION_ID_KEY];

if(self.identifier) return self.identifier;

self.identifier = [[NSUUID UUID] UUIDString];

[self storeIdentifier:self.identifier];

return self.identifier;
}

- (void) storeIdentifier:(NSString*)uuid {
[[NSUserDefaults standardUserDefaults] setObject:uuid
forKey:kNRMA_SESSION_ID_KEY];
[[NSUserDefaults standardUserDefaults] synchronize];
}

// Function included for testing.
+ (void) purge {
[[NSUserDefaults standardUserDefaults] setObject:nil
forKey:kNRMA_SESSION_ID_KEY];
[[NSUserDefaults standardUserDefaults] synchronize];
}

@end
//#import "NRMASessionIdentifierManager.h"
//
//#define kNRMA_SESSION_ID_KEY @"NewRelicSessionIdentifier"
//
//@interface NRMASessionIdentifierManager ()
//@property(strong) NSString* identifier;
//@end
//
//@implementation NRMASessionIdentifierManager
//
//- (NSString*) sessionIdentifier {
// if (self.identifier) return self.identifier;
//
// self.identifier = [[NSUserDefaults standardUserDefaults] objectForKey:kNRMA_SESSION_ID_KEY];
//
// if(self.identifier) return self.identifier;
//
// self.identifier = [[NSUUID UUID] UUIDString];
//
// [self storeIdentifier:self.identifier];
//
// return self.identifier;
//}
//
//- (void) storeIdentifier:(NSString*)uuid {
// [[NSUserDefaults standardUserDefaults] setObject:uuid
// forKey:kNRMA_SESSION_ID_KEY];
// [[NSUserDefaults standardUserDefaults] synchronize];
//}
//
//// Function included for testing.
//+ (void) purge {
// [[NSUserDefaults standardUserDefaults] setObject:nil
// forKey:kNRMA_SESSION_ID_KEY];
// [[NSUserDefaults standardUserDefaults] synchronize];
//}
//@end
10 changes: 10 additions & 0 deletions Test Harness/NRTestApp/NRTestApp/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}


// Background fetch handling\

// TODO: Must add this and then we will capture in NewRelicAgentInternal
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {

NewRelic.logVerbose("performFetchWithCompletionHandler called")
completionHandler(.newData)
}
}
9 changes: 9 additions & 0 deletions Test Harness/NRTestApp/NRTestApp/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.newrelic.NRApp.bitcode</string>
</array>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>NSAppTransportSecurity</key>
Expand All @@ -26,5 +30,10 @@
</array>
</dict>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ - (void) testCrashReport {
XCTAssertNoThrow([report JSONObject]);
}

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

NSError *error;
Expand Down
Loading

0 comments on commit 37bbb15

Please sign in to comment.