Skip to content

Commit d6382c1

Browse files
authored
[DI] Only send probes whos condition is actually met (#5714)
This fixes a bug where if two probes were attached to the same line and both had a condition, if only one of the conditions match, we would wrongly assume both had matched and send both probe results to the backend. This changes that behavior to always re-evaluate the conditions no matter what.
1 parent aa76e2a commit d6382c1

File tree

3 files changed

+49
-13
lines changed

3 files changed

+49
-13
lines changed

integration-tests/debugger/basic.spec.js

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ describe('Dynamic Instrumentation', function () {
310310
}
311311
} else if (diagnostics.status === 'EMITTING') {
312312
const expected = expectedPayloads.get(diagnostics.probeId)
313+
assert.ok(expected, `expected payload not found for probe ${diagnostics.probeId}`)
313314
expectedPayloads.delete(diagnostics.probeId)
314315
assertObjectContains(event, expected)
315316
}
@@ -348,9 +349,52 @@ describe('Dynamic Instrumentation', function () {
348349
t.agent.addRemoteConfig(rcConfig2)
349350
})
350351

351-
it('should support only triggering the probes whos conditions are met', function (done) {
352+
it('should only trigger the probes whos conditions are met (all have conditions)', function (done) {
352353
let installed = 0
353-
const rcConfig1 = t.generateRemoteConfig({ when: { json: { eq: [{ ref: 'foo' }, 'bar'] } } })
354+
const rcConfig1 = t.generateRemoteConfig({
355+
when: { json: { eq: [{ getmember: [{ getmember: [{ ref: 'request' }, 'params'] }, 'name'] }, 'invalid'] } }
356+
})
357+
const rcConfig2 = t.generateRemoteConfig({
358+
when: { json: { eq: [{ getmember: [{ getmember: [{ ref: 'request' }, 'params'] }, 'name'] }, 'bar'] } }
359+
})
360+
const expectedPayloads = new Map([
361+
[rcConfig2.config.id, {
362+
ddsource: 'dd_debugger',
363+
service: 'node',
364+
debugger: { diagnostics: { probeId: rcConfig2.config.id, probeVersion: 0, status: 'EMITTING' } }
365+
}]
366+
])
367+
368+
t.agent.on('debugger-diagnostics', ({ payload }) => {
369+
payload.forEach((event) => {
370+
const { diagnostics } = event.debugger
371+
if (diagnostics.status === 'INSTALLED') {
372+
if (++installed === 2) {
373+
t.axios.get(t.breakpoint.url).catch(done)
374+
}
375+
} else if (diagnostics.status === 'EMITTING') {
376+
const expected = expectedPayloads.get(diagnostics.probeId)
377+
assert.ok(expected, `expected payload not found for probe ${diagnostics.probeId}`)
378+
expectedPayloads.delete(diagnostics.probeId)
379+
assertObjectContains(event, expected)
380+
}
381+
})
382+
endIfDone()
383+
})
384+
385+
t.agent.addRemoteConfig(rcConfig1)
386+
t.agent.addRemoteConfig(rcConfig2)
387+
388+
function endIfDone () {
389+
if (expectedPayloads.size === 0) done()
390+
}
391+
})
392+
393+
it('should only trigger the probes whos conditions are met (not all have conditions)', function (done) {
394+
let installed = 0
395+
const rcConfig1 = t.generateRemoteConfig({
396+
when: { json: { eq: [{ getmember: [{ getmember: [{ ref: 'request' }, 'params'] }, 'name'] }, 'invalid'] } }
397+
})
354398
const rcConfig2 = t.generateRemoteConfig({
355399
when: { json: { eq: [{ getmember: [{ getmember: [{ ref: 'request' }, 'params'] }, 'name'] }, 'bar'] } }
356400
})
@@ -377,6 +421,7 @@ describe('Dynamic Instrumentation', function () {
377421
}
378422
} else if (diagnostics.status === 'EMITTING') {
379423
const expected = expectedPayloads.get(diagnostics.probeId)
424+
assert.ok(expected, `expected payload not found for probe ${diagnostics.probeId}`)
380425
expectedPayloads.delete(diagnostics.probeId)
381426
assertObjectContains(event, expected)
382427
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ function stop () {
187187

188188
// Only if all probes have a condition can we use a compound condition.
189189
// Otherwise, we need to evaluate each probe individually once the breakpoint is hit.
190-
// TODO: Handle errors - if there's 2 conditons, and one fails but the other returns true, we should still pause the
190+
// TODO: Handle errors - if there's 2 conditions, and one fails but the other returns true, we should still pause the
191191
// breakpoint
192192
function compileCompoundCondition (probes) {
193193
return probes.every(p => p.condition)

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

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ const getDDTagsExpression = `(() => {
3636
const threadId = parentThreadId === 0 ? `pid:${process.pid}` : `pid:${process.pid};tid:${parentThreadId}`
3737
const threadName = parentThreadId === 0 ? 'MainThread' : `WorkerThread:${parentThreadId}`
3838

39-
const SUPPORT_ITERATOR_METHODS = NODE_MAJOR >= 22
4039
const SUPPORT_ARRAY_BUFFER_RESIZE = NODE_MAJOR >= 20
4140
const oneSecondNs = 1_000_000_000n
4241
let globalSnapshotSamplingRateWindowStart = 0n
@@ -77,14 +76,6 @@ session.on('Debugger.paused', async ({ params }) => {
7776
}
7877
}
7978

80-
// If all the probes have a condition, we know that it triggered. If at least one probe doesn't have a condition, we
81-
// need to verify which conditions are met.
82-
const shouldVerifyConditions = (
83-
SUPPORT_ITERATOR_METHODS
84-
? probesAtLocation.values()
85-
: Array.from(probesAtLocation.values())
86-
).some((probe) => probe.condition === undefined)
87-
8879
for (const probe of probesAtLocation.values()) {
8980
if (start - probe.lastCaptureNs < probe.nsBetweenSampling) {
9081
continue
@@ -109,7 +100,7 @@ session.on('Debugger.paused', async ({ params }) => {
109100
maxLength = highestOrUndefined(probe.capture.maxLength, maxLength)
110101
}
111102

112-
if (shouldVerifyConditions && probe.condition !== undefined) {
103+
if (probe.condition !== undefined) {
113104
// TODO: Bundle all conditions and evaluate them in a single call
114105
// TODO: Handle errors
115106
const { result } = await session.post('Debugger.evaluateOnCallFrame', {

0 commit comments

Comments
 (0)