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/FPRScreenTraceTracker.h"
16
#import "FirebasePerformance/Sources/AppActivity/FPRScreenTraceTracker+Private.h"
17
 
18
#import <Foundation/Foundation.h>
19
#import <UIKit/UIKit.h>
20
 
21
#import "FirebasePerformance/Sources/Common/FPRDiagnostics.h"
22
 
23
NSString *const kFPRPrefixForScreenTraceName = @"_st_";
24
NSString *const kFPRFrozenFrameCounterName = @"_fr_fzn";
25
NSString *const kFPRSlowFrameCounterName = @"_fr_slo";
26
NSString *const kFPRTotalFramesCounterName = @"_fr_tot";
27
 
28
// Note: This was previously 60 FPS, but that resulted in 90% +  of all frames collected to be
29
// flagged as slow frames, and so the threshold for iOS is being changed to 59 FPS.
30
// TODO(b/73498642): Make these configurable.
31
CFTimeInterval const kFPRSlowFrameThreshold = 1.0 / 59.0;  // Anything less than 59 FPS is slow.
32
CFTimeInterval const kFPRFrozenFrameThreshold = 700.0 / 1000.0;
33
 
34
/** Constant that indicates an invalid time. */
35
CFAbsoluteTime const kFPRInvalidTime = -1.0;
36
 
37
/** Returns the class name without the prefixed module name present in Swift classes
38
 * (e.g. MyModule.MyViewController -> MyViewController).
39
 */
40
static NSString *FPRUnprefixedClassName(Class theClass) {
41
  NSString *className = NSStringFromClass(theClass);
42
  NSRange periodRange = [className rangeOfString:@"." options:NSBackwardsSearch];
43
  if (periodRange.location == NSNotFound) {
44
    return className;
45
  }
46
  return periodRange.location < className.length - 1
47
             ? [className substringFromIndex:periodRange.location + 1]
48
             : className;
49
}
50
 
51
/** Returns the name for the screen trace for a given UIViewController. It does the following:
52
 *  - Removes module name from swift classes - (e.g. MyModule.MyViewController -> MyViewController)
53
 *  - Prepends "_st_" to the class name
54
 *  - Truncates the length if it exceeds the maximum trace length.
55
 *
56
 *  @param viewController The view controller whose screen trace name we want. Cannot be nil.
57
 *  @return An NSString containing the trace name, or a string containing an error if the
58
 *      class was nil.
59
 */
60
static NSString *FPRScreenTraceNameForViewController(UIViewController *viewController) {
61
  NSString *unprefixedClassName = FPRUnprefixedClassName([viewController class]);
62
  if (unprefixedClassName.length != 0) {
63
    NSString *traceName =
64
        [NSString stringWithFormat:@"%@%@", kFPRPrefixForScreenTraceName, unprefixedClassName];
65
    return traceName.length > kFPRMaxNameLength ? [traceName substringToIndex:kFPRMaxNameLength]
66
                                                : traceName;
67
  } else {
68
    // This is unlikely, but might happen if there's a regression on iOS where the class name
69
    // returned for a non-nil class is nil or empty.
70
    return @"_st_ERROR_NIL_CLASS_NAME";
71
  }
72
}
73
 
74
@implementation FPRScreenTraceTracker {
75
  /** Instance variable storing the total frames observed so far. */
76
  atomic_int_fast64_t _totalFramesCount;
77
 
78
  /** Instance variable storing the slow frames observed so far. */
79
  atomic_int_fast64_t _slowFramesCount;
80
 
81
  /** Instance variable storing the frozen frames observed so far. */
82
  atomic_int_fast64_t _frozenFramesCount;
83
}
84
 
85
@dynamic totalFramesCount;
86
@dynamic frozenFramesCount;
87
@dynamic slowFramesCount;
88
 
89
+ (instancetype)sharedInstance {
90
  static FPRScreenTraceTracker *instance;
91
  static dispatch_once_t onceToken;
92
  dispatch_once(&onceToken, ^{
93
    instance = [[self alloc] init];
94
  });
95
  return instance;
96
}
97
 
98
- (instancetype)init {
99
  self = [super init];
100
  if (self) {
101
    // Weakly retain viewController, use pointer hashing.
102
    NSMapTableOptions keyOptions = NSMapTableWeakMemory | NSMapTableObjectPointerPersonality;
103
    // Strongly retain the FIRTrace.
104
    NSMapTableOptions valueOptions = NSMapTableStrongMemory;
105
    _activeScreenTraces = [NSMapTable mapTableWithKeyOptions:keyOptions valueOptions:valueOptions];
106
 
107
    _previouslyVisibleViewControllers = nil;  // Will be set when there is data.
108
    _screenTraceTrackerSerialQueue =
109
        dispatch_queue_create("com.google.FPRScreenTraceTracker", DISPATCH_QUEUE_SERIAL);
110
    _screenTraceTrackerDispatchGroup = dispatch_group_create();
111
 
112
    atomic_store_explicit(&_totalFramesCount, 0, memory_order_relaxed);
113
    atomic_store_explicit(&_frozenFramesCount, 0, memory_order_relaxed);
114
    atomic_store_explicit(&_slowFramesCount, 0, memory_order_relaxed);
115
    _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkStep)];
116
    [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
117
 
118
    // We don't receive background and foreground events from analytics and so we have to listen to
119
    // them ourselves.
120
    [[NSNotificationCenter defaultCenter] addObserver:self
121
                                             selector:@selector(appDidBecomeActiveNotification:)
122
                                                 name:UIApplicationDidBecomeActiveNotification
123
                                               object:[UIApplication sharedApplication]];
124
 
125
    [[NSNotificationCenter defaultCenter] addObserver:self
126
                                             selector:@selector(appWillResignActiveNotification:)
127
                                                 name:UIApplicationWillResignActiveNotification
128
                                               object:[UIApplication sharedApplication]];
129
  }
130
  return self;
131
}
132
 
133
- (void)dealloc {
134
  [_displayLink invalidate];
135
 
136
  [[NSNotificationCenter defaultCenter] removeObserver:self
137
                                                  name:UIApplicationDidBecomeActiveNotification
138
                                                object:[UIApplication sharedApplication]];
139
  [[NSNotificationCenter defaultCenter] removeObserver:self
140
                                                  name:UIApplicationWillResignActiveNotification
141
                                                object:[UIApplication sharedApplication]];
142
}
143
 
144
- (void)appDidBecomeActiveNotification:(NSNotification *)notification {
145
  // To get the most accurate numbers of total, frozen and slow frames, we need to capture them as
146
  // soon as we're notified of an event.
147
  int64_t currentTotalFrames = atomic_load_explicit(&_totalFramesCount, memory_order_relaxed);
148
  int64_t currentFrozenFrames = atomic_load_explicit(&_frozenFramesCount, memory_order_relaxed);
149
  int64_t currentSlowFrames = atomic_load_explicit(&_slowFramesCount, memory_order_relaxed);
150
 
151
  dispatch_group_async(self.screenTraceTrackerDispatchGroup, self.screenTraceTrackerSerialQueue, ^{
152
    for (id viewController in self.previouslyVisibleViewControllers) {
153
      [self startScreenTraceForViewController:viewController
154
                           currentTotalFrames:currentTotalFrames
155
                          currentFrozenFrames:currentFrozenFrames
156
                            currentSlowFrames:currentSlowFrames];
157
    }
158
    self.previouslyVisibleViewControllers = nil;
159
  });
160
}
161
 
162
- (void)appWillResignActiveNotification:(NSNotification *)notification {
163
  // To get the most accurate numbers of total, frozen and slow frames, we need to capture them as
164
  // soon as we're notified of an event.
165
  int64_t currentTotalFrames = atomic_load_explicit(&_totalFramesCount, memory_order_relaxed);
166
  int64_t currentFrozenFrames = atomic_load_explicit(&_frozenFramesCount, memory_order_relaxed);
167
  int64_t currentSlowFrames = atomic_load_explicit(&_slowFramesCount, memory_order_relaxed);
168
 
169
  dispatch_group_async(self.screenTraceTrackerDispatchGroup, self.screenTraceTrackerSerialQueue, ^{
170
    self.previouslyVisibleViewControllers = [NSPointerArray weakObjectsPointerArray];
171
    id visibleViewControllersEnumerator = [self.activeScreenTraces keyEnumerator];
172
    id visibleViewController = nil;
173
    while (visibleViewController = [visibleViewControllersEnumerator nextObject]) {
174
      [self.previouslyVisibleViewControllers addPointer:(__bridge void *)(visibleViewController)];
175
    }
176
 
177
    for (id visibleViewController in self.previouslyVisibleViewControllers) {
178
      [self stopScreenTraceForViewController:visibleViewController
179
                          currentTotalFrames:currentTotalFrames
180
                         currentFrozenFrames:currentFrozenFrames
181
                           currentSlowFrames:currentSlowFrames];
182
    }
183
  });
184
}
185
 
186
#pragma mark - Frozen, slow and good frames
187
 
188
- (void)displayLinkStep {
189
  static CFAbsoluteTime previousTimestamp = kFPRInvalidTime;
190
  CFAbsoluteTime currentTimestamp = self.displayLink.timestamp;
191
  RecordFrameType(currentTimestamp, previousTimestamp, &_slowFramesCount, &_frozenFramesCount,
192
                  &_totalFramesCount);
193
  previousTimestamp = currentTimestamp;
194
}
195
 
196
/** This function increments the relevant frame counters based on the current and previous
197
 *  timestamp provided by the displayLink.
198
 *
199
 *  @param currentTimestamp The current timestamp of the displayLink.
200
 *  @param previousTimestamp The previous timestamp of the displayLink.
201
 *  @param slowFramesCounter The value of the slowFramesCount before this function was called.
202
 *  @param frozenFramesCounter The value of the frozenFramesCount before this function was called.
203
 *  @param totalFramesCounter The value of the totalFramesCount before this function was called.
204
 */
205
FOUNDATION_STATIC_INLINE
206
void RecordFrameType(CFAbsoluteTime currentTimestamp,
207
                     CFAbsoluteTime previousTimestamp,
208
                     atomic_int_fast64_t *slowFramesCounter,
209
                     atomic_int_fast64_t *frozenFramesCounter,
210
                     atomic_int_fast64_t *totalFramesCounter) {
211
  CFTimeInterval frameDuration = currentTimestamp - previousTimestamp;
212
  if (previousTimestamp == kFPRInvalidTime) {
213
    return;
214
  }
215
  if (frameDuration > kFPRSlowFrameThreshold) {
216
    atomic_fetch_add_explicit(slowFramesCounter, 1, memory_order_relaxed);
217
  }
218
  if (frameDuration > kFPRFrozenFrameThreshold) {
219
    atomic_fetch_add_explicit(frozenFramesCounter, 1, memory_order_relaxed);
220
  }
221
  atomic_fetch_add_explicit(totalFramesCounter, 1, memory_order_relaxed);
222
}
223
 
224
#pragma mark - Helper methods
225
 
226
/** Starts a screen trace for the given UIViewController instance if it doesn't exist. This method
227
 *  does NOT ensure thread safety - the caller is responsible for making sure that this is invoked
228
 *  in a thread safe manner.
229
 *
230
 *  @param viewController The UIViewController instance for which the trace is to be started.
231
 *  @param currentTotalFrames The value of the totalFramesCount before this method was called.
232
 *  @param currentFrozenFrames The value of the frozenFramesCount before this method was called.
233
 *  @param currentSlowFrames The value of the slowFramesCount before this method was called.
234
 */
235
- (void)startScreenTraceForViewController:(UIViewController *)viewController
236
                       currentTotalFrames:(int64_t)currentTotalFrames
237
                      currentFrozenFrames:(int64_t)currentFrozenFrames
238
                        currentSlowFrames:(int64_t)currentSlowFrames {
239
  if (![self shouldCreateScreenTraceForViewController:viewController]) {
240
    return;
241
  }
242
 
243
  // If there's a trace for this viewController, don't do anything.
244
  if (![self.activeScreenTraces objectForKey:viewController]) {
245
    NSString *traceName = FPRScreenTraceNameForViewController(viewController);
246
    FIRTrace *newTrace = [[FIRTrace alloc] initInternalTraceWithName:traceName];
247
    [newTrace start];
248
    [newTrace setIntValue:currentTotalFrames forMetric:kFPRTotalFramesCounterName];
249
    [newTrace setIntValue:currentFrozenFrames forMetric:kFPRFrozenFrameCounterName];
250
    [newTrace setIntValue:currentSlowFrames forMetric:kFPRSlowFrameCounterName];
251
    [self.activeScreenTraces setObject:newTrace forKey:viewController];
252
  }
253
}
254
 
255
/** Stops a screen trace for the given UIViewController instance if it exist. This method does NOT
256
 *  ensure thread safety - the caller is responsible for making sure that this is invoked in a
257
 *  thread safe manner.
258
 *
259
 *  @param viewController The UIViewController instance for which the trace is to be stopped.
260
 *  @param currentTotalFrames The value of the totalFramesCount before this method was called.
261
 *  @param currentFrozenFrames The value of the frozenFramesCount before this method was called.
262
 *  @param currentSlowFrames The value of the slowFramesCount before this method was called.
263
 */
264
- (void)stopScreenTraceForViewController:(UIViewController *)viewController
265
                      currentTotalFrames:(int64_t)currentTotalFrames
266
                     currentFrozenFrames:(int64_t)currentFrozenFrames
267
                       currentSlowFrames:(int64_t)currentSlowFrames {
268
  FIRTrace *previousScreenTrace = [self.activeScreenTraces objectForKey:viewController];
269
 
270
  // Get a diff between the counters now and what they were at trace start.
271
  int64_t actualTotalFrames =
272
      currentTotalFrames - [previousScreenTrace valueForIntMetric:kFPRTotalFramesCounterName];
273
  int64_t actualFrozenFrames =
274
      currentFrozenFrames - [previousScreenTrace valueForIntMetric:kFPRFrozenFrameCounterName];
275
  int64_t actualSlowFrames =
276
      currentSlowFrames - [previousScreenTrace valueForIntMetric:kFPRSlowFrameCounterName];
277
 
278
  // Update the values in the trace.
279
  if (actualTotalFrames != 0) {
280
    [previousScreenTrace setIntValue:actualTotalFrames forMetric:kFPRTotalFramesCounterName];
281
  } else {
282
    [previousScreenTrace deleteMetric:kFPRTotalFramesCounterName];
283
  }
284
 
285
  if (actualFrozenFrames != 0) {
286
    [previousScreenTrace setIntValue:actualFrozenFrames forMetric:kFPRFrozenFrameCounterName];
287
  } else {
288
    [previousScreenTrace deleteMetric:kFPRFrozenFrameCounterName];
289
  }
290
 
291
  if (actualSlowFrames != 0) {
292
    [previousScreenTrace setIntValue:actualSlowFrames forMetric:kFPRSlowFrameCounterName];
293
  } else {
294
    [previousScreenTrace deleteMetric:kFPRSlowFrameCounterName];
295
  }
296
 
297
  if (previousScreenTrace.numberOfCounters > 0) {
298
    [previousScreenTrace stop];
299
  } else {
300
    // The trace did not collect any data. Don't log it.
301
    [previousScreenTrace cancel];
302
  }
303
  [self.activeScreenTraces removeObjectForKey:viewController];
304
}
305
 
306
#pragma mark - Filtering for screen traces
307
 
308
/** Determines whether to create a screen trace for the given UIViewController instance.
309
 *
310
 *  @param viewController The UIViewController instance.
311
 *  @return YES if a screen trace should be created for the given UIViewController instance,
312
        NO otherwise.
313
 */
314
- (BOOL)shouldCreateScreenTraceForViewController:(UIViewController *)viewController {
315
  if (viewController == nil) {
316
    return NO;
317
  }
318
 
319
  // Ignore non-main bundle view controllers whose class or superclass is an internal iOS view
320
  // controller. This is borrowed from the logic for tracking screens in Firebase Analytics.
321
  NSBundle *bundle = [NSBundle bundleForClass:[viewController class]];
322
  if (bundle != [NSBundle mainBundle]) {
323
    NSString *className = FPRUnprefixedClassName([viewController class]);
324
    if ([className hasPrefix:@"_"]) {
325
      return NO;
326
    }
327
    NSString *superClassName = FPRUnprefixedClassName([viewController superclass]);
328
    if ([superClassName hasPrefix:@"_"]) {
329
      return NO;
330
    }
331
  }
332
 
333
  // We are not creating screen traces for these view controllers because they're container view
334
  // controllers. They always have a child view controller which will provide better context for a
335
  // screen trace. We are however capturing traces if a developer subclasses these as there may be
336
  // some context. Special case: We are not capturing screen traces for any input view
337
  // controllers.
338
  return !([viewController isMemberOfClass:[UINavigationController class]] ||
339
           [viewController isMemberOfClass:[UITabBarController class]] ||
340
           [viewController isMemberOfClass:[UISplitViewController class]] ||
341
           [viewController isMemberOfClass:[UIPageViewController class]] ||
342
           [viewController isKindOfClass:[UIInputViewController class]]);
343
}
344
 
345
#pragma mark - Screen Traces swizzling hooks
346
 
347
- (void)viewControllerDidAppear:(UIViewController *)viewController {
348
  // To get the most accurate numbers of total, frozen and slow frames, we need to capture them as
349
  // soon as we're notified of an event.
350
  int64_t currentTotalFrames = atomic_load_explicit(&_totalFramesCount, memory_order_relaxed);
351
  int64_t currentFrozenFrames = atomic_load_explicit(&_frozenFramesCount, memory_order_relaxed);
352
  int64_t currentSlowFrames = atomic_load_explicit(&_slowFramesCount, memory_order_relaxed);
353
 
354
  dispatch_sync(self.screenTraceTrackerSerialQueue, ^{
355
    [self startScreenTraceForViewController:viewController
356
                         currentTotalFrames:currentTotalFrames
357
                        currentFrozenFrames:currentFrozenFrames
358
                          currentSlowFrames:currentSlowFrames];
359
  });
360
}
361
 
362
- (void)viewControllerDidDisappear:(id)viewController {
363
  // To get the most accurate numbers of total, frozen and slow frames, we need to capture them as
364
  // soon as we're notified of an event.
365
  int64_t currentTotalFrames = atomic_load_explicit(&_totalFramesCount, memory_order_relaxed);
366
  int64_t currentFrozenFrames = atomic_load_explicit(&_frozenFramesCount, memory_order_relaxed);
367
  int64_t currentSlowFrames = atomic_load_explicit(&_slowFramesCount, memory_order_relaxed);
368
 
369
  dispatch_sync(self.screenTraceTrackerSerialQueue, ^{
370
    [self stopScreenTraceForViewController:viewController
371
                        currentTotalFrames:currentTotalFrames
372
                       currentFrozenFrames:currentFrozenFrames
373
                         currentSlowFrames:currentSlowFrames];
374
  });
375
}
376
 
377
#pragma mark - Test Helper Methods
378
 
379
- (int_fast64_t)totalFramesCount {
380
  return atomic_load_explicit(&_totalFramesCount, memory_order_relaxed);
381
}
382
 
383
- (void)setTotalFramesCount:(int_fast64_t)count {
384
  atomic_store_explicit(&_totalFramesCount, count, memory_order_relaxed);
385
}
386
 
387
- (int_fast64_t)slowFramesCount {
388
  return atomic_load_explicit(&_slowFramesCount, memory_order_relaxed);
389
}
390
 
391
- (void)setSlowFramesCount:(int_fast64_t)count {
392
  atomic_store_explicit(&_slowFramesCount, count, memory_order_relaxed);
393
}
394
 
395
- (int_fast64_t)frozenFramesCount {
396
  return atomic_load_explicit(&_frozenFramesCount, memory_order_relaxed);
397
}
398
 
399
- (void)setFrozenFramesCount:(int_fast64_t)count {
400
  atomic_store_explicit(&_frozenFramesCount, count, memory_order_relaxed);
401
}
402
 
403
@end