Skip to content

Commit 18c3f2a

Browse files
Merge cc6765c into 59b8697
2 parents 59b8697 + cc6765c commit 18c3f2a

32 files changed

Lines changed: 1521 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@
55
### Features
66

77
- Use `android.namespace` for AGP 8 and RN 0.73 ([#3133](https://github.com/getsentry/sentry-react-native/pull/3133))
8+
- Alpha support for Hermes JavaScript Profiling ([#3057](https://github.com/getsentry/sentry-react-native/pull/3057))
9+
10+
Profiling is disabled by default. To enable it, configure both
11+
`tracesSampleRate` and `profilesSampleRate` when initializing the SDK:
12+
13+
```javascript
14+
Sentry.init({
15+
dsn: '__DSN__',
16+
tracesSampleRate: 1.0,
17+
_experiments: {
18+
// The sampling rate for profiling is relative to TracesSampleRate.
19+
// In this case, we'll capture profiles for 100% of transactions.
20+
profilesSampleRate: 1.0,
21+
},
22+
});
23+
```
24+
25+
More documentation on profiling and current limitations [can be found here](https://docs.sentry.io/platforms/react-native/profiling/).
826

927
### Dependencies
1028

RNSentry.podspec

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
require 'json'
22
version = JSON.parse(File.read('package.json'))["version"]
33

4-
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
4+
folly_flags = ' -DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1'
5+
folly_compiler_flags = folly_flags + ' ' + '-Wno-comma -Wno-shorten-64-to-32'
6+
7+
is_new_arch_enabled = ENV["RCT_NEW_ARCH_ENABLED"] == "1"
8+
new_arch_enabled_flag = (is_new_arch_enabled ? folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED" : "")
9+
other_cflags = "$(inherited)" + new_arch_enabled_flag
510

611
Pod::Spec.new do |s|
712
s.name = 'RNSentry'
@@ -24,9 +29,9 @@ Pod::Spec.new do |s|
2429
s.source_files = 'ios/**/*.{h,mm}'
2530
s.public_header_files = 'ios/RNSentry.h'
2631

32+
s.compiler_flags = other_cflags
2733
# This guard prevent to install the dependencies when we run `pod install` in the old architecture.
28-
if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
29-
s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
34+
if is_new_arch_enabled then
3035
s.pod_target_xcconfig = {
3136
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
3237
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import androidx.annotation.Nullable;
1313
import androidx.core.app.FrameMetricsAggregator;
1414

15+
import com.facebook.hermes.instrumentation.HermesSamplingProfiler;
1516
import com.facebook.react.bridge.Arguments;
1617
import com.facebook.react.bridge.Promise;
1718
import com.facebook.react.bridge.ReactApplicationContext;
@@ -25,9 +26,11 @@
2526
import com.facebook.react.bridge.WritableNativeMap;
2627

2728
import java.io.BufferedInputStream;
29+
import java.io.BufferedReader;
2830
import java.io.File;
2931
import java.io.FileNotFoundException;
3032
import java.io.FileOutputStream;
33+
import java.io.FileReader;
3134
import java.io.InputStream;
3235
import java.nio.charset.Charset;
3336
import java.util.HashMap;
@@ -79,6 +82,7 @@ public class RNSentryModuleImpl {
7982
private final PackageInfo packageInfo;
8083
private FrameMetricsAggregator frameMetricsAggregator = null;
8184
private boolean androidXAvailable;
85+
private boolean debugEnabled = false;
8286

8387
private static boolean didFetchAppStart;
8488

@@ -118,6 +122,7 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
118122

119123
if (rnOptions.hasKey("debug") && rnOptions.getBoolean("debug")) {
120124
options.setDebug(true);
125+
this.debugEnabled = true;
121126
}
122127
if (rnOptions.hasKey("dsn") && rnOptions.getString("dsn") != null) {
123128
String dsn = rnOptions.getString("dsn");
@@ -616,6 +621,48 @@ public void disableNativeFramesTracking() {
616621
}
617622
}
618623

624+
public WritableMap startProfiling() {
625+
final WritableMap result = new WritableNativeMap();
626+
try {
627+
HermesSamplingProfiler.enable();
628+
result.putBoolean("started", true);
629+
} catch (Throwable e) {
630+
result.putBoolean("started", false);
631+
result.putString("error", e.toString());
632+
}
633+
return result;
634+
}
635+
636+
public WritableMap stopProfiling() {
637+
final WritableMap result = new WritableNativeMap();
638+
try {
639+
HermesSamplingProfiler.disable();
640+
641+
final File output = File.createTempFile(
642+
"sampling-profiler-trace", ".cpuprofile", reactApplicationContext.getCacheDir());
643+
644+
if (this.debugEnabled) {
645+
logger.log(SentryLevel.INFO, "Profile saved to: " + output.getAbsolutePath());
646+
}
647+
648+
try (final BufferedReader br = new BufferedReader(new FileReader(output));) {
649+
HermesSamplingProfiler.dumpSampledTraceToFile(output.getPath());
650+
651+
final StringBuilder text = new StringBuilder();
652+
String line;
653+
while ((line = br.readLine()) != null) {
654+
text.append(line);
655+
text.append('\n');
656+
}
657+
658+
result.putString("profile", text.toString());
659+
}
660+
} catch (Throwable e) {
661+
result.putString("error", e.toString());
662+
}
663+
return result;
664+
}
665+
619666
private void setEventOriginTag(SentryEvent event) {
620667
SdkVersion sdk = event.getSdk();
621668
if (sdk != null) {

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import androidx.annotation.NonNull;
44

5+
import com.facebook.react.bridge.JavaScriptExecutorFactory;
56
import com.facebook.react.bridge.ReactApplicationContext;
67
import com.facebook.react.bridge.ReadableArray;
78
import com.facebook.react.bridge.ReadableMap;
89
import com.facebook.react.bridge.Promise;
10+
import com.facebook.react.bridge.WritableMap;
911

1012
public class RNSentryModule extends NativeRNSentrySpec {
1113

@@ -121,4 +123,14 @@ public void fetchNativeDeviceContexts(Promise promise) {
121123
public void fetchNativeSdkInfo(Promise promise) {
122124
// Not used on android
123125
}
126+
127+
@Override
128+
public WritableMap startProfiling() {
129+
return this.impl.startProfiling();
130+
}
131+
132+
@Override
133+
public WritableMap stopProfiling() {
134+
return this.impl.stopProfiling();
135+
}
124136
}

android/src/oldarch/java/io/sentry/react/RNSentryModule.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.facebook.react.bridge.ReactContext;
88
import com.facebook.react.bridge.ReactContextBaseJavaModule;
99
import com.facebook.react.bridge.ReactMethod;
10+
import com.facebook.react.bridge.WritableMap;
1011

1112
public class RNSentryModule extends ReactContextBaseJavaModule {
1213

@@ -121,4 +122,14 @@ public void fetchNativeDeviceContexts(Promise promise) {
121122
public void fetchNativeSdkInfo(Promise promise) {
122123
// Not used on android
123124
}
125+
126+
@ReactMethod(isBlockingSynchronousMethod = true)
127+
public WritableMap startProfiling() {
128+
return this.impl.startProfiling();
129+
}
130+
131+
@ReactMethod(isBlockingSynchronousMethod = true)
132+
public WritableMap stopProfiling() {
133+
return this.impl.stopProfiling();
134+
}
124135
}

ios/RNSentry.mm

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@
1111
#import <Sentry/SentryScreenFrames.h>
1212
#import <Sentry/SentryOptions+HybridSDKs.h>
1313

14+
#if __has_include(<hermes/hermes.h>)
15+
#define SENTRY_PROFILING_ENABLED 1
16+
#else
17+
#define SENTRY_PROFILING_ENABLED 0
18+
#endif
19+
20+
// This guard prevents importing Hermes in JSC apps
21+
#if SENTRY_PROFILING_ENABLED
22+
#import <hermes/hermes.h>
23+
#endif
24+
1425
// Thanks to this guard, we won't import this header when we build for the old architecture.
1526
#ifdef RCT_NEW_ARCH_ENABLED
1627
#import "RNSentrySpec.h"
@@ -498,6 +509,57 @@ - (void)setEventEnvironmentTag:(SentryEvent *)event
498509
// the 'tracesSampleRate' or 'tracesSampler' option.
499510
}
500511

512+
static NSString* const enabledProfilingMessage = @"Enable Hermes to use Sentry Profiling.";
513+
514+
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, startProfiling)
515+
{
516+
#if SENTRY_PROFILING_ENABLED
517+
try {
518+
facebook::hermes::HermesRuntime::enableSamplingProfiler();
519+
return @{ @"started": @YES };
520+
} catch (const std::exception& ex) {
521+
return @{ @"error": [NSString stringWithCString: ex.what() encoding:[NSString defaultCStringEncoding]] };
522+
} catch (...) {
523+
return @{ @"error": @"Failed to start profiling" };
524+
}
525+
#else
526+
return @{ @"error": enabledProfilingMessage };
527+
#endif
528+
}
529+
530+
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, stopProfiling)
531+
{
532+
#if SENTRY_PROFILING_ENABLED
533+
try {
534+
facebook::hermes::HermesRuntime::disableSamplingProfiler();
535+
std::stringstream ss;
536+
facebook::hermes::HermesRuntime::dumpSampledTraceToStream(ss);
537+
538+
std::string s = ss.str();
539+
NSString *data = [NSString stringWithCString:s.c_str() encoding:[NSString defaultCStringEncoding]];
540+
541+
#if SENTRY_PROFILING_DEBUG_ENABLED
542+
NSString *rawProfileFileName = @"hermes.profile";
543+
NSError *error = nil;
544+
NSString *rawProfileFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:rawProfileFileName];
545+
if (![data writeToFile:rawProfileFilePath atomically:YES encoding:NSUTF8StringEncoding error:&error]) {
546+
NSLog(@"Error writing Raw Hermes Profile to %@: %@", rawProfileFilePath, error);
547+
} else {
548+
NSLog(@"Raw Hermes Profile saved to %@", rawProfileFilePath);
549+
}
550+
#endif
551+
552+
return @{ @"profile": data };
553+
} catch (const std::exception& ex) {
554+
return @{ @"error": [NSString stringWithCString: ex.what() encoding:[NSString defaultCStringEncoding]] };
555+
} catch (...) {
556+
return @{ @"error": @"Failed to stop profiling" };
557+
}
558+
#else
559+
return @{ @"error": enabledProfilingMessage };
560+
#endif
561+
}
562+
501563
// Thanks to this guard, we won't compile this code when we build for the old architecture.
502564
#ifdef RCT_NEW_ARCH_ENABLED
503565
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
"lint:prettier": "prettier --check \"{src,test,scripts}/**/**.ts\"",
3434
"test:watch": "jest --watch",
3535
"run-ios": "cd sample-new-architecture && yarn react-native run-ios",
36-
"run-android": "cd sample-new-architecture && yarn react-native run-android"
36+
"run-android": "cd sample-new-architecture && yarn react-native run-android",
37+
"yalc:add:sentry-javascript": "yalc add @sentry/browser @sentry/core @sentry/hub @sentry/integrations @sentry/react @sentry/types @sentry/utils"
3738
},
3839
"keywords": [
3940
"react-native",

sample-new-architecture/android/app/src/main/java/com/samplenewarchitecture/MainApplication.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
11
package com.samplenewarchitecture;
22

33
import android.app.Application;
4+
5+
import com.facebook.hermes.reactexecutor.HermesExecutorFactory;
46
import com.facebook.react.PackageList;
57
import com.facebook.react.ReactApplication;
68
import com.facebook.react.ReactNativeHost;
79
import com.facebook.react.ReactPackage;
10+
import com.facebook.react.bridge.JavaScriptExecutor;
11+
import com.facebook.react.bridge.JavaScriptExecutorFactory;
12+
import com.facebook.react.common.JavascriptException;
813
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
914
import com.facebook.react.defaults.DefaultReactNativeHost;
1015
import com.facebook.soloader.SoLoader;
1116
import java.util.List;
1217

18+
import io.sentry.react.RNSentryModuleImpl;
1319
import io.sentry.react.RNSentryPackage;
1420

1521
public class MainApplication extends Application implements ReactApplication {
16-
1722
private final ReactNativeHost mReactNativeHost =
1823
new DefaultReactNativeHost(this) {
24+
1925
@Override
2026
public boolean getUseDeveloperSupport() {
2127
return BuildConfig.DEBUG;

sample-new-architecture/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"pod-install": "cd ios; RCT_NEW_ARCH_ENABLED=1 bundle exec pod install; cd ..",
1212
"pod-install-production": "cd ios; PRODUCTION=1 RCT_NEW_ARCH_ENABLED=1 bundle exec pod install; cd ..",
1313
"pod-install-legacy": "cd ios; bundle exec pod install; cd ..",
14-
"pod-install-legacy-production": "cd ios; PRODUCTION=1 bundle exec pod install; cd .."
14+
"pod-install-legacy-production": "cd ios; PRODUCTION=1 bundle exec pod install; cd ..",
15+
"clean-ios": "cd ios; rm -rf Podfile.lock Pods build; cd .."
1516
},
1617
"dependencies": {
1718
"@react-navigation/native": "^6.1.7",

sample-new-architecture/src/App.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Sentry.init({
3131
dsn: SENTRY_INTERNAL_DSN,
3232
debug: true,
3333
beforeSend: (event: Sentry.Event) => {
34-
console.log('Event beforeSend:', event);
34+
console.log('Event beforeSend:', event.event_id);
3535
return event;
3636
},
3737
// This will be called with a boolean `didCallNativeInit` when the native SDK has been contacted.
@@ -85,6 +85,9 @@ Sentry.init({
8585
// otherwise they will not work.
8686
// release: 'myapp@1.2.3+1',
8787
// dist: `1`,
88+
_experiments: {
89+
profilesSampleRate: 1,
90+
},
8891
});
8992

9093
const Stack = createStackNavigator();

0 commit comments

Comments
 (0)