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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- Send Source Maps Debug ID for symbolicated Profiles ([#3343](https://github.com/getsentry/sentry-react-native/pull/3343))

### Fixes

- Add actual `activeThreadId` to Profiles ([#3338](https://github.com/getsentry/sentry-react-native/pull/3338))
Expand Down
38 changes: 37 additions & 1 deletion src/js/profiling/convertHermesProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { FrameId, StackId, ThreadCpuFrame, ThreadCpuSample, ThreadCpuStack,
import { logger } from '@sentry/utils';

import type * as Hermes from './hermes';
import { parseHermesJSStackFrame } from './hermes';
import { DEFAULT_BUNDLE_NAME } from './hermes';
import { MAX_PROFILE_DURATION_MS } from './integration';
import type { RawThreadCpuProfile } from './types';

Expand Down Expand Up @@ -173,3 +173,39 @@ function mapStacks(
hermesStackToSentryStackMap,
};
}

/**
* Parses Hermes StackFrame to Sentry StackFrame.
* For native frames only function name is returned, for Hermes bytecode the line and column are calculated.
*/
export function parseHermesJSStackFrame(frame: Hermes.StackFrame): ThreadCpuFrame {
if (frame.category !== 'JavaScript') {
// Native
if (frame.name === '[root]') {
return { function: frame.name, in_app: false };
}
return { function: frame.name };
}

if (frame.funcVirtAddr !== undefined && frame.offset !== undefined) {
// Hermes Bytecode
return {
function: frame.name,
abs_path: DEFAULT_BUNDLE_NAME,
// https://github.com/krystofwoldrich/metro/blob/417e6f276ff9422af6039fc4d1bce41fcf7d9f46/packages/metro-symbolicate/src/Symbolication.js#L298-L301
// Hermes lineno is hardcoded 1, currently only one bundle symbolication is supported by metro-symbolicate and thus by us.
lineno: 1,
// Hermes colno is 0-based, while Sentry is 1-based
colno: Number(frame.funcVirtAddr) + Number(frame.offset) + 1,
};
}

// JavaScript
const indexOfLeftParenthesis = frame.name.indexOf('(');
return {
function: indexOfLeftParenthesis !== -1 ? frame.name.substring(0, indexOfLeftParenthesis) || undefined : frame.name,
abs_path: DEFAULT_BUNDLE_NAME,
lineno: frame.line !== undefined ? Number(frame.line) : undefined,
colno: frame.column !== undefined ? Number(frame.column) : undefined,
};
}
45 changes: 1 addition & 44 deletions src/js/profiling/hermes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,51 +53,8 @@ export interface Profile {
stackFrames: Record<string, StackFrame>;
}

export interface ParsedHermesStackFrame {
function: string;
file?: string;
lineno?: number;
colno?: number;
}

const DEFAULT_BUNDLE_NAME =
export const DEFAULT_BUNDLE_NAME =
Platform.OS === 'android' ? ANDROID_DEFAULT_BUNDLE_NAME : Platform.OS === 'ios' ? IOS_DEFAULT_BUNDLE_NAME : undefined;
const ANONYMOUS_FUNCTION_NAME = 'anonymous';

/**
* Parses Hermes StackFrame to Sentry StackFrame.
* For native frames only function name is returned, for Hermes bytecode the line and column are calculated.
*/
export function parseHermesJSStackFrame(frame: StackFrame): ParsedHermesStackFrame {
if (frame.category !== 'JavaScript') {
// Native
return { function: frame.name };
}

if (frame.funcVirtAddr !== undefined && frame.offset !== undefined) {
// Hermes Bytecode
return {
function: frame.name || ANONYMOUS_FUNCTION_NAME,
file: DEFAULT_BUNDLE_NAME,
// https://github.com/krystofwoldrich/metro/blob/417e6f276ff9422af6039fc4d1bce41fcf7d9f46/packages/metro-symbolicate/src/Symbolication.js#L298-L301
// Hermes lineno is hardcoded 1, currently only one bundle symbolication is supported by metro-symbolicate and thus by us.
lineno: 1,
// Hermes colno is 0-based, while Sentry is 1-based
colno: Number(frame.funcVirtAddr) + Number(frame.offset) + 1,
};
}

// JavaScript
const indexOfLeftParenthesis = frame.name.indexOf('(');
return {
function:
(indexOfLeftParenthesis !== -1 && (frame.name.substring(0, indexOfLeftParenthesis) || ANONYMOUS_FUNCTION_NAME)) ||
frame.name,
file: DEFAULT_BUNDLE_NAME,
lineno: frame.line !== undefined ? Number(frame.line) : undefined,
colno: frame.column !== undefined ? Number(frame.column) : undefined,
};
}

const MS_TO_NS: number = 1e6;

Expand Down
3 changes: 2 additions & 1 deletion src/js/profiling/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ThreadCpuProfile } from '@sentry/types';
import type { ThreadCpuFrame, ThreadCpuProfile } from '@sentry/types';

export interface RawThreadCpuProfile extends ThreadCpuProfile {
frames: ThreadCpuFrame[];
profile_id?: string;
active_thread_id: string;
}
43 changes: 40 additions & 3 deletions src/js/profiling/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Envelope, Event, Profile } from '@sentry/types';
import { forEachEnvelopeItem, logger } from '@sentry/utils';
import type { DebugImage, Envelope, Event, Profile } from '@sentry/types';
import { forEachEnvelopeItem, GLOBAL_OBJ, logger } from '@sentry/utils';

import { DEFAULT_BUNDLE_NAME } from './hermes';
import type { RawThreadCpuProfile } from './types';

/**
Expand Down Expand Up @@ -136,7 +137,7 @@ function createProfilePayload(
const profile: Profile = {
event_id: profile_id,
timestamp: new Date(start_timestamp).toISOString(),
platform: 'node',
platform: 'javascript',
version: '1',
release: release,
environment: environment,
Expand All @@ -163,11 +164,47 @@ function createProfilePayload(
trace_id: trace_id || '',
active_thread_id: cpuProfile.active_thread_id,
},
debug_meta: {
images: getDebugMetadata(),
},
};

return profile;
}

/**
* Returns debug meta images of the loaded bundle.
*/
export function getDebugMetadata(): DebugImage[] {
if (!DEFAULT_BUNDLE_NAME) {
return [];
}

const debugIdMap = GLOBAL_OBJ._sentryDebugIds;
if (!debugIdMap) {
return [];
}

const debugIdsKeys = Object.keys(debugIdMap);
if (!debugIdsKeys.length) {
return [];
}

if (debugIdsKeys.length > 1) {
logger.warn(
'[Profiling] Multiple debug images found, but only one one bundle is supported. Using the first one...',
);
}

return [
{
code_file: DEFAULT_BUNDLE_NAME,
debug_id: debugIdMap[debugIdsKeys[0]],
type: 'sourcemap',
},
];
}

/**
* Adds items to envelope if they are not already present - mutates the envelope.
* @param envelope
Expand Down
9 changes: 5 additions & 4 deletions test/profiling/convertHermesProfile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,23 +84,24 @@ describe('convert hermes profile to sentry profile', () => {
frames: [
{
function: '[root]',
in_app: false,
},
{
colno: 33,
file: 'app:///main.jsbundle',
abs_path: 'app:///main.jsbundle',
function: 'fooA',
lineno: 1610,
},
{
colno: 21,
file: 'app:///main.jsbundle',
abs_path: 'app:///main.jsbundle',
function: 'fooB',
lineno: 1616,
},
{
colno: 18,
file: 'app:///main.jsbundle',
function: 'anonymous',
abs_path: 'app:///main.jsbundle',
function: undefined,
lineno: 1627,
},
],
Expand Down
20 changes: 10 additions & 10 deletions test/profiling/hermes.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ParsedHermesStackFrame } from '../../src/js/profiling/hermes';
import { parseHermesJSStackFrame } from '../../src/js/profiling/hermes';
import type { ThreadCpuFrame } from '@sentry/types';

import { parseHermesJSStackFrame } from '../../src/js/profiling/convertHermesProfile';

describe('hermes', () => {
describe('parseHermesStackFrameName', () => {
Expand All @@ -11,9 +12,9 @@ describe('hermes', () => {
column: '33',
category: 'JavaScript',
}),
).toEqual(<ParsedHermesStackFrame>{
).toEqual(<ThreadCpuFrame>{
function: 'fooA',
file: 'app:///main.jsbundle',
abs_path: 'app:///main.jsbundle',
lineno: 1610,
colno: 33,
});
Expand All @@ -25,7 +26,7 @@ describe('hermes', () => {
category: 'root',
}),
).toEqual(
expect.objectContaining(<ParsedHermesStackFrame>{
expect.objectContaining(<ThreadCpuFrame>{
function: '[root]',
}),
);
Expand All @@ -38,9 +39,8 @@ describe('hermes', () => {
column: '33',
category: 'JavaScript',
}),
).toEqual(<ParsedHermesStackFrame>{
function: 'anonymous',
file: 'app:///main.jsbundle',
).toEqual(<ThreadCpuFrame>{
abs_path: 'app:///main.jsbundle',
lineno: 1610,
colno: 33,
});
Expand All @@ -52,9 +52,9 @@ describe('hermes', () => {
category: 'JavaScript',
}),
).toEqual(
expect.objectContaining(<ParsedHermesStackFrame>{
expect.objectContaining(<ThreadCpuFrame>{
function: 'fooA',
file: 'app:///main.jsbundle',
abs_path: 'app:///main.jsbundle',
}),
);
});
Expand Down