Skip to content

Commit ceedad9

Browse files
wip!: Add TimeToDisplay integration to force fetch the data
1 parent a33f929 commit ceedad9

16 files changed

+231
-78
lines changed

packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java

+8
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,14 @@ public void clearBreadcrumbs() {
717717
});
718718
}
719719

720+
public void popTimeToDisplayFor(String screenId, Promise promise) {
721+
if (screenId != null) {
722+
promise.resolve(RNSentryTimeToDisplay.popTimeToDisplayFor(screenId));
723+
} else {
724+
promise.resolve(RNSentryTimeToDisplay.popAnonymousTimeToDisplay());
725+
}
726+
}
727+
720728
public void setExtra(String key, String extra) {
721729
if (key == null || extra == null) {
722730
logger.log(

packages/core/android/src/main/java/io/sentry/react/RNSentryOnDrawReporterManager.java

+44-48
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,9 @@ public void setFullDisplay(RNSentryOnDrawReporterView view, boolean fullDisplay)
5757
view.setFullDisplay(fullDisplay);
5858
}
5959

60-
public Map getExportedCustomBubblingEventTypeConstants() {
61-
return MapBuilder.builder()
62-
.put(
63-
"onDrawNextFrameView",
64-
MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onDrawNextFrame")))
65-
.build();
60+
@ReactProp(name = "parentSpanId")
61+
public void setParentSpanId(RNSentryOnDrawReporterView view, String parentSpanId) {
62+
view.setParentSpanId(parentSpanId);
6663
}
6764

6865
public static class RNSentryOnDrawReporterView extends View {
@@ -71,52 +68,54 @@ public static class RNSentryOnDrawReporterView extends View {
7168

7269
private final @Nullable ReactApplicationContext reactContext;
7370
private final @NotNull SentryDateProvider dateProvider = new SentryAndroidDateProvider();
74-
private final @Nullable Runnable emitInitialDisplayEvent;
75-
private final @Nullable Runnable emitFullDisplayEvent;
7671
private final @Nullable BuildInfoProvider buildInfo;
7772

73+
private boolean isInitialDisplay = false;
74+
private boolean isFullDisplay = false;
75+
private @Nullable String parentSpanId = null;
76+
7877
public RNSentryOnDrawReporterView(@NotNull Context context) {
7978
super(context);
8079
reactContext = null;
8180
buildInfo = null;
82-
emitInitialDisplayEvent = null;
83-
emitFullDisplayEvent = null;
8481
}
8582

8683
public RNSentryOnDrawReporterView(
8784
@NotNull ReactApplicationContext context, @NotNull BuildInfoProvider buildInfoProvider) {
8885
super(context);
8986
reactContext = context;
9087
buildInfo = buildInfoProvider;
91-
emitInitialDisplayEvent = () -> emitDisplayEvent("initialDisplay");
92-
emitFullDisplayEvent = () -> emitDisplayEvent("fullDisplay");
9388
}
9489

9590
public void setFullDisplay(boolean fullDisplay) {
96-
if (!fullDisplay) {
97-
return;
98-
}
99-
100-
logger.log(SentryLevel.DEBUG, "[TimeToDisplay] Register full display event emitter.");
101-
registerForNextDraw(emitFullDisplayEvent);
91+
isFullDisplay = fullDisplay;
92+
registerForNextDraw();
10293
}
10394

10495
public void setInitialDisplay(boolean initialDisplay) {
105-
if (!initialDisplay) {
106-
return;
107-
}
96+
isInitialDisplay = initialDisplay;
97+
registerForNextDraw();
98+
}
10899

109-
logger.log(SentryLevel.DEBUG, "[TimeToDisplay] Register initial display event emitter.");
110-
registerForNextDraw(emitInitialDisplayEvent);
100+
public void setParentSpanId(@Nullable String parentSpanId) {
101+
this.parentSpanId = parentSpanId;
102+
registerForNextDraw();
111103
}
112104

113-
private void registerForNextDraw(@Nullable Runnable emitter) {
114-
if (emitter == null) {
115-
logger.log(
116-
SentryLevel.ERROR,
117-
"[TimeToDisplay] Won't emit next frame drawn event, emitter is null.");
105+
private void registerForNextDraw() {
106+
if (parentSpanId == null) {
107+
return;
108+
}
109+
110+
if (isInitialDisplay) {
111+
logger.log(SentryLevel.DEBUG, "[TimeToDisplay] Register initial display event emitter.");
112+
} else if (isFullDisplay) {
113+
logger.log(SentryLevel.DEBUG, "[TimeToDisplay] Register full display event emitter.");
114+
} else {
115+
logger.log(SentryLevel.DEBUG, "[TimeToDisplay] Not ready, missing displayType prop.");
118116
return;
119117
}
118+
120119
if (buildInfo == null) {
121120
logger.log(
122121
SentryLevel.ERROR,
@@ -138,26 +137,23 @@ private void registerForNextDraw(@Nullable Runnable emitter) {
138137
return;
139138
}
140139

141-
FirstDrawDoneListener.registerForNextDraw(activity, emitter, buildInfo);
142-
}
143-
144-
private void emitDisplayEvent(String type) {
145-
final SentryDate endDate = dateProvider.now();
146-
147-
WritableMap event = Arguments.createMap();
148-
event.putString("type", type);
149-
event.putDouble("newFrameTimestampInSeconds", endDate.nanoTimestamp() / 1e9);
150-
151-
if (reactContext == null) {
152-
logger.log(
153-
SentryLevel.ERROR,
154-
"[TimeToDisplay] Recorded next frame draw but can't emit the event, reactContext is"
155-
+ " null.");
156-
return;
157-
}
158-
reactContext
159-
.getJSModule(RCTEventEmitter.class)
160-
.receiveEvent(getId(), "onDrawNextFrameView", event);
140+
FirstDrawDoneListener.registerForNextDraw(activity, () -> {
141+
final Double now = dateProvider.now().nanoTimestamp() / 1e9;
142+
if (parentSpanId == null) {
143+
logger.log(
144+
SentryLevel.ERROR,
145+
"[TimeToDisplay] parentSpanId removed before frame was rendered.");
146+
return;
147+
}
148+
149+
if (isInitialDisplay) {
150+
RNSentryTimeToDisplay.putTimeToDisplayFor("ttid-" + parentSpanId, now);
151+
} else if (isFullDisplay) {
152+
RNSentryTimeToDisplay.putTimeToDisplayFor("ttfd-" + parentSpanId, now);
153+
} else {
154+
logger.log(SentryLevel.DEBUG, "[TimeToDisplay] display type removed before frame was rendered.");
155+
}
156+
}, buildInfo);
161157
}
162158
}
163159
}

packages/core/android/src/main/java/io/sentry/react/RNSentryTimeToDisplay.java

+19
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,30 @@
66
import com.facebook.react.bridge.Promise;
77
import io.sentry.SentryDate;
88
import io.sentry.SentryDateProvider;
9+
import java.util.LinkedHashMap;
10+
import java.util.Map;
911

1012
public final class RNSentryTimeToDisplay {
1113

1214
private RNSentryTimeToDisplay() {}
1315

16+
private static final int ENTRIES_MAX_SIZE = 50;
17+
private static final Map<String, Double> screenIdToRenderDuration =
18+
new LinkedHashMap<>(ENTRIES_MAX_SIZE + 1, 0.75f, true) {
19+
@Override
20+
protected boolean removeEldestEntry(Map.Entry<String, Double> eldest) {
21+
return size() > ENTRIES_MAX_SIZE;
22+
}
23+
};
24+
25+
public static Double popTimeToDisplayFor(String screenId) {
26+
return screenIdToRenderDuration.remove(screenId);
27+
}
28+
29+
public static void putTimeToDisplayFor(String screenId, Double value) {
30+
screenIdToRenderDuration.put(screenId, value);
31+
}
32+
1433
public static void getTimeToDisplay(Promise promise, SentryDateProvider dateProvider) {
1534
Looper mainLooper = Looper.getMainLooper();
1635

packages/core/android/src/newarch/java/io/sentry/react/RNSentryModule.java

+5
Original file line numberDiff line numberDiff line change
@@ -182,4 +182,9 @@ public void getNewScreenTimeToDisplay(Promise promise) {
182182
public void getDataFromUri(String uri, Promise promise) {
183183
this.impl.getDataFromUri(uri, promise);
184184
}
185+
186+
@Override
187+
public void popTimeToDisplayFor(String key, Promise promise) {
188+
this.impl.popTimeToDisplayFor(key, promise);
189+
}
185190
}

packages/core/ios/RNSentryOnDrawReporter.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
@interface RNSentryOnDrawReporterView : UIView
1414

1515
@property (nonatomic, strong) RNSentryFramesTrackerListener *framesListener;
16-
@property (nonatomic, copy) RCTBubblingEventBlock onDrawNextFrame;
1716
@property (nonatomic) bool fullDisplay;
1817
@property (nonatomic) bool initialDisplay;
18+
@property (nonatomic, copy) NSString *parentSpanId;
1919
@property (nonatomic, weak) RNSentryOnDrawReporter *delegate;
2020

2121
@end

packages/core/ios/RNSentryOnDrawReporter.m

+4-10
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
@implementation RNSentryOnDrawReporter
88

99
RCT_EXPORT_MODULE(RNSentryOnDrawReporter)
10-
RCT_EXPORT_VIEW_PROPERTY(onDrawNextFrame, RCTBubblingEventBlock)
1110
RCT_EXPORT_VIEW_PROPERTY(initialDisplay, BOOL)
1211
RCT_EXPORT_VIEW_PROPERTY(fullDisplay, BOOL)
12+
RCT_EXPORT_VIEW_PROPERTY(parentSpanId, BOOL)
1313

1414
- (UIView *)view
1515
{
@@ -27,18 +27,12 @@ - (instancetype)init
2727
if (self) {
2828
RNSentryEmitNewFrameEvent emitNewFrameEvent = ^(NSNumber *newFrameTimestampInSeconds) {
2929
if (self->_fullDisplay) {
30-
self.onDrawNextFrame(@{
31-
@"newFrameTimestampInSeconds" : newFrameTimestampInSeconds,
32-
@"type" : @"fullDisplay"
33-
});
30+
RNSentryTimeToDisplay.putTimeToDisplayFor([@"ttfd-" stringByAppendingString:self->_parentSpanId], newFrameTimestampInSeconds);
3431
return;
3532
}
3633

3734
if (self->_initialDisplay) {
38-
self.onDrawNextFrame(@{
39-
@"newFrameTimestampInSeconds" : newFrameTimestampInSeconds,
40-
@"type" : @"initialDisplay"
41-
});
35+
RNSentryTimeToDisplay.putTimeToDisplayFor([@"ttid-" stringByAppendingString:self->_parentSpanId], newFrameTimestampInSeconds);
4236
return;
4337
}
4438
};
@@ -51,7 +45,7 @@ - (instancetype)init
5145

5246
- (void)didSetProps:(NSArray<NSString *> *)changedProps
5347
{
54-
if (_fullDisplay || _initialDisplay) {
48+
if ((_fullDisplay || _initialDisplay) && [_parentSpanId isKindOfClass:[NSString class]]) {
5549
[_framesListener startListening];
5650
}
5751
}

packages/core/ios/RNSentryTimeToDisplay.h

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
@interface RNSentryTimeToDisplay : NSObject
44

5+
+ (NSNumber *)popTimeToDisplayFor:(NSString *)screenId;
6+
+ (void)putTimeToDisplayFor:(NSString *)screenId value:(NSNumber *)value;
7+
58
- (void)getTimeToDisplay:(RCTResponseSenderBlock)callback;
69

710
@end

packages/core/ios/RNSentryTimeToDisplay.m

+24
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,30 @@ @implementation RNSentryTimeToDisplay {
77
RCTResponseSenderBlock resolveBlock;
88
}
99

10+
static const int ENTRIES_MAX_SIZE = 50;
11+
static NSMutableDictionary<NSString *, NSNumber *> *screenIdToRenderDuration;
12+
13+
+ (void)initialize {
14+
if (self == [RNSentryTimeToDisplay class]) {
15+
screenIdToRenderDuration = [[NSMutableDictionary alloc] init];
16+
}
17+
}
18+
19+
+ (NSNumber *)popTimeToDisplayFor:(NSString *)screenId {
20+
NSNumber *value = screenIdToRenderDuration[screenId];
21+
[screenIdToRenderDuration removeObjectForKey:screenId];
22+
return value;
23+
}
24+
25+
+ (void)putTimeToDisplayFor:(NSString *)screenId value:(NSNumber *)value {
26+
// Remove oldest entry if at max size
27+
if (screenIdToRenderDuration.count >= ENTRIES_MAX_SIZE) {
28+
NSString *firstKey = screenIdToRenderDuration.allKeys.firstObject;
29+
[screenIdToRenderDuration removeObjectForKey:firstKey];
30+
}
31+
screenIdToRenderDuration[screenId] = value;
32+
}
33+
1034
// Rename requestAnimationFrame to getTimeToDisplay
1135
- (void)getTimeToDisplay:(RCTResponseSenderBlock)callback
1236
{

packages/core/src/js/NativeRNSentry.ts

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export interface Spec extends TurboModule {
4949
getCurrentReplayId(): string | undefined | null;
5050
crashedLastRun(): Promise<boolean | undefined | null>;
5151
getDataFromUri(uri: string): Promise<number[]>;
52+
popTimeToDisplayFor(key: string): Promise<number | undefined | null>;
5253
}
5354

5455
export type NativeStackFrame = {

packages/core/src/js/integrations/default.ts

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
sdkInfoIntegration,
3434
spotlightIntegration,
3535
stallTrackingIntegration,
36+
timeToDisplayIntegration,
3637
userInteractionIntegration,
3738
viewHierarchyIntegration,
3839
} from './exports';
@@ -115,6 +116,7 @@ export function getDefaultIntegrations(options: ReactNativeClientOptions): Integ
115116
integrations.push(appRegistryIntegration());
116117
integrations.push(reactNativeTracingIntegration());
117118
}
119+
integrations.push(timeToDisplayIntegration());
118120
if (options.enableCaptureFailedRequests) {
119121
integrations.push(httpClientIntegration());
120122
}

packages/core/src/js/integrations/exports.ts

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export { stallTrackingIntegration } from '../tracing/integrations/stalltracking'
2121
export { userInteractionIntegration } from '../tracing/integrations/userInteraction';
2222
export { createReactNativeRewriteFrames } from './rewriteframes';
2323
export { appRegistryIntegration } from './appRegistry';
24+
export { timeToDisplayIntegration } from '../tracing/integrations/timetodisplay';
2425

2526
export {
2627
breadcrumbsIntegration,

0 commit comments

Comments
 (0)