Преглед на файлове

Merge sessions-main with master (#10787)

Sam Edson преди 3 години
родител
ревизия
729eb40cb2
променени са 36 файла, в които са добавени 519 реда и са изтрити 188 реда
  1. 3 0
      Crashlytics/CHANGELOG.md
  2. 7 4
      Crashlytics/Crashlytics/Components/FIRCLSContext.h
  3. 15 16
      Crashlytics/Crashlytics/Components/FIRCLSContext.m
  4. 43 0
      Crashlytics/Crashlytics/Controllers/FIRCLSContextManager.h
  5. 78 0
      Crashlytics/Crashlytics/Controllers/FIRCLSContextManager.m
  6. 4 0
      Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h
  7. 2 0
      Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.m
  8. 1 0
      Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.h
  9. 7 1
      Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.m
  10. 42 4
      Crashlytics/Crashlytics/FIRCrashlytics.m
  11. 1 0
      Crashlytics/Crashlytics/Models/Record/FIRCLSRecordIdentity.h
  12. 1 0
      Crashlytics/Crashlytics/Models/Record/FIRCLSRecordIdentity.m
  13. 1 0
      Crashlytics/Crashlytics/Models/Record/FIRCLSReportAdapter.m
  14. 119 0
      Crashlytics/UnitTests/FIRCLSContextManagerTests.m
  15. 5 1
      Crashlytics/UnitTests/FIRCLSOnDemandModelTests.m
  16. 5 1
      Crashlytics/UnitTests/FIRRecordExceptionModelTests.m
  17. 2 1
      FirebaseCrashlytics.podspec
  18. 2 1
      FirebasePerformance.podspec
  19. 4 0
      FirebasePerformance/CHANGELOG.md
  20. 0 3
      FirebasePerformance/Sources/AppActivity/FPRAppActivityTracker.m
  21. 1 1
      FirebasePerformance/Sources/AppActivity/FPRSessionDetails.h
  22. 2 2
      FirebasePerformance/Sources/AppActivity/FPRSessionDetails.m
  23. 17 3
      FirebasePerformance/Sources/AppActivity/FPRSessionManager+Private.h
  24. 4 4
      FirebasePerformance/Sources/AppActivity/FPRSessionManager.h
  25. 26 50
      FirebasePerformance/Sources/AppActivity/FPRSessionManager.m
  26. 34 3
      FirebasePerformance/Sources/FPRClient.m
  27. 2 2
      FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.m
  28. 2 2
      FirebasePerformance/Sources/Timer/FIRTrace.m
  29. 5 13
      FirebasePerformance/Tests/Unit/FPRNetworkTraceTest.m
  30. 4 2
      FirebasePerformance/Tests/Unit/FPRSessionDetailsTest.m
  31. 69 54
      FirebasePerformance/Tests/Unit/FPRSessionManagerTest.m
  32. 5 15
      FirebasePerformance/Tests/Unit/Timer/FIRTraceTest.m
  33. 2 2
      FirebaseSessions.podspec
  34. 1 1
      FirebaseSessions/Sources/Public/SessionsSubscriber.swift
  35. 2 1
      Package.swift
  36. 1 1
      scripts/localize_podfile.swift

+ 3 - 0
Crashlytics/CHANGELOG.md

@@ -1,3 +1,6 @@
+# Unreleased
+- [added] Added dependency on Firebase Sessions SDK to power future metrics and debugging features in Crashlytics
+
 # 10.4.0
 - [added] Updated Crashlytics to include the Firebase Installation ID for consistency with other products (#10645).
 

+ 7 - 4
Crashlytics/Crashlytics/Components/FIRCLSContext.h

@@ -86,6 +86,7 @@ typedef struct {
   const char* rootPath;
   const char* previouslyCrashedFileRootPath;
   const char* sessionId;
+  const char* appQualitySessionId;
   const char* betaToken;
   bool errorsEnabled;
   bool customExceptionsEnabled;
@@ -96,10 +97,12 @@ typedef struct {
 } FIRCLSContextInitData;
 
 #ifdef __OBJC__
-bool FIRCLSContextInitialize(FIRCLSInternalReport* report,
-                             FIRCLSSettings* settings,
-                             FIRCLSFileManager* fileManager);
-
+bool FIRCLSContextInitialize(FIRCLSContextInitData* initData, FIRCLSFileManager* fileManager);
+FIRCLSContextInitData FIRCLSContextBuildInitData(FIRCLSInternalReport* report,
+                                                 FIRCLSSettings* settings,
+                                                 FIRCLSFileManager* fileManager,
+                                                 NSString* appQualitySessionId);
+bool FIRCLSContextRecordMetadata(NSString* rootPath, const FIRCLSContextInitData* initData);
 #endif
 
 void FIRCLSContextBaseInit(void);

+ 15 - 16
Crashlytics/Crashlytics/Components/FIRCLSContext.m

@@ -47,13 +47,13 @@
 
 static const int64_t FIRCLSContextInitWaitTime = 5LL * NSEC_PER_SEC;
 
-static bool FIRCLSContextRecordMetadata(const char* path, const FIRCLSContextInitData* initData);
 static const char* FIRCLSContextAppendToRoot(NSString* root, NSString* component);
 static void FIRCLSContextAllocate(FIRCLSContext* context);
 
 FIRCLSContextInitData FIRCLSContextBuildInitData(FIRCLSInternalReport* report,
                                                  FIRCLSSettings* settings,
-                                                 FIRCLSFileManager* fileManager) {
+                                                 FIRCLSFileManager* fileManager,
+                                                 NSString* appQualitySessionId) {
   // Because we need to start the crash reporter right away,
   // it starts up either with default settings, or cached settings
   // from the last time they were fetched
@@ -64,6 +64,7 @@ FIRCLSContextInitData FIRCLSContextBuildInitData(FIRCLSInternalReport* report,
 
   initData.customBundleId = nil;
   initData.sessionId = [[report identifier] UTF8String];
+  initData.appQualitySessionId = [appQualitySessionId UTF8String];
   initData.rootPath = [[report path] UTF8String];
   initData.previouslyCrashedFileRootPath = [[fileManager rootPath] UTF8String];
   initData.errorsEnabled = [settings errorReportingEnabled];
@@ -77,12 +78,7 @@ FIRCLSContextInitData FIRCLSContextBuildInitData(FIRCLSInternalReport* report,
   return initData;
 }
 
-bool FIRCLSContextInitialize(FIRCLSInternalReport* report,
-                             FIRCLSSettings* settings,
-                             FIRCLSFileManager* fileManager) {
-  FIRCLSContextInitData initDataObj = FIRCLSContextBuildInitData(report, settings, fileManager);
-  FIRCLSContextInitData* initData = &initDataObj;
-
+bool FIRCLSContextInitialize(FIRCLSContextInitData* initData, FIRCLSFileManager* fileManager) {
   if (!initData) {
     return false;
   }
@@ -100,7 +96,7 @@ bool FIRCLSContextInitialize(FIRCLSInternalReport* report,
 
   // setup our SDK log file synchronously, because other calls may depend on it
   _firclsContext.readonly->logPath = FIRCLSContextAppendToRoot(rootPath, @"sdk.log");
-  _firclsContext.readonly->initialReportPath = FIRCLSDupString([report.path UTF8String]);
+  _firclsContext.readonly->initialReportPath = FIRCLSDupString(initData->rootPath);
   if (!FIRCLSUnlinkIfExists(_firclsContext.readonly->logPath)) {
     FIRCLSErrorLog(@"Unable to write initialize SDK write paths %s", strerror(errno));
   }
@@ -209,9 +205,7 @@ bool FIRCLSContextInitialize(FIRCLSInternalReport* report,
   }
 
   dispatch_group_async(group, queue, ^{
-    const char* metaDataPath = [[rootPath stringByAppendingPathComponent:FIRCLSReportMetadataFile]
-        fileSystemRepresentation];
-    if (!FIRCLSContextRecordMetadata(metaDataPath, initData)) {
+    if (!FIRCLSContextRecordMetadata(rootPath, initData)) {
       FIRCLSSDKLog("Unable to record context metadata\n");
     }
   });
@@ -360,7 +354,8 @@ static const char* FIRCLSContextAppendToRoot(NSString* root, NSString* component
 
 static bool FIRCLSContextRecordIdentity(FIRCLSFile* file,
                                         const char* sessionId,
-                                        const char* betaToken) {
+                                        const char* betaToken,
+                                        const char* appQualitySessionId) {
   FIRCLSFileWriteSectionStart(file, "identity");
 
   FIRCLSFileWriteHashStart(file);
@@ -371,6 +366,8 @@ static bool FIRCLSContextRecordIdentity(FIRCLSFile* file,
   FIRCLSFileWriteHashEntryUint64(file, "started_at", time(NULL));
 
   FIRCLSFileWriteHashEntryString(file, "session_id", sessionId);
+  FIRCLSFileWriteHashEntryString(file, "app_quality_session_id", appQualitySessionId);
+
   // install_id is written into the proto directly. This is only left here to
   // support Apple Report Converter.
   FIRCLSFileWriteHashEntryString(file, "install_id", "");
@@ -404,11 +401,13 @@ static bool FIRCLSContextRecordApplication(FIRCLSFile* file, const char* customB
   return true;
 }
 
-static bool FIRCLSContextRecordMetadata(const char* path, const FIRCLSContextInitData* initData) {
+bool FIRCLSContextRecordMetadata(NSString* rootPath, const FIRCLSContextInitData* initData) {
   const char* sessionId = initData->sessionId;
   const char* betaToken = initData->betaToken;
   const char* customBundleId = initData->customBundleId;
-
+  const char* appQualitySessionId = initData->appQualitySessionId;
+  const char* path =
+      [[rootPath stringByAppendingPathComponent:FIRCLSReportMetadataFile] fileSystemRepresentation];
   if (!FIRCLSUnlinkIfExists(path)) {
     FIRCLSSDKLog("Unable to unlink existing metadata file %s\n", strerror(errno));
   }
@@ -420,7 +419,7 @@ static bool FIRCLSContextRecordMetadata(const char* path, const FIRCLSContextIni
     return false;
   }
 
-  if (!FIRCLSContextRecordIdentity(&file, sessionId, betaToken)) {
+  if (!FIRCLSContextRecordIdentity(&file, sessionId, betaToken, appQualitySessionId)) {
     FIRCLSSDKLog("Unable to write out identity metadata\n");
   }
 

+ 43 - 0
Crashlytics/Crashlytics/Controllers/FIRCLSContextManager.h

@@ -0,0 +1,43 @@
+//
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import <Foundation/Foundation.h>
+
+#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
+#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
+#import "Crashlytics/Crashlytics/Models/FIRCLSSettings.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+///
+/// The ContextManager determines when to build the context object,
+/// and write its metadata. It was created because the FIRCLSContext
+/// is interacted with via functions, which makes it hard to include in tests.
+/// In addition, we this class is responsible for re-writing the Metadata object
+/// when the App Quality Session ID changes.
+///
+@interface FIRCLSContextManager : NSObject
+
+/// This should be set immediately when the FirebaseSessions SDK generates
+/// a new Session ID.
+@property(nonatomic, copy) NSString *appQualitySessionId;
+
+- (BOOL)setupContextWithReport:(FIRCLSInternalReport *)report
+                      settings:(FIRCLSSettings *)settings
+                   fileManager:(FIRCLSFileManager *)fileManager;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 78 - 0
Crashlytics/Crashlytics/Controllers/FIRCLSContextManager.m

@@ -0,0 +1,78 @@
+//
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "Crashlytics/Crashlytics/Controllers/FIRCLSContextManager.h"
+
+#import "Crashlytics/Crashlytics/Components/FIRCLSContext.h"
+
+@interface FIRCLSContextManager ()
+
+@property(nonatomic, assign) BOOL hasInitializedContext;
+
+@property(nonatomic, strong) FIRCLSInternalReport *report;
+@property(nonatomic, strong) FIRCLSSettings *settings;
+@property(nonatomic, strong) FIRCLSFileManager *fileManager;
+
+@end
+
+@implementation FIRCLSContextManager
+
+- (instancetype)init {
+  self = [super init];
+  if (!self) {
+    return self;
+  }
+
+  _appQualitySessionId = @"";
+
+  return self;
+}
+
+- (BOOL)setupContextWithReport:(FIRCLSInternalReport *)report
+                      settings:(FIRCLSSettings *)settings
+                   fileManager:(FIRCLSFileManager *)fileManager {
+  _report = report;
+  _settings = settings;
+  _fileManager = fileManager;
+
+  _hasInitializedContext = true;
+
+  FIRCLSContextInitData initDataObj = self.buildInitData;
+  return FIRCLSContextInitialize(&initDataObj, self.fileManager);
+}
+
+- (void)setAppQualitySessionId:(NSString *)appQualitySessionId {
+  _appQualitySessionId = appQualitySessionId;
+
+  // This may be called before the context is originally initialized. In that case
+  // skip the write because it will be written as soon as the context is initialized.
+  // On future Session ID updates, this will be true and the context metadata will be
+  // rewritten.
+  if (!self.hasInitializedContext) {
+    return;
+  }
+
+  FIRCLSContextInitData initDataObj = self.buildInitData;
+  if (!FIRCLSContextRecordMetadata(self.report.path, &initDataObj)) {
+    FIRCLSErrorLog(@"Failed to write context file while updating App Quality Session ID");
+  }
+}
+
+- (FIRCLSContextInitData)buildInitData {
+  return FIRCLSContextBuildInitData(self.report, self.settings, self.fileManager,
+                                    self.appQualitySessionId);
+}
+
+@end

+ 4 - 0
Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h

@@ -25,6 +25,7 @@ NS_ASSUME_NONNULL_BEGIN
 @class FIRCLSOnDemandModel;
 @class FIRCLSSettings;
 @class FIRCLSLaunchMarkerModel;
+@class FIRCLSContextManager;
 @class GDTCORTransport;
 @protocol FIRAnalyticsInterop;
 
@@ -80,6 +81,9 @@ NS_ASSUME_NONNULL_BEGIN
 // Settings fetched from the server
 @property(nonatomic, strong) FIRCLSSettings *settings;
 
+// Sets up the Context and writes Metadata files to the crash report
+@property(nonatomic, strong) FIRCLSContextManager *contextManager;
+
 // These queues function together as a single startup queue
 @property(nonatomic, strong) NSOperationQueue *operationQueue;
 @property(nonatomic, strong) dispatch_queue_t dispatchQueue;

+ 2 - 0
Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.m

@@ -15,6 +15,7 @@
 #import "Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h"
 
 #import "Crashlytics/Crashlytics/Components/FIRCLSApplication.h"
+#import "Crashlytics/Crashlytics/Controllers/FIRCLSContextManager.h"
 #import "Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.h"
 #import "Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.h"
 #import "Crashlytics/Crashlytics/Models/FIRCLSSettings.h"
@@ -44,6 +45,7 @@
   _dataArbiter = dataArbiter;
   _settings = settings;
   _onDemandModel = onDemandModel;
+  _contextManager = [[FIRCLSContextManager alloc] init];
 
   _appIDModel = [[FIRCLSApplicationIdentifierModel alloc] init];
   _installIDModel = [[FIRCLSInstallIdentifierModel alloc] initWithInstallations:installations];

+ 1 - 0
Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.h

@@ -22,6 +22,7 @@
 @class FIRCLSExistingReportManager;
 @class FIRCLSAnalyticsManager;
 @class FIRCLSManagerData;
+@class FIRCLSContextManager;
 
 NS_ASSUME_NONNULL_BEGIN
 

+ 7 - 1
Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.m

@@ -41,6 +41,7 @@
 #import "Crashlytics/Crashlytics/Components/FIRCLSApplication.h"
 #import "Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h"
 #import "Crashlytics/Crashlytics/Controllers/FIRCLSAnalyticsManager.h"
+#import "Crashlytics/Crashlytics/Controllers/FIRCLSContextManager.h"
 #import "Crashlytics/Crashlytics/Controllers/FIRCLSExistingReportManager.h"
 #import "Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h"
 #import "Crashlytics/Crashlytics/Controllers/FIRCLSMetricKitManager.h"
@@ -135,6 +136,8 @@ typedef NSNumber FIRCLSWrappedReportAction;
 @property(nonatomic, strong) FIRCLSAnalyticsManager *analyticsManager;
 @property(nonatomic, strong) FIRCLSExistingReportManager *existingReportManager;
 
+@property(nonatomic, strong) FIRCLSContextManager *contextManager;
+
 // Internal Managers
 @property(nonatomic, strong) FIRCLSSettingsManager *settingsManager;
 @property(nonatomic, strong) FIRCLSNotificationManager *notificationManager;
@@ -165,6 +168,7 @@ typedef NSNumber FIRCLSWrappedReportAction;
   _installIDModel = managerData.installIDModel;
   _settings = managerData.settings;
   _executionIDModel = managerData.executionIDModel;
+  _contextManager = managerData.contextManager;
 
   _existingReportManager = existingReportManager;
   _analyticsManager = analyticsManager;
@@ -416,7 +420,9 @@ typedef NSNumber FIRCLSWrappedReportAction;
     return NO;
   }
 
-  if (!FIRCLSContextInitialize(report, self.settings, _fileManager)) {
+  if (![self.contextManager setupContextWithReport:report
+                                          settings:self.settings
+                                       fileManager:_fileManager]) {
     return NO;
   }
 

+ 42 - 4
Crashlytics/Crashlytics/FIRCrashlytics.m

@@ -41,6 +41,7 @@
 #import "Crashlytics/Shared/FIRCLSFABHost.h"
 
 #import "Crashlytics/Crashlytics/Controllers/FIRCLSAnalyticsManager.h"
+#import "Crashlytics/Crashlytics/Controllers/FIRCLSContextManager.h"
 #import "Crashlytics/Crashlytics/Controllers/FIRCLSExistingReportManager.h"
 #import "Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h"
 #import "Crashlytics/Crashlytics/Controllers/FIRCLSNotificationManager.h"
@@ -56,6 +57,8 @@
 
 #import <GoogleDataTransport/GoogleDataTransport.h>
 
+@import FirebaseSessions;
+
 #if TARGET_OS_IPHONE
 #import <UIKit/UIKit.h>
 #endif
@@ -73,7 +76,7 @@ NSString *const FIRCLSGoogleTransportMappingID = @"1206";
 @protocol FIRCrashlyticsInstanceProvider <NSObject>
 @end
 
-@interface FIRCrashlytics () <FIRLibrary, FIRCrashlyticsInstanceProvider>
+@interface FIRCrashlytics () <FIRLibrary, FIRCrashlyticsInstanceProvider, FIRSessionsSubscriber>
 
 @property(nonatomic) BOOL didPreviouslyCrash;
 @property(nonatomic, copy) NSString *googleAppID;
@@ -100,7 +103,8 @@ NSString *const FIRCLSGoogleTransportMappingID = @"1206";
 - (instancetype)initWithApp:(FIRApp *)app
                     appInfo:(NSDictionary *)appInfo
               installations:(FIRInstallations *)installations
-                  analytics:(id<FIRAnalyticsInterop>)analytics {
+                  analytics:(id<FIRAnalyticsInterop>)analytics
+                   sessions:(id<FIRSessionsProvider>)sessions {
   self = [super init];
 
   if (self) {
@@ -141,6 +145,18 @@ NSString *const FIRCLSGoogleTransportMappingID = @"1206";
                                                          settings:settings
                                                     onDemandModel:onDemandModel];
 
+    if (sessions) {
+      FIRCLSDebugLog(@"Registering Sessions SDK subscription for session data");
+
+      // Subscription should be made after the DataCollectionArbiter
+      // is initialized so that the Sessions SDK can immediately get
+      // the data collection state.
+      //
+      // It should also be made after managerData is initialized so
+      // that the ContextManager can accept data
+      [sessions registerWithSubscriber:self];
+    }
+
     _reportUploader = [[FIRCLSReportUploader alloc] initWithManagerData:_managerData];
 
     _existingReportManager =
@@ -180,12 +196,16 @@ NSString *const FIRCLSGoogleTransportMappingID = @"1206";
 
 + (void)load {
   [FIRApp registerInternalLibrary:(Class<FIRLibrary>)self withName:@"firebase-crashlytics"];
+  [FIRSessionsDependencies addDependencyWithName:FIRSessionsSubscriberNameCrashlytics];
 }
 
 + (NSArray<FIRComponent *> *)componentsToRegister {
   FIRDependency *analyticsDep =
       [FIRDependency dependencyWithProtocol:@protocol(FIRAnalyticsInterop)];
 
+  FIRDependency *sessionsDep =
+      [FIRDependency dependencyWithProtocol:@protocol(FIRSessionsProvider)];
+
   FIRComponentCreationBlock creationBlock =
       ^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) {
     if (!container.app.isDefaultApp) {
@@ -194,6 +214,7 @@ NSString *const FIRCLSGoogleTransportMappingID = @"1206";
     }
 
     id<FIRAnalyticsInterop> analytics = FIR_COMPONENT(FIRAnalyticsInterop, container);
+    id<FIRSessionsProvider> sessions = FIR_COMPONENT(FIRSessionsProvider, container);
 
     FIRInstallations *installations = [FIRInstallations installationsWithApp:container.app];
 
@@ -202,13 +223,14 @@ NSString *const FIRCLSGoogleTransportMappingID = @"1206";
     return [[FIRCrashlytics alloc] initWithApp:container.app
                                        appInfo:NSBundle.mainBundle.infoDictionary
                                  installations:installations
-                                     analytics:analytics];
+                                     analytics:analytics
+                                      sessions:sessions];
   };
 
   FIRComponent *component =
       [FIRComponent componentWithProtocol:@protocol(FIRCrashlyticsInstanceProvider)
                       instantiationTiming:FIRInstantiationTimingEagerInDefaultApp
-                             dependencies:@[ analyticsDep ]
+                             dependencies:@[ analyticsDep, sessionsDep ]
                             creationBlock:creationBlock];
   return @[ component ];
 }
@@ -369,4 +391,20 @@ NSString *const FIRCLSGoogleTransportMappingID = @"1206";
           usingExistingReportManager:self.existingReportManager];
 }
 
+#pragma mark - FIRSessionsSubscriber
+
+- (void)onSessionChanged:(FIRSessionDetails *_Nonnull)session {
+  FIRCLSDebugLog(@"Session ID changed: %@", session.sessionId.copy);
+
+  [self.managerData.contextManager setAppQualitySessionId:session.sessionId.copy];
+}
+
+- (BOOL)isDataCollectionEnabled {
+  return self.dataArbiter.isCrashlyticsCollectionEnabled;
+}
+
+- (FIRSessionsSubscriberName)sessionsSubscriberName {
+  return FIRSessionsSubscriberNameCrashlytics;
+}
+
 @end

+ 1 - 0
Crashlytics/Crashlytics/Models/Record/FIRCLSRecordIdentity.h

@@ -19,5 +19,6 @@
 @interface FIRCLSRecordIdentity : FIRCLSRecordBase
 
 @property(nonatomic, copy) NSString *build_version;
+@property(nonatomic, copy) NSString *app_quality_session_id;
 
 @end

+ 1 - 0
Crashlytics/Crashlytics/Models/Record/FIRCLSRecordIdentity.m

@@ -22,6 +22,7 @@
   self = [super initWithDict:dict];
   if (self) {
     _build_version = dict[@"build_version"];
+    _app_quality_session_id = dict[@"app_quality_session_id"];
   }
   return self;
 }

+ 1 - 0
Crashlytics/Crashlytics/Models/Record/FIRCLSReportAdapter.m

@@ -155,6 +155,7 @@
   report.platform = [self protoPlatformFromString:self.host.platform];
   report.installation_uuid = FIRCLSEncodeString(self.installIDModel.installID);
   report.firebase_installation_id = FIRCLSEncodeString(self.fiid);
+  report.app_quality_session_id = FIRCLSEncodeString(self.identity.app_quality_session_id);
   report.build_version = FIRCLSEncodeString(self.application.build_version);
   report.display_version = FIRCLSEncodeString(self.application.display_version);
   report.apple_payload = [self protoFilesPayload];

+ 119 - 0
Crashlytics/UnitTests/FIRCLSContextManagerTests.m

@@ -0,0 +1,119 @@
+//
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import <XCTest/XCTest.h>
+
+#import "Crashlytics/Crashlytics/Controllers/FIRCLSContextManager.h"
+#import "Crashlytics/Crashlytics/Models/Record/FIRCLSReportAdapter.h"
+#import "Crashlytics/Crashlytics/Models/Record/FIRCLSReportAdapter_Private.h"
+#import "Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.h"
+
+#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
+#import "Crashlytics/UnitTests/Mocks/FIRCLSMockFileManager.h"
+#import "Crashlytics/UnitTests/Mocks/FIRCLSMockSettings.h"
+#import "Crashlytics/UnitTests/Mocks/FIRMockInstallations.h"
+
+NSString *const TestContextReportID = @"TestContextReportID";
+NSString *const TestContextSessionID = @"TestContextSessionID";
+NSString *const TestContextSessionID2 = @"TestContextSessionID2";
+
+@interface FIRCLSContextManagerTests : XCTestCase
+
+@property(nonatomic, strong) FIRCLSMockFileManager *fileManager;
+@property(nonatomic, strong) FIRCLSMockSettings *mockSettings;
+@property(nonatomic, strong) FIRCLSContextManager *contextManager;
+@property(nonatomic, strong) FIRCLSInternalReport *report;
+@property(nonatomic, strong) FIRCLSInstallIdentifierModel *installIDModel;
+@end
+
+@implementation FIRCLSContextManagerTests
+
+- (void)setUp {
+  self.fileManager = [[FIRCLSMockFileManager alloc] init];
+  [self.fileManager createReportDirectories];
+  [self.fileManager setupNewPathForExecutionIdentifier:TestContextReportID];
+
+  FIRCLSApplicationIdentifierModel *appIDModel = [[FIRCLSApplicationIdentifierModel alloc] init];
+  _mockSettings = [[FIRCLSMockSettings alloc] initWithFileManager:self.fileManager
+                                                       appIDModel:appIDModel];
+
+  //  NSString *name = @"exception_model_report";
+  NSString *reportPath =
+      [self.fileManager.activePath stringByAppendingPathComponent:TestContextReportID];
+
+  self.report = [[FIRCLSInternalReport alloc] initWithPath:reportPath
+                                       executionIdentifier:TestContextReportID];
+
+  self.contextManager = [[FIRCLSContextManager alloc] init];
+
+  FIRMockInstallations *iid = [[FIRMockInstallations alloc] initWithFID:@"test_token"];
+  self.installIDModel = [[FIRCLSInstallIdentifierModel alloc] initWithInstallations:iid];
+}
+
+- (void)tearDown {
+  [[NSFileManager defaultManager] removeItemAtPath:self.fileManager.rootPath error:nil];
+  [super tearDown];
+}
+
+- (void)test_notSettingSessionID_protoHasNilSessionID {
+  [self.contextManager setupContextWithReport:self.report
+                                     settings:self.mockSettings
+                                  fileManager:self.fileManager];
+
+  FIRCLSReportAdapter *adapter = [[FIRCLSReportAdapter alloc] initWithPath:self.report.path
+                                                               googleAppId:@"TestGoogleAppID"
+                                                            installIDModel:self.installIDModel
+                                                                      fiid:@"TestFIID"];
+
+  XCTAssertEqualObjects(adapter.identity.app_quality_session_id, @"");
+}
+
+- (void)test_settingSessionIDMultipleTimes_protoHasLastSessionID {
+  [self.contextManager setAppQualitySessionId:TestContextSessionID];
+
+  [self.contextManager setupContextWithReport:self.report
+                                     settings:self.mockSettings
+                                  fileManager:self.fileManager];
+
+  [self.contextManager setAppQualitySessionId:TestContextSessionID2];
+
+  FIRCLSReportAdapter *adapter = [[FIRCLSReportAdapter alloc] initWithPath:self.report.path
+                                                               googleAppId:@"TestGoogleAppID"
+                                                            installIDModel:self.installIDModel
+                                                                      fiid:@"TestFIID"];
+  NSLog(@"reportPath: %@", self.report.path);
+
+  XCTAssertEqualObjects(adapter.identity.app_quality_session_id, TestContextSessionID2);
+}
+
+- (void)test_settingSessionIDOutOfOrder_protoHasLastSessionID {
+  [self.contextManager setupContextWithReport:self.report
+                                     settings:self.mockSettings
+                                  fileManager:self.fileManager];
+
+  [self.contextManager setAppQualitySessionId:TestContextSessionID];
+
+  [self.contextManager setAppQualitySessionId:TestContextSessionID2];
+
+  FIRCLSReportAdapter *adapter = [[FIRCLSReportAdapter alloc] initWithPath:self.report.path
+                                                               googleAppId:@"TestGoogleAppID"
+                                                            installIDModel:self.installIDModel
+                                                                      fiid:@"TestFIID"];
+  NSLog(@"reportPath: %@", self.report.path);
+
+  XCTAssertEqualObjects(adapter.identity.app_quality_session_id, TestContextSessionID2);
+}
+
+@end

+ 5 - 1
Crashlytics/UnitTests/FIRCLSOnDemandModelTests.m

@@ -15,6 +15,7 @@
 #import <XCTest/XCTest.h>
 
 #include "Crashlytics/Crashlytics/Components/FIRCLSContext.h"
+#import "Crashlytics/Crashlytics/Controllers/FIRCLSContextManager.h"
 #import "Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h"
 #import "Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.h"
 #import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
@@ -96,7 +97,10 @@
       [[FIRCLSInternalReport alloc] initWithPath:reportPath
                              executionIdentifier:@"TEST_EXECUTION_IDENTIFIER"];
 
-  FIRCLSContextInitialize(report, self.mockSettings, self.fileManager);
+  FIRCLSContextManager *contextManager = [[FIRCLSContextManager alloc] init];
+  [contextManager setupContextWithReport:report
+                                settings:self.mockSettings
+                             fileManager:self.fileManager];
 }
 
 - (void)tearDown {

+ 5 - 1
Crashlytics/UnitTests/FIRRecordExceptionModelTests.m

@@ -18,6 +18,7 @@
 #import "Crashlytics/Crashlytics/Public/FirebaseCrashlytics/FIRStackFrame.h"
 
 #import "Crashlytics/Crashlytics/Components/FIRCLSContext.h"
+#import "Crashlytics/Crashlytics/Controllers/FIRCLSContextManager.h"
 #import "Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.h"
 #import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
 #import "Crashlytics/UnitTests/Mocks/FABMockApplicationIdentifierModel.h"
@@ -52,7 +53,10 @@
       [[FIRCLSInternalReport alloc] initWithPath:self.reportPath
                              executionIdentifier:@"TEST_EXECUTION_IDENTIFIER"];
 
-  FIRCLSContextInitialize(report, self.mockSettings, self.fileManager);
+  FIRCLSContextManager *contextManager = [[FIRCLSContextManager alloc] init];
+  [contextManager setupContextWithReport:report
+                                settings:self.mockSettings
+                             fileManager:self.fileManager];
 }
 
 - (void)tearDown {

+ 2 - 1
FirebaseCrashlytics.podspec

@@ -53,8 +53,9 @@ Pod::Spec.new do |s|
     cp -f ./Crashlytics/upload-symbols ./upload-symbols
   PREPARE_COMMAND_END
 
-  s.dependency 'FirebaseCore', '~> 10.0'
+  s.dependency 'FirebaseCore', '~> 10.5'
   s.dependency 'FirebaseInstallations', '~> 10.0'
+  s.dependency 'FirebaseSessions', '~> 10.5'
   s.dependency 'PromisesObjC', '~> 2.1'
   s.dependency 'GoogleDataTransport', '~> 9.2'
   s.dependency 'GoogleUtilities/Environment', '~> 7.8'

+ 2 - 1
FirebasePerformance.podspec

@@ -59,9 +59,10 @@ Firebase Performance library to measure performance of Mobile and Web Apps.
   s.ios.framework = 'CoreTelephony'
   s.framework = 'QuartzCore'
   s.framework = 'SystemConfiguration'
-  s.dependency 'FirebaseCore', '~> 10.0'
+  s.dependency 'FirebaseCore', '~> 10.5'
   s.dependency 'FirebaseInstallations', '~> 10.0'
   s.dependency 'FirebaseRemoteConfig', '~> 10.0'
+  s.dependency 'FirebaseSessions', '~> 10.5'
   s.dependency 'GoogleDataTransport', '~> 9.2'
   s.dependency 'GoogleUtilities/Environment', '~> 7.8'
   s.dependency 'GoogleUtilities/ISASwizzler', '~> 7.8'

+ 4 - 0
FirebasePerformance/CHANGELOG.md

@@ -1,3 +1,7 @@
+# Unreleased
+- [added] Added dependency on Firebase Sessions SDK to power future metrics and debugging features in Performance Monitoring.
+- [changed] Changed definition of sessions, as Performance Monitoring now depends on the new Firebase Sessions SDK.
+
 # 9.3.0
 - [changed] Update the console logging URL to troubleshooting page.
 

+ 0 - 3
FirebasePerformance/Sources/AppActivity/FPRAppActivityTracker.m

@@ -257,9 +257,6 @@ NSString *const kFPRAppCounterNameActivePrewarm = @"_fsapc";
       }
     });
   }
-
-  // Let the session manager to start tracking app activity changes.
-  [[FPRSessionManager sharedInstance] startTrackingAppStateChanges];
 }
 
 /**

+ 1 - 1
FirebasePerformance/Sources/AppActivity/FPRSessionDetails.h

@@ -31,7 +31,7 @@ typedef NS_OPTIONS(NSUInteger, FPRSessionOptions) {
 @property(nonatomic, readonly) FPRSessionOptions options;
 
 /* Length of the session in minutes. */
-@property(nonatomic, readonly) NSUInteger sessionLengthInMinutes;
+- (NSUInteger)sessionLengthInMinutesFromDate:(nonnull NSDate *)now;
 
 /**
  * Creates an instance of FPRSessionDetails with the provided sessionId and the list of available

+ 2 - 2
FirebasePerformance/Sources/AppActivity/FPRSessionDetails.m

@@ -40,8 +40,8 @@
   return detailsCopy;
 }
 
-- (NSUInteger)sessionLengthInMinutes {
-  NSTimeInterval sessionLengthInSeconds = ABS([self.sessionCreationTime timeIntervalSinceNow]);
+- (NSUInteger)sessionLengthInMinutesFromDate:(NSDate *)now {
+  NSTimeInterval sessionLengthInSeconds = ABS([now timeIntervalSinceDate:self.sessionCreationTime]);
   return (NSUInteger)(sessionLengthInSeconds / 60);
 }
 

+ 17 - 3
FirebasePerformance/Sources/AppActivity/FPRSessionManager+Private.h

@@ -15,20 +15,34 @@
 #import <Foundation/Foundation.h>
 
 #import "FirebasePerformance/Sources/AppActivity/FPRSessionManager.h"
+#import "FirebasePerformance/Sources/Gauges/FPRGaugeManager.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
 /** This extension should only be used for testing. */
 @interface FPRSessionManager ()
 
+@property(nonatomic) FPRGaugeManager *gaugeManager;
+
 /** The current active session managed by the session manager. Modifiable for unit tests */
 @property(atomic, nullable, readwrite) FPRSessionDetails *sessionDetails;
 
 /**
- * Checks if the currently active session is beyond maximum allowed time. If so renew the session,
- * else no-op.
+ * Creates an instance of FPRSesssionManager with the notification center provided. All the
+ * notifications from the session manager will sent using this notification center.
+ *
+ * @param gaugeManager Gauge manager used by the session manager to work with gauges.
+ * @param notificationCenter Notification center with which the session manager with be initialized.
+ * @return Returns an instance of the session manager.
+ */
+- (FPRSessionManager *)initWithGaugeManager:(FPRGaugeManager *)gaugeManager
+                         notificationCenter:(NSNotificationCenter *)notificationCenter;
+
+/**
+ * Checks if the currently active session is beyond maximum allowed time for gauge-collection. If so
+ * stop gauges, else no-op.
  */
-- (void)renewSessionIdIfRunningTooLong;
+- (void)stopGaugesIfRunningTooLong;
 
 @end
 

+ 4 - 4
FirebasePerformance/Sources/AppActivity/FPRSessionManager.h

@@ -46,9 +46,9 @@ NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions."
 
 - (nullable instancetype)init NS_UNAVAILABLE;
 
-/**
- * Starts tracking the application state changes to begin session ID state changes.
- */
-- (void)startTrackingAppStateChanges;
+- (void)updateSessionId:(nonnull NSString *)sessionIdString;
+
+// Collects all the enabled gauge metrics once.
+- (void)collectAllGaugesOnce;
 
 @end

+ 26 - 50
FirebasePerformance/Sources/AppActivity/FPRSessionManager.m

@@ -17,7 +17,6 @@
 
 #import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
 #import "FirebasePerformance/Sources/FPRConsoleLogger.h"
-#import "FirebasePerformance/Sources/Gauges/FPRGaugeManager.h"
 
 #import <UIKit/UIKit.h>
 
@@ -28,17 +27,6 @@ NSString *const kFPRSessionIdNotificationKey = @"kFPRSessionIdNotificationKey";
 
 @property(nonatomic, readwrite) NSNotificationCenter *sessionNotificationCenter;
 
-@property(nonatomic) BOOL trackingApplicationStateChanges;
-
-/**
- * Creates an instance of FPRSesssionManager with the notification center provided. All the
- * notifications from the session manager will sent using this notification center.
- *
- * @param notificationCenter Notification center with which the session manager with be initialized.
- * @return Returns an instance of the session manager.
- */
-- (instancetype)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter;
-
 @end
 
 @implementation FPRSessionManager
@@ -48,60 +36,52 @@ NSString *const kFPRSessionIdNotificationKey = @"kFPRSessionIdNotificationKey";
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
     NSNotificationCenter *notificationCenter = [[NSNotificationCenter alloc] init];
-    instance = [[FPRSessionManager alloc] initWithNotificationCenter:notificationCenter];
+    FPRGaugeManager *gaugeManager = [FPRGaugeManager sharedInstance];
+    instance = [[FPRSessionManager alloc] initWithGaugeManager:gaugeManager
+                                            notificationCenter:notificationCenter];
   });
   return instance;
 }
 
-- (FPRSessionManager *)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter {
+- (FPRSessionManager *)initWithGaugeManager:(FPRGaugeManager *)gaugeManager
+                         notificationCenter:(NSNotificationCenter *)notificationCenter {
   self = [super init];
   if (self) {
+    _gaugeManager = gaugeManager;
     _sessionNotificationCenter = notificationCenter;
-    _trackingApplicationStateChanges = NO;
-    [self updateSessionId:nil];
+    // Empty string is immediately replaced when FirebaseCore runs Fireperf's
+    // FIRComponentCreationBlock, because in the creation block we register Fireperf with Sessions,
+    // and the registration function immediately propagates real sessionId. This is at an early time
+    // in initialization that any trace is yet to be created.
+    _sessionDetails = [[FPRSessionDetails alloc] initWithSessionId:@""
+                                                           options:FPRSessionOptionsNone];
   }
   return self;
 }
 
-- (void)startTrackingAppStateChanges {
-  if (!self.trackingApplicationStateChanges) {
-    // Starts tracking the application life cycle events during which the session Ids change.
-    [[NSNotificationCenter defaultCenter] addObserver:self
-                                             selector:@selector(updateSessionId:)
-                                                 name:UIApplicationWillEnterForegroundNotification
-                                               object:[UIApplication sharedApplication]];
-    self.trackingApplicationStateChanges = YES;
-  }
-}
-
-- (void)renewSessionIdIfRunningTooLong {
+- (void)stopGaugesIfRunningTooLong {
   NSUInteger maxSessionLength = [[FPRConfigurations sharedInstance] maxSessionLengthInMinutes];
-  if (self.sessionDetails.sessionLengthInMinutes > maxSessionLength) {
-    [self updateSessionId:nil];
+  if ([self.sessionDetails sessionLengthInMinutesFromDate:[NSDate date]] >= maxSessionLength) {
+    [self.gaugeManager stopCollectingGauges:FPRGaugeCPU | FPRGaugeMemory];
   }
 }
 
 /**
- * Updates the sessionId on the arrival of a notification.
+ * Stops current session, and create a new session with new session id.
  *
- * @param notification Notification received.
+ * @param sessionIdString New session id.
  */
-- (void)updateSessionId:(NSNotification *)notification {
-  NSUUID *uuid = [NSUUID UUID];
-  NSString *sessionIdString = [uuid UUIDString];
-  sessionIdString = [sessionIdString stringByReplacingOccurrencesOfString:@"-" withString:@""];
-  sessionIdString = [sessionIdString lowercaseString];
-
+- (void)updateSessionId:(NSString *)sessionIdString {
   FPRSessionOptions sessionOptions = FPRSessionOptionsNone;
-  FPRGaugeManager *gaugeManager = [FPRGaugeManager sharedInstance];
   if ([self isGaugeCollectionEnabledForSessionId:sessionIdString]) {
-    [gaugeManager startCollectingGauges:FPRGaugeCPU | FPRGaugeMemory forSessionId:sessionIdString];
+    [self.gaugeManager startCollectingGauges:FPRGaugeCPU | FPRGaugeMemory
+                                forSessionId:sessionIdString];
     sessionOptions = FPRSessionOptionsGauges;
   } else {
-    [gaugeManager stopCollectingGauges:FPRGaugeCPU | FPRGaugeMemory];
+    [self.gaugeManager stopCollectingGauges:FPRGaugeCPU | FPRGaugeMemory];
   }
 
-  FPRLogDebug(kFPRSessionId, @"Session Id generated - %@", sessionIdString);
+  FPRLogDebug(kFPRSessionId, @"Session Id changed - %@", sessionIdString);
   FPRSessionDetails *sessionInfo = [[FPRSessionDetails alloc] initWithSessionId:sessionIdString
                                                                         options:sessionOptions];
   self.sessionDetails = sessionInfo;
@@ -113,6 +93,10 @@ NSString *const kFPRSessionIdNotificationKey = @"kFPRSessionIdNotificationKey";
                                               userInfo:[userInfo copy]];
 }
 
+- (void)collectAllGaugesOnce {
+  [self.gaugeManager collectAllGauges];
+}
+
 /**
  * Checks if the provided sessionId can have gauge data collection enabled.
  *
@@ -126,12 +110,4 @@ NSString *const kFPRSessionIdNotificationKey = @"kFPRSessionIdNotificationKey";
   return sessionsEnabled;
 }
 
-- (void)dealloc {
-  if (self.trackingApplicationStateChanges) {
-    [[NSNotificationCenter defaultCenter] removeObserver:self
-                                                    name:UIApplicationDidBecomeActiveNotification
-                                                  object:[UIApplication sharedApplication]];
-  }
-}
-
 @end

+ 34 - 3
FirebasePerformance/Sources/FPRClient.m

@@ -35,7 +35,9 @@
 
 #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
 
-@interface FPRClient () <FIRLibrary, FIRPerformanceProvider>
+@import FirebaseSessions;
+
+@interface FPRClient () <FIRLibrary, FIRPerformanceProvider, FIRSessionsSubscriber>
 
 /** The original configuration object used to initialize the client. */
 @property(nonatomic, strong) FPRConfiguration *config;
@@ -51,17 +53,23 @@
   [FIRApp registerInternalLibrary:[FPRClient class]
                          withName:@"fire-perf"
                       withVersion:[NSString stringWithUTF8String:kFPRSDKVersion]];
+  [FIRSessionsDependencies addDependencyWithName:FIRSessionsSubscriberNamePerformance];
 }
 
 #pragma mark - Component registration system
 
 + (nonnull NSArray<FIRComponent *> *)componentsToRegister {
+  FIRDependency *sessionsDep =
+      [FIRDependency dependencyWithProtocol:@protocol(FIRSessionsProvider)];
+
   FIRComponentCreationBlock creationBlock =
       ^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) {
     if (!container.app.isDefaultApp) {
       return nil;
     }
 
+    id<FIRSessionsProvider> sessions = FIR_COMPONENT(FIRSessionsProvider, container);
+
     NSString *appName = container.app.name;
     FIRApp *app = [FIRApp appNamed:appName];
     FIROptions *options = app.options;
@@ -83,6 +91,15 @@
       FPRLogError(kFPRClientInitialize, @"Failed to initialize the client with error:  %@.", error);
     }
 
+    if (sessions) {
+      FPRLogDebug(kFPRClientInitialize, @"Registering Sessions SDK subscription for session data");
+
+      // Subscription should be made after the first call to [FPRClient sharedInstance] where
+      // _configuration is initialized so that the sessions SDK can immediately get the data
+      // collection state.
+      [sessions registerWithSubscriber:[self sharedInstance]];
+    }
+
     *isCacheable = YES;
 
     return [self sharedInstance];
@@ -91,7 +108,7 @@
   FIRComponent *component =
       [FIRComponent componentWithProtocol:@protocol(FIRPerformanceProvider)
                       instantiationTiming:FIRInstantiationTimingEagerInDefaultApp
-                             dependencies:@[]
+                             dependencies:@[ sessionsDep ]
                             creationBlock:creationBlock];
 
   return @[ component ];
@@ -251,7 +268,7 @@
   });
 
   // Check and update the sessionID if the session is running for too long.
-  [[FPRSessionManager sharedInstance] renewSessionIdIfRunningTooLong];
+  [[FPRSessionManager sharedInstance] stopGaugesIfRunningTooLong];
 }
 
 - (void)processAndLogEvent:(firebase_perf_v1_PerfMetric)event {
@@ -340,4 +357,18 @@
   [self.configuration setInstrumentationEnabled:NO];
 }
 
+#pragma mark - FIRSessionsSubscriber
+
+- (void)onSessionChanged:(FIRSessionDetails *_Nonnull)session {
+  [[FPRSessionManager sharedInstance] updateSessionId:session.sessionId];
+}
+
+- (BOOL)isDataCollectionEnabled {
+  return self.configuration.isDataCollectionEnabled;
+}
+
+- (FIRSessionsSubscriberName)sessionsSubscriberName {
+  return FIRSessionsSubscriberNamePerformance;
+}
+
 @end

+ 2 - 2
FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.m

@@ -194,7 +194,7 @@ NSString *const kFPRNetworkTracePropertyName = @"fpr_networkTrace";
 
 - (void)start {
   if (!self.traceCompleted) {
-    [[FPRGaugeManager sharedInstance] collectAllGauges];
+    [[FPRSessionManager sharedInstance] collectAllGaugesOnce];
     self.traceStarted = YES;
     self.backgroundActivityTracker = [[FPRTraceBackgroundActivityTracker alloc] init];
     [self checkpointState:FPRNetworkTraceCheckpointStateInitiated];
@@ -254,7 +254,7 @@ NSString *const kFPRNetworkTracePropertyName = @"fpr_networkTrace";
     [self checkpointState:FPRNetworkTraceCheckpointStateResponseCompleted];
 
     // Send the network trace for logging.
-    [[FPRGaugeManager sharedInstance] collectAllGauges];
+    [[FPRSessionManager sharedInstance] collectAllGaugesOnce];
     [[FPRClient sharedInstance] logNetworkTrace:self];
 
     self.traceCompleted = YES;

+ 2 - 2
FirebasePerformance/Sources/Timer/FIRTrace.m

@@ -136,7 +136,7 @@
 - (void)start {
   if (![self isTraceStarted]) {
     if (!self.isStage) {
-      [[FPRGaugeManager sharedInstance] collectAllGauges];
+      [[FPRSessionManager sharedInstance] collectAllGaugesOnce];
     }
     self.startTime = [NSDate date];
     self.backgroundActivityTracker = [[FPRTraceBackgroundActivityTracker alloc] init];
@@ -169,7 +169,7 @@
     self.stopTime = [NSDate date];
     [self.fprClient logTrace:self];
     if (!self.isStage) {
-      [[FPRGaugeManager sharedInstance] collectAllGauges];
+      [[FPRSessionManager sharedInstance] collectAllGaugesOnce];
     }
   } else {
     FPRLogError(kFPRTraceNotStarted,

+ 5 - 13
FirebasePerformance/Tests/Unit/FPRNetworkTraceTest.m

@@ -14,6 +14,7 @@
 
 #import <XCTest/XCTest.h>
 
+#import "FirebasePerformance/Sources/AppActivity/FPRSessionManager.h"
 #import "FirebasePerformance/Sources/Common/FPRConstants.h"
 #import "FirebasePerformance/Sources/Configurations/FPRConfigurations+Private.h"
 #import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
@@ -437,6 +438,7 @@
 
 /** Validates that every trace contains a session Id. */
 - (void)testSessionId {
+  [[FPRSessionManager sharedInstance] updateSessionId:@"testSessionId"];
   FPRNetworkTrace *trace = [[FPRNetworkTrace alloc] initWithURLRequest:self.testURLRequest];
   [trace start];
   [trace checkpointState:FPRNetworkTraceCheckpointStateInitiated];
@@ -451,10 +453,6 @@
   NSString *string = @"Successful response";
   NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
 
-  NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
-  [defaultCenter postNotificationName:UIApplicationDidBecomeActiveNotification
-                               object:[UIApplication sharedApplication]];
-
   [trace didReceiveData:data];
   [trace didCompleteRequestWithResponse:response error:nil];
   XCTAssertNotNil(trace.sessions);
@@ -463,20 +461,14 @@
 
 /** Validates if a trace contains multiple session Ids on changing app state. */
 - (void)testMultipleSessionIds {
+  [[FPRSessionManager sharedInstance] updateSessionId:@"testSessionId"];
   FPRNetworkTrace *trace = [[FPRNetworkTrace alloc] initWithURLRequest:self.testURLRequest];
   [trace start];
   [trace checkpointState:FPRNetworkTraceCheckpointStateInitiated];
   [trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived];
 
-  NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
-  [defaultCenter postNotificationName:UIWindowDidBecomeVisibleNotification
-                               object:[UIApplication sharedApplication]];
-  [defaultCenter postNotificationName:UIApplicationDidBecomeActiveNotification
-                               object:[UIApplication sharedApplication]];
-  [defaultCenter postNotificationName:UIApplicationWillEnterForegroundNotification
-                               object:[UIApplication sharedApplication]];
-  [defaultCenter postNotificationName:UIApplicationWillEnterForegroundNotification
-                               object:[UIApplication sharedApplication]];
+  [[FPRSessionManager sharedInstance] updateSessionId:@"testSessionId2"];
+  [[FPRSessionManager sharedInstance] updateSessionId:@"testSessionId3"];
 
   NSDictionary<NSString *, NSString *> *headerFields = @{@"Content-Type" : @"text/json"};
   NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:self.testURLRequest.URL

+ 4 - 2
FirebasePerformance/Tests/Unit/FPRSessionDetailsTest.m

@@ -34,9 +34,11 @@
   FPRSessionDetails *details = [[FPRSessionDetails alloc] initWithSessionId:@"random"
                                                                     options:FPRSessionOptionsNone];
   FPRSessionDetails *detailsCopy = [details copy];
+  NSDate *now = [NSDate date];
   XCTAssertEqual(details.sessionId, detailsCopy.sessionId);
   XCTAssertEqual(details.options, detailsCopy.options);
-  XCTAssertEqual(details.sessionLengthInMinutes, detailsCopy.sessionLengthInMinutes);
+  XCTAssertEqual([details sessionLengthInMinutesFromDate:now],
+                 [detailsCopy sessionLengthInMinutesFromDate:now]);
   XCTAssertNotNil(details);
 }
 
@@ -46,7 +48,7 @@
                                                                     options:FPRSessionOptionsNone];
   XCTAssertEqual(details.sessionId, @"random");
   XCTAssertEqual(details.options, FPRSessionOptionsNone);
-  XCTAssertEqual(details.sessionLengthInMinutes, 0);
+  XCTAssertEqual([details sessionLengthInMinutesFromDate:[NSDate date]], 0);
 }
 
 /** Validates that the session details equality with another object. */

+ 69 - 54
FirebasePerformance/Tests/Unit/FPRSessionManagerTest.m

@@ -16,17 +16,31 @@
 
 #import "FirebasePerformance/Sources/AppActivity/FPRSessionManager+Private.h"
 #import "FirebasePerformance/Sources/AppActivity/FPRSessionManager.h"
-
-#import "FirebasePerformance/Sources/Configurations/FPRConfigurations+Private.h"
+#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
+#import "FirebasePerformance/Sources/Gauges/FPRGaugeManager+Private.h"
 
 #import <OCMock/OCMock.h>
 
+NSString *const testSessionId = @"testSessionId";
+
 @interface FPRSessionManagerTest : XCTestCase
 
+@property FPRSessionManager *instance;
+
+@property FPRGaugeManager *gaugeManager;
+
 @end
 
 @implementation FPRSessionManagerTest
 
+- (void)setUp {
+  [super setUp];
+  NSNotificationCenter *notificationCenter = [[NSNotificationCenter alloc] init];
+  _gaugeManager = [[FPRGaugeManager alloc] initWithGauges:FPRGaugeCPU | FPRGaugeMemory];
+  _instance = [[FPRSessionManager alloc] initWithGaugeManager:_gaugeManager
+                                           notificationCenter:notificationCenter];
+}
+
 /** Validate the instance gets created and it is a singleton. */
 - (void)testInstanceCreation {
   FPRSessionManager *instance = [FPRSessionManager sharedInstance];
@@ -34,74 +48,77 @@
   XCTAssertEqual(instance, [FPRSessionManager sharedInstance]);
 }
 
-/** Validate that valid sessionId always exists. */
-- (void)testSessionIdExistance {
-  FPRSessionManager *instance = [FPRSessionManager sharedInstance];
-  [instance startTrackingAppStateChanges];
-
-  NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
-  [notificationCenter postNotificationName:UIApplicationWillEnterForegroundNotification
-                                    object:[UIApplication sharedApplication]];
+/** Validate that gauge collection does not change when calling renew method immediately. */
+- (void)testGaugeDoesNotStopBeforeMaxDuration {
+  FPRSessionManager *manager =
+      [[FPRSessionManager alloc] initWithGaugeManager:self.gaugeManager
+                                   notificationCenter:[NSNotificationCenter defaultCenter]];
+  id mockInstance = [OCMockObject partialMockForObject:[FPRConfigurations sharedInstance]];
+  OCMStub([mockInstance sessionsSamplingPercentage]).andReturn(100);
+  [manager updateSessionId:testSessionId];
+  XCTAssertTrue(manager.gaugeManager.activeGauges > 0);
 
-  XCTAssertNotNil(instance.sessionDetails.sessionId);
+  OCMStub([mockInstance maxSessionLengthInMinutes]).andReturn(5);
+  XCTAssertTrue(manager.gaugeManager.activeGauges > 0);
 
-  NSString *lowercaseSessionId = [instance.sessionDetails.sessionId lowercaseString];
-  XCTAssertEqualObjects(lowercaseSessionId, instance.sessionDetails.sessionId);
+  [mockInstance stopMocking];
 }
 
-/** Validate that sessionId does not change when calling renew method immediately. */
-- (void)testSessionIdNotGettingRenewed {
-  FPRSessionManager *instance = [FPRSessionManager sharedInstance];
-  [instance startTrackingAppStateChanges];
-  NSString *sessionId = instance.sessionDetails.sessionId;
-  [instance renewSessionIdIfRunningTooLong];
-  XCTAssertEqualObjects(sessionId, instance.sessionDetails.sessionId);
+/** Validate that gauge collection stops when calling renew method after max duration reached. */
+- (void)testGaugeStopsAfterMaxDuration {
+  FPRSessionManager *manager =
+      [[FPRSessionManager alloc] initWithGaugeManager:self.gaugeManager
+                                   notificationCenter:[NSNotificationCenter defaultCenter]];
+  id mockInstance = [OCMockObject partialMockForObject:[FPRConfigurations sharedInstance]];
+  OCMStub([mockInstance sessionsSamplingPercentage]).andReturn(100);
+  [manager updateSessionId:testSessionId];
+  XCTAssertTrue(manager.gaugeManager.activeGauges > 0);
+
+  XCTAssertEqual(manager.sessionDetails.options & FPRSessionOptionsGauges, FPRSessionOptionsGauges);
+  OCMStub([mockInstance maxSessionLengthInMinutes]).andReturn(0);
+  [manager stopGaugesIfRunningTooLong];
+  XCTAssertEqual(manager.gaugeManager.activeGauges, 0);
+
+  [mockInstance stopMocking];
 }
 
-/** Validate that sessionId changes on application state changes. */
-- (void)testSessionIdUpdation {
-  FPRSessionManager *instance = [FPRSessionManager sharedInstance];
-  [instance startTrackingAppStateChanges];
-  NSString *sessionId = instance.sessionDetails.sessionId;
-  NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
-  [notificationCenter postNotificationName:UIApplicationWillEnterForegroundNotification
-                                    object:[UIApplication sharedApplication]];
-  XCTAssertNotEqual(sessionId, instance.sessionDetails.sessionId);
+/** Validate that sessionId changes on new session. */
+- (void)testUpdateSessionId {
+  [self.instance updateSessionId:testSessionId];
+  NSString *sessionId = self.instance.sessionDetails.sessionId;
+  [self.instance updateSessionId:@"testSessionId2"];
+  XCTAssertNotEqual(sessionId, self.instance.sessionDetails.sessionId);
 }
 
 /** Validate that sessionId changes sends notifications. */
-- (void)testSessionIdUpdationThrowsNotification {
-  FPRSessionManager *instance = [FPRSessionManager sharedInstance];
-  [instance startTrackingAppStateChanges];
-  NSString *sessionId = instance.sessionDetails.sessionId;
+- (void)testUpdateSessionIdPostsNotification {
+  [self.instance updateSessionId:testSessionId];
+  NSString *sessionId = self.instance.sessionDetails.sessionId;
 
   __block BOOL receivedNotification = NO;
-  [instance.sessionNotificationCenter addObserverForName:kFPRSessionIdUpdatedNotification
-                                                  object:instance
-                                                   queue:[NSOperationQueue mainQueue]
-                                              usingBlock:^(NSNotification *note) {
-                                                receivedNotification = YES;
-                                              }];
+  [self.instance.sessionNotificationCenter addObserverForName:kFPRSessionIdUpdatedNotification
+                                                       object:self.instance
+                                                        queue:[NSOperationQueue mainQueue]
+                                                   usingBlock:^(NSNotification *note) {
+                                                     receivedNotification = YES;
+                                                   }];
 
-  NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
-  [notificationCenter postNotificationName:UIApplicationWillEnterForegroundNotification
-                                    object:[UIApplication sharedApplication]];
+  [self.instance updateSessionId:@"testSessionId2"];
 
   XCTAssertTrue(receivedNotification);
-  XCTAssertNotEqual(sessionId, instance.sessionDetails.sessionId);
+  XCTAssertNotEqual(sessionId, self.instance.sessionDetails.sessionId);
 }
 
 /** Validate that sessionId changes sends notifications with the session details. */
-- (void)testSessionIdUpdationSendsNotificationWithSessionDetails {
-  FPRSessionManager *instance = [FPRSessionManager sharedInstance];
-  [instance startTrackingAppStateChanges];
-  NSString *sessionId = instance.sessionDetails.sessionId;
+- (void)testUpdateSessionIdPostsNotificationWithSessionDetails {
+  [self.instance updateSessionId:testSessionId];
+  NSString *sessionId = self.instance.sessionDetails.sessionId;
 
   __block BOOL containsSessionDetails = NO;
   __block FPRSessionDetails *updatedSessionDetails = nil;
-  [instance.sessionNotificationCenter
+  [self.instance.sessionNotificationCenter
       addObserverForName:kFPRSessionIdUpdatedNotification
-                  object:instance
+                  object:self.instance
                    queue:[NSOperationQueue mainQueue]
               usingBlock:^(NSNotification *note) {
                 NSDictionary<NSString *, FPRSessionDetails *> *userInfo = note.userInfo;
@@ -113,13 +130,11 @@
                 }
               }];
 
-  NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
-  [notificationCenter postNotificationName:UIApplicationWillEnterForegroundNotification
-                                    object:[UIApplication sharedApplication]];
+  [self.instance updateSessionId:@"testSessionId2"];
 
   XCTAssertTrue(containsSessionDetails);
-  XCTAssertNotEqual(sessionId, instance.sessionDetails.sessionId);
-  XCTAssertEqual(updatedSessionDetails.sessionId, instance.sessionDetails.sessionId);
+  XCTAssertNotEqual(sessionId, self.instance.sessionDetails.sessionId);
+  XCTAssertEqual(updatedSessionDetails.sessionId, self.instance.sessionDetails.sessionId);
 }
 
 @end

+ 5 - 15
FirebasePerformance/Tests/Unit/Timer/FIRTraceTest.m

@@ -15,6 +15,7 @@
 #import <XCTest/XCTest.h>
 
 #import "FirebasePerformance/Sources/AppActivity/FPRAppActivityTracker.h"
+#import "FirebasePerformance/Sources/AppActivity/FPRSessionManager.h"
 #import "FirebasePerformance/Sources/Common/FPRConstants.h"
 #import "FirebasePerformance/Sources/Configurations/FPRConfigurations+Private.h"
 #import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
@@ -722,13 +723,9 @@
 
 /** Validates if every trace contains a session Id. */
 - (void)testSessionId {
+  [[FPRSessionManager sharedInstance] updateSessionId:@"testSessionId"];
   FIRTrace *trace = [[FIRTrace alloc] initWithName:@"Random"];
   [trace start];
-
-  NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
-  [defaultCenter postNotificationName:UIApplicationDidBecomeActiveNotification
-                               object:[UIApplication sharedApplication]];
-
   [trace stop];
   XCTAssertNotNil(trace.sessions);
   XCTAssertTrue(trace.sessions.count > 0);
@@ -736,18 +733,11 @@
 
 /** Validates if every trace contains multiple session Ids on changing app state. */
 - (void)testMultipleSessionIds {
+  [[FPRSessionManager sharedInstance] updateSessionId:@"testSessionId"];
   FIRTrace *trace = [[FIRTrace alloc] initWithName:@"Random"];
   [trace start];
-  NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
-  [defaultCenter postNotificationName:UIWindowDidBecomeVisibleNotification
-                               object:[UIApplication sharedApplication]];
-  [defaultCenter postNotificationName:UIApplicationDidBecomeActiveNotification
-                               object:[UIApplication sharedApplication]];
-  [defaultCenter postNotificationName:UIApplicationWillEnterForegroundNotification
-                               object:[UIApplication sharedApplication]];
-
-  [defaultCenter postNotificationName:UIApplicationWillEnterForegroundNotification
-                               object:[UIApplication sharedApplication]];
+  [[FPRSessionManager sharedInstance] updateSessionId:@"testSessionId2"];
+  [[FPRSessionManager sharedInstance] updateSessionId:@"testSessionId3"];
 
   XCTestExpectation *expectation = [self expectationWithDescription:@"Expectation - Wait for 2s"];
   dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)),

+ 2 - 2
FirebaseSessions.podspec

@@ -1,6 +1,6 @@
 Pod::Spec.new do |s|
   s.name             = 'FirebaseSessions'
-  s.version          = '10.0.0'
+  s.version          = '10.5.0'
   s.summary          = 'Firebase Sessions'
 
   s.description      = <<-DESC
@@ -39,7 +39,7 @@ Pod::Spec.new do |s|
     base_dir + 'SourcesObjC/**/*.{c,h,m,mm}',
   ]
 
-  s.dependency 'FirebaseCore', '~> 10.0'
+  s.dependency 'FirebaseCore', '~> 10.5'
   s.dependency 'FirebaseCoreExtension', '~> 10.0'
   s.dependency 'FirebaseInstallations', '~> 10.0'
   s.dependency 'GoogleDataTransport', '~> 9.2'

+ 1 - 1
FirebaseSessions/Sources/Public/SessionsSubscriber.swift

@@ -28,7 +28,7 @@ public protocol SessionsSubscriber {
 /// whenever the Session changes
 @objc(FIRSessionDetails)
 public class SessionDetails: NSObject {
-  var sessionId: String?
+  @objc public var sessionId: String?
 
   public init(sessionId: String?) {
     self.sessionId = sessionId

+ 2 - 1
Package.swift

@@ -490,7 +490,7 @@ let package = Package(
     ),
     .target(
       name: "FirebaseCrashlytics",
-      dependencies: ["FirebaseCore", "FirebaseInstallations",
+      dependencies: ["FirebaseCore", "FirebaseInstallations", "FirebaseSessions",
                      .product(name: "GoogleDataTransport", package: "GoogleDataTransport"),
                      .product(name: "GULEnvironment", package: "GoogleUtilities"),
                      .product(name: "FBLPromises", package: "Promises"),
@@ -948,6 +948,7 @@ let package = Package(
         "FirebaseCore",
         "FirebaseInstallations",
         "FirebaseRemoteConfig",
+        "FirebaseSessions",
         .product(name: "GoogleDataTransport", package: "GoogleDataTransport"),
         .product(name: "GULEnvironment", package: "GoogleUtilities"),
         .product(name: "GULISASwizzler", package: "GoogleUtilities"),

+ 1 - 1
scripts/localize_podfile.swift

@@ -38,7 +38,7 @@ let implicitPods = [
   "FirebaseRemoteConfig", "FirebaseCoreExtension",
   "FirebaseAppCheckInterop", "FirebaseAuthInterop",
   "FirebaseMessagingInterop", "FirebaseCoreInternal",
-  "FirebaseSharedSwift",
+  "FirebaseSessions", "FirebaseSharedSwift",
 ]
 
 let binaryPods = [