Skip to content

Commit 9a727cb

Browse files
authored
Add flag to disable native profilers (#4094)
1 parent 4f3eb7e commit 9a727cb

File tree

11 files changed

+97
-31
lines changed

11 files changed

+97
-31
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Features
6+
7+
- Add an option to disable native (iOS and Android) profiling for the `HermesProfiling` integration ([#4094](https://github.com/getsentry/sentry-react-native/pull/4094))
8+
9+
To disable native profilers add the `hermesProfilingIntegration`.
10+
11+
```js
12+
import * as Sentry from '@sentry/react-native';
13+
14+
Sentry.init({
15+
integrations: [
16+
Sentry.hermesProfilingIntegration({ platformProfilers: false }),
17+
],
18+
});
19+
```
20+
321
## 5.32.0
422

523
### Features

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

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -718,15 +718,17 @@ private void initializeAndroidProfiler() {
718718
);
719719
}
720720

721-
public WritableMap startProfiling() {
721+
public WritableMap startProfiling(boolean platformProfilers) {
722722
final WritableMap result = new WritableNativeMap();
723-
if (androidProfiler == null) {
723+
if (androidProfiler == null && platformProfilers) {
724724
initializeAndroidProfiler();
725725
}
726726

727727
try {
728728
HermesSamplingProfiler.enable();
729-
androidProfiler.start();
729+
if (androidProfiler != null) {
730+
androidProfiler.start();
731+
}
730732

731733
result.putBoolean("started", true);
732734
} catch (Throwable e) {
@@ -741,7 +743,10 @@ public WritableMap stopProfiling() {
741743
final WritableMap result = new WritableNativeMap();
742744
File output = null;
743745
try {
744-
AndroidProfiler.ProfileEndData end = androidProfiler.endAndCollect(false, null);
746+
AndroidProfiler.ProfileEndData end = null;
747+
if (androidProfiler != null) {
748+
end = androidProfiler.endAndCollect(false, null);
749+
}
745750
HermesSamplingProfiler.disable();
746751

747752
output = File.createTempFile(
@@ -753,14 +758,16 @@ public WritableMap stopProfiling() {
753758
HermesSamplingProfiler.dumpSampledTraceToFile(output.getPath());
754759
result.putString("profile", readStringFromFile(output));
755760

756-
WritableMap androidProfile = new WritableNativeMap();
757-
byte[] androidProfileBytes = FileUtils.readBytesFromFile(end.traceFile.getPath(), maxTraceFileSize);
758-
String base64AndroidProfile = Base64.encodeToString(androidProfileBytes, NO_WRAP | NO_PADDING);
761+
if (end != null) {
762+
WritableMap androidProfile = new WritableNativeMap();
763+
byte[] androidProfileBytes = FileUtils.readBytesFromFile(end.traceFile.getPath(), maxTraceFileSize);
764+
String base64AndroidProfile = Base64.encodeToString(androidProfileBytes, NO_WRAP | NO_PADDING);
759765

760-
androidProfile.putString("sampled_profile", base64AndroidProfile);
761-
androidProfile.putInt("android_api_level", buildInfo.getSdkInfoVersion());
762-
androidProfile.putString("build_id", getProguardUuid());
763-
result.putMap("androidProfile", androidProfile);
766+
androidProfile.putString("sampled_profile", base64AndroidProfile);
767+
androidProfile.putInt("android_api_level", buildInfo.getSdkInfoVersion());
768+
androidProfile.putString("build_id", getProguardUuid());
769+
result.putMap("androidProfile", androidProfile);
770+
}
764771
} catch (Throwable e) {
765772
result.putString("error", e.toString());
766773
} finally {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ public void fetchNativeSdkInfo(Promise promise) {
139139
}
140140

141141
@Override
142-
public WritableMap startProfiling() {
143-
return this.impl.startProfiling();
142+
public WritableMap startProfiling(boolean platformProfilers) {
143+
return this.impl.startProfiling(platformProfilers);
144144
}
145145

146146
@Override

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ public void fetchNativeSdkInfo(Promise promise) {
139139
}
140140

141141
@ReactMethod(isBlockingSynchronousMethod = true)
142-
public WritableMap startProfiling() {
143-
return this.impl.startProfiling();
142+
public WritableMap startProfiling(boolean platformProfilers) {
143+
return this.impl.startProfiling(platformProfilers);
144144
}
145145

146146
@ReactMethod(isBlockingSynchronousMethod = true)

ios/RNSentry.mm

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -649,18 +649,22 @@ - (NSDictionary*) fetchNativeStackFramesBy: (NSArray<NSNumber*>*)instructionsAdd
649649
static SentryId* nativeProfileTraceId = nil;
650650
static uint64_t nativeProfileStartTime = 0;
651651

652-
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, startProfiling)
652+
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, startProfiling: (BOOL)platformProfilers)
653653
{
654654
#if SENTRY_PROFILING_ENABLED
655655
try {
656656
facebook::hermes::HermesRuntime::enableSamplingProfiler();
657-
if (nativeProfileTraceId == nil && nativeProfileStartTime == 0) {
657+
if (nativeProfileTraceId == nil && nativeProfileStartTime == 0 && platformProfilers) {
658658
#if SENTRY_TARGET_PROFILING_SUPPORTED
659659
nativeProfileTraceId = [RNSentryId newId];
660660
nativeProfileStartTime = [PrivateSentrySDKOnly startProfilerForTrace: nativeProfileTraceId];
661661
#endif
662662
} else {
663-
NSLog(@"Native profiling already in progress. Currently existing trace: %@", nativeProfileTraceId);
663+
if (!platformProfilers) {
664+
NSLog(@"Native profiling is disabled. Only starting Hermes profiling.");
665+
} else {
666+
NSLog(@"Native profiling already in progress. Currently existing trace: %@", nativeProfileTraceId);
667+
}
664668
}
665669
return @{ @"started": @YES };
666670
} catch (const std::exception& ex) {

samples/react-native/ios/sentryreactnativesample.xcodeproj/project.pbxproj

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,13 +632,14 @@
632632
ONLY_ACTIVE_ARCH = YES;
633633
OTHER_CFLAGS = (
634634
"$(inherited)",
635-
" ",
635+
"-DRN_FABRIC_ENABLED",
636636
);
637637
OTHER_CPLUSPLUSFLAGS = (
638638
"$(OTHER_CFLAGS)",
639639
"-DFOLLY_NO_CONFIG",
640640
"-DFOLLY_MOBILE=1",
641641
"-DFOLLY_USE_LIBCPP=1",
642+
"-DRN_FABRIC_ENABLED",
642643
);
643644
OTHER_LDFLAGS = "$(inherited)";
644645
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
@@ -714,13 +715,14 @@
714715
MTL_ENABLE_DEBUG_INFO = NO;
715716
OTHER_CFLAGS = (
716717
"$(inherited)",
717-
" ",
718+
"-DRN_FABRIC_ENABLED",
718719
);
719720
OTHER_CPLUSPLUSFLAGS = (
720721
"$(OTHER_CFLAGS)",
721722
"-DFOLLY_NO_CONFIG",
722723
"-DFOLLY_MOBILE=1",
723724
"-DFOLLY_USE_LIBCPP=1",
725+
"-DRN_FABRIC_ENABLED",
724726
);
725727
OTHER_LDFLAGS = "$(inherited)";
726728
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";

src/js/NativeRNSentry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export interface Spec extends TurboModule {
3434
enableNativeFramesTracking(): void;
3535
fetchModules(): Promise<string | undefined | null>;
3636
fetchViewHierarchy(): Promise<number[] | undefined | null>;
37-
startProfiling(): { started?: boolean; error?: string };
37+
startProfiling(platformProfilers: boolean): { started?: boolean; error?: string };
3838
stopProfiling(): {
3939
profile?: string;
4040
nativeProfile?: UnsafeObject;

src/js/profiling/integration.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type {
55
Event,
66
Integration,
77
IntegrationClass,
8-
IntegrationFn,
8+
IntegrationFnResult,
99
ThreadCpuProfile,
1010
Transaction,
1111
} from '@sentry/types';
@@ -31,19 +31,35 @@ const INTEGRATION_NAME = 'HermesProfiling';
3131

3232
const MS_TO_NS: number = 1e6;
3333

34+
export interface HermesProfilingOptions {
35+
/**
36+
* Enable or disable profiling of native (iOS and Android) threads
37+
*
38+
* @default true
39+
*/
40+
platformProfilers?: boolean;
41+
}
42+
43+
const defaultOptions: Required<HermesProfilingOptions> = {
44+
platformProfilers: true,
45+
};
46+
3447
/**
3548
* Profiling integration creates a profile for each transaction and adds it to the event envelope.
3649
*
3750
* @experimental
3851
*/
39-
export const hermesProfilingIntegration: IntegrationFn = () => {
52+
export const hermesProfilingIntegration = (
53+
initOptions: HermesProfilingOptions = defaultOptions,
54+
): IntegrationFnResult => {
4055
let _currentProfile:
4156
| {
4257
profile_id: string;
4358
startTimestampNs: number;
4459
}
4560
| undefined;
4661
let _currentProfileTimeout: number | undefined;
62+
const usePlatformProfilers = initOptions.platformProfilers ?? true;
4763

4864
const setupOnce = (): void => {
4965
if (!isHermesEnabled()) {
@@ -138,7 +154,7 @@ export const hermesProfilingIntegration: IntegrationFn = () => {
138154
* Starts a new profile and links it to the transaction.
139155
*/
140156
const _startNewProfile = (transaction: Transaction): void => {
141-
const profileStartTimestampNs = startProfiling();
157+
const profileStartTimestampNs = startProfiling(usePlatformProfilers);
142158
if (!profileStartTimestampNs) {
143159
return;
144160
}
@@ -227,8 +243,8 @@ export const HermesProfiling = convertIntegrationFnToClass(
227243
/**
228244
* Starts Profilers and returns the timestamp when profiling started in nanoseconds.
229245
*/
230-
export function startProfiling(): number | null {
231-
const started = NATIVE.startProfiling();
246+
export function startProfiling(platformProfilers: boolean): number | null {
247+
const started = NATIVE.startProfiling(platformProfilers);
232248
if (!started) {
233249
return null;
234250
}

src/js/wrapper.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ interface SentryNativeWrapper {
9595
fetchModules(): Promise<Record<string, string> | null>;
9696
fetchViewHierarchy(): PromiseLike<Uint8Array | null>;
9797

98-
startProfiling(): boolean;
98+
startProfiling(platformProfilers: boolean): boolean;
9999
stopProfiling(): {
100100
hermesProfile: Hermes.Profile;
101101
nativeProfile?: NativeProfileEvent;
@@ -531,15 +531,15 @@ export const NATIVE: SentryNativeWrapper = {
531531
return raw ? new Uint8Array(raw) : null;
532532
},
533533

534-
startProfiling(): boolean {
534+
startProfiling(platformProfilers: boolean): boolean {
535535
if (!this.enableNative) {
536536
throw this._DisabledNativeError;
537537
}
538538
if (!this._isModuleLoaded(RNSentry)) {
539539
throw this._NativeClientError;
540540
}
541541

542-
const { started, error } = RNSentry.startProfiling();
542+
const { started, error } = RNSentry.startProfiling(platformProfilers);
543543
if (started) {
544544
logger.log('[NATIVE] Start Profiling');
545545
} else {

test/profiling/integration.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { Envelope, Event, Profile, ThreadCpuProfile, Transaction, Transport
99
import * as Sentry from '../../src/js';
1010
import type { NativeDeviceContextsResponse } from '../../src/js/NativeRNSentry';
1111
import { getDebugMetadata } from '../../src/js/profiling/debugid';
12+
import type { HermesProfilingOptions } from '../../src/js/profiling/integration';
1213
import { hermesProfilingIntegration } from '../../src/js/profiling/integration';
1314
import type { AndroidProfileEvent } from '../../src/js/profiling/types';
1415
import { getDefaultEnvironment, isHermesEnabled, notWeb } from '../../src/js/utils/environment';
@@ -351,12 +352,24 @@ describe('profiling integration', () => {
351352
jest.runAllTimers();
352353
});
353354
});
355+
356+
test('platformProviders flag passed down to native', () => {
357+
mock = initTestClient({ withProfiling: true, hermesProfilingOptions: { platformProfilers: false } });
358+
const transaction: Transaction = Sentry.startTransaction({
359+
name: 'test-name',
360+
});
361+
transaction.finish();
362+
jest.runAllTimers();
363+
364+
expect(mockWrapper.NATIVE.startProfiling).toBeCalledWith(false);
365+
});
354366
});
355367

356368
function initTestClient(
357369
testOptions: {
358370
withProfiling?: boolean;
359371
environment?: string;
372+
hermesProfilingOptions?: HermesProfilingOptions;
360373
} = {
361374
withProfiling: true,
362375
},
@@ -372,6 +385,12 @@ function initTestClient(
372385
if (!testOptions.withProfiling) {
373386
return integrations.filter(i => i.name !== 'HermesProfiling');
374387
}
388+
return integrations.map(integration => {
389+
if (integration.name === 'HermesProfiling') {
390+
return hermesProfilingIntegration(testOptions.hermesProfilingOptions ?? {});
391+
}
392+
return integration;
393+
});
375394
return integrations;
376395
},
377396
transport: () => ({

0 commit comments

Comments
 (0)