Skip to content

Commit

Permalink
feat(performance): Add Time to Full Display and manual API for TTID (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
krystofwoldrich authored Mar 18, 2024
1 parent 9496de1 commit cc9666e
Show file tree
Hide file tree
Showing 27 changed files with 1,321 additions and 44 deletions.
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### Features

- Add automatic tracing of time to initial display for `react-navigation` ([#3588](https://github.com/getsentry/sentry-react-native/pull/3588))
- Automatic tracing of time to initial display for `react-navigation` ([#3588](https://github.com/getsentry/sentry-react-native/pull/3588))

When enabled the instrumentation will create TTID spans and measurements.
The TTID timestamp represent moment when the `react-navigation` screen
Expand All @@ -20,6 +20,25 @@
});
```

- Tracing of full display using manual API ([#3654](https://github.com/getsentry/sentry-react-native/pull/3654))

In combination with the `react-navigation` automatic instrumentation you can record when
the application screen is fully rendered.

For more examples and manual time to initial display see [the documentation](https://docs.sentry.io/platforms/react-native/performance/instrumentation/time-to-display).

```javascript
function Example() {
const [loaded] = React.useState(false);

return <View>
<Sentry.TimeToFullDisplay record={loaded}>
<Text>Example content</Text>
</Sentry.TimeToFullDisplay>
</View>;
}
```

### Fixes

- Allow custom `sentryUrl` for Expo updates source maps uploads ([#3664](https://github.com/getsentry/sentry-react-native/pull/3664))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package io.sentry.react;

import android.app.Activity;
import android.content.Context;
import android.view.View;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.events.RCTEventEmitter;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Map;

import io.sentry.SentryDate;
import io.sentry.SentryDateProvider;
import io.sentry.android.core.AndroidLogger;
import io.sentry.android.core.BuildInfoProvider;
import io.sentry.android.core.SentryAndroidDateProvider;
import io.sentry.android.core.internal.util.FirstDrawDoneListener;

public class RNSentryOnDrawReporterManager extends SimpleViewManager<RNSentryOnDrawReporterManager.RNSentryOnDrawReporterView> {

public static final String REACT_CLASS = "RNSentryOnDrawReporter";
private final @NotNull ReactApplicationContext mCallerContext;

public RNSentryOnDrawReporterManager(ReactApplicationContext reactContext) {
mCallerContext = reactContext;
}

@NotNull
@Override
public String getName() {
return REACT_CLASS;
}

@NotNull
@Override
protected RNSentryOnDrawReporterView createViewInstance(@NotNull ThemedReactContext themedReactContext) {
return new RNSentryOnDrawReporterView(mCallerContext, new BuildInfoProvider(new AndroidLogger()));
}

@ReactProp(name = "initialDisplay", defaultBoolean = false)
public void setInitialDisplay(RNSentryOnDrawReporterView view, boolean initialDisplay) {
view.setInitialDisplay(initialDisplay);
}

@ReactProp(name = "fullDisplay", defaultBoolean = false)
public void setFullDisplay(RNSentryOnDrawReporterView view, boolean fullDisplay) {
view.setFullDisplay(fullDisplay);
}

public Map getExportedCustomBubblingEventTypeConstants() {
return MapBuilder.builder().put(
"onDrawNextFrameView",
MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of("bubbled", "onDrawNextFrame")
)
).build();
}

public static class RNSentryOnDrawReporterView extends View {

private final @Nullable ReactApplicationContext reactContext;
private final @NotNull SentryDateProvider dateProvider = new SentryAndroidDateProvider();
private final @Nullable Runnable emitInitialDisplayEvent;
private final @Nullable Runnable emitFullDisplayEvent;
private final @Nullable BuildInfoProvider buildInfo;


public RNSentryOnDrawReporterView(@NotNull Context context) {
super(context);
reactContext = null;
buildInfo = null;
emitInitialDisplayEvent = null;
emitFullDisplayEvent = null;
}

public RNSentryOnDrawReporterView(@NotNull ReactApplicationContext context, @NotNull BuildInfoProvider buildInfoProvider) {
super(context);
reactContext = context;
buildInfo = buildInfoProvider;
emitInitialDisplayEvent = () -> emitDisplayEvent("initialDisplay");
emitFullDisplayEvent = () -> emitDisplayEvent("fullDisplay");
}

public void setFullDisplay(boolean fullDisplay) {
if (!fullDisplay) {
return;
}

registerForNextDraw(emitFullDisplayEvent);
}

public void setInitialDisplay(boolean initialDisplay) {
if (!initialDisplay) {
return;
}

registerForNextDraw(emitInitialDisplayEvent);
}

private void registerForNextDraw(@Nullable Runnable emitter) {
if (reactContext == null) {
return;
}

@Nullable Activity activity = reactContext.getCurrentActivity();
if (activity == null || emitter == null || buildInfo == null) {
return;
}

FirstDrawDoneListener
.registerForNextDraw(activity, emitter, buildInfo);
}

private void emitDisplayEvent(String type) {
final SentryDate endDate = dateProvider.now();

WritableMap event = Arguments.createMap();
event.putString("type", type);
event.putDouble("newFrameTimestampInSeconds", endDate.nanoTimestamp() / 1e9);

if (reactContext == null) {
return;
}
reactContext
.getJSModule(RCTEventEmitter.class)
.receiveEvent(getId(), "onDrawNextFrameView", event);
}
}
}
13 changes: 13 additions & 0 deletions android/src/main/java/io/sentry/react/RNSentryPackage.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package io.sentry.react;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.TurboReactPackage;
import com.facebook.react.uimanager.ViewManager;

public class RNSentryPackage extends TurboReactPackage {

Expand Down Expand Up @@ -43,4 +47,13 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() {
};
}

@NonNull
@Override
public List<ViewManager> createViewManagers(
ReactApplicationContext reactContext) {
return Arrays.asList(
new RNSentryOnDrawReporterManager(reactContext)
);
}

}
2 changes: 1 addition & 1 deletion ios/RNSentryFramesTrackerListener.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ - (void)framesTrackerHasNewFrame:(NSDate *)newFrameDate
{
[_framesTracker removeListener:self];
NSNumber *newFrameTimestampInSeconds = [NSNumber numberWithDouble:[newFrameDate timeIntervalSince1970]];

if (_emitNewFrameEvent) {
_emitNewFrameEvent(newFrameTimestampInSeconds);
}
Expand Down
70 changes: 70 additions & 0 deletions ios/RNSentryOnDrawReporter.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#import <UIKit/UIKit.h>
#import <React/RCTViewManager.h>
#import "RNSentryFramesTrackerListener.h"
#import <Sentry/SentryDependencyContainer.h>

@interface RNSentryOnDrawReporter : RCTViewManager

@end

@interface RNSentryOnDrawReporterView : UIView

@property (nonatomic, strong) RNSentryFramesTrackerListener* framesListener;
@property (nonatomic, copy) RCTBubblingEventBlock onDrawNextFrame;
@property (nonatomic) bool fullDisplay;
@property (nonatomic) bool initialDisplay;
@property (nonatomic, weak) RNSentryOnDrawReporter* delegate;

@end

@implementation RNSentryOnDrawReporter

RCT_EXPORT_MODULE(RNSentryOnDrawReporter)
RCT_EXPORT_VIEW_PROPERTY(onDrawNextFrame, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(initialDisplay, BOOL)
RCT_EXPORT_VIEW_PROPERTY(fullDisplay, BOOL)

- (UIView *)view
{
RNSentryOnDrawReporterView* view = [[RNSentryOnDrawReporterView alloc] init];
return view;
}

@end

@implementation RNSentryOnDrawReporterView

- (instancetype)init {
self = [super init];
if (self) {
RNSentryEmitNewFrameEvent emitNewFrameEvent = ^(NSNumber *newFrameTimestampInSeconds) {
if (self->_fullDisplay) {
self.onDrawNextFrame(@{
@"newFrameTimestampInSeconds": newFrameTimestampInSeconds,
@"type": @"fullDisplay"
});
return;
}

if (self->_initialDisplay) {
self.onDrawNextFrame(@{
@"newFrameTimestampInSeconds": newFrameTimestampInSeconds,
@"type": @"initialDisplay"
});
return;
}
};
_framesListener = [[RNSentryFramesTrackerListener alloc] initWithSentryFramesTracker:[[SentryDependencyContainer sharedInstance] framesTracker]
andEventEmitter:emitNewFrameEvent];
}
return self;
}

- (void)didSetProps:(NSArray<NSString *> *)changedProps
{
if (_fullDisplay || _initialDisplay) {
[_framesListener startListening];
}
}

@end
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
"@sentry/wizard": "3.16.3",
"@types/jest": "^29.5.3",
"@types/node": "^20.9.3",
"@types/react": "^18.2.14",
"@types/react": "^18.2.64",
"@types/uglify-js": "^3.17.2",
"@types/uuid": "^9.0.4",
"@types/xmlhttprequest": "^1.8.2",
Expand All @@ -106,6 +106,7 @@
"prettier": "^2.0.5",
"react": "18.2.0",
"react-native": "0.73.2",
"react-test-renderer": "^18.2.0",
"replace-in-file": "^7.0.1",
"rimraf": "^4.1.1",
"ts-jest": "^29.1.1",
Expand Down
1 change: 1 addition & 0 deletions samples/expo/app/(tabs)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export default function TabOneScreen() {

return (
<View style={styles.container}>
<Sentry.TimeToInitialDisplay record />
<Text>Welcome to Sentry Expo Sample App!</Text>
<Button
title="Capture message"
Expand Down
3 changes: 2 additions & 1 deletion samples/react-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@react-navigation/native": "^6.1.9",
"@react-navigation/native-stack": "^6.9.17",
"@react-navigation/stack": "^6.3.20",
"delay": "^6.0.0",
"react": "18.2.0",
"react-native": "0.73.2",
"react-native-gesture-handler": "^2.14.0",
Expand All @@ -38,7 +39,7 @@
"@react-native/eslint-config": "^0.73.1",
"@react-native/metro-config": "^0.73.1",
"@react-native/typescript-config": "^0.73.1",
"@types/react": "^18.2.13",
"@types/react": "^18.2.65",
"@types/react-native-vector-icons": "^6.4.18",
"@types/react-test-renderer": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.37.0",
Expand Down
Loading

0 comments on commit cc9666e

Please sign in to comment.