-
-
Notifications
You must be signed in to change notification settings - Fork 413
Expand file tree
/
Copy pathSentryTimeToDisplayTracker.m
More file actions
217 lines (180 loc) · 7.84 KB
/
Copy pathSentryTimeToDisplayTracker.m
File metadata and controls
217 lines (180 loc) · 7.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#import "SentryTimeToDisplayTracker.h"
#if SENTRY_HAS_UIKIT
# import "SentryInternalDefines.h"
# import "SentryLogC.h"
# import "SentryProfilingConditionals.h"
# import "SentrySDK+Private.h"
# import "SentrySpanContext.h"
# import "SentrySpanId.h"
# import "SentrySpanOperation.h"
# import "SentrySwift.h"
# import "SentryTraceOrigin.h"
# import "SentryTracer.h"
# import <UIKit/UIKit.h>
# if SENTRY_TARGET_PROFILING_SUPPORTED
# import "SentryLaunchProfiling.h"
# import "SentryProfiler+Private.h"
# endif // SENTRY_TARGET_PROFILING_SUPPORTED
@interface SentryTimeToDisplayTracker () <SentryFramesTrackerListener>
@property (nonatomic, weak) id<SentrySpan> initialDisplaySpan;
@property (nonatomic, weak) id<SentrySpan> fullDisplaySpan;
@property (nonatomic, strong, readonly) SentryDispatchQueueWrapper *dispatchQueueWrapper;
@end
@implementation SentryTimeToDisplayTracker {
BOOL _waitForFullDisplay;
BOOL _initialDisplayReported;
BOOL _fullyDisplayedReported;
NSString *_name;
}
- (instancetype)initWithName:(NSString *)name
waitForFullDisplay:(BOOL)waitForFullDisplay
dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper
{
if (self = [super init]) {
_name = name;
_waitForFullDisplay = waitForFullDisplay;
_dispatchQueueWrapper = dispatchQueueWrapper;
_initialDisplayReported = NO;
_fullyDisplayedReported = NO;
}
return self;
}
- (BOOL)startForTracer:(SentryTracer *)tracer
{
if (SentryDependencyContainer.sharedInstance.framesTracker.isRunning == NO) {
SENTRY_LOG_DEBUG(@"Skipping TTID/TTFD spans, because can't report them correctly when the "
@"frames tracker isn't running.");
return NO;
}
SENTRY_LOG_DEBUG(@"Starting initial display span");
self.initialDisplaySpan =
[tracer startChildWithOperation:SentrySpanOperationUiLoadInitialDisplay
description:[NSString stringWithFormat:@"%@ initial display", _name]];
self.initialDisplaySpan.origin = SentryTraceOriginAutoUITimeToDisplay;
if (self.waitForFullDisplay) {
SENTRY_LOG_DEBUG(@"Starting full display span");
self.fullDisplaySpan =
[tracer startChildWithOperation:SentrySpanOperationUiLoadFullDisplay
description:[NSString stringWithFormat:@"%@ full display", _name]];
self.fullDisplaySpan.origin = SentryTraceOriginManualUITimeToDisplay;
// By concept TTID and TTFD spans should have the same beginning,
// which also should be the same of the transaction starting.
self.fullDisplaySpan.startTimestamp = tracer.startTimestamp;
}
self.initialDisplaySpan.startTimestamp = tracer.startTimestamp;
[SentryDependencyContainer.sharedInstance.framesTracker addListener:self];
[tracer setShouldIgnoreWaitForChildrenCallback:^(id<SentrySpan> span) {
if ([span.origin isEqualToString:SentryTraceOriginAutoUITimeToDisplay]) {
return YES;
}
return NO;
}];
[tracer setFinishCallback:^(SentryTracer *_tracer) {
[SentryDependencyContainer.sharedInstance.framesTracker removeListener:self];
// Strongify the weak span references so they can't become dangling
// mid-callback if the tracer's children are released concurrently.
id<SentrySpan> initialSpan = self.initialDisplaySpan;
id<SentrySpan> fullSpan = self.fullDisplaySpan;
if (initialSpan != nil && initialSpan.isFinished == NO) {
[initialSpan finish];
}
if (initialSpan != nil) {
initialSpan.startTimestamp = _tracer.startTimestamp;
[self addTimeToDisplayMeasurement:initialSpan name:@"time_to_initial_display"];
}
if (fullSpan == nil) {
return;
}
fullSpan.startTimestamp = _tracer.startTimestamp;
[self addTimeToDisplayMeasurement:fullSpan name:@"time_to_full_display"];
if (fullSpan.status != kSentrySpanStatusDeadlineExceeded) {
return;
}
if (initialSpan != nil) {
fullSpan.timestamp = initialSpan.timestamp;
}
fullSpan.spanDescription =
[NSString stringWithFormat:@"%@ - Deadline Exceeded", fullSpan.spanDescription];
[self addTimeToDisplayMeasurement:fullSpan name:@"time_to_full_display"];
}];
return YES;
}
- (void)reportInitialDisplay
{
SENTRY_LOG_DEBUG(@"Reporting initial display for %@", _name);
_initialDisplayReported = YES;
}
- (void)reportFullyDisplayed
{
SENTRY_LOG_DEBUG(@"Reporting full display for %@", _name);
// All other accesses to _fullyDisplayedReported run on the main thread.
// To avoid using locks, we execute this on the main queue instead.
[_dispatchQueueWrapper
dispatchAsyncOnMainQueueIfNotMainThread:^{ self->_fullyDisplayedReported = YES; }];
}
- (void)finishSpansIfNotFinished
{
[SentryDependencyContainer.sharedInstance.framesTracker removeListener:self];
if (self.initialDisplaySpan.isFinished == NO) {
[self.initialDisplaySpan finish];
}
if (self.fullDisplaySpan.isFinished == NO) {
if (_fullyDisplayedReported) {
SENTRY_LOG_DEBUG(
@"SentrySDK.reportFullyDisplayed() was called but didn't receive a new frame to "
@"finish the TTFD span. Finishing the full display span so the SDK can start a new "
@"time to display tracker.");
[self.fullDisplaySpan finish];
return;
}
SENTRY_LOG_WARN(@"You didn't call SentrySDK.reportFullyDisplayed() for UIViewController: "
@"%@. Finishing full display span with status: %@.",
_name, nameForSentrySpanStatus(kSentrySpanStatusDeadlineExceeded));
[self.fullDisplaySpan finishWithStatus:kSentrySpanStatusDeadlineExceeded];
}
}
- (void)framesTrackerHasNewFrame:(NSDate *)newFrameDate
{
// The purpose of TTID and TTFD is to measure how long
// takes to the content of the screen to change.
// That's why we need to wait for the next frame to be drawn.
if (_initialDisplayReported && self.initialDisplaySpan.isFinished == NO) {
SENTRY_LOG_DEBUG(@"Finishing initial display span");
self.initialDisplaySpan.timestamp = newFrameDate;
[self.initialDisplaySpan finish];
if (!_waitForFullDisplay) {
[SentryDependencyContainer.sharedInstance.framesTracker removeListener:self];
# if SENTRY_TARGET_PROFILING_SUPPORTED
if (sentry_isLaunchProfileCorrelatedToTraces()) {
sentry_stopAndDiscardLaunchProfileTracer(SentrySDKInternal.currentHub);
}
# endif // SENTRY_TARGET_PROFILING_SUPPORTED
}
}
if (_waitForFullDisplay && _fullyDisplayedReported && self.fullDisplaySpan.isFinished == NO
&& self.initialDisplaySpan.isFinished == YES) {
SENTRY_LOG_DEBUG(@"Finishing full display span");
self.fullDisplaySpan.timestamp = newFrameDate;
[self.fullDisplaySpan finish];
# if SENTRY_TARGET_PROFILING_SUPPORTED
if (sentry_isLaunchProfileCorrelatedToTraces()) {
sentry_stopAndDiscardLaunchProfileTracer(SentrySDKInternal.currentHub);
}
# endif // SENTRY_TARGET_PROFILING_SUPPORTED
}
if (self.initialDisplaySpan.isFinished == YES && self.fullDisplaySpan.isFinished == YES) {
[SentryDependencyContainer.sharedInstance.framesTracker removeListener:self];
}
}
- (void)addTimeToDisplayMeasurement:(id<SentrySpan>)span name:(NSString *)name
{
if (!span.startTimestamp) {
return;
}
NSTimeInterval duration =
[span.timestamp timeIntervalSinceDate:SENTRY_UNWRAP_NULLABLE(NSDate, span.startTimestamp)]
* 1000;
[span setMeasurement:name value:@(duration) unit:SentryMeasurementUnitDuration.millisecond];
}
@end
#endif // SENTRY_HAS_UIKIT