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
|