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

Offline storage to file system on failed upload #189

Merged
merged 23 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0672057
Offline storing the data that would be sent to the data endpoint
mbruin-NR Nov 21, 2023
22f23af
Added some better logic for when to record the json for offline storage
mbruin-NR Nov 30, 2023
120f3e0
removed unneeded change
mbruin-NR Nov 30, 2023
ea1b939
Merge branch 'develop' into offlineStoragePOC
mbruin-NR Nov 30, 2023
8b876f3
Added tests and better logging
mbruin-NR Dec 13, 2023
97cb3bf
added an offline attribute to events created offline and added an off…
mbruin-NR Dec 14, 2023
252780c
Added the offline attribute to sessions
mbruin-NR Dec 19, 2023
3f40154
Cleaned up some unused stuff
mbruin-NR Dec 20, 2023
2a641f0
Added a feature flag for offline storage and a maximum amount of stor…
mbruin-NR Jan 5, 2024
69a40aa
Merge branch 'develop' into offlineStoragePOC
mbruin-NR Jan 5, 2024
4fa0b18
Fixing some mistakes
mbruin-NR Jan 10, 2024
1394872
trying to fix a test
mbruin-NR Jan 10, 2024
c5c102d
Some changes after review
mbruin-NR Jan 11, 2024
13b0073
Update Agent/Public/NewRelic.h
mbruin-NR Jan 11, 2024
907d730
Fixed not adding the attribute to metrics payload
mbruin-NR Jan 11, 2024
1efd215
Merge commit '13b0073e7a7bdabfa7be4dcc8b27f2e8acb70b4d' into offlineS…
mbruin-NR Jan 11, 2024
e300cfa
Changed offline to enabled by default and fix a few place to check fo…
mbruin-NR Jan 25, 2024
a87bd97
Changing offline to only care about the persisted reports to eliminat…
mbruin-NR Jan 29, 2024
e35b3fa
Merge branch 'develop' into offlineStoragePOC
mbruin-NR Jan 31, 2024
1f432a3
moving the offline attribute to be per event and adding it to handled…
mbruin-NR Feb 9, 2024
eb8f5ff
Merge branch 'develop' into offlineStoragePOC
mbruin-NR Feb 9, 2024
02b80c2
Mixed up my offline check
mbruin-NR Feb 9, 2024
e783273
Merge commit 'eb8f5ff988606a39c5ac0d74f8569a83298680a2' into offlineS…
mbruin-NR Feb 9, 2024
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 Agent.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,8 @@
F8A455492AFBE31E0057B1E0 /* NRMAURLSessionHeaderTrackingTestsOldEventSystem.m in Sources */ = {isa = PBXBuildFile; fileRef = F8A455472AFBE31E0057B1E0 /* NRMAURLSessionHeaderTrackingTestsOldEventSystem.m */; };
F8AC3E932938FD6C002B4AA8 /* NRMAFakeDataHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = F8AC3E922938FD6C002B4AA8 /* NRMAFakeDataHelper.m */; };
F8AC3E942938FD6C002B4AA8 /* NRMAFakeDataHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = F8AC3E922938FD6C002B4AA8 /* NRMAFakeDataHelper.m */; };
F8E202DE2B07BA61008E0B7B /* NRMAOfflineStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E202DD2B07BA61008E0B7B /* NRMAOfflineStorage.m */; };
F8E202DF2B07BA61008E0B7B /* NRMAOfflineStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E202DD2B07BA61008E0B7B /* NRMAOfflineStorage.m */; };
F8FBFA512A71ACB200CDC8C5 /* NRMARequestEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = F8FBFA502A71ACB200CDC8C5 /* NRMARequestEvent.m */; };
F8FBFA522A71ACB200CDC8C5 /* NRMARequestEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = F8FBFA502A71ACB200CDC8C5 /* NRMARequestEvent.m */; };
F8FBFA552A71BA2900CDC8C5 /* NRMAPayload.m in Sources */ = {isa = PBXBuildFile; fileRef = F8FBFA542A71BA2900CDC8C5 /* NRMAPayload.m */; };
Expand Down Expand Up @@ -1966,6 +1968,8 @@
F8A455472AFBE31E0057B1E0 /* NRMAURLSessionHeaderTrackingTestsOldEventSystem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NRMAURLSessionHeaderTrackingTestsOldEventSystem.m; sourceTree = "<group>"; };
F8AC3E922938FD6C002B4AA8 /* NRMAFakeDataHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NRMAFakeDataHelper.m; sourceTree = "<group>"; };
F8AC3EA72938FDDB002B4AA8 /* NRMAFakeDataHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NRMAFakeDataHelper.h; sourceTree = "<group>"; };
F8E202DD2B07BA61008E0B7B /* NRMAOfflineStorage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NRMAOfflineStorage.m; sourceTree = "<group>"; };
F8E202F32B07BA6E008E0B7B /* NRMAOfflineStorage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NRMAOfflineStorage.h; sourceTree = "<group>"; };
F8FBFA502A71ACB200CDC8C5 /* NRMARequestEvent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NRMARequestEvent.m; sourceTree = "<group>"; };
F8FBFA532A71ACC000CDC8C5 /* NRMARequestEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NRMARequestEvent.h; sourceTree = "<group>"; };
F8FBFA542A71BA2900CDC8C5 /* NRMAPayload.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NRMAPayload.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3105,6 +3109,8 @@
02FF4AB324DC653000115469 /* Debugging */,
02FF4A8924DC652D00115469 /* NewRelicInternalUtils.h */,
02FF4A9024DC652D00115469 /* NewRelicInternalUtils.m */,
F8E202F32B07BA6E008E0B7B /* NRMAOfflineStorage.h */,
F8E202DD2B07BA61008E0B7B /* NRMAOfflineStorage.m */,
02FF4A7E24DC652B00115469 /* NRLogger.m */,
02FF4A8224DC652C00115469 /* NRMAAppToken.h */,
02FF4A8E24DC652D00115469 /* NRMAAppToken.m */,
Expand Down Expand Up @@ -4492,6 +4498,7 @@
02FF4A2324DC648600115469 /* NRMAMeasurementConsumer.m in Sources */,
02FF4AA824DC652E00115469 /* NRMAUDIDManager.m in Sources */,
02FF48DC24DC622700115469 /* NRMACrashDataUploader.m in Sources */,
F8E202DE2B07BA61008E0B7B /* NRMAOfflineStorage.m in Sources */,
02FF49E824DC644900115469 /* NRMAKeyAttributes.m in Sources */,
02FF49EF24DC645B00115469 /* NRMAAppInstallMetricGenerator.m in Sources */,
02FF4A7724DC64FC00115469 /* NRTimer.m in Sources */,
Expand Down Expand Up @@ -4725,6 +4732,7 @@
2B33E5D32AA9160E00AEB7B4 /* NRMASessionEvent.m in Sources */,
F8FBFA632A78416300CDC8C5 /* NRMAMobileEvent.m in Sources */,
02FF4C0524E3201400115469 /* NRMAMetric.m in Sources */,
F8E202DF2B07BA61008E0B7B /* NRMAOfflineStorage.m in Sources */,
02FF4C0624E3201400115469 /* NRMAInteractionHistory.c in Sources */,
02FF4C0724E3201400115469 /* NRMAActivityNameGenerator.m in Sources */,
02FF4C0824E3201400115469 /* NRMAScopedMeasurement.m in Sources */,
Expand Down
1 change: 1 addition & 0 deletions Agent/Analytics/Constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ extern NSString *const kNRMA_Val_errorType_Network;
extern NSString *const kNRMA_Attrib_file;
extern NSString *const kNRMA_Attrib_file_private;

extern NSString *const kNRMA_Offline_file;

// Integer Analytics Constants
static int kNRMA_Attrib_Max_Name_Length = 256;
Expand Down
2 changes: 2 additions & 0 deletions Agent/Analytics/Constants.m
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,5 @@

NSString * const kNRMA_Attrib_file = @"attributes.txt";
NSString * const kNRMA_Attrib_file_private = @"privateAttributes.txt";

NSString * const kNRMA_Offline_file = @"offlineStorage";
16 changes: 14 additions & 2 deletions Agent/Harvester/NRMAHarvester.mm
Original file line number Diff line number Diff line change
Expand Up @@ -333,13 +333,25 @@ - (void) connected
//TODO: add addition collector response processing.
if (response.isError) {
// failure
[self fireOnHarvestFailure];
if(response.error.code == NSURLErrorNotConnectedToInternet || response.error.code == NSURLErrorTimedOut) {
NSError* error = nil;
NSData* jsonData = [NRMAJSON dataWithJSONABLEObject:self.harvestData options:0 error:&error];
if (error) {
NRLOG_ERROR(@"Failed to generate JSON");
goto continues;
}
[connection.offlineStorage persistDataToDisk:jsonData];
[self.harvestData clear];
} else {
[self fireOnHarvestFailure];
}
} else {
// success
[self.harvestData clear];
[connection sendOfflineStorage];
}
//Supportability/MobileAgent/Collector/Harvest
continues:
[harvestTimer stopTimer];
#ifndef DISABLE_NRMA_EXCEPTION_WRAPPER
@try {
Expand Down
3 changes: 3 additions & 0 deletions Agent/Harvester/NRMAHarvesterConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#import "NRLogger.h"
#import "NRMAJSON.h"
#import "NRMAConnection.h"
#import "NRMAOfflineStorage.h"

#define kCOLLECTOR_CONNECT_URI @"/mobile/v4/connect"
#define kCOLLECTOR_DATA_URL @"/mobile/v3/data"
Expand All @@ -23,6 +24,7 @@
@property(assign) long long serverTimestamp;
@property(strong) NRMAConnectInformation* connectionInformation;
@property(strong) NSURLSession* harvestSession;
@property(strong) NRMAOfflineStorage* offlineStorage;

- (id) init;
- (NSURLRequest*) createPostWithURI:(NSString*)uri message:(NSString*)message;
Expand All @@ -31,4 +33,5 @@
- (NRMAHarvestResponse*) sendData:(NRMAHarvestable*)harvestable;
- (NSURLRequest*) createConnectPost:(NSString*)message;
- (NSURLRequest*) createDataPost:(NSString*)message;
- (void) sendOfflineStorage;
@end
23 changes: 23 additions & 0 deletions Agent/Harvester/NRMAHarvesterConnection.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,31 @@ - (id) init
self = [super init];
if (self) {
self.harvestSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
self.offlineStorage = [[NRMAOfflineStorage alloc] initWithEndpoint:@"data"];
}
return self;
}

-(void) sendOfflineStorage {
NSArray<NSData *> * offlineData = [self.offlineStorage getAllOfflineData];
if(offlineData.count == 0){
return;
}
NRLOG_VERBOSE(@"Number of offline data posts: %lu", (unsigned long)offlineData.count);

[offlineData enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSData *jsonData = (NSData *)obj;

NSURLRequest* post = [self createDataPost:[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]];
if (post == nil) {
NRLOG_ERROR(@"Failed to create data POST");
return;
}

[self send:post];
}];
}

- (NSURLRequest*) createPostWithURI:(NSString*)url message:(NSString*)message
{
NSMutableURLRequest * postRequest = [super newPostWithURI:url];
Expand Down Expand Up @@ -169,6 +191,7 @@ - (NRMAHarvestResponse*) sendData:(NRMAHarvestable *)harvestable
NRLOG_ERROR(@"Failed to generate JSON");
return nil;
}

NSURLRequest* post = [self createDataPost:[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]];
if (post == nil) {
NRLOG_ERROR(@"Failed to create data POST");
Expand Down
16 changes: 16 additions & 0 deletions Agent/Utilities/NRMAOfflineStorage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// NRMAOfflineStorage.h
// Agent
//
// Created by Mike Bruin on 11/17/23.
// Copyright © 2023 New Relic. All rights reserved.
//

@interface NRMAOfflineStorage : NSObject


- (id)initWithEndpoint:(NSString*) name;
- (BOOL)persistDataToDisk:(NSData*) data;
- (NSArray<NSData *> *) getAllOfflineData;

@end
90 changes: 90 additions & 0 deletions Agent/Utilities/NRMAOfflineStorage.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//
// NRMAOfflineStorage.m
// Agent
//
// Created by Mike Bruin on 11/17/23.
// Copyright © 2023 New Relic. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "NewRelicInternalUtils.h"
#import "NRLogger.h"
#import "NRMAOfflineStorage.h"
#import "Constants.h"
#import "NRMAJSON.h"

@implementation NRMAOfflineStorage {
}
static NSString* _name;


- (id)initWithEndpoint:(NSString*) name {
self = [super init];
if (self) {
_name = name;
}
return self;
}

- (void) createDirectory {
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[NRMAOfflineStorage offlineDirectoryPath] isDirectory:nil];
if(!fileExists){
NSError *error = nil;
if(![[NSFileManager defaultManager] createDirectoryAtPath:[NRMAOfflineStorage offlineDirectoryPath] withIntermediateDirectories:YES attributes:nil error:&error]) {
NSLog(@"Failed to create directory \"%@\". Error: %@", [NRMAOfflineStorage offlineDirectoryPath], error);
}
}
}

- (BOOL) persistDataToDisk:(NSData*) data {
@synchronized (_name) {
[self createDirectory];

NSError *error = nil;
if (data) {
if ([data writeToFile:[NRMAOfflineStorage newOfflineFilePath] options:NSDataWritingAtomic error:&error]) {
return YES;
}
}
NRLOG_ERROR(@"Failed to persist data to disk %@", error.description);

return NO;
}
}

- (NSArray<NSData *> *) getAllOfflineData {
@synchronized (_name) {
NSMutableArray<NSData *> *combinedPosts = [NSMutableArray array];

NSArray* dirs = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[NRMAOfflineStorage offlineDirectoryPath]
error:NULL];
[dirs enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString *filename = (NSString *)obj;
NSData * data = [NSData dataWithContentsOfFile:[NSString stringWithFormat:@"%@/%@",[NRMAOfflineStorage offlineDirectoryPath],filename]];

[combinedPosts addObject:data];
}];

[self clearAllOfflineFiles];

return [combinedPosts copy];
}
}

- (BOOL) clearAllOfflineFiles {
return [[NSFileManager defaultManager] removeItemAtPath:[NRMAOfflineStorage offlineDirectoryPath] error:NULL];
}

+ (NSString*)offlineDirectoryPath {
return [NSString stringWithFormat:@"%@/%@/%@",[NewRelicInternalUtils getStorePath],kNRMA_Offline_file,_name];
}

+ (NSString*)newOfflineFilePath {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd-HH-mm-ss"];
NSString *date = [dateFormatter stringFromDate:[NSDate date]];

return [NSString stringWithFormat:@"%@/%@%@",[NRMAOfflineStorage offlineDirectoryPath],date,@".txt"];
}

@end
2 changes: 1 addition & 1 deletion Test Harness/NRTestApp/NRTestApp/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
#endif
NewRelic.addHTTPHeaderTracking(for: ["Test"])
NewRelic.enableFeatures([NRMAFeatureFlags.NRFeatureFlag_SwiftAsyncURLSessionSupport,
NRMAFeatureFlags.NRFeatureFlag_SwiftInteractionTracing])
NRMAFeatureFlags.NRFeatureFlag_NewEventSystem])

// NewRelic.enableFeatures([NRMAFeatureFlags.NRFeatureFlag_NewEventSystem])

Expand Down
18 changes: 16 additions & 2 deletions Test Harness/NRTestApp/NRTestApp/ViewModels/UtilViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class UtilViewModel {
var badAttribute = false
var attributes = ""
var events = 0

var timer:Timer?

var uniqueInteractionTraceIdentifier: String? = nil

Expand All @@ -37,14 +39,26 @@ class UtilViewModel {
options.append(UtilOption(title: "Record Error", handler: { [self] in makeError()}))
options.append(UtilOption(title: "Record Handled Exception", handler: { triggerException.testing()}))
options.append(UtilOption(title: "Set UserID", handler: { [self] in changeUserID()}))
options.append(UtilOption(title: "Make 100 events", handler: { [self] in make100Events()}))
options.append(UtilOption(title: "Make 100 events every 10 seconds", handler: { [self] in startCustomEventTimer()}))
options.append(UtilOption(title: "Stop 100 events every 10 seconds", handler: { [self] in stopCustomEventTimer()}))
options.append(UtilOption(title: "Start Interaction Trace", handler: { [self] in startInteractionTrace()}))
options.append(UtilOption(title: "End Interaction Trace", handler: { [self] in stopInteractionTrace()}))
options.append(UtilOption(title: "Notice Network Request", handler: { [self] in noticeNWRequest()}))
options.append(UtilOption(title: "Notice Network Failure", handler: { [self] in noticeFailedNWRequest()}))
options.append(UtilOption(title: "URLSession dataTask", handler: { [self] in doDataTask()}))
options.append(UtilOption(title: "Shut down New Relic Agent", handler: { [self] in shutDown()}))
}

func startCustomEventTimer(){
NewRelic.logInfo("Staring custom event timer")
timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(make100Events), userInfo: nil, repeats: true)
}

func stopCustomEventTimer(){
NewRelic.logInfo("Stopping custom event timer")
timer?.invalidate()
timer = nil
}

func crash() {
// This will cause a crash to test the crash uploader, crash files may not get recorded if the debugger is running.
Expand Down Expand Up @@ -101,7 +115,7 @@ class UtilViewModel {
}
}

func make100Events() {
@objc func make100Events() {
for _ in 0...100 {
NewRelic.recordCustomEvent("ButtonPress")
}
Expand Down