Skip to content

Commit fd17aa8

Browse files
committed
bench(sirun): bound native benchmark runtime
Native-mode benchmark shards timed out because several variants either kept finished native spans in WASM storage or ran operation counts sized for the JS-only path. Share the native drain sequence across benchmarks, drain spans and Redis benchmark exports in bounded batches, and tune the slow native-mode counts so shard runtime stays under the CI job timeout.
1 parent f2129aa commit fd17aa8

12 files changed

Lines changed: 212 additions & 225 deletions

File tree

.gitlab/benchmarks/gitlab-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ variables:
2222
BASE_CI_IMAGE_PLATFORM: linux/amd64
2323

2424
SLS_CI_IMAGE: registry.ddbuild.io/ci/serverless-tools:1
25-
SLS_CI_BRANCH: main
25+
SLS_CI_BRANCH: bengl-layer-size-50mb-v2
2626

2727
# Benchmark's env variables. Modify to tweak benchmark parameters.
2828
UNCONFIDENCE_THRESHOLD: "2.0"
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict'
2+
3+
const DEFAULT_DRAIN_THRESHOLD = 5000
4+
5+
function createNativeSpanDrain (tracer, threshold = DEFAULT_DRAIN_THRESHOLD) {
6+
const nativeSpans = tracer._tracer._nativeSpans
7+
const pendingSpanIds = nativeSpans ? [] : null
8+
9+
function add (span) {
10+
if (pendingSpanIds) {
11+
pendingSpanIds.push(span.context()._nativeSpanId)
12+
}
13+
}
14+
15+
function addAll (spans) {
16+
if (!pendingSpanIds) return
17+
18+
for (const span of spans) {
19+
pendingSpanIds.push(span.context()._nativeSpanId)
20+
}
21+
}
22+
23+
async function drain () {
24+
if (!pendingSpanIds || pendingSpanIds.length === 0) return
25+
26+
nativeSpans.flushChangeQueue()
27+
28+
const spanIds = Buffer.allocUnsafe(pendingSpanIds.length * 8)
29+
let offset = 0
30+
for (const spanId of pendingSpanIds) {
31+
spanIds.set(spanId, offset)
32+
offset += 8
33+
}
34+
35+
nativeSpans._state.prepareChunk(pendingSpanIds.length, false, spanIds)
36+
await nativeSpans._state.sendPreparedChunk().catch(() => {})
37+
pendingSpanIds.length = 0
38+
}
39+
40+
function needsDrain () {
41+
return pendingSpanIds && pendingSpanIds.length >= threshold
42+
}
43+
44+
return { add, addAll, drain, needsDrain }
45+
}
46+
47+
module.exports = { createNativeSpanDrain }

benchmark/sirun/native-spans/creation.js

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
const nock = require('nock')
1515

16+
const { createNativeSpanDrain } = require('../native-span-drain')
17+
1618
// Mock the agent so the periodic drain's send resolves instantly and never
1719
// touches the network (the drain exists only to bound memory, not to measure
1820
// export).
@@ -21,46 +23,24 @@ nock('http://127.0.0.1:8126').persist().put(/.*/).reply(200, '{}').post(/.*/).re
2123

2224
const tracer = require('../../..').init({ hostname: '127.0.0.1', port: 8126 })
2325

24-
const nativeSpans = tracer._tracer._nativeSpans
25-
const pendingNativeIds = nativeSpans ? [] : null
26-
const DRAIN_THRESHOLD = 5000
26+
const nativeSpanDrain = createNativeSpanDrain(tracer)
2727

2828
tracer._tracer._processor.process = function (span) {
29-
if (pendingNativeIds) {
30-
pendingNativeIds.push(span.context()._nativeSpanId)
31-
}
29+
nativeSpanDrain.add(span)
3230
this._erase(span.context()._trace, [])
3331
}
3432

35-
// Extract the accumulated spans from the WASM map (bounds the map) and send the
36-
// staged chunk (bounds prepared-chunk memory — prepareChunk stages one chunk per
37-
// call and only sendPreparedChunk drains the staging). Span ids are 8-byte u64
38-
// LE, written straight into the flush buffer.
39-
async function drainNative () {
40-
if (!pendingNativeIds || pendingNativeIds.length === 0) return
41-
nativeSpans.flushChangeQueue()
42-
const buf = Buffer.alloc(pendingNativeIds.length * 8)
43-
let idx = 0
44-
for (const spanId of pendingNativeIds) {
45-
buf.set(spanId, idx)
46-
idx += 8
47-
}
48-
nativeSpans._state.prepareChunk(pendingNativeIds.length, false, buf)
49-
await nativeSpans._state.sendPreparedChunk().catch(() => {})
50-
pendingNativeIds.length = 0
51-
}
52-
53-
const ITERATIONS = 1_000_000
33+
const OPERATIONS = Number(process.env.OPERATIONS) || 100_000
5434
const scenario = process.env.SCENARIO || 'bare'
5535

5636
async function main () {
5737
if (scenario === 'bare') {
58-
for (let i = 0; i < ITERATIONS; i++) {
38+
for (let i = 0; i < OPERATIONS; i++) {
5939
tracer.startSpan('bench.create.bare').finish()
60-
if (pendingNativeIds && pendingNativeIds.length >= DRAIN_THRESHOLD) await drainNative()
40+
if (nativeSpanDrain.needsDrain()) await nativeSpanDrain.drain()
6141
}
6242
} else if (scenario === '10tags') {
63-
for (let i = 0; i < ITERATIONS; i++) {
43+
for (let i = 0; i < OPERATIONS; i++) {
6444
const span = tracer.startSpan('bench.create.10tags', {
6545
tags: {
6646
'service.name': 'my-service',
@@ -76,10 +56,10 @@ async function main () {
7656
},
7757
})
7858
span.finish()
79-
if (pendingNativeIds && pendingNativeIds.length >= DRAIN_THRESHOLD) await drainNative()
59+
if (nativeSpanDrain.needsDrain()) await nativeSpanDrain.drain()
8060
}
8161
}
82-
await drainNative()
62+
await nativeSpanDrain.drain()
8363
}
8464

8565
main()

benchmark/sirun/native-spans/get-tag.js

Lines changed: 35 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,68 +10,54 @@
1010

1111
const nock = require('nock')
1212

13+
const { createNativeSpanDrain } = require('../native-span-drain')
14+
1315
nock.disableNetConnect()
1416
nock('http://127.0.0.1:8126').persist().put(/.*/).reply(200, '{}').post(/.*/).reply(200, '{}')
1517

1618
const tracer = require('../../..').init({ hostname: '127.0.0.1', port: 8126 })
1719

18-
const nativeSpans = tracer._tracer._nativeSpans
19-
const pendingNativeIds = nativeSpans ? [] : null
20+
const nativeSpanDrain = createNativeSpanDrain(tracer)
2021

2122
tracer._tracer._processor.process = function (span) {
22-
if (pendingNativeIds) {
23-
pendingNativeIds.push(span.context()._nativeSpanId)
24-
}
23+
nativeSpanDrain.add(span)
2524
this._erase(span.context()._trace, [])
2625
}
2726

28-
// Extract the accumulated spans (bounds the WASM map) and drain the staged
29-
// chunk via a mocked-agent send (bounds prepared-chunk memory). Span ids are
30-
// 8-byte u64 LE.
31-
async function drainNative () {
32-
if (!pendingNativeIds || pendingNativeIds.length === 0) return
33-
nativeSpans.flushChangeQueue()
34-
const buf = Buffer.alloc(pendingNativeIds.length * 8)
35-
let idx = 0
36-
for (const spanId of pendingNativeIds) {
37-
buf.set(spanId, idx)
38-
idx += 8
39-
}
40-
nativeSpans._state.prepareChunk(pendingNativeIds.length, false, buf)
41-
await nativeSpans._state.sendPreparedChunk().catch(() => {})
42-
pendingNativeIds.length = 0
43-
}
44-
4527
const ITERATIONS = 1_000_000
4628

47-
// Pre-create spans with tags, then measure read cost in a separate loop
48-
// to isolate reads from writes.
49-
const spans = new Array(1000)
50-
for (let i = 0; i < spans.length; i++) {
51-
spans[i] = tracer.startSpan('bench.gettag', {
52-
tags: {
53-
'http.method': 'GET',
54-
'http.url': 'https://api.example.com/users/123',
55-
'http.status_code': 200,
56-
'service.name': 'my-service',
57-
'resource.name': 'GET /users/:id',
58-
},
59-
})
60-
}
29+
async function main () {
30+
// Pre-create spans with tags, then measure read cost in a separate loop
31+
// to isolate reads from writes.
32+
const spans = new Array(1000)
33+
for (let i = 0; i < spans.length; i++) {
34+
spans[i] = tracer.startSpan('bench.gettag', {
35+
tags: {
36+
'http.method': 'GET',
37+
'http.url': 'https://api.example.com/users/123',
38+
'http.status_code': 200,
39+
'service.name': 'my-service',
40+
'resource.name': 'GET /users/:id',
41+
},
42+
})
43+
}
6144

62-
// Read tags in a tight loop across the pre-created spans
63-
for (let i = 0; i < ITERATIONS; i++) {
64-
const span = spans[i % spans.length]
65-
const ctx = span.context()
45+
// Read tags in a tight loop across the pre-created spans
46+
for (let i = 0; i < ITERATIONS; i++) {
47+
const span = spans[i % spans.length]
48+
const ctx = span.context()
6649

67-
// Individual reads (common in plugin code)
68-
ctx.getTag('http.method')
69-
ctx.getTag('http.status_code')
70-
ctx.getTag('resource.name')
71-
}
50+
// Individual reads (common in plugin code)
51+
ctx.getTag('http.method')
52+
ctx.getTag('http.status_code')
53+
ctx.getTag('resource.name')
54+
}
7255

73-
// Clean up
74-
for (const span of spans) {
75-
span.finish()
56+
// Clean up
57+
for (const span of spans) {
58+
span.finish()
59+
}
60+
await nativeSpanDrain.drain()
7661
}
77-
drainNative()
62+
63+
main()

benchmark/sirun/native-spans/meta.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,40 @@
77
"creation-bare": {
88
"run": "node creation.js",
99
"run_with_affinity": "bash -c \"taskset -c $CPU_AFFINITY node creation.js\"",
10-
"env": { "SCENARIO": "bare", "DD_TRACE_SCOPE": "noop" }
10+
"env": { "SCENARIO": "bare", "DD_TRACE_SCOPE": "noop", "OPERATIONS": "100000" }
1111
},
1212
"creation-10tags": {
1313
"run": "node creation.js",
1414
"run_with_affinity": "bash -c \"taskset -c $CPU_AFFINITY node creation.js\"",
15-
"env": { "SCENARIO": "10tags", "DD_TRACE_SCOPE": "noop" }
15+
"env": { "SCENARIO": "10tags", "DD_TRACE_SCOPE": "noop", "OPERATIONS": "50000" }
1616
},
1717

1818
"tagging-settag": {
1919
"run": "node tagging.js",
2020
"run_with_affinity": "bash -c \"taskset -c $CPU_AFFINITY node tagging.js\"",
21-
"env": { "SCENARIO": "settag", "DD_TRACE_SCOPE": "noop" }
21+
"env": { "SCENARIO": "settag", "DD_TRACE_SCOPE": "noop", "OPERATIONS": "100000" }
2222
},
2323
"tagging-addtags": {
2424
"run": "node tagging.js",
2525
"run_with_affinity": "bash -c \"taskset -c $CPU_AFFINITY node tagging.js\"",
26-
"env": { "SCENARIO": "addtags", "DD_TRACE_SCOPE": "noop" }
26+
"env": { "SCENARIO": "addtags", "DD_TRACE_SCOPE": "noop", "OPERATIONS": "100000" }
2727
},
2828

2929
"parent-child-3deep": {
3030
"run": "node parent-child.js",
3131
"run_with_affinity": "bash -c \"taskset -c $CPU_AFFINITY node parent-child.js\"",
32-
"env": { "DEPTH": "3", "DD_TRACE_SCOPE": "noop" }
32+
"env": { "DEPTH": "3", "DD_TRACE_SCOPE": "noop", "OPERATIONS": "50000" }
3333
},
3434
"parent-child-10deep": {
3535
"run": "node parent-child.js",
3636
"run_with_affinity": "bash -c \"taskset -c $CPU_AFFINITY node parent-child.js\"",
37-
"env": { "DEPTH": "10", "DD_TRACE_SCOPE": "noop" }
37+
"env": { "DEPTH": "10", "DD_TRACE_SCOPE": "noop", "OPERATIONS": "20000" }
3838
},
3939

4040
"pipeline": {
4141
"run": "node pipeline.js",
4242
"run_with_affinity": "bash -c \"taskset -c $CPU_AFFINITY node pipeline.js\"",
43-
"env": { "DD_TRACE_SCOPE": "noop" }
43+
"env": { "DD_TRACE_SCOPE": "noop", "OPERATIONS": "50000" }
4444
},
4545

4646
"getTag": {

benchmark/sirun/native-spans/parent-child.js

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,40 +12,21 @@
1212

1313
const nock = require('nock')
1414

15+
const { createNativeSpanDrain } = require('../native-span-drain')
16+
1517
nock.disableNetConnect()
1618
nock('http://127.0.0.1:8126').persist().put(/.*/).reply(200, '{}').post(/.*/).reply(200, '{}')
1719

1820
const tracer = require('../../..').init({ hostname: '127.0.0.1', port: 8126 })
1921

20-
const nativeSpans = tracer._tracer._nativeSpans
21-
const pendingNativeIds = nativeSpans ? [] : null
22-
const DRAIN_THRESHOLD = 5000
22+
const nativeSpanDrain = createNativeSpanDrain(tracer)
2323

2424
tracer._tracer._processor.process = function (span) {
25-
if (pendingNativeIds) {
26-
pendingNativeIds.push(span.context()._nativeSpanId)
27-
}
25+
nativeSpanDrain.add(span)
2826
this._erase(span.context()._trace, [])
2927
}
3028

31-
// Extract the accumulated spans (bounds the WASM map) and drain the staged
32-
// chunk via a mocked-agent send (bounds prepared-chunk memory). Span ids are
33-
// 8-byte u64 LE.
34-
async function drainNative () {
35-
if (!pendingNativeIds || pendingNativeIds.length === 0) return
36-
nativeSpans.flushChangeQueue()
37-
const buf = Buffer.alloc(pendingNativeIds.length * 8)
38-
let idx = 0
39-
for (const spanId of pendingNativeIds) {
40-
buf.set(spanId, idx)
41-
idx += 8
42-
}
43-
nativeSpans._state.prepareChunk(pendingNativeIds.length, false, buf)
44-
await nativeSpans._state.sendPreparedChunk().catch(() => {})
45-
pendingNativeIds.length = 0
46-
}
47-
48-
const ITERATIONS = 500_000
29+
const OPERATIONS = Number(process.env.OPERATIONS) || 50_000
4930
const depth = Number(process.env.DEPTH) || 3
5031

5132
const tagSets = [
@@ -62,7 +43,7 @@ const tagSets = [
6243
]
6344

6445
async function main () {
65-
for (let i = 0; i < ITERATIONS; i++) {
46+
for (let i = 0; i < OPERATIONS; i++) {
6647
const spans = new Array(depth)
6748

6849
// Create the chain top-down
@@ -78,9 +59,9 @@ async function main () {
7859
spans[d].finish()
7960
}
8061

81-
if (pendingNativeIds && pendingNativeIds.length >= DRAIN_THRESHOLD) await drainNative()
62+
if (nativeSpanDrain.needsDrain()) await nativeSpanDrain.drain()
8263
}
83-
await drainNative()
64+
await nativeSpanDrain.drain()
8465
}
8566

8667
main()

0 commit comments

Comments
 (0)