Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

### Features

- Add memory, CPU, and frame measurements to Android profiling ([#6250](https://github.com/getsentry/sentry-react-native/pull/6250))
- Add `enableAutoConsoleLogs` option to opt out of automatic `console.*` capture while keeping `enableLogs: true` for manual `Sentry.logger.*` calls ([#6235](https://github.com/getsentry/sentry-react-native/pull/6235))
- Warn when Gradle resolves `sentry-android` to a version incompatible with the SDK ([#6238](https://github.com/getsentry/sentry-react-native/pull/6238))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
import io.sentry.android.core.internal.debugmeta.AssetsDebugMetaLoader;
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
import io.sentry.android.core.performance.AppStartMetrics;
import io.sentry.profilemeasurements.ProfileMeasurement;
import io.sentry.profilemeasurements.ProfileMeasurementValue;
import io.sentry.protocol.Geo;
import io.sentry.protocol.SdkVersion;
import io.sentry.protocol.SentryId;
Expand Down Expand Up @@ -823,11 +825,23 @@ private void initializeAndroidProfiler() {
}
final String tracesFilesDirPath = getProfilingTracesDirPath();

SentryFrameMetricsCollector collector = null;
try {
final SentryOptions options = Sentry.getCurrentScopes().getOptions();
if (options instanceof SentryAndroidOptions) {
collector = ((SentryAndroidOptions) options).getFrameMetricsCollector();
}
} catch (Throwable ignored) { // NOPMD - Best-effort
}
if (collector == null) {
collector = new SentryFrameMetricsCollector(reactApplicationContext, logger, buildInfo);
}

androidProfiler.set(
new AndroidProfiler(
tracesFilesDirPath,
(int) SECONDS.toMicros(1) / profilingTracesHz,
new SentryFrameMetricsCollector(reactApplicationContext, logger, buildInfo),
collector,
() -> executorService,
logger));
}
Expand Down Expand Up @@ -908,6 +922,27 @@ public WritableMap stopProfiling() {
androidProfile.putString("sampled_profile", base64AndroidProfile);
androidProfile.putInt("android_api_level", buildInfo.getSdkInfoVersion());
androidProfile.putString("build_id", getProguardUuid());

if (end.measurementsMap != null && !end.measurementsMap.isEmpty()) {
WritableMap measurements = new WritableNativeMap();
for (Map.Entry<String, ProfileMeasurement> entry : end.measurementsMap.entrySet()) {
WritableMap measurement = new WritableNativeMap();
measurement.putString("unit", entry.getValue().getUnit());
WritableArray values = new WritableNativeArray();
if (entry.getValue().getValues() != null) {
for (ProfileMeasurementValue pmv : entry.getValue().getValues()) {
WritableMap value = new WritableNativeMap();
value.putString("elapsed_since_start_ns", pmv.getRelativeStartNs());
value.putDouble("value", pmv.getValue());
values.pushMap(value);
}
}
measurement.putArray("values", values);
measurements.putMap(entry.getKey(), measurement);
}
androidProfile.putMap("measurements", measurements);
}

result.putMap("androidProfile", androidProfile);
}
} catch (Throwable e) { // NOPMD - We don't want to crash in any case
Expand Down
22 changes: 21 additions & 1 deletion packages/core/src/js/profiling/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,12 +305,32 @@ export function createAndroidWithHermesProfile(
nativeAndroid: NativeAndroidProfileEvent,
durationNs: number,
): AndroidCombinedProfileEvent {
const { measurements: nativeMeasurements, ...rest } = nativeAndroid;
let measurements: AndroidCombinedProfileEvent['measurements'];
if (nativeMeasurements) {
measurements = {};
for (const key of Object.keys(nativeMeasurements)) {
const nativeMeasurement = nativeMeasurements[key];
if (!nativeMeasurement) {
continue;
}
measurements[key] = {
unit: nativeMeasurement.unit,
values: nativeMeasurement.values.map(v => ({
elapsed_since_start_ns: Number(v.elapsed_since_start_ns),
value: v.value,
})),
};
}
}

return {
...nativeAndroid,
...rest,
platform: 'android',
js_profile: hermes.profile,
duration_ns: durationNs.toString(10),
active_thread_id: hermes.transaction.active_thread_id,
...(measurements && Object.keys(measurements).length > 0 && { measurements }),
};
}

Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/js/profiling/nativeTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,14 @@ export interface NativeAndroidProfileEvent {
* Proguard mapping file hash
*/
build_id?: string;
measurements?: Record<
string,
{
unit: string;
values: {
elapsed_since_start_ns: string;
value: number;
}[];
}
>;
}
1 change: 1 addition & 0 deletions packages/core/src/js/profiling/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type AndroidCombinedProfileEvent = {
duration_ns: string;
active_thread_id: string;
profilingStartTimestampNs?: number;
measurements?: AndroidProfileEvent['measurements'];
};

/*
Expand Down
27 changes: 27 additions & 0 deletions packages/core/test/profiling/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,30 @@ export function createMockMinimalValidAndroidProfile(): NativeAndroidProfileEven
build_id: 'mocked-build-id',
};
}

export function createMockMinimalValidAndroidProfileWithMeasurements(): NativeAndroidProfileEvent {
return {
...createMockMinimalValidAndroidProfile(),
measurements: {
frozen_frame_renders: {
unit: 'nanosecond',
values: [{ elapsed_since_start_ns: '1000000', value: 800000000 }],
},
slow_frame_renders: {
unit: 'nanosecond',
values: [{ elapsed_since_start_ns: '2000000', value: 20000000 }],
},
cpu_usage: {
unit: 'percent',
values: [
{ elapsed_since_start_ns: '0', value: 35.5 },
{ elapsed_since_start_ns: '5000000', value: 42.1 },
],
},
memory_footprint: {
unit: 'byte',
values: [{ elapsed_since_start_ns: '0', value: 104857600 }],
},
},
};
}
43 changes: 42 additions & 1 deletion packages/core/test/profiling/integration.android.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type { AndroidCombinedProfileEvent } from '../../src/js/profiling/types';

import { createAndroidWithHermesProfile } from '../../src/js/profiling/integration';
import { createMockMinimalValidAndroidProfile, createMockMinimalValidHermesProfileEvent } from './fixtures';
import {
createMockMinimalValidAndroidProfile,
createMockMinimalValidAndroidProfileWithMeasurements,
createMockMinimalValidHermesProfileEvent,
} from './fixtures';

describe('merge Hermes and Android profiles - createAndroidWithHermesProfile', () => {
it('should create Android profile structure with hermes profile', () => {
Expand Down Expand Up @@ -49,4 +53,41 @@ describe('merge Hermes and Android profiles - createAndroidWithHermesProfile', (
active_thread_id: '123',
});
});

it('should include measurements when present in native Android profile', () => {
const androidProfile = createMockMinimalValidAndroidProfileWithMeasurements();
const result = createAndroidWithHermesProfile(createMockMinimalValidHermesProfileEvent(), androidProfile, 987);

expect(result.measurements).toEqual({
frozen_frame_renders: {
unit: 'nanosecond',
values: [{ elapsed_since_start_ns: 1000000, value: 800000000 }],
},
slow_frame_renders: {
unit: 'nanosecond',
values: [{ elapsed_since_start_ns: 2000000, value: 20000000 }],
},
cpu_usage: {
unit: 'percent',
values: [
{ elapsed_since_start_ns: 0, value: 35.5 },
{ elapsed_since_start_ns: 5000000, value: 42.1 },
],
},
memory_footprint: {
unit: 'byte',
values: [{ elapsed_since_start_ns: 0, value: 104857600 }],
},
});
});

it('should not include measurements when absent from native Android profile', () => {
const result = createAndroidWithHermesProfile(
createMockMinimalValidHermesProfileEvent(),
createMockMinimalValidAndroidProfile(),
987,
);

expect(result.measurements).toBeUndefined();
});
});
33 changes: 33 additions & 0 deletions packages/core/test/profiling/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,37 @@ describe('enrichAndroidProfileWithEventContext', () => {
expect(result).not.toBeNull();
expect(result).not.toHaveProperty('profilingStartTimestampNs');
});

test('should include measurements when present', () => {
const measurements = {
cpu_usage: {
unit: 'percent' as const,
values: [
{ elapsed_since_start_ns: 0, value: 35.5 },
{ elapsed_since_start_ns: 5000000, value: 42.1 },
],
},
memory_footprint: {
unit: 'byte' as const,
values: [{ elapsed_since_start_ns: 0, value: 104857600 }],
},
};
const profile = createMockAndroidCombinedProfile({ measurements });
const event = createMockEvent();

const result = enrichAndroidProfileWithEventContext('profile-id', profile, event);

expect(result).not.toBeNull();
expect(result!.measurements).toEqual(measurements);
});

test('should not include measurements when absent', () => {
const profile = createMockAndroidCombinedProfile();
const event = createMockEvent();

const result = enrichAndroidProfileWithEventContext('profile-id', profile, event);

expect(result).not.toBeNull();
expect(result!.measurements).toBeUndefined();
});
});
Loading