Skip to content

Commit 9a0606f

Browse files
committed
put util functions in utils
1 parent 718be4f commit 9a0606f

File tree

3 files changed

+120
-89
lines changed

3 files changed

+120
-89
lines changed

packages/browser/src/profiling/integration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { debug, defineIntegration, getActiveSpan, getRootSpan, hasSpansEnabled }
33
import type { BrowserOptions } from '../client';
44
import { DEBUG_BUILD } from '../debug-build';
55
import { WINDOW } from '../helpers';
6-
import { BrowserTraceLifecycleProfiler } from './session/traceLifecycleProfiler';
6+
import { BrowserTraceLifecycleProfiler } from './lifecycleMode/traceLifecycleProfiler';
77
import { startProfileForSpan } from './startProfileForSpan';
88
import type { ProfiledEvent } from './utils';
99
import {

packages/browser/src/profiling/session/traceLifecycleProfiler.ts renamed to packages/browser/src/profiling/lifecycleMode/traceLifecycleProfiler.ts

Lines changed: 4 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from '@sentry/core';
1212
import { DEBUG_BUILD } from '../../debug-build';
1313
import type { JSSelfProfiler } from '../jsSelfProfiling';
14-
import { applyDebugMetadata as applyDebugImages, startJSSelfProfile } from '../utils';
14+
import { createProfileChunkPayload, startJSSelfProfile } from '../utils';
1515

1616
const CHUNK_INTERVAL_MS = 60_000;
1717

@@ -222,8 +222,9 @@ export class BrowserTraceLifecycleProfiler {
222222

223223
try {
224224
const profile = await profiler.stop();
225-
const continuous = this._toContinuousProfile(profile);
226-
const chunk: ProfileChunk = this._makeProfileChunk(continuous);
225+
226+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
227+
const chunk = createProfileChunkPayload(profile, this._client!, this._profilerId);
227228

228229
this._sendProfileChunk(chunk);
229230
DEBUG_BUILD && debug.log('[Profiling] Collected browser profile chunk.');
@@ -257,88 +258,4 @@ export class BrowserTraceLifecycleProfiler {
257258
DEBUG_BUILD && debug.error('Error while sending profile chunk envelope:', reason);
258259
});
259260
}
260-
261-
/**
262-
* Convert from JSSelfProfile format to ContinuousThreadCpuProfile format.
263-
*/
264-
private _toContinuousProfile(input: {
265-
frames: { name: string; resourceId?: number; line?: number; column?: number }[];
266-
stacks: { frameId: number; parentId?: number }[];
267-
samples: { timestamp: number; stackId?: number }[];
268-
resources: string[];
269-
}): ContinuousThreadCpuProfile {
270-
const frames: ContinuousThreadCpuProfile['frames'] = [];
271-
for (let i = 0; i < input.frames.length; i++) {
272-
const f = input.frames[i];
273-
if (!f) {
274-
continue;
275-
}
276-
frames[i] = {
277-
function: f.name,
278-
abs_path: typeof f.resourceId === 'number' ? input.resources[f.resourceId] : undefined,
279-
lineno: f.line,
280-
colno: f.column,
281-
};
282-
}
283-
284-
const stacks: ContinuousThreadCpuProfile['stacks'] = [];
285-
for (let i = 0; i < input.stacks.length; i++) {
286-
const s = input.stacks[i];
287-
if (!s) {
288-
continue;
289-
}
290-
const list: number[] = [];
291-
let cur: { frameId: number; parentId?: number } | undefined = s;
292-
while (cur) {
293-
list.push(cur.frameId);
294-
cur = cur.parentId === undefined ? undefined : input.stacks[cur.parentId];
295-
}
296-
stacks[i] = list;
297-
}
298-
299-
const samples: ContinuousThreadCpuProfile['samples'] = [];
300-
for (let i = 0; i < input.samples.length; i++) {
301-
const s = input.samples[i];
302-
if (!s) {
303-
continue;
304-
}
305-
samples[i] = {
306-
stack_id: s.stackId ?? 0,
307-
thread_id: '0',
308-
timestamp: performance.timeOrigin + s.timestamp,
309-
};
310-
}
311-
312-
return {
313-
frames,
314-
stacks,
315-
samples,
316-
thread_metadata: { '0': { name: 'main' } },
317-
};
318-
}
319-
320-
/**
321-
* Create a profile chunk envelope item from a ContinuousThreadCpuProfile.
322-
*/
323-
private _makeProfileChunk(profile: ContinuousThreadCpuProfile): ProfileChunk {
324-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
325-
const client = this._client!;
326-
const options = client.getOptions();
327-
const sdk = client.getSdkMetadata?.()?.sdk;
328-
329-
return {
330-
chunk_id: uuid4(),
331-
client_sdk: {
332-
name: sdk?.name ?? 'sentry.javascript.browser',
333-
version: sdk?.version ?? '0.0.0',
334-
},
335-
profiler_id: this._profilerId || uuid4(),
336-
platform: 'javascript',
337-
version: '2',
338-
release: options.release ?? '',
339-
environment: options.environment ?? 'production',
340-
debug_meta: { images: applyDebugImages([]) },
341-
profile,
342-
};
343-
}
344261
}

packages/browser/src/profiling/utils.ts

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
/* eslint-disable max-lines */
2-
import type { DebugImage, Envelope, Event, EventEnvelope, Profile, Span, ThreadCpuProfile } from '@sentry/core';
2+
import type {
3+
Client,
4+
ContinuousThreadCpuProfile,
5+
DebugImage,
6+
Envelope,
7+
Event,
8+
EventEnvelope,
9+
Profile,
10+
ProfileChunk,
11+
Span,
12+
ThreadCpuProfile,
13+
} from '@sentry/core';
314
import {
415
browserPerformanceTimeOrigin,
516
debug,
@@ -196,6 +207,109 @@ export function createProfilePayload(
196207
return profile;
197208
}
198209

210+
/**
211+
* Create a profile chunk envelope item
212+
*/
213+
export function createProfileChunkPayload(
214+
jsSelfProfile: JSSelfProfile,
215+
client: Client,
216+
profilerId?: string,
217+
): ProfileChunk {
218+
if (jsSelfProfile === undefined || jsSelfProfile === null) {
219+
throw new TypeError(
220+
`Cannot construct profiling event envelope without a valid profile. Got ${jsSelfProfile} instead.`,
221+
);
222+
}
223+
224+
const continuousProfile = convertToContinuousProfile(jsSelfProfile);
225+
226+
const options = client.getOptions();
227+
const sdk = client.getSdkMetadata?.()?.sdk;
228+
229+
return {
230+
chunk_id: uuid4(),
231+
client_sdk: {
232+
name: sdk?.name ?? 'sentry.javascript.browser',
233+
version: sdk?.version ?? '0.0.0',
234+
},
235+
profiler_id: profilerId || uuid4(),
236+
platform: 'javascript',
237+
version: '2',
238+
release: options.release ?? '',
239+
environment: options.environment ?? 'production',
240+
debug_meta: { images: applyDebugMetadata([]) },
241+
profile: continuousProfile,
242+
};
243+
}
244+
245+
/**
246+
* Convert from JSSelfProfile format to ContinuousThreadCpuProfile format.
247+
*/
248+
function convertToContinuousProfile(input: {
249+
frames: { name: string; resourceId?: number; line?: number; column?: number }[];
250+
stacks: { frameId: number; parentId?: number }[];
251+
samples: { timestamp: number; stackId?: number }[];
252+
resources: string[];
253+
}): ContinuousThreadCpuProfile {
254+
// Frames map 1:1 by index; fill only when present to avoid sparse writes
255+
const frames: ContinuousThreadCpuProfile['frames'] = [];
256+
for (let i = 0; i < input.frames.length; i++) {
257+
const frame = input.frames[i];
258+
if (!frame) {
259+
continue;
260+
}
261+
frames[i] = {
262+
function: frame.name,
263+
abs_path: typeof frame.resourceId === 'number' ? input.resources[frame.resourceId] : undefined,
264+
lineno: frame.line,
265+
colno: frame.column,
266+
};
267+
}
268+
269+
// Build stacks by following parent links, top->down order (root last)
270+
const stacks: ContinuousThreadCpuProfile['stacks'] = [];
271+
for (let i = 0; i < input.stacks.length; i++) {
272+
const stackHead = input.stacks[i];
273+
if (!stackHead) {
274+
continue;
275+
}
276+
const list: number[] = [];
277+
let current: { frameId: number; parentId?: number } | undefined = stackHead;
278+
while (current) {
279+
list.push(current.frameId);
280+
current = current.parentId === undefined ? undefined : input.stacks[current.parentId];
281+
}
282+
stacks[i] = list;
283+
}
284+
285+
// Align timestamps to SDK time origin to match span/event timelines
286+
const perfOrigin = browserPerformanceTimeOrigin();
287+
const origin = typeof performance.timeOrigin === 'number' ? performance.timeOrigin : perfOrigin || 0;
288+
const adjustForOriginChange = origin - (perfOrigin || origin);
289+
290+
const samples: ContinuousThreadCpuProfile['samples'] = [];
291+
for (let i = 0; i < input.samples.length; i++) {
292+
const sample = input.samples[i];
293+
if (!sample) {
294+
continue;
295+
}
296+
// Convert ms to seconds epoch-based timestamp
297+
const timestampSeconds = (origin + (sample.timestamp - adjustForOriginChange)) / 1000;
298+
samples[i] = {
299+
stack_id: sample.stackId ?? 0,
300+
thread_id: THREAD_ID_STRING,
301+
timestamp: timestampSeconds,
302+
};
303+
}
304+
305+
return {
306+
frames,
307+
stacks,
308+
samples,
309+
thread_metadata: { [THREAD_ID_STRING]: { name: THREAD_NAME } },
310+
};
311+
}
312+
199313
/**
200314
*
201315
*/

0 commit comments

Comments
 (0)