Skip to content

Commit ecf4d0c

Browse files
committed
feat(debugger): implement intelligent snapshot pruning for oversized payloads
Replace the crude "delete all captures" approach with an intelligent pruning algorithm that selectively removes the largest and deepest leaf nodes while preserving the schema structure. The algorithm prunes like so: - Parses snapshots into a tree structure tracking JSON object positions - Uses a priority queue to select nodes for pruning based on: 1. Presence of `notCapturedReason: 'depth'` 2. Depth level (deeper nodes pruned first) 3. Presence of any `notCapturedReason` 4. Size (larger nodes pruned first) - Only prunes nodes at level 6 or deeper (`locals`) - Promotes parent nodes when all children are pruned to reduce overhead - Iteratively prunes if needed to reach target size
1 parent 25fa1e4 commit ecf4d0c

File tree

5 files changed

+1019
-26
lines changed

5 files changed

+1019
-26
lines changed

integration-tests/debugger/snapshot-pruning.spec.js

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict'
22

3-
const { assert } = require('chai')
3+
const assert = require('node:assert/strict')
4+
45
const { setup } = require('./utils')
56

67
describe('Dynamic Instrumentation', function () {
@@ -12,14 +13,12 @@ describe('Dynamic Instrumentation', function () {
1213

1314
it('should prune snapshot if payload is too large', function (done) {
1415
t.agent.on('debugger-input', ({ payload: [payload] }) => {
15-
assert.isBelow(Buffer.byteLength(JSON.stringify(payload)), 1024 * 1024) // 1MB
16-
assert.notProperty(payload.debugger.snapshot, 'captures')
17-
assert.strictEqual(
18-
payload.debugger.snapshot.captureError,
19-
'Snapshot was too large (max allowed size is 1 MiB). ' +
20-
'Consider reducing the capture depth or turn off "Capture Variables" completely, ' +
21-
'and instead include the variables of interest directly in the message template.'
22-
)
16+
const payloadSize = Buffer.byteLength(JSON.stringify(payload))
17+
assert.ok(payloadSize < 1024 * 1024) // 1MB
18+
19+
const capturesJson = JSON.stringify(payload.debugger.snapshot.captures)
20+
assert.ok(capturesJson.includes('"pruned":true'))
21+
2322
done()
2423
})
2524

packages/dd-trace/src/debugger/devtools_client/send.js

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const { GIT_COMMIT_SHA, GIT_REPOSITORY_URL } = require('../../plugins/util/tags'
1010
const log = require('./log')
1111
const { version } = require('../../../../../package.json')
1212
const { getEnvironmentVariable } = require('../../config-helper')
13+
const { pruneSnapshot } = require('./snapshot-pruner')
1314

1415
module.exports = send
1516

@@ -55,14 +56,26 @@ function send (message, logger, dd, snapshot) {
5556
let size = Buffer.byteLength(json)
5657

5758
if (size > MAX_LOG_PAYLOAD_SIZE_BYTES) {
58-
// TODO: This is a very crude way to handle large payloads. Proper pruning will be implemented later (DEBUG-2624)
59-
delete payload.debugger.snapshot.captures
60-
payload.debugger.snapshot.captureError =
61-
`Snapshot was too large (max allowed size is ${MAX_LOG_PAYLOAD_SIZE_MB} MiB). ` +
62-
'Consider reducing the capture depth or turn off "Capture Variables" completely, ' +
63-
'and instead include the variables of interest directly in the message template.'
64-
json = JSON.stringify(payload)
65-
size = Buffer.byteLength(json)
59+
let pruned
60+
try {
61+
pruned = pruneSnapshot(json, size, MAX_LOG_PAYLOAD_SIZE_BYTES)
62+
} catch (err) {
63+
log.error('[debugger:devtools_client] Error pruning snapshot', err)
64+
}
65+
66+
if (pruned) {
67+
json = pruned
68+
size = Buffer.byteLength(json)
69+
} else {
70+
// Fallback if pruning fails
71+
delete payload.debugger.snapshot.captures
72+
payload.debugger.snapshot.captureError =
73+
`Snapshot was too large and could not be pruned (max allowed size is ${MAX_LOG_PAYLOAD_SIZE_MB} MiB). ` +
74+
'Consider reducing the capture depth or turn off "Capture Variables" completely, ' +
75+
'and instead include the variables of interest directly in the message template.'
76+
json = JSON.stringify(payload)
77+
size = Buffer.byteLength(json)
78+
}
6679
}
6780

6881
jsonBuffer.write(json, size)

0 commit comments

Comments
 (0)