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.

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: 0.00%
Total Coverage: 55.53% (-4.15%)

View detailed report

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: ff9acbe | 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 1758219687 1758247031
git_commit_sha 1052f49 ff9acbe
release_version 1.54.0-SNAPSHOT~1052f4935d 1.54.0-SNAPSHOT~ff9acbe38f
See matching parameters
Baseline Candidate
application insecure-bank insecure-bank
ci_job_date 1758248880 1758248880
ci_job_id 1137502793 1137502793
ci_pipeline_id 76941692 76941692
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-nsc3i2lx 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-nsc3i2lx 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 50 metrics, 9 unstable metrics.

Startup time reports for insecure-bank
gantt
    title insecure-bank - global startup overhead: candidate=1.54.0-SNAPSHOT~ff9acbe38f, baseline=1.54.0-SNAPSHOT~1052f4935d

    dateFormat X
    axisFormat %s
section tracing
Agent [baseline] (1.07 s) : 0, 1070020
Total [baseline] (8.719 s) : 0, 8719243
Agent [candidate] (1.066 s) : 0, 1065585
Total [candidate] (8.653 s) : 0, 8652838
section iast
Agent [baseline] (1.191 s) : 0, 1190964
Total [baseline] (9.28 s) : 0, 9280479
Agent [candidate] (1.2 s) : 0, 1199536
Total [candidate] (9.364 s) : 0, 9364320
Loading
  • baseline results
Module Variant Duration Δ tracing
Agent tracing 1.07 s -
Agent iast 1.191 s 120.944 ms (11.3%)
Total tracing 8.719 s -
Total iast 9.28 s 561.236 ms (6.4%)
  • candidate results
Module Variant Duration Δ tracing
Agent tracing 1.066 s -
Agent iast 1.2 s 133.95 ms (12.6%)
Total tracing 8.653 s -
Total iast 9.364 s 711.482 ms (8.2%)
gantt
    title insecure-bank - break down per module: candidate=1.54.0-SNAPSHOT~ff9acbe38f, baseline=1.54.0-SNAPSHOT~1052f4935d

    dateFormat X
    axisFormat %s
section tracing
crashtracking [baseline] (1.465 ms) : 0, 1465
crashtracking [candidate] (1.466 ms) : 0, 1466
BytebuddyAgent [baseline] (738.37 ms) : 0, 738370
BytebuddyAgent [candidate] (734.825 ms) : 0, 734825
GlobalTracer [baseline] (254.592 ms) : 0, 254592
GlobalTracer [candidate] (254.045 ms) : 0, 254045
AppSec [baseline] (30.905 ms) : 0, 30905
AppSec [candidate] (30.794 ms) : 0, 30794
Debugger [baseline] (6.456 ms) : 0, 6456
Debugger [candidate] (6.401 ms) : 0, 6401
Remote Config [baseline] (703.372 µs) : 0, 703
Remote Config [candidate] (684.955 µs) : 0, 685
Telemetry [baseline] (16.488 ms) : 0, 16488
Telemetry [candidate] (16.391 ms) : 0, 16391
section iast
crashtracking [baseline] (1.457 ms) : 0, 1457
crashtracking [candidate] (1.474 ms) : 0, 1474
BytebuddyAgent [baseline] (852.615 ms) : 0, 852615
BytebuddyAgent [candidate] (859.297 ms) : 0, 859297
GlobalTracer [baseline] (244.811 ms) : 0, 244811
GlobalTracer [candidate] (248.587 ms) : 0, 248587
AppSec [baseline] (29.785 ms) : 0, 29785
AppSec [candidate] (26.859 ms) : 0, 26859
Debugger [baseline] (6.053 ms) : 0, 6053
Debugger [candidate] (5.988 ms) : 0, 5988
Remote Config [baseline] (589.444 µs) : 0, 589
Remote Config [candidate] (594.935 µs) : 0, 595
Telemetry [baseline] (8.217 ms) : 0, 8217
Telemetry [candidate] (8.304 ms) : 0, 8304
IAST [baseline] (26.53 ms) : 0, 26530
IAST [candidate] (27.389 ms) : 0, 27389
Loading
Startup time reports for petclinic
gantt
    title petclinic - global startup overhead: candidate=1.54.0-SNAPSHOT~ff9acbe38f, baseline=1.54.0-SNAPSHOT~1052f4935d

    dateFormat X
    axisFormat %s
section tracing
Agent [baseline] (1.062 s) : 0, 1061827
Total [baseline] (10.725 s) : 0, 10725341
Agent [candidate] (1.064 s) : 0, 1063797
Total [candidate] (10.691 s) : 0, 10690810
section appsec
Agent [baseline] (1.243 s) : 0, 1242619
Total [baseline] (11.076 s) : 0, 11075861
Agent [candidate] (1.234 s) : 0, 1234329
Total [candidate] (10.986 s) : 0, 10985998
section iast
Agent [baseline] (1.193 s) : 0, 1192701
Total [baseline] (11.112 s) : 0, 11112354
Agent [candidate] (1.191 s) : 0, 1190785
Total [candidate] (11.176 s) : 0, 11175537
section profiling
Agent [baseline] (1.212 s) : 0, 1211939
Total [baseline] (11.025 s) : 0, 11024999
Agent [candidate] (1.207 s) : 0, 1207286
Total [candidate] (10.849 s) : 0, 10849240
Loading
  • baseline results
Module Variant Duration Δ tracing
Agent tracing 1.062 s -
Agent appsec 1.243 s 180.792 ms (17.0%)
Agent iast 1.193 s 130.873 ms (12.3%)
Agent profiling 1.212 s 150.111 ms (14.1%)
Total tracing 10.725 s -
Total appsec 11.076 s 350.52 ms (3.3%)
Total iast 11.112 s 387.013 ms (3.6%)
Total profiling 11.025 s 299.657 ms (2.8%)
  • candidate results
Module Variant Duration Δ tracing
Agent tracing 1.064 s -
Agent appsec 1.234 s 170.532 ms (16.0%)
Agent iast 1.191 s 126.988 ms (11.9%)
Agent profiling 1.207 s 143.489 ms (13.5%)
Total tracing 10.691 s -
Total appsec 10.986 s 295.188 ms (2.8%)
Total iast 11.176 s 484.727 ms (4.5%)
Total profiling 10.849 s 158.43 ms (1.5%)
gantt
    title petclinic - break down per module: candidate=1.54.0-SNAPSHOT~ff9acbe38f, baseline=1.54.0-SNAPSHOT~1052f4935d

    dateFormat X
    axisFormat %s
section tracing
crashtracking [baseline] (1.448 ms) : 0, 1448
crashtracking [candidate] (1.446 ms) : 0, 1446
BytebuddyAgent [baseline] (733.135 ms) : 0, 733135
BytebuddyAgent [candidate] (733.368 ms) : 0, 733368
GlobalTracer [baseline] (252.321 ms) : 0, 252321
GlobalTracer [candidate] (254.011 ms) : 0, 254011
AppSec [baseline] (30.567 ms) : 0, 30567
AppSec [candidate] (30.743 ms) : 0, 30743
Debugger [baseline] (6.393 ms) : 0, 6393
Debugger [candidate] (6.384 ms) : 0, 6384
Remote Config [baseline] (682.008 µs) : 0, 682
Remote Config [candidate] (685.231 µs) : 0, 685
Telemetry [baseline] (16.231 ms) : 0, 16231
Telemetry [candidate] (16.091 ms) : 0, 16091
section appsec
crashtracking [baseline] (1.462 ms) : 0, 1462
crashtracking [candidate] (1.45 ms) : 0, 1450
BytebuddyAgent [baseline] (762.288 ms) : 0, 762288
BytebuddyAgent [candidate] (754.439 ms) : 0, 754439
GlobalTracer [baseline] (246.994 ms) : 0, 246994
GlobalTracer [candidate] (246.638 ms) : 0, 246638
AppSec [baseline] (171.444 ms) : 0, 171444
AppSec [candidate] (170.716 ms) : 0, 170716
Debugger [baseline] (5.978 ms) : 0, 5978
Debugger [candidate] (6.041 ms) : 0, 6041
Remote Config [baseline] (608.169 µs) : 0, 608
Remote Config [candidate] (642.886 µs) : 0, 643
Telemetry [baseline] (8.55 ms) : 0, 8550
Telemetry [candidate] (9.388 ms) : 0, 9388
IAST [baseline] (23.956 ms) : 0, 23956
IAST [candidate] (23.792 ms) : 0, 23792
section iast
crashtracking [baseline] (1.464 ms) : 0, 1464
crashtracking [candidate] (1.445 ms) : 0, 1445
BytebuddyAgent [baseline] (853.434 ms) : 0, 853434
BytebuddyAgent [candidate] (851.893 ms) : 0, 851893
GlobalTracer [baseline] (244.798 ms) : 0, 244798
GlobalTracer [candidate] (247.512 ms) : 0, 247512
AppSec [baseline] (28.161 ms) : 0, 28161
AppSec [candidate] (26.216 ms) : 0, 26216
Debugger [baseline] (6.104 ms) : 0, 6104
Debugger [candidate] (6.008 ms) : 0, 6008
Remote Config [baseline] (610.456 µs) : 0, 610
Remote Config [candidate] (597.65 µs) : 0, 598
Telemetry [baseline] (8.347 ms) : 0, 8347
Telemetry [candidate] (8.233 ms) : 0, 8233
IAST [baseline] (28.771 ms) : 0, 28771
IAST [candidate] (27.884 ms) : 0, 27884
section profiling
crashtracking [baseline] (1.44 ms) : 0, 1440
crashtracking [candidate] (1.429 ms) : 0, 1429
BytebuddyAgent [baseline] (762.651 ms) : 0, 762651
BytebuddyAgent [candidate] (760.458 ms) : 0, 760458
GlobalTracer [baseline] (233.085 ms) : 0, 233085
GlobalTracer [candidate] (233.124 ms) : 0, 233124
AppSec [baseline] (31.34 ms) : 0, 31340
AppSec [candidate] (31.193 ms) : 0, 31193
Debugger [baseline] (10.588 ms) : 0, 10588
Debugger [candidate] (13.527 ms) : 0, 13527
Remote Config [baseline] (721.959 µs) : 0, 722
Remote Config [candidate] (706.143 µs) : 0, 706
Telemetry [baseline] (11.857 ms) : 0, 11857
Telemetry [candidate] (8.714 ms) : 0, 8714
ProfilingAgent [baseline] (108.969 ms) : 0, 108969
ProfilingAgent [candidate] (106.979 ms) : 0, 106979
Profiling [baseline] (109.591 ms) : 0, 109591
Profiling [candidate] (107.646 ms) : 0, 107646
Loading

Load

Parameters

Baseline Candidate
baseline_or_candidate baseline candidate
git_branch master dougqh/spanbuilder-pooling
git_commit_date 1758219687 1758247031
git_commit_sha 1052f49 ff9acbe
release_version 1.54.0-SNAPSHOT~1052f4935d 1.54.0-SNAPSHOT~ff9acbe38f
See matching parameters
Baseline Candidate
application insecure-bank insecure-bank
ci_job_date 1758248549 1758248549
ci_job_id 1137502794 1137502794
ci_pipeline_id 76941692 76941692
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-1-al8rj8jc 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-1-al8rj8jc 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 2 performance improvements and 2 performance regressions! Performance is the same for 8 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:tracing:high_load worse
[+316.662µs; +557.190µs] or [+4.167%; +7.333%]
unstable
[-108.888op/s; +43.451op/s] or [-17.866%; +7.129%]
8.035ms 576.750op/s 7.598ms 609.469op/s
scenario:load:petclinic:profiling:high_load better
[-3.279ms; -2.286ms] or [-6.657%; -4.640%]
unstable
[-1.270op/s; +12.795op/s] or [-1.336%; +13.463%]
46.476ms 100.800op/s 49.258ms 95.037op/s
scenario:load:petclinic:no_agent:high_load worse
[+1.257ms; +1.895ms] or [+3.507%; +5.287%]
unstable
[-14.357op/s; +3.357op/s] or [-11.003%; +2.573%]
37.413ms 124.987op/s 35.837ms 130.488op/s
scenario:load:petclinic:appsec:high_load better
[-3.000ms; -2.076ms] or [-5.955%; -4.120%]
unstable
[-1.920op/s; +11.720op/s] or [-2.066%; +12.612%]
47.844ms 97.825op/s 50.382ms 92.925op/s
Request duration reports for petclinic
gantt
    title petclinic - request duration [CI 0.99] : candidate=1.54.0-SNAPSHOT~ff9acbe38f, baseline=1.54.0-SNAPSHOT~1052f4935d
    dateFormat X
    axisFormat %s
section baseline
no_agent (35.837 ms) : 35548, 36126
.   : milestone, 35837,
appsec (50.382 ms) : 49936, 50827
.   : milestone, 50382,
code_origins (45.609 ms) : 45232, 45985
.   : milestone, 45609,
iast (45.547 ms) : 45154, 45941
.   : milestone, 45547,
profiling (49.258 ms) : 48788, 49729
.   : milestone, 49258,
tracing (45.527 ms) : 45133, 45920
.   : milestone, 45527,
section candidate
no_agent (37.413 ms) : 37109, 37716
.   : milestone, 37413,
appsec (47.844 ms) : 47431, 48257
.   : milestone, 47844,
code_origins (46.011 ms) : 45614, 46408
.   : milestone, 46011,
iast (45.01 ms) : 44620, 45399
.   : milestone, 45010,
profiling (46.476 ms) : 46023, 46928
.   : milestone, 46476,
tracing (45.746 ms) : 45332, 46160
.   : milestone, 45746,
Loading
  • baseline results
Variant Request duration [CI 0.99] Δ no_agent
no_agent 35.837 ms [35.548 ms, 36.126 ms] -
appsec 50.382 ms [49.936 ms, 50.827 ms] 14.545 ms (40.6%)
code_origins 45.609 ms [45.232 ms, 45.985 ms] 9.772 ms (27.3%)
iast 45.547 ms [45.154 ms, 45.941 ms] 9.71 ms (27.1%)
profiling 49.258 ms [48.788 ms, 49.729 ms] 13.421 ms (37.4%)
tracing 45.527 ms [45.133 ms, 45.92 ms] 9.689 ms (27.0%)
  • candidate results
Variant Request duration [CI 0.99] Δ no_agent
no_agent 37.413 ms [37.109 ms, 37.716 ms] -
appsec 47.844 ms [47.431 ms, 48.257 ms] 10.431 ms (27.9%)
code_origins 46.011 ms [45.614 ms, 46.408 ms] 8.598 ms (23.0%)
iast 45.01 ms [44.62 ms, 45.399 ms] 7.597 ms (20.3%)
profiling 46.476 ms [46.023 ms, 46.928 ms] 9.063 ms (24.2%)
tracing 45.746 ms [45.332 ms, 46.16 ms] 8.333 ms (22.3%)
Request duration reports for insecure-bank
gantt
    title insecure-bank - request duration [CI 0.99] : candidate=1.54.0-SNAPSHOT~ff9acbe38f, baseline=1.54.0-SNAPSHOT~1052f4935d
    dateFormat X
    axisFormat %s
section baseline
no_agent (4.397 ms) : 4341, 4453
.   : milestone, 4397,
iast (9.349 ms) : 9193, 9505
.   : milestone, 9349,
iast_FULL (13.94 ms) : 13667, 14213
.   : milestone, 13940,
iast_GLOBAL (10.511 ms) : 10326, 10696
.   : milestone, 10511,
profiling (8.98 ms) : 8841, 9120
.   : milestone, 8980,
tracing (7.598 ms) : 7492, 7705
.   : milestone, 7598,
section candidate
no_agent (4.332 ms) : 4283, 4382
.   : milestone, 4332,
iast (9.443 ms) : 9283, 9602
.   : milestone, 9443,
iast_FULL (13.869 ms) : 13598, 14140
.   : milestone, 13869,
iast_GLOBAL (10.612 ms) : 10411, 10814
.   : milestone, 10612,
profiling (9.15 ms) : 8995, 9305
.   : milestone, 9150,
tracing (8.035 ms) : 7919, 8152
.   : milestone, 8035,
Loading
  • baseline results
Variant Request duration [CI 0.99] Δ no_agent
no_agent 4.397 ms [4.341 ms, 4.453 ms] -
iast 9.349 ms [9.193 ms, 9.505 ms] 4.952 ms (112.6%)
iast_FULL 13.94 ms [13.667 ms, 14.213 ms] 9.543 ms (217.0%)
iast_GLOBAL 10.511 ms [10.326 ms, 10.696 ms] 6.114 ms (139.0%)
profiling 8.98 ms [8.841 ms, 9.12 ms] 4.583 ms (104.2%)
tracing 7.598 ms [7.492 ms, 7.705 ms] 3.201 ms (72.8%)
  • candidate results
Variant Request duration [CI 0.99] Δ no_agent
no_agent 4.332 ms [4.283 ms, 4.382 ms] -
iast 9.443 ms [9.283 ms, 9.602 ms] 5.111 ms (118.0%)
iast_FULL 13.869 ms [13.598 ms, 14.14 ms] 9.537 ms (220.1%)
iast_GLOBAL 10.612 ms [10.411 ms, 10.814 ms] 6.28 ms (145.0%)
profiling 9.15 ms [8.995 ms, 9.305 ms] 4.818 ms (111.2%)
tracing 8.035 ms [7.919 ms, 8.152 ms] 3.703 ms (85.5%)

Dacapo

Parameters

Baseline Candidate
baseline_or_candidate baseline candidate
git_branch master dougqh/spanbuilder-pooling
git_commit_date 1758219687 1758247031
git_commit_sha 1052f49 ff9acbe
release_version 1.54.0-SNAPSHOT~1052f4935d 1.54.0-SNAPSHOT~ff9acbe38f
See matching parameters
Baseline Candidate
application biojava biojava
ci_job_date 1758249126 1758249126
ci_job_id 1137502795 1137502795
ci_pipeline_id 76941692 76941692
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-hcyurlai 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-hcyurlai 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 1 performance improvements and 0 performance regressions! Performance is the same for 10 metrics, 1 unstable metrics.

scenario Δ mean execution_time candidate mean execution_time baseline mean execution_time
scenario:dacapo:tomcat:appsec better
[-1.418ms; -1.080ms] or [-38.590%; -29.389%]
2.426ms 3.675ms
Execution time for biojava
gantt
    title biojava - execution time [CI 0.99] : candidate=1.54.0-SNAPSHOT~ff9acbe38f, baseline=1.54.0-SNAPSHOT~1052f4935d
    dateFormat X
    axisFormat %s
section baseline
no_agent (14.889 s) : 14889000, 14889000
.   : milestone, 14889000,
appsec (14.856 s) : 14856000, 14856000
.   : milestone, 14856000,
iast (18.772 s) : 18772000, 18772000
.   : milestone, 18772000,
iast_GLOBAL (17.997 s) : 17997000, 17997000
.   : milestone, 17997000,
profiling (15.39 s) : 15390000, 15390000
.   : milestone, 15390000,
tracing (14.83 s) : 14830000, 14830000
.   : milestone, 14830000,
section candidate
no_agent (14.969 s) : 14969000, 14969000
.   : milestone, 14969000,
appsec (15.028 s) : 15028000, 15028000
.   : milestone, 15028000,
iast (18.298 s) : 18298000, 18298000
.   : milestone, 18298000,
iast_GLOBAL (17.885 s) : 17885000, 17885000
.   : milestone, 17885000,
profiling (15.823 s) : 15823000, 15823000
.   : milestone, 15823000,
tracing (15.129 s) : 15129000, 15129000
.   : milestone, 15129000,
Loading
  • baseline results
Variant Execution Time [CI 0.99] Δ no_agent
no_agent 14.889 s [14.889 s, 14.889 s] -
appsec 14.856 s [14.856 s, 14.856 s] -33.0 ms (-0.2%)
iast 18.772 s [18.772 s, 18.772 s] 3.883 s (26.1%)
iast_GLOBAL 17.997 s [17.997 s, 17.997 s] 3.108 s (20.9%)
profiling 15.39 s [15.39 s, 15.39 s] 501.0 ms (3.4%)
tracing 14.83 s [14.83 s, 14.83 s] -59.0 ms (-0.4%)
  • candidate results
Variant Execution Time [CI 0.99] Δ no_agent
no_agent 14.969 s [14.969 s, 14.969 s] -
appsec 15.028 s [15.028 s, 15.028 s] 59.0 ms (0.4%)
iast 18.298 s [18.298 s, 18.298 s] 3.329 s (22.2%)
iast_GLOBAL 17.885 s [17.885 s, 17.885 s] 2.916 s (19.5%)
profiling 15.823 s [15.823 s, 15.823 s] 854.0 ms (5.7%)
tracing 15.129 s [15.129 s, 15.129 s] 160.0 ms (1.1%)
Execution time for tomcat
gantt
    title tomcat - execution time [CI 0.99] : candidate=1.54.0-SNAPSHOT~ff9acbe38f, baseline=1.54.0-SNAPSHOT~1052f4935d
    dateFormat X
    axisFormat %s
section baseline
no_agent (1.472 ms) : 1460, 1483
.   : milestone, 1472,
appsec (3.675 ms) : 3458, 3891
.   : milestone, 3675,
iast (2.194 ms) : 2132, 2256
.   : milestone, 2194,
iast_GLOBAL (2.236 ms) : 2173, 2298
.   : milestone, 2236,
profiling (2.486 ms) : 2318, 2654
.   : milestone, 2486,
tracing (2.015 ms) : 1967, 2064
.   : milestone, 2015,
section candidate
no_agent (1.473 ms) : 1462, 1485
.   : milestone, 1473,
appsec (2.426 ms) : 2376, 2475
.   : milestone, 2426,
iast (2.193 ms) : 2131, 2255
.   : milestone, 2193,
iast_GLOBAL (2.24 ms) : 2177, 2303
.   : milestone, 2240,
profiling (2.06 ms) : 2009, 2112
.   : milestone, 2060,
tracing (2.019 ms) : 1970, 2068
.   : milestone, 2019,
Loading
  • baseline results
Variant Execution Time [CI 0.99] Δ no_agent
no_agent 1.472 ms [1.46 ms, 1.483 ms] -
appsec 3.675 ms [3.458 ms, 3.891 ms] 2.203 ms (149.7%)
iast 2.194 ms [2.132 ms, 2.256 ms] 722.362 µs (49.1%)
iast_GLOBAL 2.236 ms [2.173 ms, 2.298 ms] 764.033 µs (51.9%)
profiling 2.486 ms [2.318 ms, 2.654 ms] 1.014 ms (68.9%)
tracing 2.015 ms [1.967 ms, 2.064 ms] 543.721 µs (36.9%)
  • candidate results
Variant Execution Time [CI 0.99] Δ no_agent
no_agent 1.473 ms [1.462 ms, 1.485 ms] -
appsec 2.426 ms [2.376 ms, 2.475 ms] 952.247 µs (64.6%)
iast 2.193 ms [2.131 ms, 2.255 ms] 719.402 µs (48.8%)
iast_GLOBAL 2.24 ms [2.177 ms, 2.303 ms] 766.388 µs (52.0%)
profiling 2.06 ms [2.009 ms, 2.112 ms] 587.043 µs (39.8%)
tracing 2.019 ms [1.97 ms, 2.068 ms] 545.509 µs (37.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

@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 no 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)
@PerfectSlayer
Copy link
Contributor

PerfectSlayer commented Sep 18, 2025

I would make the singleSpanBuilder() always reuse the builder and trying to reset it -if it's still unused, it can create a > new one. And if build() is called multiple time on a singleSpanBuilder() I would raise an error (or at least something > for the developer to be aware of).

The current code does try to always reuse the SpanBuilder, it just safe guards against the case where the prior caller hasn't finished with it.
I debated raising an exception, but I was worried about exception leakage.
I'm mostly okay with an exception in an internal method, but I might just go with an assert.

However, the assert won't be completely reliable. If the SpanBuilder gets reset before the next call to buildSpan, then I cannot detect that. Or at least, I don't think I can detect it without adding a wrapper object (which is counter to the aim).

An alternative option could be to only have the buildSpan() method but to overload it with a third parameter "reusable".
It could be false by default, enabling the optimisation by default, but set to true when creating builder using the > public APIs (dd-trace-api and OTel Tracing API) to ensure compatibility.

Yes, I considered that. I kind of like the explicitness of singleSpanBuilder(), since it tells you the imposed limitation. I'm curious what others think.

Comment on lines +247 to +248
private static final boolean SPAN_BUILDER_REUSE_ENABLED =
Config.get().isSpanBuilderReuseEnabled();
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not super familiar with dd-java-agent configuration, just curious, is there a possibility for remote on-the-fly configuration? Or this flag can be applied only with restart? Just thinking out loud about usability.

Copy link
Contributor Author

@dougqh dougqh Sep 18, 2025

Choose a reason for hiding this comment

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

Not currently - although, that is something that we've discussed doing off and on
I'm honestly not expecting any customer to ever set this, so I'm going with the approach that works best for the JIT


// TODO: counter for how often the fallback is used?
ReusableSingleSpanBuilder newSpanBuilder = new ReusableSingleSpanBuilder(tracer);
newSpanBuilder.reset(instrumentationName, operationName);
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, why we need reset fresh builder? Can it be created in proper state?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I debated that myself.
It is technically in a nearly prepared state after construction.
I could have a "fast" reset or another constructor instead of explicitly calling reset.

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?

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

Added some asserts to catch misuse of the API
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.

3 participants