Skip to content

Commit b47fabe

Browse files
authored
bench(propagation): add extract and inject benchmarks (#8203)
Wire-protocol propagation is the broadest customer hot path -- extract on every incoming traced request, inject on every outgoing one -- and has no sirun coverage. Five variants drive the real `TextMapPropagator` against a realistic 4-vendor `tracestate` and a 4-key baggage carrier; ASCII and percent-encoded baggage variants cover both branches of the `decodeURIComponent` gating path.
1 parent 6e86edf commit b47fabe

4 files changed

Lines changed: 101 additions & 0 deletions

File tree

.github/CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@
161161
# API SDK Capabilities
162162
/eslint-rules/ @DataDog/apm-sdk-capabilities-js
163163

164+
/benchmark/sirun/propagation/ @DataDog/apm-sdk-capabilities-js
165+
164166
/integration-tests/log_injection.spec.js @DataDog/apm-sdk-capabilities-js
165167
/integration-tests/opentelemetry/ @DataDog/apm-sdk-capabilities-js
166168
/integration-tests/opentelemetry-logs.spec.js @DataDog/apm-sdk-capabilities-js
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This benchmark measures the wire-protocol propagation hot path that fires on every
2+
traced HTTP request: extract on incoming, inject on outgoing. Both go through
3+
`text_map.js` and `tracestate.js`, the broadest customer surface in the library.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
'use strict'
2+
3+
const assert = require('node:assert/strict')
4+
5+
const id = require('../../../packages/dd-trace/src/id')
6+
const SpanContext = require('../../../packages/dd-trace/src/opentracing/span_context')
7+
const TextMapPropagator = require('../../../packages/dd-trace/src/opentracing/propagation/text_map')
8+
9+
const { VARIANT } = process.env
10+
11+
const ITERATIONS = 300_000
12+
13+
// Duck-typed config keeps the bench out of the full `Config` singleton (telemetry
14+
// registration, env reads). The propagator only reads the fields below.
15+
const propagator = new TextMapPropagator({
16+
tracePropagationStyle: {
17+
extract: ['datadog', 'tracecontext', 'baggage'],
18+
inject: ['datadog', 'tracecontext', 'baggage'],
19+
},
20+
legacyBaggageEnabled: false,
21+
baggageMaxItems: 64,
22+
baggageMaxBytes: 8192,
23+
tagsHeaderMaxLength: 512,
24+
tracePropagationExtractFirst: false,
25+
tracePropagationBehaviorExtract: 'continue',
26+
baggageTagKeys: ['user.id', 'session.id', 'account.id'],
27+
})
28+
29+
// Realistic incoming carrier: traceparent + 4-vendor tracestate + 4-key baggage. Most
30+
// production baggage is plain ASCII (tenant / user / session / locale); the percent
31+
// variant lives below for the slow-path no-regression check.
32+
const EXTRACT_CARRIER_ASCII = {
33+
traceparent: '00-1234567890abcdef1234567890abcdef-1234567890abcdef-01',
34+
tracestate: 'dd=s:2;p:abc,vendor1=k1:v1,vendor2=k1:v1;k2:v2,foo=bar',
35+
baggage: 'tenant=acme,user=ada,session=abcdef0123456789,locale=en-US',
36+
}
37+
38+
const EXTRACT_CARRIER_PERCENT = {
39+
...EXTRACT_CARRIER_ASCII,
40+
baggage: 'tenant=acme%20corp,path=%2Forders%2Fnew,note=hello%20world',
41+
}
42+
43+
const injectContext = new SpanContext({
44+
traceId: id('1234567890abcdef'),
45+
spanId: id('abcdef1234567890'),
46+
sampling: { priority: 1 },
47+
baggageItems: { tenant: 'acme', user: 'ada', session: 'abcdef0123456789' },
48+
trace: {
49+
tags: { '_dd.p.dm': '-1', '_dd.p.tid': '1234567890abcdef' },
50+
started: [],
51+
finished: [],
52+
},
53+
})
54+
55+
// Pre-flight: confirm extract / inject are doing real work; catches a silent
56+
// breakage where the duck-typed config is missing a field the propagator now reads.
57+
const sanityExtract = propagator.extract(EXTRACT_CARRIER_ASCII)
58+
assert.ok(sanityExtract?._traceId, 'extract returned no trace id')
59+
60+
const sanityInjected = {}
61+
propagator.inject(injectContext, sanityInjected)
62+
assert.ok(sanityInjected.traceparent && sanityInjected['x-datadog-trace-id'], 'inject populated no headers')
63+
64+
if (VARIANT === 'extract' || VARIANT === 'extract-baggage-ascii') {
65+
for (let iteration = 0; iteration < ITERATIONS; iteration++) {
66+
propagator.extract(EXTRACT_CARRIER_ASCII)
67+
}
68+
} else if (VARIANT === 'extract-baggage-percent') {
69+
for (let iteration = 0; iteration < ITERATIONS; iteration++) {
70+
propagator.extract(EXTRACT_CARRIER_PERCENT)
71+
}
72+
} else if (VARIANT === 'inject') {
73+
for (let iteration = 0; iteration < ITERATIONS; iteration++) {
74+
propagator.inject(injectContext, {})
75+
}
76+
} else if (VARIANT === 'extract-inject') {
77+
for (let iteration = 0; iteration < ITERATIONS; iteration++) {
78+
const extracted = propagator.extract(EXTRACT_CARRIER_ASCII)
79+
propagator.inject(extracted, {})
80+
}
81+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "propagation",
3+
"run": "node index.js",
4+
"run_with_affinity": "bash -c \"taskset -c $CPU_AFFINITY node index.js\"",
5+
"cachegrind": false,
6+
"iterations": 30,
7+
"instructions": true,
8+
"variants": {
9+
"extract": { "env": { "VARIANT": "extract" } },
10+
"inject": { "env": { "VARIANT": "inject" } },
11+
"extract-inject": { "env": { "VARIANT": "extract-inject" } },
12+
"extract-baggage-ascii": { "baseline": "extract", "env": { "VARIANT": "extract-baggage-ascii" } },
13+
"extract-baggage-percent": { "baseline": "extract", "env": { "VARIANT": "extract-baggage-percent" } }
14+
}
15+
}

0 commit comments

Comments
 (0)