Skip to content

Id-keyed TagMap.set(long) + insertion-comparison benchmark (phase 2) - #11901

Draft
dougqh wants to merge 1 commit into
dougqh/sizing-hintfrom
dougqh/tag-id-api
Draft

Id-keyed TagMap.set(long) + insertion-comparison benchmark (phase 2)#11901
dougqh wants to merge 1 commit into
dougqh/sizing-hintfrom
dougqh/tag-id-api

Conversation

@dougqh

Copy link
Copy Markdown
Contributor

What

On top of the bloom fast-path (#11900):

  1. TagMap.set(long id, Object) — the id-keyed insertion path: given a resolved KnownTags.*_ID, store densely and skip keyOf name resolution. Sole TagMap impl (OptimizedTagMap); the id must be a stored known id (custom names keep the name setters).
  2. TagMapInsertionComparisonBenchmark — HashMap / 1.0-bucket / 2.0-name / 2.0-id, alloc + throughput; the numbers behind the deck's slides 3/7/8.

Results (idle box, -prof gc -f 5 -wi 5 -i 5; persisted in the benchmark header)

armalloc B/op (7 / 12)thrpt vs HashMap (7 / 12)
hashMap352 / 5121.00× / 1.00×
tagMapById184 / 4080.99× / 0.63×
tagMapByName184 / 4080.66× / 0.50×
tagMapCustom (1.0 bucket)416 / 7120.59× / 0.46×
  • Alloc: dense ~half of HashMap; id == name (the bloom/dense win is CPU, not alloc).
  • Throughput: id-insertion reaches HashMap parity at typical tag counts (0.99× @7) and beats the 1.0 bucket path 1.4–1.7×. The 12-tag gap is a sizing/resize artifact (SpanPrototype's right-sizing / a larger init cap closes it), not a scan — the tags don't collide under the bloom's fieldPos & 63.

Scope

Minimal id API (TagMap level). Full AgentSpan.setTag(long) + the KnownTags call-site migration are follow-ons. set(long) has no production caller yet — by design, it lands with its benchmark evidence ahead of the migration that consumes it (each PR stands alone in sequence).

Stacking

#11814 (dense storage) → #11900 (bloom) → this (id API + comparison benchmark).

Follow-ups

TagMap.Ledger + SpanBuilder id support; per-type graph coloring; master run to pin true-1.0 (no keyOf).

🤖 Generated with Claude Code

@dougqhdougqh added comp: core Tracer core tag: ai generated Largely based on code generated by an AI or LLM tag: no release notes Changes to exclude from release notes type: refactoring labels Jul 9, 2026
@dd-octo-sts

dd-octo-stsBot commented Jul 9, 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:Agent14.72 s14.63 s[-0.3%; +1.4%] (no difference)
startup:insecure-bank:tracing:Agent13.58 s13.69 s[-1.6%; +0.0%] (no difference)
startup:petclinic:appsec:Agent17.49 s17.40 s[-0.3%; +1.4%] (no difference)
startup:petclinic:iast:Agent17.56 s17.52 s[-0.6%; +1.0%] (no difference)
startup:petclinic:profiling:Agent17.47 s17.26 s[-0.1%; +2.5%] (no difference)
startup:petclinic:sca:Agent17.59 s17.55 s[-0.8%; +1.2%] (no difference)
startup:petclinic:tracing:Agent16.61 s16.77 s[-1.8%; -0.1%] (maybe better)

Commit:e102e67f · 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.

@dougqh
dougqhforce-pushed the dougqh/dense-bloom-filter branch 2 times, most recently from 267ebad to 56550cbCompareJuly 15, 2026 21:23
@dougqh
dougqh changed the base branch from dougqh/dense-bloom-filter to dougqh/tag-registry-generatorJuly 15, 2026 21:27
@dougqh
dougqhforce-pushed the dougqh/tag-id-api branch from f0cb351 to 7e3ed31CompareJuly 15, 2026 21:27
@datadog-prod-us1-6

datadog-prod-us1-6Bot commented Jul 15, 2026

Copy link
Copy Markdown

🎯 Code Coverage (details)
Patch Coverage: 5.94%
Overall Coverage: 57.36% (-0.07%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: e102e67 | Docs | View more details | Give us feedback!

@dougqh
dougqhforce-pushed the dougqh/tag-registry-generator branch from ad3c1f7 to 369a591CompareJuly 22, 2026 16:32
@dougqhdougqh changed the title Id-keyed TagMap.set(long) + insertion-comparison benchmarkId-keyed TagMap.set(long) + insertion-comparison benchmark (phase 2)Jul 23, 2026
@datadog-prod-us1-6

datadog-prod-us1-6Bot commented Jul 23, 2026

Copy link
Copy Markdown

Bits has a CI fix ready

🟢 Investigated · 🟢 Fix prepared · ⚪ Validation skipped · 🟠 Ready

Added focused coverage for KnownTagCodec fallback and tag-ID paths, plus SizingHelper identity/content/hash-mismatch matching branches. The codec fallback test runs in an isolated JVM so it does not affect resolver-based tests.

Commit fix to this PR


View in Datadog | Reviewed commit cca1b81 · Any feedback? Reach out in #deveng-pr-agent

@dougqh
dougqh changed the base branch from dougqh/tag-registry-generator to dougqh/generator-v2July 23, 2026 03:08
@dougqh
dougqhforce-pushed the dougqh/tag-id-api branch from 7e3ed31 to 3295baaCompareJuly 23, 2026 03:08
@dougqh
dougqhforce-pushed the dougqh/tag-id-api branch from 3295baa to cca1b81CompareJuly 23, 2026 13:35
@dougqh
dougqh changed the base branch from dougqh/generator-v2 to dougqh/sizing-hintJuly 23, 2026 13:35
dougqh added a commit that referenced this pull request Aug 18, 2026
Reconciles #11901's SizingHint half onto the colored generator-v2 base as
its own layer. A per-operation SizingHint (kept in a bounded, self-tuning
SizingHintTable keyed by operation name, two lanes for entry vs child spans)
sizes a span's dense TagMap at create and records the observed known-tag
high-water mark back on finish, so the reused hint converges to the
operation's real size. Erases the fixed KNOWN_INIT_CAP dense-array floor tax
that regressed bare/small spans in the #12047-vs-1.65 A/B; the id-keyed write
API stacks on top of this as a separate layer.
New: SizingHint, SizingHelper, SizingHintTable, FlatHashtable (+ tests, jmh).
TagMap: create(SizingHint), recordSize, denseCapHint. DDSpanContext threads a
sizingHint through the primary ctor + recordDenseSize on finish; DDSpan hooks
it at finishAndAddToTrace; CoreTracer resolves the per-operation hint/lane.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dougqh
dougqhforce-pushed the dougqh/sizing-hint branch from 956f2c9 to 8fb6bc3CompareAugust 18, 2026 12:36
@dougqh

Copy link
Copy Markdown
ContributorAuthor

@codex review

@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:8886a69b70

ℹ️ 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".

Comment on lines +525 to +528
if (KnownTagCodec.isIntercepted(id)) {
return setTag(KnownTagCodec.nameOf(id), value);
}
context.setTag(id, value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep ID setters functional when dense tags are disabled

With the normal configuration where dd.trace.dense.tags.enabled is unset, CoreTracer never calls KnownTags.init(), so KnownTagCodec.nameOf(id) returns null. Once an instrumentation uses these new overloads, intercepted IDs such as SPAN_KIND_ID therefore delegate to a null-key String setter and silently lose the tag; non-intercepted IDs are stored but later materialize with null names. Initialize the resolver independently of the experimental dense-store flag, or otherwise make the ID path handle an inactive codec.

Useful? React with 👍 / 👎.

Comment on lines +573 to +576
if (value == null) {
context.setTag(id, (Object) null);
} else {
context.setTag(id, value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Remove empty String values on the ID path

For a non-intercepted known ID, calling setTag(id, "") stores the empty value, whereas the existing setTag(String, String) contract removes null or empty strings and the adjacent CharSequence ID overload does the same. This makes behavior depend on whether the compile-time value type is String or CharSequence; include value.isEmpty() in this removal branch.

Useful? React with 👍 / 👎.

Comment on lines +534 to +537
if (KnownTagCodec.isIntercepted(id)) {
return setTag(KnownTagCodec.nameOf(id), value);
}
context.setTag(id, value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve HTTP status handling for its known ID

When a caller uses the permitted stored ID KnownTags.HTTP_STATUS_CODE_ID, that ID lacks the intercepted flag, so the int overload takes this direct context path. Unlike setTag(Tags.HTTP_STATUS, int), it never updates DDSpanContext.httpStatusCode or runs interceptHttpStatusCode; consequently getHttpStatusCode(), 404 resource naming, and status serialization from the dedicated field remain incorrect. Route this ID through the existing String/status path or mark it intercepted.

Useful? React with 👍 / 👎.

Comment on lines +525 to +528
if (KnownTagCodec.isIntercepted(id)) {
return setTag(KnownTagCodec.nameOf(id), value);
}
context.setTag(id, value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor split-by-tags for ID-keyed tags

With dd.trace.split-by-tags configured to a known but normally non-intercepted key such as component, TagInterceptor.needsIntercept(tag) dynamically returns true and the name-keyed setter derives the service name. The ID path consults only the static intercepted bit, bypasses the interceptor, and stores the tag without applying the configured service split. Ensure the fast path also accounts for dynamically configured interceptor keys before migrating such call sites.

Useful? React with 👍 / 👎.

@dougqh
dougqhforce-pushed the dougqh/tag-id-api branch 2 times, most recently from 991fde5 to 42b7e00CompareAugust 18, 2026 13:24
@dougqh

Copy link
Copy Markdown
ContributorAuthor

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit:42b7e0075d

ℹ️ 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".

dougqh added a commit that referenced this pull request Aug 18, 2026
Reconciles #11901's SizingHint half onto the colored generator-v2 base as
its own layer. A per-operation SizingHint (kept in a bounded, self-tuning
SizingHintTable keyed by operation name, two lanes for entry vs child spans)
sizes a span's dense TagMap at create and records the observed known-tag
high-water mark back on finish, so the reused hint converges to the
operation's real size. Erases the fixed KNOWN_INIT_CAP dense-array floor tax
that regressed bare/small spans in the #12047-vs-1.65 A/B; the id-keyed write
API stacks on top of this as a separate layer.
New: SizingHint, SizingHelper, SizingHintTable, FlatHashtable (+ tests, jmh).
TagMap: create(SizingHint), recordSize, denseCapHint. DDSpanContext threads a
sizingHint through the primary ctor + recordDenseSize on finish; DDSpan hooks
it at finishAndAddToTrace; CoreTracer resolves the per-operation hint/lane.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dougqh
dougqhforce-pushed the dougqh/sizing-hint branch from 390a343 to 9f8ff10CompareAugust 18, 2026 14:12
@dougqh

Copy link
Copy Markdown
ContributorAuthor

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit:8a99344dfd

ℹ️ 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".

Threads pre-resolved KnownTags.* ids through the span API so callers can
skip keyOf name resolution and, for non-intercepted tags, the tag
interceptor:
- TagMap.set(long, ...) family -> putKnownById -> putKnownValue.
- AgentSpan gets default id-keyed setTag methods that resolve the id back
to its name and delegate to the String setter (correctness-preserving
fallback; only DDSpan overrides for the fast path, so the other 20
implementers are untouched).
- DDSpan.setTag(long, ...) routes intercepted ids (span.kind, http.method,
http.url, db.statement) back through the String path and sends the rest
straight to DDSpanContext.setTag(long, ...) -> unsafeTags.set(long, ...).
- TagMapSetByIdForkedTest proves the id path is observationally identical
to the name path; SpanCreationByIdBenchmark measures the throughput win.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dougqh added a commit that referenced this pull request Aug 18, 2026
Reconciles #11901's SizingHint half onto the colored generator-v2 base as
its own layer. A per-operation SizingHint (kept in a bounded, self-tuning
SizingHintTable keyed by operation name, two lanes for entry vs child spans)
sizes a span's dense TagMap at create and records the observed known-tag
high-water mark back on finish, so the reused hint converges to the
operation's real size. Erases the fixed KNOWN_INIT_CAP dense-array floor tax
that regressed bare/small spans in the #12047-vs-1.65 A/B; the id-keyed write
API stacks on top of this as a separate layer.
New: SizingHint, SizingHelper, SizingHintTable, FlatHashtable (+ tests, jmh).
TagMap: create(SizingHint), recordSize, denseCapHint. DDSpanContext threads a
sizingHint through the primary ctor + recordDenseSize on finish; DDSpan hooks
it at finishAndAddToTrace; CoreTracer resolves the per-operation hint/lane.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dougqh
dougqhforce-pushed the dougqh/sizing-hint branch from 9f8ff10 to b424214CompareAugust 18, 2026 16:12
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: no release notesChanges to exclude from release notestype: refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@dougqh