Skip to content

Commit 4392fe1

Browse files
authored
feat: default to zstd compression for profile uploads on Node.js 24+ (#7172)
1 parent ecce729 commit 4392fe1

3 files changed

Lines changed: 23 additions & 14 deletions

File tree

integration-tests/profiler/profiler.spec.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ if (process.platform !== 'win32') {
2424
}
2525

2626
const TIMEOUT = 30000
27+
const isAtLeast24 = satisfies(process.versions.node, '>=24.0.0')
2728

2829
function checkProfiles (agent, proc, timeout,
2930
expectedProfileTypes = DEFAULT_PROFILE_TYPES, expectBadExit = false, expectSeq = true
@@ -95,9 +96,9 @@ function processExitPromise (proc, timeout, expectBadExit = false) {
9596
}
9697

9798
async function getLatestProfile (cwd, pattern) {
98-
const pprofGzipped = await readLatestFile(cwd, pattern)
99-
const pprofUnzipped = zlib.gunzipSync(pprofGzipped)
100-
return { profile: Profile.decode(pprofUnzipped), encoded: pprofGzipped.toString('base64') }
99+
const pprofCompressed = await readLatestFile(cwd, pattern)
100+
const pprofUncompressed = zlib[isAtLeast24 ? 'zstdDecompressSync' : 'gunzipSync'](pprofCompressed)
101+
return { profile: Profile.decode(pprofUncompressed), encoded: pprofCompressed.toString('base64') }
101102
}
102103

103104
async function readLatestFile (cwd, pattern) {
@@ -360,7 +361,7 @@ describe('profiler', () => {
360361
const execArgv = []
361362
if (satisfies(process.versions.node, '>=22.9.0')) {
362363
env.DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED = 1
363-
if (!satisfies(process.versions.node, '>=24.0.0')) {
364+
if (!isAtLeast24) {
364365
// For Node 22.9.0+, use the experimental command line flag for Node to enable
365366
// async context frame. Node 24 has it enabled by default.
366367
execArgv.push('--experimental-async-context-frame')

packages/dd-trace/src/profiling/config.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,9 @@ class Config {
179179

180180
this.heapSamplingInterval = options.heapSamplingInterval ??
181181
(Number(DD_PROFILING_HEAP_SAMPLING_INTERVAL) || 512 * 1024)
182+
183+
const isAtLeast24 = satisfies(process.versions.node, '>=24.0.0')
184+
182185
const uploadCompression0 = options.uploadCompression ?? DD_PROFILING_DEBUG_UPLOAD_COMPRESSION ?? 'on'
183186
let [uploadCompression, level0] = uploadCompression0.split('-')
184187
if (!['on', 'off', 'gzip', 'zstd'].includes(uploadCompression)) {
@@ -206,9 +209,12 @@ class Config {
206209
}
207210
}
208211

209-
// Default to gzip
212+
// Default to either zstd (on Node.js 24+) or gzip (earlier Node.js). We could default to ztsd
213+
// everywhere as we ship a Rust zstd compressor for older Node.js versions, but on 24+ we use
214+
// the built-in one that runs asynchronously on libuv worker threads, just as gzip does. This is
215+
// the least disruptive choice.
210216
if (uploadCompression === 'on') {
211-
uploadCompression = 'gzip'
217+
uploadCompression = isAtLeast24 ? 'zstd' : 'gzip'
212218
}
213219

214220
this.uploadCompression = { method: uploadCompression, level }
@@ -224,7 +230,7 @@ class Config {
224230

225231
let canUseAsyncContextFrame = false
226232
if (samplingContextsAvailable) {
227-
if (satisfies(process.versions.node, '>=24.0.0')) {
233+
if (isAtLeast24) {
228234
canUseAsyncContextFrame = !hasExecArg('--no-async-context-frame')
229235
} else if (satisfies(process.versions.node, '>=22.9.0')) {
230236
canUseAsyncContextFrame = hasExecArg('--experimental-async-context-frame')
@@ -234,7 +240,7 @@ class Config {
234240
if (this.asyncContextFrameEnabled && !canUseAsyncContextFrame) {
235241
if (!samplingContextsAvailable) {
236242
turnOffAsyncContextFrame(`on ${process.platform}`)
237-
} else if (satisfies(process.versions.node, '>=24.0.0')) {
243+
} else if (isAtLeast24) {
238244
turnOffAsyncContextFrame('with --no-async-context-frame')
239245
} else if (satisfies(process.versions.node, '>=22.9.0')) {
240246
turnOffAsyncContextFrame('without --experimental-async-context-frame')

packages/dd-trace/test/profiling/config.spec.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const { ConsoleLogger } = require('../../src/profiling/loggers/console')
1818

1919
const samplingContextsAvailable = process.platform !== 'win32'
2020
const oomMonitoringSupported = process.platform !== 'win32'
21+
const isAtLeast24 = satisfies(process.versions.node, '>=24.0.0')
22+
const zstdOrGzip = isAtLeast24 ? 'zstd' : 'gzip'
2123

2224
describe('config', () => {
2325
let Config
@@ -59,7 +61,7 @@ describe('config', () => {
5961
assert.strictEqual(config.profilers[1].codeHotspotsEnabled(), samplingContextsAvailable)
6062
assert.strictEqual(config.v8ProfilerBugWorkaroundEnabled, true)
6163
assert.strictEqual(config.cpuProfilingEnabled, samplingContextsAvailable)
62-
assert.strictEqual(config.uploadCompression.method, 'gzip')
64+
assert.strictEqual(config.uploadCompression.method, zstdOrGzip)
6365
assert.strictEqual(config.uploadCompression.level, undefined)
6466
})
6567

@@ -509,7 +511,7 @@ describe('config', () => {
509511
}
510512

511513
describe('async context', () => {
512-
const isSupported = samplingContextsAvailable && satisfies(process.versions.node, '>=24.0.0')
514+
const isSupported = samplingContextsAvailable && isAtLeast24
513515
describe('where supported', () => {
514516
it('should be on by default', function () {
515517
if (!isSupported) {
@@ -589,15 +591,15 @@ describe('config', () => {
589591
}
590592

591593
it('should accept known methods', () => {
592-
expectConfig(undefined, 'gzip', undefined)
594+
expectConfig(undefined, zstdOrGzip, undefined)
593595
expectConfig('off', 'off', undefined)
594-
expectConfig('on', 'gzip', undefined)
596+
expectConfig('on', zstdOrGzip, undefined)
595597
expectConfig('gzip', 'gzip', undefined)
596598
expectConfig('zstd', 'zstd', undefined)
597599
})
598600

599601
it('should reject unknown methods', () => {
600-
expectConfig('foo', 'gzip', undefined, 'Invalid profile upload compression method "foo". Will use "on".')
602+
expectConfig('foo', zstdOrGzip, undefined, 'Invalid profile upload compression method "foo". Will use "on".')
601603
})
602604

603605
it('should accept supported compression levels in methods that support levels', () => {
@@ -617,7 +619,7 @@ describe('config', () => {
617619

618620
it('should reject compression levels in methods that do not support levels', () => {
619621
['on', 'off'].forEach((method) => {
620-
const effectiveMethod = method === 'on' ? 'gzip' : method
622+
const effectiveMethod = method === 'on' ? zstdOrGzip : method
621623
expectConfig(`${method}-3`, effectiveMethod, undefined,
622624
`Compression levels are not supported for "${method}".`)
623625
expectConfig(`${method}-foo`, effectiveMethod, undefined,

0 commit comments

Comments
 (0)