FPRAppActivityTracker.m 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. // Copyright 2020 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "FirebasePerformance/Sources/AppActivity/FPRAppActivityTracker.h"
  15. #import <Foundation/Foundation.h>
  16. #import <UIKit/UIKit.h>
  17. #import "FirebasePerformance/Sources/AppActivity/FPRSessionManager.h"
  18. #import "FirebasePerformance/Sources/Gauges/CPU/FPRCPUGaugeCollector+Private.h"
  19. #import "FirebasePerformance/Sources/Gauges/FPRGaugeManager.h"
  20. #import "FirebasePerformance/Sources/Gauges/Memory/FPRMemoryGaugeCollector+Private.h"
  21. #import "FirebasePerformance/Sources/Timer/FIRTrace+Internal.h"
  22. #import "FirebasePerformance/Sources/Timer/FIRTrace+Private.h"
  23. static NSDate *appStartTime = nil;
  24. static NSTimeInterval gAppStartMaxValidDuration = 60 * 60; // 60 minutes.
  25. static FPRCPUGaugeData *gAppStartCPUGaugeData = nil;
  26. static FPRMemoryGaugeData *gAppStartMemoryGaugeData = nil;
  27. NSString *const kFPRAppStartTraceName = @"_as";
  28. NSString *const kFPRAppStartStageNameTimeToUI = @"_astui";
  29. NSString *const kFPRAppStartStageNameTimeToFirstDraw = @"_astfd";
  30. NSString *const kFPRAppStartStageNameTimeToUserInteraction = @"_asti";
  31. NSString *const kFPRAppTraceNameForegroundSession = @"_fs";
  32. NSString *const kFPRAppTraceNameBackgroundSession = @"_bs";
  33. NSString *const kFPRAppCounterNameTraceEventsRateLimited = @"_fstec";
  34. NSString *const kFPRAppCounterNameNetworkTraceEventsRateLimited = @"_fsntc";
  35. NSString *const kFPRAppCounterNameTraceNotStopped = @"_tsns";
  36. @interface FPRAppActivityTracker ()
  37. /** The foreground session trace. Will be set only when the app is in the foreground. */
  38. @property(nonatomic, readwrite) FIRTrace *foregroundSessionTrace;
  39. /** The background session trace. Will be set only when the app is in the background. */
  40. @property(nonatomic, readwrite) FIRTrace *backgroundSessionTrace;
  41. /** Current running state of the application. */
  42. @property(nonatomic, readwrite) FPRApplicationState applicationState;
  43. /** Trace to measure the app start performance. */
  44. @property(nonatomic) FIRTrace *appStartTrace;
  45. /** Tracks if the gauge metrics are dispatched. */
  46. @property(nonatomic) BOOL appStartGaugeMetricDispatched;
  47. /** Starts tracking app active sessions. */
  48. - (void)startAppActivityTracking;
  49. @end
  50. @implementation FPRAppActivityTracker
  51. + (void)load {
  52. // This is an approximation of the app start time.
  53. appStartTime = [NSDate date];
  54. gAppStartCPUGaugeData = fprCollectCPUMetric();
  55. gAppStartMemoryGaugeData = fprCollectMemoryMetric();
  56. [[NSNotificationCenter defaultCenter] addObserver:self
  57. selector:@selector(windowDidBecomeVisible:)
  58. name:UIWindowDidBecomeVisibleNotification
  59. object:nil];
  60. }
  61. + (void)windowDidBecomeVisible:(NSNotification *)notification {
  62. FPRAppActivityTracker *activityTracker = [self sharedInstance];
  63. [activityTracker startAppActivityTracking];
  64. [[NSNotificationCenter defaultCenter] removeObserver:self
  65. name:UIWindowDidBecomeVisibleNotification
  66. object:nil];
  67. }
  68. + (instancetype)sharedInstance {
  69. static FPRAppActivityTracker *instance;
  70. static dispatch_once_t onceToken;
  71. dispatch_once(&onceToken, ^{
  72. instance = [[self alloc] initAppActivityTracker];
  73. });
  74. return instance;
  75. }
  76. /**
  77. * Custom initializer to create an app activity tracker.
  78. */
  79. - (instancetype)initAppActivityTracker {
  80. self = [super init];
  81. _applicationState = FPRApplicationStateUnknown;
  82. _appStartGaugeMetricDispatched = NO;
  83. return self;
  84. }
  85. - (void)startAppActivityTracking {
  86. [[NSNotificationCenter defaultCenter] addObserver:self
  87. selector:@selector(appDidBecomeActiveNotification:)
  88. name:UIApplicationDidBecomeActiveNotification
  89. object:[UIApplication sharedApplication]];
  90. [[NSNotificationCenter defaultCenter] addObserver:self
  91. selector:@selector(appWillResignActiveNotification:)
  92. name:UIApplicationWillResignActiveNotification
  93. object:[UIApplication sharedApplication]];
  94. }
  95. - (FIRTrace *)activeTrace {
  96. if (self.foregroundSessionTrace) {
  97. return self.foregroundSessionTrace;
  98. }
  99. return self.backgroundSessionTrace;
  100. }
  101. /**
  102. * This gets called whenever the app becomes active. A new trace will be created to track the active
  103. * foreground session. Any background session trace that was running in the past will be stopped.
  104. *
  105. * @param notification Notification received during app launch.
  106. */
  107. - (void)appDidBecomeActiveNotification:(NSNotification *)notification {
  108. self.applicationState = FPRApplicationStateForeground;
  109. static dispatch_once_t onceToken;
  110. dispatch_once(&onceToken, ^{
  111. self.appStartTrace = [[FIRTrace alloc] initInternalTraceWithName:kFPRAppStartTraceName];
  112. [self.appStartTrace startWithStartTime:appStartTime];
  113. // [self.appStartTrace startStageNamed:kFPRAppStartStageNameTimeToUI startTime:appStartTime];
  114. // Start measuring time to first draw on the App start trace.
  115. // [self.appStartTrace startStageNamed:kFPRAppStartStageNameTimeToFirstDraw];
  116. });
  117. // If ever the app start trace had it life in background stage, do not send the trace.
  118. if (self.appStartTrace.backgroundTraceState != FPRTraceStateForegroundOnly) {
  119. self.appStartTrace = nil;
  120. }
  121. // Stop the active background session trace.
  122. [self.backgroundSessionTrace stop];
  123. self.backgroundSessionTrace = nil;
  124. // Start foreground session trace.
  125. FIRTrace *appTrace =
  126. [[FIRTrace alloc] initInternalTraceWithName:kFPRAppTraceNameForegroundSession];
  127. [appTrace start];
  128. self.foregroundSessionTrace = appTrace;
  129. // Start measuring time to make the app interactive on the App start trace.
  130. static BOOL TTIStageStarted = NO;
  131. if (!TTIStageStarted) {
  132. // [self.appStartTrace startStageNamed:kFPRAppStartStageNameTimeToUserInteraction];
  133. TTIStageStarted = YES;
  134. // Assumption here is that - the app becomes interactive in the next runloop cycle.
  135. // It is possible that the app does more things later, but for now we are not measuring that.
  136. dispatch_async(dispatch_get_main_queue(), ^{
  137. NSTimeInterval startTimeSinceEpoch = [self.appStartTrace startTimeSinceEpoch];
  138. NSTimeInterval currentTimeSinceEpoch = [[NSDate date] timeIntervalSince1970];
  139. // The below check is to account for 2 scenarios.
  140. // 1. The app gets started in the background and might come to foreground a lot later.
  141. // 2. The app is launched, but immediately backgrounded for some reason and the actual launch
  142. // happens a lot later.
  143. // Dropping the app start trace in such situations where the launch time is taking more than
  144. // 60 minutes. This is an approximation, but a more agreeable timelimit for app start.
  145. if (currentTimeSinceEpoch - startTimeSinceEpoch < gAppStartMaxValidDuration) {
  146. [self.appStartTrace stop];
  147. } else {
  148. [self.appStartTrace cancel];
  149. }
  150. });
  151. }
  152. // Let the session manager to start tracking app activity changes.
  153. [[FPRSessionManager sharedInstance] startTrackingAppStateChanges];
  154. }
  155. /**
  156. * This gets called whenever the app resigns its active status. The currently active foreground
  157. * session trace will be stopped and a background session trace will be started.
  158. *
  159. * @param notification Notification received during app resigning active status.
  160. */
  161. - (void)appWillResignActiveNotification:(NSNotification *)notification {
  162. // Dispatch the collected gauge metrics.
  163. if (!self.appStartGaugeMetricDispatched) {
  164. [[FPRGaugeManager sharedInstance] dispatchMetric:gAppStartCPUGaugeData];
  165. [[FPRGaugeManager sharedInstance] dispatchMetric:gAppStartMemoryGaugeData];
  166. self.appStartGaugeMetricDispatched = YES;
  167. }
  168. self.applicationState = FPRApplicationStateBackground;
  169. // Stop foreground session trace.
  170. [self.foregroundSessionTrace stop];
  171. self.foregroundSessionTrace = nil;
  172. // Start background session trace.
  173. self.backgroundSessionTrace =
  174. [[FIRTrace alloc] initInternalTraceWithName:kFPRAppTraceNameBackgroundSession];
  175. [self.backgroundSessionTrace start];
  176. }
  177. - (void)dealloc {
  178. [[NSNotificationCenter defaultCenter] removeObserver:self
  179. name:UIApplicationDidBecomeActiveNotification
  180. object:[UIApplication sharedApplication]];
  181. [[NSNotificationCenter defaultCenter] removeObserver:self
  182. name:UIApplicationWillResignActiveNotification
  183. object:[UIApplication sharedApplication]];
  184. }
  185. @end