diff --git a/packages/profiling-node/src/integration.ts b/packages/profiling-node/src/integration.ts index b05a919fc949..50f240bff732 100644 --- a/packages/profiling-node/src/integration.ts +++ b/packages/profiling-node/src/integration.ts @@ -1,3 +1,5 @@ +/* eslint-disable max-lines */ + import { defineIntegration, getCurrentScope, @@ -231,11 +233,17 @@ class ContinuousProfiler { } DEBUG_BUILD && logger.log(`[Profiling] Profile chunk ${this._chunkData.id} sent to Sentry.`); - const chunk = createProfilingChunkEvent(this._client, this._client.getOptions(), profile, { - chunk_id: this._chunkData.id, - trace_id: this._chunkData.startTraceID, - profiler_id: this._profilerId, - }); + const chunk = createProfilingChunkEvent( + this._client, + this._client.getOptions(), + profile, + this._client.getSdkMetadata()?.sdk, + { + chunk_id: this._chunkData.id, + trace_id: this._chunkData.startTraceID, + profiler_id: this._profilerId, + }, + ); if (!chunk) { DEBUG_BUILD && logger.log(`[Profiling] Failed to create profile chunk for: ${this._chunkData.id}`); diff --git a/packages/profiling-node/src/utils.ts b/packages/profiling-node/src/utils.ts index 673901990d90..0eb4fe4281b4 100644 --- a/packages/profiling-node/src/utils.ts +++ b/packages/profiling-node/src/utils.ts @@ -194,12 +194,14 @@ function createProfileChunkPayload( trace_id, profiler_id, chunk_id, + sdk, }: { release: string; environment: string; trace_id: string | undefined; chunk_id: string; profiler_id: string; + sdk: SdkInfo | undefined; }, ): ProfileChunk { // Log a warning if the profile has an invalid traceId (should be uuidv4). @@ -213,6 +215,10 @@ function createProfileChunkPayload( const profile: ProfileChunk = { chunk_id: chunk_id, + client_sdk: { + name: sdk?.name ?? 'sentry.javascript.node', + version: sdk?.version ?? '0.0.0', + }, profiler_id: profiler_id, platform: 'node', version: CONTINUOUS_FORMAT_VERSION, @@ -235,6 +241,7 @@ export function createProfilingChunkEvent( client: Client, options: { release?: string; environment?: string }, profile: RawChunkCpuProfile, + sdk: SdkInfo | undefined, identifiers: { trace_id: string | undefined; chunk_id: string; profiler_id: string }, ): ProfileChunk | null { if (!isValidProfileChunk(profile)) { @@ -247,6 +254,7 @@ export function createProfilingChunkEvent( trace_id: identifiers.trace_id ?? '', chunk_id: identifiers.chunk_id, profiler_id: identifiers.profiler_id, + sdk, }); } diff --git a/packages/profiling-node/test/spanProfileUtils.test.ts b/packages/profiling-node/test/spanProfileUtils.test.ts index 766a0059d02e..4a90caa0f353 100644 --- a/packages/profiling-node/test/spanProfileUtils.test.ts +++ b/packages/profiling-node/test/spanProfileUtils.test.ts @@ -2,7 +2,7 @@ import * as Sentry from '@sentry/node'; import { getMainCarrier } from '@sentry/core'; import type { NodeClientOptions } from '@sentry/node/build/types/types'; -import type { Transport } from '@sentry/types'; +import type { ProfileChunk, Transport } from '@sentry/types'; import { GLOBAL_OBJ, createEnvelope, logger } from '@sentry/utils'; import { CpuProfilerBindings } from '../src/cpu_profiler'; import { type ProfilingIntegration, _nodeProfilingIntegration } from '../src/integration'; @@ -402,6 +402,50 @@ describe('continuous profiling', () => { delete getMainCarrier().__SENTRY__; }); + it('attaches sdk metadata to chunks', () => { + // @ts-expect-error we just mock the return type and ignore the signature + jest.spyOn(CpuProfilerBindings, 'stopProfiling').mockImplementation(() => { + return { + samples: [ + { + stack_id: 0, + thread_id: '0', + elapsed_since_start_ns: '10', + }, + { + stack_id: 0, + thread_id: '0', + elapsed_since_start_ns: '10', + }, + ], + measurements: {}, + stacks: [[0]], + frames: [], + resources: [], + profiler_logging_mode: 'lazy', + }; + }); + + const [client, transport] = makeContinuousProfilingClient(); + Sentry.setCurrentClient(client); + client.init(); + + const transportSpy = jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); + + const integration = client.getIntegrationByName('ProfilingIntegration'); + if (!integration) { + throw new Error('Profiling integration not found'); + } + integration._profiler.start(); + jest.advanceTimersByTime(1000); + integration._profiler.stop(); + jest.advanceTimersByTime(1000); + + const profile = transportSpy.mock.calls?.[0]?.[0]?.[1]?.[0]?.[1] as ProfileChunk; + expect(profile.client_sdk.name).toBe('sentry.javascript.node'); + expect(profile.client_sdk.version).toEqual(expect.stringMatching(/\d+\.\d+\.\d+/)); + }); + it('initializes the continuous profiler and binds the sentry client', () => { const startProfilingSpy = jest.spyOn(CpuProfilerBindings, 'startProfiling'); diff --git a/packages/types/src/profiling.ts b/packages/types/src/profiling.ts index 48dd797492bf..8f5f4cc2e890 100644 --- a/packages/types/src/profiling.ts +++ b/packages/types/src/profiling.ts @@ -126,4 +126,8 @@ export interface Profile extends BaseProfile { export interface ProfileChunk extends BaseProfile { chunk_id: string; profiler_id: string; + client_sdk: { + name: string; + version: string; + }; }