Skip to content

Pre-construct TagMap.Entry objects in InternalTagsAdder - #11555

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 8 commits into
masterfrom
dougqh/preconstruct-internal-tags
Jun 23, 2026
Merged

Pre-construct TagMap.Entry objects in InternalTagsAdder#11555
gh-worker-dd-mergequeue-cf854d[bot] merged 8 commits into
masterfrom
dougqh/preconstruct-internal-tags

Conversation

@dougqh

@dougqhdougqh commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

What Does This Do

InternalTagsAdder sets _dd.base_service / version on spans via TagMap.set(tag, value), which allocates a fresh TagMap.Entryper span. Both values are fixed for the life of the tracer, and TagMap.Entry objects are explicitly safe to share across maps (the OptimizedTagMap collision design via BucketGroup relies on it). This pre-builds the two Entry objects once in the constructor and reuses them via set(entry).

Motivation

A JFR allocation profile of spring-petclinic under load (full agent, 2026-06-03) attributed ~52 allocation samples to InternalTagsAdder.processTags — one Entry allocated per span for base.service. Tag-map handling (TagMap$Entry + OptimizedTagMap) was the single largest non-core tracer allocation theme in that profile, ahead of the CSS metrics system. This change drops the InternalTagsAdder contribution to zero.

Notes

  • Re-applies the logic from Reuse TagMap.Entry objects in InternalTagsAdder #10965 (dd/pre-construct-tagmap-entries), which had drifted 415 commits behind master and changed processTags's signature; this version sits on current master with the unchanged AppendableSpanLinks signature. Supersedes Reuse TagMap.Entry objects in InternalTagsAdder #10965.
  • Behavior preserved: Entry.stringValue() for the OBJECT entry resolves to obj.toString() (cached), identical to the prior ddService.toString() in the equalsIgnoreCase comparison. The only edge case — empty ddService now yields an early return — is unreachable in production (Config.getServiceName() is never "").
  • Existing test InternalTagsAdderTest.groovy passes unchanged.

🤖 Generated with Claude Code

InternalTagsAdder set base.service / version via TagMap.set(tag, value),
allocating a fresh TagMap.Entry per span. Both values are fixed for the
life of the tracer, and TagMap.Entry objects are safe to share across maps
(the OptimizedTagMap collision design relies on it), so build the two
Entry objects once in the constructor and reuse them via set(entry).
A JFR profile of petclinic (2026-06-03) attributed ~52 allocation samples
to InternalTagsAdder.processTags (one Entry per span); this drops them to
zero. Re-applies the change from the stale PR #10965 (415 commits behind
master, drifted signature) onto current master.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dougqh
dougqh marked this pull request as ready for review June 4, 2026 00:14
@dougqh
dougqh requested a review from a team as a code ownerJune 4, 2026 00:14
@dougqh
dougqh requested a review from ygreeJune 4, 2026 00:14
@dd-octo-stsdd-octo-stsBot added the tag: ai generated Largely based on code generated by an AI or LLM label Jun 4, 2026
@dd-octo-sts

dd-octo-stsBot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

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.

@datadog-datadog-prod-us1-2

This comment has been minimized.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:934fd5dc3b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@dougqhdougqh added comp: core Tracer core type: feature Enhancements and improvements tag: performance Performance related changes labels Jun 4, 2026
Addresses the Codex review comment on #11555: pre-building the
base.service Entry must not change behavior for an explicitly-empty
DD_SERVICE. Entry.create rejects empty values, so baseServiceEntry is
null in that case; the processTags branch now falls back to
set(BASE_SERVICE, ddService) to preserve byte-identical behavior, and
the version branch is still reached when the span service also matches
the empty configured service.
Migrate InternalTagsAdderTest from Groovy/Spock to JUnit 5 (parameterized
with @MethodSource) and add regression coverage for the empty-DD_SERVICE
case (9 migrated cases + 2 new = 11).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dd-octo-sts

dd-octo-stsBot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

SuiteStatus
Startup🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
ScenarioCandidatemasterΔ (95% CI of mean)
startup:insecure-bank:iast:Agent13.97 s13.93 s[-0.5%; +1.1%] (no difference)
startup:insecure-bank:tracing:Agent12.86 s12.99 s[-1.6%; -0.3%] (maybe better)
startup:petclinic:appsec:Agent16.94 s16.74 s[+0.3%; +2.0%] (maybe worse)
startup:petclinic:iast:Agent17.00 s17.02 s[-0.9%; +0.6%] (no difference)
startup:petclinic:profiling:Agent16.68 s17.03 s[-3.3%; -0.8%] (maybe better)
startup:petclinic:sca:Agent16.80 s16.70 s[-0.3%; +1.5%] (no difference)
startup:petclinic:tracing:Agent15.68 s15.98 s[-6.0%; +2.3%] (no difference)

Commit:6b1a26f9 · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

@amarzialiamarziali left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

it looks a good idea. I left improvements for the comments specifically that looks a bit too verbosely generated

dougqhand others added 4 commits June 9, 2026 13:02
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…gression test
Preserves the @TableTest versions of the two existing tests that landed on
master, and adds the empty-DD_SERVICE regression test (from the PR) as a
plain @test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extend the processTags null guard to also exit when ddService.length()==0,
which prevents writing _dd.base_service="" via the TagMap.set path that has
no empty-value guard (unlike Entry.create). Empty ddService now behaves the
same as null/unset.
- Remove the manual null+length>0 pre-check before TagMap.Entry.create in the
constructor; Entry.create already returns null for null or empty values, so
the guard was redundant.
- Update the regression test to assert the new early-return behavior.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dougqh added a commit that referenced this pull request Jun 10, 2026
Handle the per-span "common" tags (base.service / version) via the tag-id fast
path. These values are fixed for the tracer's life, so build their TagMap.Entry
once and share across every span (Entry is immutable + safe to share) — dropping
InternalTagsAdder's per-span Entry allocation to zero (cf. PR #11555, the
string-keyed precursor), and making the entries tag-id-bearing so they also land
in their positional slot.
- TagMap.Entry.create(long, Object)/create(long, CharSequence): tag-id keyed,
null/empty-rejecting factories mirroring the String create().
- CoreTagIds.BASE_SERVICE / VERSION (stored range) + resolver entries.
- InternalTagsAdder prebuilds baseServiceEntry/versionEntry in its ctor and
set()s the shared entry; empty DD_SERVICE early-returns (regression test added).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
public void processTags(
TagMap unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) {
if (spanContext == null || ddService == null) {
if (spanContext == null || ddService == null || ddService.length() == 0) {

@amarzialiamarzialiJun 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

when that processor is created (in createEagerChain) the ddService value is taken from Config.getServiceName() and this cannot be neither null neither empty. So it looks that instead adding a check for the size we can also remove the null check. I would suggest to improve the documentation (i.e. also add @NonNull to the constructor and simplify that instructions

@dougqhdougqhJun 10, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This was primarily out of an abundance of caution. Although, both AI and human reviewers had raised this as potential concern, so it was easiest to just be cautious.

If you are confident that empty won't reach here, then we can change it.

@dougqhdougqhJun 10, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

From Claude...
Good call — confirmed that createEagerChain() always passes Config.getServiceName() which defaults to "unnamed-java-app" and is never null or empty. Changed the constructor param to @Nonnull, dropped the null check on ddService, and simplified the guard in processTags to just spanContext == null.

// Regression: empty DD_SERVICE is treated the same as unset — processTags exits early and
// writes no tags, regardless of the span's service name.
@Test
void emptyDdServiceWritesNoTags() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This test should not be added. I don't understand why the regression word is used here. And the service name cannot be empty in our tracer. I would suggest to remove it and improve the documentation of this processor as suggested above

@dougqhdougqhJun 10, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It is a change in behavior of the InternalTagsAdder, so this test was guarding against that change/regression being introduced again. Mostly, it just comes down to the contract being unclear before, so the AI had to be cautious.

@dougqhdougqhJun 10, 2026

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

From Claude...
Agreed — removed the test and the @Test import. The contract is now documented by the @Nonnull annotation on the constructor instead.

…ession test
Config.getServiceName() always returns a non-null non-empty string
(defaults to "unnamed-java-app"), so the null/@empty guard in processTags
and the corresponding regression test for empty DD_SERVICE are unnecessary.
Replaced @nullable with @nonnull on the constructor param to document
the actual contract.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dougqh
dougqh added this pull request to the merge queueJun 22, 2026
@dd-octo-sts

Copy link
Copy Markdown
Contributor

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351Bot commented Jun 22, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-06-22 22:55:46 UTC ℹ️ Start processing command /merge


2026-06-22 22:55:51 UTC ℹ️ MergeQueue: pull request added to the queue

The expected merge time in master is approximately 1h (p90).


2026-06-23 00:35:32 UTC ℹ️ MergeQueue: This merge request was merged

@github-merge-queue
github-merge-queueBot removed this pull request from the merge queue due to failed status checks Jun 22, 2026
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854dBot merged commit e3a0877 into masterJun 23, 2026
583 checks passed
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854dBot deleted the dougqh/preconstruct-internal-tags branch June 23, 2026 00:35
@github-actionsgithub-actionsBot added this to the 1.64.0 milestone Jun 23, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: coreTracer coretag: ai generatedLargely based on code generated by an AI or LLMtag: performancePerformance related changestype: featureEnhancements and improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@dougqh@bric3@amarziali@sarahchen6