Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 
15
#import "FirebasePerformance/Sources/AppActivity/FPRAppActivityTracker.h"
16
 
17
#import <Foundation/Foundation.h>
18
#import <UIKit/UIKit.h>
19
 
20
#import "FirebasePerformance/Sources/AppActivity/FPRSessionManager.h"
21
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
22
#import "FirebasePerformance/Sources/Gauges/CPU/FPRCPUGaugeCollector+Private.h"
23
#import "FirebasePerformance/Sources/Gauges/FPRGaugeManager.h"
24
#import "FirebasePerformance/Sources/Gauges/Memory/FPRMemoryGaugeCollector+Private.h"
25
#import "FirebasePerformance/Sources/Timer/FIRTrace+Internal.h"
26
#import "FirebasePerformance/Sources/Timer/FIRTrace+Private.h"
27
 
28
static NSDate *appStartTime = nil;
29
static NSDate *doubleDispatchTime = nil;
30
static NSDate *applicationDidFinishLaunchTime = nil;
31
static NSTimeInterval gAppStartMaxValidDuration = 60 * 60;  // 60 minutes.
32
static FPRCPUGaugeData *gAppStartCPUGaugeData = nil;
33
static FPRMemoryGaugeData *gAppStartMemoryGaugeData = nil;
34
static BOOL isActivePrewarm = NO;
35
 
36
NSString *const kFPRAppStartTraceName = @"_as";
37
NSString *const kFPRAppStartStageNameTimeToUI = @"_astui";
38
NSString *const kFPRAppStartStageNameTimeToFirstDraw = @"_astfd";
39
NSString *const kFPRAppStartStageNameTimeToUserInteraction = @"_asti";
40
NSString *const kFPRAppTraceNameForegroundSession = @"_fs";
41
NSString *const kFPRAppTraceNameBackgroundSession = @"_bs";
42
NSString *const kFPRAppCounterNameTraceEventsRateLimited = @"_fstec";
43
NSString *const kFPRAppCounterNameNetworkTraceEventsRateLimited = @"_fsntc";
44
NSString *const kFPRAppCounterNameTraceNotStopped = @"_tsns";
45
NSString *const kFPRAppCounterNameActivePrewarm = @"_fsapc";
46
 
47
@interface FPRAppActivityTracker ()
48
 
49
/** The foreground session trace. Will be set only when the app is in the foreground. */
50
@property(nonatomic, readwrite) FIRTrace *foregroundSessionTrace;
51
 
52
/** The background session trace. Will be set only when the app is in the background. */
53
@property(nonatomic, readwrite) FIRTrace *backgroundSessionTrace;
54
 
55
/** Current running state of the application. */
56
@property(nonatomic, readwrite) FPRApplicationState applicationState;
57
 
58
/** Trace to measure the app start performance. */
59
@property(nonatomic) FIRTrace *appStartTrace;
60
 
61
/** Tracks if the gauge metrics are dispatched. */
62
@property(nonatomic) BOOL appStartGaugeMetricDispatched;
63
 
64
/** Firebase Performance Configuration object */
65
@property(nonatomic) FPRConfigurations *configurations;
66
 
67
/** Starts tracking app active sessions. */
68
- (void)startAppActivityTracking;
69
 
70
@end
71
 
72
@implementation FPRAppActivityTracker
73
 
74
+ (void)load {
75
  // This is an approximation of the app start time.
76
  appStartTime = [NSDate date];
77
 
78
  // When an app is prewarmed, Apple sets env variable ActivePrewarm to 1, then the env variable is
79
  // deleted after didFinishLaunching
80
  isActivePrewarm = [NSProcessInfo.processInfo.environment[@"ActivePrewarm"] isEqualToString:@"1"];
81
 
82
  gAppStartCPUGaugeData = fprCollectCPUMetric();
83
  gAppStartMemoryGaugeData = fprCollectMemoryMetric();
84
  [[NSNotificationCenter defaultCenter] addObserver:self
85
                                           selector:@selector(windowDidBecomeVisible:)
86
                                               name:UIWindowDidBecomeVisibleNotification
87
                                             object:nil];
88
 
89
  [[NSNotificationCenter defaultCenter] addObserver:self
90
                                           selector:@selector(applicationDidFinishLaunching:)
91
                                               name:UIApplicationDidFinishLaunchingNotification
92
                                             object:nil];
93
}
94
 
95
+ (void)windowDidBecomeVisible:(NSNotification *)notification {
96
  FPRAppActivityTracker *activityTracker = [self sharedInstance];
97
  [activityTracker startAppActivityTracking];
98
 
99
  [[NSNotificationCenter defaultCenter] removeObserver:self
100
                                                  name:UIWindowDidBecomeVisibleNotification
101
                                                object:nil];
102
}
103
 
104
+ (void)applicationDidFinishLaunching:(NSNotification *)notification {
105
  applicationDidFinishLaunchTime = [NSDate date];
106
  [[NSNotificationCenter defaultCenter] removeObserver:self
107
                                                  name:UIApplicationDidFinishLaunchingNotification
108
                                                object:nil];
109
}
110
 
111
+ (instancetype)sharedInstance {
112
  static FPRAppActivityTracker *instance;
113
  static dispatch_once_t onceToken;
114
  dispatch_once(&onceToken, ^{
115
    instance = [[self alloc] initAppActivityTracker];
116
  });
117
  return instance;
118
}
119
 
120
/**
121
 * Custom initializer to create an app activity tracker.
122
 */
123
- (instancetype)initAppActivityTracker {
124
  self = [super init];
125
  _applicationState = FPRApplicationStateUnknown;
126
  _appStartGaugeMetricDispatched = NO;
127
  _configurations = [FPRConfigurations sharedInstance];
128
  return self;
129
}
130
 
131
- (void)startAppActivityTracking {
132
  [[NSNotificationCenter defaultCenter] addObserver:self
133
                                           selector:@selector(appDidBecomeActiveNotification:)
134
                                               name:UIApplicationDidBecomeActiveNotification
135
                                             object:[UIApplication sharedApplication]];
136
 
137
  [[NSNotificationCenter defaultCenter] addObserver:self
138
                                           selector:@selector(appWillResignActiveNotification:)
139
                                               name:UIApplicationWillResignActiveNotification
140
                                             object:[UIApplication sharedApplication]];
141
}
142
 
143
- (FIRTrace *)activeTrace {
144
  if (self.foregroundSessionTrace) {
145
    return self.foregroundSessionTrace;
146
  }
147
  return self.backgroundSessionTrace;
148
}
149
 
150
/**
151
 * Checks if the prewarming feature is available on the current device.
152
 *
153
 * @return true if the OS could prewarm apps on the current device
154
 */
155
- (BOOL)isPrewarmAvailable {
156
  BOOL canPrewarm = NO;
157
  // Guarding for double dispatch which does not work below iOS 13, and 0.1% of app start also show
158
  // signs of prewarming on iOS 14 go/paste/5533761933410304
159
  if (@available(iOS 13, *)) {
160
    canPrewarm = YES;
161
  }
162
  return canPrewarm;
163
}
164
 
165
/**
166
 RC flag for dropping all app start events
167
 */
168
- (BOOL)isAppStartEnabled {
169
  return [self.configurations prewarmDetectionMode] != PrewarmDetectionModeKeepNone;
170
}
171
 
172
/**
173
 RC flag for enabling prewarm-detection using ActivePrewarm environment variable
174
 */
175
- (BOOL)isActivePrewarmEnabled {
176
  PrewarmDetectionMode mode = [self.configurations prewarmDetectionMode];
177
  return (mode == PrewarmDetectionModeActivePrewarm);
178
}
179
 
180
/**
181
 Checks if the current app start is a prewarmed app start
182
 */
183
- (BOOL)isApplicationPreWarmed {
184
  if (![self isPrewarmAvailable]) {
185
    return NO;
186
  }
187
 
188
  BOOL isPrewarmed = NO;
189
 
190
  if (isActivePrewarm == YES) {
191
    isPrewarmed = isPrewarmed || [self isActivePrewarmEnabled];
192
    [self.activeTrace incrementMetric:kFPRAppCounterNameActivePrewarm byInt:1];
193
  } else {
194
    [self.activeTrace incrementMetric:kFPRAppCounterNameActivePrewarm byInt:0];
195
  }
196
 
197
  return isPrewarmed;
198
}
199
 
200
/**
201
 * This gets called whenever the app becomes active. A new trace will be created to track the active
202
 * foreground session. Any background session trace that was running in the past will be stopped.
203
 *
204
 * @param notification Notification received during app launch.
205
 */
206
- (void)appDidBecomeActiveNotification:(NSNotification *)notification {
207
  self.applicationState = FPRApplicationStateForeground;
208
 
209
  static dispatch_once_t onceToken;
210
  dispatch_once(&onceToken, ^{
211
    self.appStartTrace = [[FIRTrace alloc] initInternalTraceWithName:kFPRAppStartTraceName];
212
    [self.appStartTrace startWithStartTime:appStartTime];
213
    [self.appStartTrace startStageNamed:kFPRAppStartStageNameTimeToUI startTime:appStartTime];
214
 
215
    // Start measuring time to first draw on the App start trace.
216
    [self.appStartTrace startStageNamed:kFPRAppStartStageNameTimeToFirstDraw];
217
  });
218
 
219
  // If ever the app start trace had it life in background stage, do not send the trace.
220
  if (self.appStartTrace.backgroundTraceState != FPRTraceStateForegroundOnly) {
221
    self.appStartTrace = nil;
222
  }
223
 
224
  // Stop the active background session trace.
225
  [self.backgroundSessionTrace stop];
226
  self.backgroundSessionTrace = nil;
227
 
228
  // Start foreground session trace.
229
  FIRTrace *appTrace =
230
      [[FIRTrace alloc] initInternalTraceWithName:kFPRAppTraceNameForegroundSession];
231
  [appTrace start];
232
  self.foregroundSessionTrace = appTrace;
233
 
234
  // Start measuring time to make the app interactive on the App start trace.
235
  static BOOL TTIStageStarted = NO;
236
  if (!TTIStageStarted) {
237
    [self.appStartTrace startStageNamed:kFPRAppStartStageNameTimeToUserInteraction];
238
    TTIStageStarted = YES;
239
 
240
    // Assumption here is that - the app becomes interactive in the next runloop cycle.
241
    // It is possible that the app does more things later, but for now we are not measuring that.
242
    dispatch_async(dispatch_get_main_queue(), ^{
243
      NSTimeInterval startTimeSinceEpoch = [self.appStartTrace startTimeSinceEpoch];
244
      NSTimeInterval currentTimeSinceEpoch = [[NSDate date] timeIntervalSince1970];
245
 
246
      // The below check is to account for 2 scenarios.
247
      // 1. The app gets started in the background and might come to foreground a lot later.
248
      // 2. The app is launched, but immediately backgrounded for some reason and the actual launch
249
      // happens a lot later.
250
      // Dropping the app start trace in such situations where the launch time is taking more than
251
      // 60 minutes. This is an approximation, but a more agreeable timelimit for app start.
252
      if ((currentTimeSinceEpoch - startTimeSinceEpoch < gAppStartMaxValidDuration) &&
253
          [self isAppStartEnabled] && ![self isApplicationPreWarmed]) {
254
        [self.appStartTrace stop];
255
      } else {
256
        [self.appStartTrace cancel];
257
      }
258
    });
259
  }
260
 
261
  // Let the session manager to start tracking app activity changes.
262
  [[FPRSessionManager sharedInstance] startTrackingAppStateChanges];
263
}
264
 
265
/**
266
 * This gets called whenever the app resigns its active status. The currently active foreground
267
 * session trace will be stopped and a background session trace will be started.
268
 *
269
 * @param notification Notification received during app resigning active status.
270
 */
271
- (void)appWillResignActiveNotification:(NSNotification *)notification {
272
  // Dispatch the collected gauge metrics.
273
  if (!self.appStartGaugeMetricDispatched) {
274
    [[FPRGaugeManager sharedInstance] dispatchMetric:gAppStartCPUGaugeData];
275
    [[FPRGaugeManager sharedInstance] dispatchMetric:gAppStartMemoryGaugeData];
276
    self.appStartGaugeMetricDispatched = YES;
277
  }
278
 
279
  self.applicationState = FPRApplicationStateBackground;
280
 
281
  // Stop foreground session trace.
282
  [self.foregroundSessionTrace stop];
283
  self.foregroundSessionTrace = nil;
284
 
285
  // Start background session trace.
286
  self.backgroundSessionTrace =
287
      [[FIRTrace alloc] initInternalTraceWithName:kFPRAppTraceNameBackgroundSession];
288
  [self.backgroundSessionTrace start];
289
}
290
 
291
- (void)dealloc {
292
  [[NSNotificationCenter defaultCenter] removeObserver:self
293
                                                  name:UIApplicationDidBecomeActiveNotification
294
                                                object:[UIApplication sharedApplication]];
295
 
296
  [[NSNotificationCenter defaultCenter] removeObserver:self
297
                                                  name:UIApplicationWillResignActiveNotification
298
                                                object:[UIApplication sharedApplication]];
299
}
300
 
301
@end