Skip to content

profileSessionSampleRate is ignored when profileLifecycle is 'trace' (Node continuous profiling) #22927

Description

@Jxxunnn

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/node

SDK Version

@sentry/profiling-node 10.62.0 (also reproduced on develop as of 2026-08-01)

Framework Version

No response

Link to Sentry event

No response

Reproduction Example/SDK Setup

const Sentry = require("@sentry/node");
const { nodeProfilingIntegration } = require("@sentry/profiling-node");

Sentry.init({
  dsn: "<dsn>",
  integrations: [nodeProfilingIntegration()],
  tracesSampleRate: 1.0,
  profileSessionSampleRate: 0, // session NOT sampled -> expect NO profiling
  profileLifecycle: "trace",
});

// Start any span. A profiling chunk is still started even though
// profileSessionSampleRate is 0.
Sentry.startSpan({ name: "test" }, () => {
  // CpuProfilerBindings.startProfiling(...) runs here
});

Steps to Reproduce

  1. Initialize the SDK with profileLifecycle: 'trace' and profileSessionSampleRate: 0 (see SDK setup above).
  2. Start any span, e.g. Sentry.startSpan({ name: "test" }, () => {}).
  3. Observe that chunk profiling starts anyway — with debug enabled, the SDK logs [Profiling] Profiling mode is trace. and CpuProfilerBindings.startProfiling(...) is invoked on span start, even though the session sampling decision is false.

Expected Result

With profileSessionSampleRate: 0, the session is not sampled (_sampled === false), so no profiling should occur in any lifecycle mode.

Actual Result

In trace mode, chunk profiling starts on spanStart regardless of the session
sampling decision. With profileSessionSampleRate: 0, this._sampled is computed
as false at init, but the trace lifecycle path never consults it —
CpuProfilerBindings.startProfiling(...) runs as soon as the active-span counter
goes from 0 to 1.

With debug enabled, the SDK logs [Profiling] Profiling mode is trace. and then
starts a profiling chunk. There is no "not sampled" log, because the only place
that emits it (_startProfiler()) is the manual-mode entry point, which
early-returns for trace mode anyway.

Verified in 10.10.0, 10.62.0, and current develop (as of 2026-08-01) — the
behavior is identical.

Additional Context

Root cause (source walkthrough)

In packages/profiling-node/src/integration.ts (develop):

  • initialize() computes the session sampling decision:
    this._sampled = this._sessionSamplingRate < (options.profileSessionSampleRate ?? 0)
  • For current mode with _profileLifecycle === 'trace', setup calls
    this._startTraceLifecycleProfiling() unconditionally — no _sampled check.
  • _startTraceLifecycleProfiling() registers a spanStart handler that calls
    this._startChunkProfiling() directly when the active-span counter goes 0 → 1.
  • Neither _startTraceLifecycleProfiling() nor _startChunkProfiling() checks
    this._sampled.
  • this._sampled is only checked inside _startProfiler(), the manual-mode
    entry point, which early-returns for trace mode.

So the session sampling gate is effectively manual-only.

Comparison with the browser SDK

The browser UI profiling implementation does gate trace-lifecycle profiling on
the session sampling decision. In packages/browser/src/profiling/UIProfiler.ts,
initialize() resolves _sessionSampled via shouldProfileSession(), and the
spanStart listener registered by _setupTraceLifecycleListeners() returns
early when the session is not sampled. The Node implementation registers the
equivalent listener without any such check, so the two platforms behave
differently for the same option.

Docs

The Node profiling docs describe profileSessionSampleRate as a session-level
gate evaluated once at SDK init, useful when you "would only like a subset of
those services to be profiled". There is no note limiting it to the manual
lifecycle, and the trace-lifecycle setup example includes the option.

Impact

Users setting a low profileSessionSampleRate with profileLifecycle: 'trace'
to limit profiling overhead and continuous-profile-hours across a fleet will
instead profile on every process that has active spans, producing more overhead
and billed profile hours than intended.

Question

Is this intended? If trace mode is designed to ignore profileSessionSampleRate,
the docs should note that the session-level gate applies only to the manual
lifecycle. If not, _startTraceLifecycleProfiling() (or its setup) should
short-circuit when !this._sampled, matching the browser implementation.

Priority

React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it.

Metadata

Metadata

Assignees

No one assigned

    Projects

    Status
    Waiting for: Product Owner

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions