Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -908,6 +910,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