Skip to content

Conversation

dougqh
Copy link
Contributor

@dougqh dougqh commented Sep 15, 2025

What Does This Do

This changes reuses a SpanBuilder within a thread.

Motivation

SpanBuilders are one of the major causes of allocation within the client library, but typically only one SpanBuilder is needed at a time in a thread. By reseting and reusing the same SpanBuilder, tracing allocation can be reduced significantly.

By reducing allocation, the garbage collector needs to run less frequently improving the maximum sustainable throughput of the host application. In local tests with Spring Petclinic, this reduces the throughput penalty with tiny heaps (<= 80m) significantly from -40% to -20%. On larger heaps, the gain is more modest, since garbage collector has less of an impact on the application.

In this initial version, the thread local cache isn't used in virtual threads. This restriction exists to avoid creating more memory churn than we save, since virtual threads are created more often than regular threads.

Initial performance experiment

The idea is to store a CoreSpanBuilder per thread, since usually only SpanBuilder is in use at a given time per thread -- and CoreSpanBuilder isn't thread safe

This simple change provides a giant boost in small heaps
Improving Spring petclinic throughput from -39% to -19% with 80m heap
@dougqh dougqh requested a review from a team as a code owner September 15, 2025 19:47
Copy link
Contributor

github-actions bot commented Sep 15, 2025

Hi! 👋 Thanks for your pull request! 🎉

To help us review it, please make sure to:

  • Add at least one type, and one component or instrumentation label to the pull request

If you need help, please check our contributing guidelines.

@dougqh dougqh added comp: core Tracer core type: enhancement Enhancements and improvements tag: performance Performance related changes labels Sep 15, 2025
@datadog-datadog-prod-us1
Copy link
Contributor

datadog-datadog-prod-us1 bot commented Sep 15, 2025

🎯 Code Coverage
Patch Coverage: 57.84%
Total Coverage: 59.78% (-0.05%)

View detailed report

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 054485a | Docs | Was this helpful? Give us feedback!

@pr-commenter
Copy link

pr-commenter bot commented Sep 15, 2025

Benchmarks

Startup

Parameters

Baseline Candidate
baseline_or_candidate baseline candidate
git_branch master dougqh/spanbuilder-pooling
git_commit_date 1760629469 1760632318
git_commit_sha 4737935 054485a
release_version 1.55.0-SNAPSHOT~4737935c38 1.54.0-SNAPSHOT~054485ac66
See matching parameters
Baseline Candidate
application insecure-bank insecure-bank
ci_job_date 1760634050 1760634050
ci_job_id 1183352192 1183352192
ci_pipeline_id 79528869 79528869
cpu_model Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz
kernel_version Linux runner-zfyrx7zua-project-304-concurrent-0-5oj51zhb 6.8.0-1031-aws #33~22.04.1-Ubuntu SMP Thu Jun 26 14:22:30 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux Linux runner-zfyrx7zua-project-304-concurrent-0-5oj51zhb 6.8.0-1031-aws #33~22.04.1-Ubuntu SMP Thu Jun 26 14:22:30 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
module Agent Agent
parent None None

Summary

Found 0 performance improvements and 0 performance regressions! Performance is the same for 59 metrics, 6 unstable metrics.

Startup time reports for insecure-bank
gantt
    title insecure-bank - global startup overhead: candidate=1.54.0-SNAPSHOT~054485ac66, baseline=1.55.0-SNAPSHOT~4737935c38

    dateFormat X
    axisFormat %s
section tracing
Agent [baseline] (1.031 s) : 0, 1030894
Total [baseline] (8.791 s) : 0, 8791101
Agent [candidate] (1.021 s) : 0, 1020594
Total [candidate] (8.677 s) : 0, 8677273
section iast
Agent [baseline] (1.157 s) : 0, 1156686
Total [baseline] (9.278 s) : 0, 9277857
Agent [candidate] (1.151 s) : 0, 1150976
Total [candidate] (9.355 s) : 0, 9354802
Loading
  • baseline results
Module Variant Duration Δ tracing
Agent tracing 1.031 s -
Agent iast 1.157 s 125.792 ms (12.2%)
Total tracing 8.791 s -
Total iast 9.278 s 486.755 ms (5.5%)
  • candidate results
Module Variant Duration Δ tracing
Agent tracing 1.021 s -
Agent iast 1.151 s 130.382 ms (12.8%)
Total tracing 8.677 s -
Total iast 9.355 s 677.528 ms (7.8%)
gantt
    title insecure-bank - break down per module: candidate=1.54.0-SNAPSHOT~054485ac66, baseline=1.55.0-SNAPSHOT~4737935c38

    dateFormat X
    axisFormat %s
section tracing
crashtracking [baseline] (1.498 ms) : 0, 1498
crashtracking [candidate] (1.46 ms) : 0, 1460
BytebuddyAgent [baseline] (701.501 ms) : 0, 701501
BytebuddyAgent [candidate] (694.373 ms) : 0, 694373
GlobalTracer [baseline] (245.755 ms) : 0, 245755
GlobalTracer [candidate] (243.386 ms) : 0, 243386
AppSec [baseline] (33.293 ms) : 0, 33293
AppSec [candidate] (32.435 ms) : 0, 32435
Debugger [baseline] (6.688 ms) : 0, 6688
Debugger [candidate] (6.481 ms) : 0, 6481
Remote Config [baseline] (713.347 µs) : 0, 713
Remote Config [candidate] (694.441 µs) : 0, 694
Telemetry [baseline] (9.513 ms) : 0, 9513
Telemetry [candidate] (9.338 ms) : 0, 9338
Flare Poller [baseline] (10.502 ms) : 0, 10502
Flare Poller [candidate] (11.201 ms) : 0, 11201
section iast
crashtracking [baseline] (1.505 ms) : 0, 1505
crashtracking [candidate] (1.487 ms) : 0, 1487
BytebuddyAgent [baseline] (819.315 ms) : 0, 819315
BytebuddyAgent [candidate] (815.201 ms) : 0, 815201
GlobalTracer [baseline] (232.404 ms) : 0, 232404
GlobalTracer [candidate] (232.207 ms) : 0, 232207
AppSec [baseline] (35.481 ms) : 0, 35481
AppSec [candidate] (33.793 ms) : 0, 33793
Debugger [baseline] (6.205 ms) : 0, 6205
Debugger [candidate] (6.097 ms) : 0, 6097
Remote Config [baseline] (606.02 µs) : 0, 606
Remote Config [candidate] (598.719 µs) : 0, 599
Telemetry [baseline] (8.764 ms) : 0, 8764
Telemetry [candidate] (8.677 ms) : 0, 8677
Flare Poller [baseline] (4.171 ms) : 0, 4171
Flare Poller [candidate] (4.132 ms) : 0, 4132
IAST [baseline] (26.737 ms) : 0, 26737
IAST [candidate] (27.236 ms) : 0, 27236
Loading
Startup time reports for petclinic
gantt
    title petclinic - global startup overhead: candidate=1.54.0-SNAPSHOT~054485ac66, baseline=1.55.0-SNAPSHOT~4737935c38

    dateFormat X
    axisFormat %s
section tracing
Agent [baseline] (1.017 s) : 0, 1016578
Total [baseline] (10.765 s) : 0, 10764670
Agent [candidate] (1.023 s) : 0, 1023285
Total [candidate] (10.705 s) : 0, 10704667
section appsec
Agent [baseline] (1.194 s) : 0, 1194228
Total [baseline] (11.031 s) : 0, 11031390
Agent [candidate] (1.193 s) : 0, 1193237
Total [candidate] (10.988 s) : 0, 10988333
section iast
Agent [baseline] (1.151 s) : 0, 1151288
Total [baseline] (11.006 s) : 0, 11006240
Agent [candidate] (1.152 s) : 0, 1152385
Total [candidate] (11.054 s) : 0, 11053612
section profiling
Agent [baseline] (1.169 s) : 0, 1169030
Total [baseline] (11.111 s) : 0, 11110782
Agent [candidate] (1.161 s) : 0, 1161237
Total [candidate] (10.974 s) : 0, 10974114
Loading
  • baseline results
Module Variant Duration Δ tracing
Agent tracing 1.017 s -
Agent appsec 1.194 s 177.65 ms (17.5%)
Agent iast 1.151 s 134.709 ms (13.3%)
Agent profiling 1.169 s 152.452 ms (15.0%)
Total tracing 10.765 s -
Total appsec 11.031 s 266.72 ms (2.5%)
Total iast 11.006 s 241.571 ms (2.2%)
Total profiling 11.111 s 346.113 ms (3.2%)
  • candidate results
Module Variant Duration Δ tracing
Agent tracing 1.023 s -
Agent appsec 1.193 s 169.952 ms (16.6%)
Agent iast 1.152 s 129.1 ms (12.6%)
Agent profiling 1.161 s 137.952 ms (13.5%)
Total tracing 10.705 s -
Total appsec 10.988 s 283.666 ms (2.6%)
Total iast 11.054 s 348.946 ms (3.3%)
Total profiling 10.974 s 269.447 ms (2.5%)
gantt
    title petclinic - break down per module: candidate=1.54.0-SNAPSHOT~054485ac66, baseline=1.55.0-SNAPSHOT~4737935c38

    dateFormat X
    axisFormat %s
section tracing
crashtracking [baseline] (1.459 ms) : 0, 1459
crashtracking [candidate] (1.459 ms) : 0, 1459
BytebuddyAgent [baseline] (692.726 ms) : 0, 692726
BytebuddyAgent [candidate] (696.627 ms) : 0, 696627
GlobalTracer [baseline] (242.043 ms) : 0, 242043
GlobalTracer [candidate] (244.102 ms) : 0, 244102
AppSec [baseline] (32.457 ms) : 0, 32457
AppSec [candidate] (32.499 ms) : 0, 32499
Debugger [baseline] (6.447 ms) : 0, 6447
Debugger [candidate] (6.531 ms) : 0, 6531
Remote Config [baseline] (701.827 µs) : 0, 702
Remote Config [candidate] (696.033 µs) : 0, 696
Telemetry [baseline] (9.404 ms) : 0, 9404
Telemetry [candidate] (9.443 ms) : 0, 9443
Flare Poller [baseline] (10.127 ms) : 0, 10127
Flare Poller [candidate] (10.709 ms) : 0, 10709
section appsec
crashtracking [baseline] (1.467 ms) : 0, 1467
crashtracking [candidate] (1.456 ms) : 0, 1456
BytebuddyAgent [baseline] (717.369 ms) : 0, 717369
BytebuddyAgent [candidate] (715.159 ms) : 0, 715159
GlobalTracer [baseline] (234.627 ms) : 0, 234627
GlobalTracer [candidate] (236.148 ms) : 0, 236148
AppSec [baseline] (175.708 ms) : 0, 175708
AppSec [candidate] (175.338 ms) : 0, 175338
Debugger [baseline] (6.107 ms) : 0, 6107
Debugger [candidate] (6.118 ms) : 0, 6118
Remote Config [baseline] (635.232 µs) : 0, 635
Remote Config [candidate] (634.52 µs) : 0, 635
Telemetry [baseline] (8.422 ms) : 0, 8422
Telemetry [candidate] (8.48 ms) : 0, 8480
Flare Poller [baseline] (3.927 ms) : 0, 3927
Flare Poller [candidate] (3.979 ms) : 0, 3979
IAST [baseline] (24.832 ms) : 0, 24832
IAST [candidate] (24.833 ms) : 0, 24833
section iast
crashtracking [baseline] (1.474 ms) : 0, 1474
crashtracking [candidate] (1.453 ms) : 0, 1453
BytebuddyAgent [baseline] (815.653 ms) : 0, 815653
BytebuddyAgent [candidate] (815.825 ms) : 0, 815825
GlobalTracer [baseline] (231.427 ms) : 0, 231427
GlobalTracer [candidate] (232.401 ms) : 0, 232401
AppSec [baseline] (35.165 ms) : 0, 35165
AppSec [candidate] (34.851 ms) : 0, 34851
Debugger [baseline] (6.143 ms) : 0, 6143
Debugger [candidate] (6.214 ms) : 0, 6214
Remote Config [baseline] (618.049 µs) : 0, 618
Remote Config [candidate] (616.482 µs) : 0, 616
Telemetry [baseline] (8.705 ms) : 0, 8705
Telemetry [candidate] (8.795 ms) : 0, 8795
Flare Poller [baseline] (4.267 ms) : 0, 4267
Flare Poller [candidate] (4.237 ms) : 0, 4237
IAST [baseline] (26.291 ms) : 0, 26291
IAST [candidate] (26.435 ms) : 0, 26435
section profiling
crashtracking [baseline] (1.447 ms) : 0, 1447
crashtracking [candidate] (1.412 ms) : 0, 1412
BytebuddyAgent [baseline] (727.133 ms) : 0, 727133
BytebuddyAgent [candidate] (719.352 ms) : 0, 719352
GlobalTracer [baseline] (219.028 ms) : 0, 219028
GlobalTracer [candidate] (218.629 ms) : 0, 218629
AppSec [baseline] (32.574 ms) : 0, 32574
AppSec [candidate] (32.253 ms) : 0, 32253
Debugger [baseline] (6.491 ms) : 0, 6491
Debugger [candidate] (6.48 ms) : 0, 6480
Remote Config [baseline] (791.872 µs) : 0, 792
Remote Config [candidate] (757.151 µs) : 0, 757
Telemetry [baseline] (16.069 ms) : 0, 16069
Telemetry [candidate] (16.145 ms) : 0, 16145
Flare Poller [baseline] (4.152 ms) : 0, 4152
Flare Poller [candidate] (4.176 ms) : 0, 4176
ProfilingAgent [baseline] (107.698 ms) : 0, 107698
ProfilingAgent [candidate] (108.613 ms) : 0, 108613
Profiling [baseline] (108.968 ms) : 0, 108968
Profiling [candidate] (109.891 ms) : 0, 109891
Loading

Load

Parameters

Baseline Candidate
baseline_or_candidate baseline candidate
git_branch master dougqh/spanbuilder-pooling
git_commit_date 1760629469 1760632318
git_commit_sha 4737935 054485a
release_version 1.55.0-SNAPSHOT~4737935c38 1.54.0-SNAPSHOT~054485ac66
See matching parameters
Baseline Candidate
application insecure-bank insecure-bank
ci_job_date 1760633802 1760633802
ci_job_id 1183352193 1183352193
ci_pipeline_id 79528869 79528869
cpu_model Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz
kernel_version Linux runner-zfyrx7zua-project-304-concurrent-0-pe4voijo 6.8.0-1031-aws #33~22.04.1-Ubuntu SMP Thu Jun 26 14:22:30 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux Linux runner-zfyrx7zua-project-304-concurrent-0-pe4voijo 6.8.0-1031-aws #33~22.04.1-Ubuntu SMP Thu Jun 26 14:22:30 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux

Summary

Found 4 performance improvements and 2 performance regressions! Performance is the same for 6 metrics, 12 unstable metrics.

scenario Δ mean http_req_duration Δ mean throughput candidate mean http_req_duration candidate mean throughput baseline mean http_req_duration baseline mean throughput
scenario:load:insecure-bank:iast:high_load better
[-602.839µs; -242.433µs] or [-6.045%; -2.431%]
unstable
[-39.282op/s; +79.782op/s] or [-8.431%; +17.123%]
9.551ms 486.188op/s 9.973ms 465.938op/s
scenario:load:insecure-bank:profiling:high_load better
[-875.379µs; -551.622µs] or [-9.503%; -5.988%]
unstable
[-27.577op/s; +111.202op/s] or [-5.473%; +22.068%]
8.498ms 545.719op/s 9.212ms 503.906op/s
scenario:load:insecure-bank:tracing:high_load worse
[+224.371µs; +475.623µs] or [+2.851%; +6.043%]
unstable
[-95.817op/s; +46.504op/s] or [-16.277%; +7.900%]
8.220ms 564.000op/s 7.870ms 588.656op/s
scenario:load:insecure-bank:iast_GLOBAL:high_load worse
[+380.270µs; +814.729µs] or [+3.548%; +7.602%]
unstable
[-73.230op/s; +27.855op/s] or [-16.878%; +6.420%]
11.314ms 411.188op/s 10.717ms 433.875op/s
scenario:load:petclinic:tracing:high_load better
[-2.542ms; -1.716ms] or [-5.568%; -3.758%]
unstable
[-2.534op/s; +12.534op/s] or [-2.472%; +12.226%]
43.529ms 107.513op/s 45.659ms 102.513op/s
scenario:load:petclinic:profiling:high_load better
[-2.303ms; -1.314ms] or [-4.500%; -2.567%]
unstable
[-3.514op/s; +10.114op/s] or [-3.840%; +11.053%]
49.374ms 94.800op/s 51.182ms 91.500op/s
Request duration reports for insecure-bank
gantt
    title insecure-bank - request duration [CI 0.99] : candidate=1.54.0-SNAPSHOT~054485ac66, baseline=1.55.0-SNAPSHOT~4737935c38
    dateFormat X
    axisFormat %s
section baseline
no_agent (4.267 ms) : 4211, 4324
.   : milestone, 4267,
iast (9.973 ms) : 9801, 10145
.   : milestone, 9973,
iast_FULL (14.411 ms) : 14121, 14701
.   : milestone, 14411,
iast_GLOBAL (10.717 ms) : 10525, 10908
.   : milestone, 10717,
profiling (9.212 ms) : 9042, 9382
.   : milestone, 9212,
tracing (7.87 ms) : 7757, 7984
.   : milestone, 7870,
section candidate
no_agent (4.359 ms) : 4311, 4407
.   : milestone, 4359,
iast (9.551 ms) : 9388, 9714
.   : milestone, 9551,
iast_FULL (14.186 ms) : 13908, 14464
.   : milestone, 14186,
iast_GLOBAL (11.314 ms) : 11103, 11526
.   : milestone, 11314,
profiling (8.498 ms) : 8370, 8627
.   : milestone, 8498,
tracing (8.22 ms) : 8100, 8340
.   : milestone, 8220,
Loading
  • baseline results
Variant Request duration [CI 0.99] Δ no_agent
no_agent 4.267 ms [4.211 ms, 4.324 ms] -
iast 9.973 ms [9.801 ms, 10.145 ms] 5.706 ms (133.7%)
iast_FULL 14.411 ms [14.121 ms, 14.701 ms] 10.144 ms (237.7%)
iast_GLOBAL 10.717 ms [10.525 ms, 10.908 ms] 6.449 ms (151.1%)
profiling 9.212 ms [9.042 ms, 9.382 ms] 4.945 ms (115.9%)
tracing 7.87 ms [7.757 ms, 7.984 ms] 3.603 ms (84.4%)
  • candidate results
Variant Request duration [CI 0.99] Δ no_agent
no_agent 4.359 ms [4.311 ms, 4.407 ms] -
iast 9.551 ms [9.388 ms, 9.714 ms] 5.192 ms (119.1%)
iast_FULL 14.186 ms [13.908 ms, 14.464 ms] 9.827 ms (225.5%)
iast_GLOBAL 11.314 ms [11.103 ms, 11.526 ms] 6.955 ms (159.6%)
profiling 8.498 ms [8.37 ms, 8.627 ms] 4.14 ms (95.0%)
tracing 8.22 ms [8.1 ms, 8.34 ms] 3.861 ms (88.6%)
Request duration reports for petclinic
gantt
    title petclinic - request duration [CI 0.99] : candidate=1.54.0-SNAPSHOT~054485ac66, baseline=1.55.0-SNAPSHOT~4737935c38
    dateFormat X
    axisFormat %s
section baseline
no_agent (36.621 ms) : 36322, 36920
.   : milestone, 36621,
appsec (48.344 ms) : 47913, 48776
.   : milestone, 48344,
code_origins (45.164 ms) : 44765, 45562
.   : milestone, 45164,
iast (45.342 ms) : 44956, 45728
.   : milestone, 45342,
profiling (51.182 ms) : 50702, 51662
.   : milestone, 51182,
tracing (45.659 ms) : 45268, 46049
.   : milestone, 45659,
section candidate
no_agent (36.505 ms) : 36217, 36793
.   : milestone, 36505,
appsec (49.345 ms) : 48909, 49780
.   : milestone, 49345,
code_origins (44.866 ms) : 44486, 45245
.   : milestone, 44866,
iast (45.015 ms) : 44624, 45405
.   : milestone, 45015,
profiling (49.374 ms) : 48935, 49812
.   : milestone, 49374,
tracing (43.529 ms) : 43152, 43906
.   : milestone, 43529,
Loading
  • baseline results
Variant Request duration [CI 0.99] Δ no_agent
no_agent 36.621 ms [36.322 ms, 36.92 ms] -
appsec 48.344 ms [47.913 ms, 48.776 ms] 11.723 ms (32.0%)
code_origins 45.164 ms [44.765 ms, 45.562 ms] 8.543 ms (23.3%)
iast 45.342 ms [44.956 ms, 45.728 ms] 8.721 ms (23.8%)
profiling 51.182 ms [50.702 ms, 51.662 ms] 14.561 ms (39.8%)
tracing 45.659 ms [45.268 ms, 46.049 ms] 9.038 ms (24.7%)
  • candidate results
Variant Request duration [CI 0.99] Δ no_agent
no_agent 36.505 ms [36.217 ms, 36.793 ms] -
appsec 49.345 ms [48.909 ms, 49.78 ms] 12.84 ms (35.2%)
code_origins 44.866 ms [44.486 ms, 45.245 ms] 8.361 ms (22.9%)
iast 45.015 ms [44.624 ms, 45.405 ms] 8.51 ms (23.3%)
profiling 49.374 ms [48.935 ms, 49.812 ms] 12.869 ms (35.3%)
tracing 43.529 ms [43.152 ms, 43.906 ms] 7.024 ms (19.2%)

Dacapo

Parameters

Baseline Candidate
baseline_or_candidate baseline candidate
git_branch master dougqh/spanbuilder-pooling
git_commit_date 1760629469 1760632318
git_commit_sha 4737935 054485a
release_version 1.55.0-SNAPSHOT~4737935c38 1.54.0-SNAPSHOT~054485ac66
See matching parameters
Baseline Candidate
application biojava biojava
ci_job_date 1760634375 1760634375
ci_job_id 1183352195 1183352195
ci_pipeline_id 79528869 79528869
cpu_model Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz
kernel_version Linux runner-zfyrx7zua-project-304-concurrent-0-x3zmim3i 6.8.0-1031-aws #33~22.04.1-Ubuntu SMP Thu Jun 26 14:22:30 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux Linux runner-zfyrx7zua-project-304-concurrent-0-x3zmim3i 6.8.0-1031-aws #33~22.04.1-Ubuntu SMP Thu Jun 26 14:22:30 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux

Summary

Found 0 performance improvements and 0 performance regressions! Performance is the same for 11 metrics, 1 unstable metrics.

Execution time for biojava
gantt
    title biojava - execution time [CI 0.99] : candidate=1.54.0-SNAPSHOT~054485ac66, baseline=1.55.0-SNAPSHOT~4737935c38
    dateFormat X
    axisFormat %s
section baseline
no_agent (15.532 s) : 15532000, 15532000
.   : milestone, 15532000,
appsec (14.99 s) : 14990000, 14990000
.   : milestone, 14990000,
iast (18.618 s) : 18618000, 18618000
.   : milestone, 18618000,
iast_GLOBAL (18.016 s) : 18016000, 18016000
.   : milestone, 18016000,
profiling (15.035 s) : 15035000, 15035000
.   : milestone, 15035000,
tracing (15.372 s) : 15372000, 15372000
.   : milestone, 15372000,
section candidate
no_agent (15.292 s) : 15292000, 15292000
.   : milestone, 15292000,
appsec (15.281 s) : 15281000, 15281000
.   : milestone, 15281000,
iast (18.43 s) : 18430000, 18430000
.   : milestone, 18430000,
iast_GLOBAL (17.597 s) : 17597000, 17597000
.   : milestone, 17597000,
profiling (15.615 s) : 15615000, 15615000
.   : milestone, 15615000,
tracing (15.283 s) : 15283000, 15283000
.   : milestone, 15283000,
Loading
  • baseline results
Variant Execution Time [CI 0.99] Δ no_agent
no_agent 15.532 s [15.532 s, 15.532 s] -
appsec 14.99 s [14.99 s, 14.99 s] -542.0 ms (-3.5%)
iast 18.618 s [18.618 s, 18.618 s] 3.086 s (19.9%)
iast_GLOBAL 18.016 s [18.016 s, 18.016 s] 2.484 s (16.0%)
profiling 15.035 s [15.035 s, 15.035 s] -497.0 ms (-3.2%)
tracing 15.372 s [15.372 s, 15.372 s] -160.0 ms (-1.0%)
  • candidate results
Variant Execution Time [CI 0.99] Δ no_agent
no_agent 15.292 s [15.292 s, 15.292 s] -
appsec 15.281 s [15.281 s, 15.281 s] -11.0 ms (-0.1%)
iast 18.43 s [18.43 s, 18.43 s] 3.138 s (20.5%)
iast_GLOBAL 17.597 s [17.597 s, 17.597 s] 2.305 s (15.1%)
profiling 15.615 s [15.615 s, 15.615 s] 323.0 ms (2.1%)
tracing 15.283 s [15.283 s, 15.283 s] -9.0 ms (-0.1%)
Execution time for tomcat
gantt
    title tomcat - execution time [CI 0.99] : candidate=1.54.0-SNAPSHOT~054485ac66, baseline=1.55.0-SNAPSHOT~4737935c38
    dateFormat X
    axisFormat %s
section baseline
no_agent (1.48 ms) : 1468, 1491
.   : milestone, 1480,
appsec (3.744 ms) : 3525, 3963
.   : milestone, 3744,
iast (2.214 ms) : 2151, 2277
.   : milestone, 2214,
iast_GLOBAL (2.255 ms) : 2190, 2319
.   : milestone, 2255,
profiling (2.085 ms) : 2032, 2138
.   : milestone, 2085,
tracing (2.036 ms) : 1986, 2086
.   : milestone, 2036,
section candidate
no_agent (1.483 ms) : 1472, 1495
.   : milestone, 1483,
appsec (3.773 ms) : 3549, 3997
.   : milestone, 3773,
iast (2.211 ms) : 2147, 2275
.   : milestone, 2211,
iast_GLOBAL (2.261 ms) : 2197, 2325
.   : milestone, 2261,
profiling (2.063 ms) : 2011, 2114
.   : milestone, 2063,
tracing (2.047 ms) : 1997, 2097
.   : milestone, 2047,
Loading
  • baseline results
Variant Execution Time [CI 0.99] Δ no_agent
no_agent 1.48 ms [1.468 ms, 1.491 ms] -
appsec 3.744 ms [3.525 ms, 3.963 ms] 2.264 ms (153.0%)
iast 2.214 ms [2.151 ms, 2.277 ms] 734.076 µs (49.6%)
iast_GLOBAL 2.255 ms [2.19 ms, 2.319 ms] 774.625 µs (52.3%)
profiling 2.085 ms [2.032 ms, 2.138 ms] 604.974 µs (40.9%)
tracing 2.036 ms [1.986 ms, 2.086 ms] 555.965 µs (37.6%)
  • candidate results
Variant Execution Time [CI 0.99] Δ no_agent
no_agent 1.483 ms [1.472 ms, 1.495 ms] -
appsec 3.773 ms [3.549 ms, 3.997 ms] 2.29 ms (154.4%)
iast 2.211 ms [2.147 ms, 2.275 ms] 727.875 µs (49.1%)
iast_GLOBAL 2.261 ms [2.197 ms, 2.325 ms] 778.275 µs (52.5%)
profiling 2.063 ms [2.011 ms, 2.114 ms] 579.652 µs (39.1%)
tracing 2.047 ms [1.997 ms, 2.097 ms] 563.489 µs (38.0%)

public static final String OPTIMIZED_MAP_ENABLED = "optimized.map.enabled";
public static final String TAG_NAME_UTF8_CACHE_SIZE = "tag.name.utf8.cache.size";
public static final String TAG_VALUE_UTF8_CACHE_SIZE = "tag.value.utf8.cache.size";
public static final String SPAN_BUILDER_REUSE_ENABLED = "span.builder.reuse.enabled";
Copy link
Contributor Author

@dougqh dougqh Sep 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is best class to be placing these in. Or if this is the proper namespace. dd.trace... might be better

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree dd.trace prefix fits better

Copy link
Contributor

@PerfectSlayer PerfectSlayer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❔ question: ‏Quick question for my understanding: how is that supposed to work with manual tracing API? (DD or OTel)

Let’s say:

var spanBuilder = tracer.spanBuilder("span1");
// …
var span = tracer.spanBuilder("span2").startSpan();
// …
spanBuilder.setAttribute("key", "value").startSpan(); 

The second startSpan() call will start the span with the wrong name ("span2" instead of "span1") and there is nothing we can do on the API to prevent such use case.

@dougqh
Copy link
Contributor Author

dougqh commented Sep 16, 2025

The second startSpan() call will start the span with the wrong name ("span2" instead of "span1") and there is nothing we can do on the API to prevent such use case.
Actually, I updated my approach yesterday to handle that specific case; however, other cases are definitely worth considering.

The solution that I put in place yesterday was that I track whether or not the SpanBuilder is "in-use". A SpanBuilder is considered "in -use" from the point that it is reset during CoreTracer.buildSpan up until a span is created with SpanBuilder.buildSpan.

So CoreTracer.buildSpan can check if the ThreadLocal SpanBuilder is still "in-use". If it is, then the CoreTracer still constructs a new SpanBuilder and returns that instead.

So the 'in-use' check solves the case mentioned in the original comment. You'll still get two distinct SpanBuilders.
I definitely should add a test for that. Right now, I just wanted a sanity check from others and the automated benchmarking.

But there are still other cases that worry me, specifically, I'm worried that you can actually call build on the SpanBuilder multiple times. That's not idiomatic, but it would probably work today. My solution doesn't handle that.

I don't believe our own instrumentation produces multiple Spans per builder today, so we could solve the problem by having two slightly different sets of rules: one for automatic and one for manual. We can accomplish that by having two sets of methods: one set that will reuse SpanBuilders that we use in automatic instrumentation, and another set that always creates a new SpanBuilder that we use in manual instrumentation (and the OTel bridge).

UPDATE:
I realized that there was an easy middle ground with builder reuse issues.

buildSpan can maintain the existing semantics including allowing building multiple spans.
The startSpan convenience methods can safely reuse a SpanBuilder, since we know that it only builds a single span.
And to do that, I introduced a new singleSpanBuilder method that we can use in automatic instrumentation. singleSpanBuilder can use SpanBuilder recycling under the covers as long as we know it is not "in-use" (see above)

That does mean that we have to update some instrumentation to get the full benefit, but at least, we know the change is safe.

Refactored code, so tests work regardless of Config
To avoid breaking any potential code that builds multiple spans from the same SpanBuilder, updated the SpanBuilder pooling approach

Introduced a new method singleSpanBuilder which can build one and only one span, this method can be used by automatic instrumentation as an optimization.

singleSpanBuilder is now used inside the startSpan convenience methods, since we know they only build and return one span.  Any automatic instrumentation using startSpan gets the optimization for free.

buildSpan maintains its original semantics, so all existing continues to work as is.
@dougqh dougqh requested a review from a team as a code owner September 16, 2025 20:11
In a microbenchmark, buildSpan was performing worse than previously.

To address, that shortcoming and to clean-up the code...
Made CoreSpanBuilder abstract and introduced two child classes: MultiSpanBuilder and ReusableSingleSpanBuilder

MultiSpanBuilder is used by buildSpan
ReusableSingleSpanBuilder is used by singleSpanBuilder / startSpan (indirectly)
Comment on lines +1935 to +1937
static final class ReusableSingleSpanBuilderThreadLocalCache
extends ThreadLocal<ReusableSingleSpanBuilder> {
private final CoreTracer tracer;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious if it will be OK on latest JDKs with virtual threads?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added an init instead of calling reset when creating a new ReusableSingleSpanBuilder

Added some asserts to catch misuse of the API
Adding ThreadUtils class that enables checking if Threads are virtual threads
@dougqh dougqh requested a review from a team as a code owner October 7, 2025 15:23
@dougqh dougqh requested review from bric3 and removed request for a team October 7, 2025 15:23
}
}

static final class MultiSpanBuilder extends CoreSpanBuilder {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Some javadoc

  /**
   * A span builder that creates a new instance for each span.
   *
   * <p>This builder is used when span builder reuse is disabled or when building spans that may
   * have multiple children. Each call to {@link #buildSpan()} creates a fresh span instance
   * without any state reuse.
   */
  static final class MultiSpanBuilder extends CoreSpanBuilder {

}
}

static final class ReusableSingleSpanBuilder extends CoreSpanBuilder {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Some javadoc suggestions

Suggested change
static final class ReusableSingleSpanBuilder extends CoreSpanBuilder {
/**
* A reusable span builder optimized for building single spans with no children.
*
* <p>This builder is pooled in a thread-local cache to reduce allocation overhead for
* high-frequency span creation.
*
* <p><b>Virtual thread handling:</b> Virtual threads do not use thread-local caching because
* they are created and destroyed frequently. Instead, a new builder instance is allocated for
* each span created on a virtual thread.
*
* @see CoreTracer#singleSpanBuilder(String, CharSequence)
* @see ReusableSingleSpanBuilderThreadLocalCache
*/
static final class ReusableSingleSpanBuilder extends CoreSpanBuilder {


@Override
public CoreSpanBuilder ignoreActiveSpan() {
public final CoreSpanBuilder ignoreActiveSpan() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Just noted that some method are private, and may not need to be final, but I understand your perspective on anticipating changes here since the class is not final. Also it aligns with other methods. So feel free to dismiss :)

Comment on lines +2003 to +2019
this.instrumentationName = instrumentationName;
this.operationName = operationName;

if (this.tagLedger != null) this.tagLedger.reset();
this.timestampMicro = 0L;
this.parent = null;
this.serviceName = null;
this.resourceName = null;
this.errorFlag = false;
this.spanType = null;
this.ignoreScope = false;
this.builderRequestContextDataAppSec = null;
this.builderRequestContextDataIast = null;
this.builderCiVisibilityContextData = null;
this.links = null;
this.spanId = 0L;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: Is there way to test nothing go forbidden here if CoreSpanBuilder gets a new field ? Maybe via some tests ?

Copy link
Contributor

@AlexeyKuznetsov-DD AlexeyKuznetsov-DD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just left several minor comments.


@Test
public void isVirtualThread_true() throws InterruptedException {
if (!ThreadUtils.supportsVirtualThreads()) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is better to use JUnit assume here? Test will be marked as skipped.

Comment on lines +88 to +105
MethodHandle h_startVThread;
try {
h_startVThread =
MethodHandles.lookup()
.findStatic(
Thread.class,
"startVirtualThread",
MethodType.methodType(Thread.class, Runnable.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new IllegalStateException(e);
}

try {
return (Thread) h_startVThread.invoke(runnable);
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a very minor nitpick: I guess we can simplify code by removing try/catch?
We already checking for V-threads before calling this function.


@Override
public CoreSpanBuilder withTag(final String tag, final Number number) {
public final CoreSpanBuilder withTag(final String tag, final Number number) {
Copy link
Contributor

@AlexeyKuznetsov-DD AlexeyKuznetsov-DD Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still curious why we are using final so often? It will force Java compiler to produce more optimal code?

@AlexeyKuznetsov-DD
Copy link
Contributor

Also noticed in build logs on CI:

branches covered ratio is 0.3, but expected minimum is 0.5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: core Tracer core tag: performance Performance related changes type: enhancement Enhancements and improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants