Repository navigation
Conversation
Move BaseDecorator.onPeerConnection's InetAddress / InetSocketAddress handling into PeerAddress and PeerSocketAddress extractors, applied with setTagsFrom so the span's tag lock is taken once per call. This is the first step toward freezing the extracted tags once per long-lived address instead of rebuilding them, and the new IP string, on every span. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FrozenTags.of(source, extractor) records the tags an extractor or contributor writes, once, into a frozen TagMap. span.setTagsFrom(frozen) then applies them as if the writes were made again, without reading the source. On DDSpan this reuses the prototype seed plan: with the dense store, known tags that need no interception and aren't already on the span are copied in at once; otherwise they go one at a time through the interceptor, as setTag does. The recorder rejects reads of the span and span-level setters, since neither can be replayed against a different span. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client handler rebuilt peer.hostname / peer.ipv4 / peer.port from the channel's remote address on every request, allocating a new IP string each time. The address is fixed for the connection's life, so freeze the tags on first use, keep them on the connection channel (HTTP/2 streams share their parent's), and apply them with setTagsFrom(FrozenTags). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Frozen tags often overlap tags the span already has: an HTTP client sets peer.hostname and peer.port from the URL, then the connection's peer tags overwrite them. Falling back to tag-by-tag for the whole set made that common shape lose the bulk copy. Add a partial IdStore.seedAbsent that appends the source tags whose slot bits are clear and hands the ones that may be present to the caller, which sets them through the interceptor as setTag does. The result is the same as the recorded writes; only the overlapping tags pay for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each consumed record's span got messaging.destination.name, kafka.group, messaging.kafka.cluster.id and messaging.kafka.bootstrap.servers set one by one, though all four are the same for every record of a topic in a poll. The poll's iterator now freezes them per topic (records come out partition by partition, so one cached topic suffices) and applies them with setTagsFrom(FrozenTags), at the same point in onConsume as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review fixes, keeping FrozenTags exactly equivalent to the live writes: - FrozenTags records each write (same setter, same value) and replays them in order on every tag-by-tag path. Split-by-tags is last-write- wins, so replaying in store order could pick a different service. A recorded null or empty value now replays as written instead of being dropped, and keeps the set off the bulk path, as does a custom tag that split-by-tags routes. - BaseDecorator.onPeerConnection calls the peer extractors directly: through setTagsFrom, the hostname's reverse DNS lookup ran under the span's tag lock. - netty reads the address from the request's channel as before, shares the connection's frozen tags only when the parent reports the same address object (HTTP/2 streams, not a server channel), and refreezes when the address changes. - Rename the partial seed to IdStore.seedPartial. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Contributor
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
Kafka / producer-benchmarkParameters
See matching parameters
SummaryFound 0 performance improvements and 1 performance regressions! Performance is the same for 2 metrics, 0 unstable metrics.
See unchanged results
|
Kafka / consumer-benchmarkParameters
See matching parameters
SummaryFound 0 performance improvements and 0 performance regressions! Performance is the same for 3 metrics, 0 unstable metrics. See unchanged results
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What Does This Do
Adds
FrozenTags, a cached form ofsetTagsFrom: run aTagExtractor/TagContributoronce against a source that outlives its spans, freeze the tags it writes, and apply them to each span withspan.setTagsFrom(frozen)instead of re-reading the source. Two first users:peer.hostname,peer.ipv4/ipv6,peer.port) are frozen once per connection channel (HTTP/2 streams share their parent's). Previously every request rebuilt the IP string (InetAddress.getHostAddress()) and re-resolved the hostname.Commits, in order:
BaseDecorator.onPeerConnection'sInetAddresshandling intoPeerAddress/PeerSocketAddressextractors (no caching yet).FrozenTags+AgentSpan.setTagsFrom(FrozenTags). OnDDSpanit reuses the prototype seed plan: with the dense store, known tags that need no interception are copied in at once.peer.hostname/peer.portfrom the URL inonURI, then the connection's tags overwrite them). Non-overlapping tags are copied in bulk; the overlapping ones go through the interceptor, assetTagdoes.Motivation
APMLP-2176: many tag values are constant per framework object but get rebuilt — often as new objects — on every span. Instrumentation stays simple (a plain extractor over the framework object); the core records, freezes and applies.
Behavior
No intended behavior change, including ordering and interception:
FrozenTagsrecords each write (same setter, same value, in order). Applying it replays them exactly; the bulk copy of their net result is used only where it's provably the same: no write needs the interceptor (incl. split-by-tags on custom tags), and none removes a tag or sets an empty value. Tags already on the span are overwritten through the interceptor.BaseDecorator.onPeerConnectioncalls the extractors directly rather than throughsetTagsFrom, so a reverse DNS lookup never runs under the span's tag lock.getTagand span-level setters (they can't be replayed against another span).Additional Notes
dd.trace.experimental.dense.tags.enabled=true; on the default hash store the gain is not re-reading the source (no per-span IP string / hostname lookup).Stringinstances (read-side cost is APMLP-2175).-prof gcrun at 1, 2 and 10 records per poll.Tests
FrozenTagsTest,DDSpanFrozenTagsTest+DDSpanFrozenTagsDenseForkedTest(both stores; pins which path each shape takes, incl. the HTTP-client overlap shape, recorded removals, and split-by-tags write order vs. live writes), new partial-seed cases inTagStoresTest,PeerAddressTest,KafkaConsumerTagsTest.Jira ticket: APMLP-2176
🤖 Generated with Claude Code