Skip to content

Implement Client-Side Stats (CSS) 1.2.0#8420

Open
andrewlock wants to merge 48 commits intomasterfrom
andrew/client-side-stats/client-side-stats-1-2
Open

Implement Client-Side Stats (CSS) 1.2.0#8420
andrewlock wants to merge 48 commits intomasterfrom
andrew/client-side-stats/client-side-stats-1-2

Conversation

@andrewlock
Copy link
Copy Markdown
Member

Summary of changes

Updates the existing client-side-stats implementation to match version 1.2.0 as defined in the RFC and as implemented in the agent.

Reason for change

Our implementation is severely lagging the latest implementation in the agent. This hasn't been a big deal, as it's not documented and not enabled by default, but we'd like to fix the implementation to make it usable.

Implementation details

This was driven almost entirely by 🤖, by comparing our existing implementation to the RFC, and also taking the agent/go implementation as the canonical implementation.

Implementing this in .NET highlighted a number of missing aspects in the RFC, which I've raised elsewhere, and aim to get incorporated into the RFC.

At a high level, the PR contains the following changes:

  • Stats Wire Format & Serialization
    • Added new aggregation dimensions: SpanKind, IsTraceRoot (as Trilean), HTTPMethod, HTTPEndpoint, GRPCStatusCode, ServiceSource, PeerTags to both the aggregation key and the msgpack wire format
    • Fixed GRPCStatusCode type: Changed from int to string to match Go agent's wire format (agent was returning 400 Bad Request in system tests)
    • gRPC status code extraction: Checks 4 tag names in priority order (rpc.grpc.status_code, grpc.code, rpc.grpc.status.code, grpc.status.code)
    • Stochastic rounding: Hits, Errors, Duration, TopLevelHits accumulated as double (weighted by sampling rate) then rounded probabilistically to int64 for serialization
    • Duration weighting: Durations are now multiplied by sampling weight (1/rate), matching Go agent behavior
    • Default env: Serializes "unknown-env" when environment is not configured
    • git_commit_sha: Added as optional field in stats payload
    • Service: Added as top-level field in stats payload
    • Empty bucket suppression: HasHits() check prevents sending payloads with zero-hit buckets (stale keys retained for sketch reuse)
  • Bucket Timing
    • 10-second alignment: Bucket Start timestamps aligned to 10-second boundaries (ts - ts % 10_000_000_000) matching Go tracer's alignTs
    • Removed unused StartTime property (only Start as aligned nanoseconds)
  • Agent Discovery (/info Endpoint)
    • peer_tags: Parsed, sorted, deduplicated; used for peer tag extraction on client/producer/consumer spans
    • span_kinds_stats_computed: Parsed to override eligible span kinds
    • obfuscation_version: Parsed for obfuscation negotiation
    • Trace filters: Parsed filter_tags, filter_tags_regex, ignore_resources from /info
  • Trace Filtering
    • TraceFilter implementation: Evaluates agent-configured filters (exact tags, regex tags, resource patterns) against root spans before stats computation
    • Tag-only filters: Handles filter entries that match on tag key presence without a specific value
  • SQL Obfuscation
    • Operator splitters: Added * / = < > ! & ^ % ~ ? @ : # as token splitters (matching Go agent's go-sqllexer isOperator()) so queries like WHERE id='1' are properly obfuscated
    • Whitespace normalization: Post-processing pass adds spaces around comparison operators (=, <, >, !) adjacent to ? placeholders, matching Go agent's normalizer output (e.g., id='1'id = ?)
    • Obfuscation gating: Only runs when obfuscation_version is negotiated with agent; sends Datadog-Obfuscation-Version header
  • Peer Tags
    • IP quantization: Peer tag values run through IpAddressObfuscationUtil.QuantizePeerIpAddresses() replacing non-allowed IPs with "blocked-ip-address"
    • Base service handling: Internal/missing-kind spans with non-default service name use _dd.base_service:{serviceName} as sole peer tag
    • FNV-1a hashing: Peer tags hashed with null-byte separators for aggregation key
  • Other
    • No retries for stats: Stats sends are fire-and-forget (retry limit = 0)
    • Synthetics detection: Uses StartsWith("synthetics") prefix matching (not exact match)
    • Mock agent updates: Test mock agent returns obfuscation_version in /info response

Test coverage

There's a lot going on in this PR, because we were so far behind. I could split this into implementing individual features, but there would be a lot of duplication between PRs, and it didn't seem like it would be that easy to track. At least with this big bang we can compare directly against the system tests etc.

The existing system tests for stats computation were checked, and made to pass (which identified a number of hidden expectations which will be added to the RFC). I'll create a PR to enable these in the system-tests repo

Other details

Part of a stack

There are still some theoretical gaps between the go implementation and the .NET implementation, but I think these are non-issues in most cases:

Area Gap Impact
gRPC status code normalization Go agent normalizes string statuses (e.g., "CANCELLED""1"); .NET passes raw tag value Stats mismatch if gRPC library uses string-form status codes
HTTP status code tags Go agent checks both http.status_code and http.response.status_code (OTel convention); .NET only checks http.status_code OTel spans using newer convention would get 0 in .NET
Duration precision truncation Go agent uses float bit masking; .NET uses integer shifting — both target ~10 bits but may produce slightly different values Minor histogram differences
SQL obfuscation Go agent uses full tokenizer + normalizer; .NET uses character-level splitter with targeted normalization around comparison operators only Complex SQL with unusual formatting may produce different resource strings
HTTP_method / HTTP_endpoint .NET always populates from span tags; Go agent only populates from newer OTel pipeline paths Creates different aggregation keys — Go groups all methods/routes together in default path
_top_level metric Go checks both _top_level and _dd.top_level; .NET only checks _dd.top_level Spans using older metric name would be missed by .NET
span_derived_primary_tags Still in Go agent code but implemented in v1.3.0 RFC, which was reverted; removed from .NET No current impact; may need to re-add if spec reverts back

There is another big elephant in the room, which is perf. The peer tags, in particular, currently requires a bunch of allocation. I'd rather defer trying to fight against that to another PR if possible, unless anyone has some clear ideas 😄

Another aspect I'm not sure about is how this interacts with @zacharycmontoya's recent work to publish OTLP stats. I took a random guess and fought the refactoring, but need to verify it.

@andrewlock andrewlock added the area:tracer The core tracer library (Datadog.Trace, does not include OpenTracing, native code, or integrations) label Apr 7, 2026
@andrewlock andrewlock requested a review from a team as a code owner April 7, 2026 17:47
@andrewlock andrewlock added the AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos label Apr 7, 2026
@lucaspimentel lucaspimentel changed the title Implement Client Side Stats 1.2.0 Implement Client-Side Stats 1.2.0 Apr 7, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

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: e52cc41a00

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tracer/src/Datadog.Trace/Agent/StatsAggregator.cs Outdated
Comment on lines +463 to +466
{
if (span.Context.ParentId is null or 0)
{
return !filter.ShouldKeepTrace(span);
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 Apply trace filters when chunks lack a parentless root span

Trace filtering only runs when a span has ParentId null/0, so chunks where every span has a non-zero parent (common in distributed traces with propagated context, or some partial chunks) bypass filter_tags/ignore_resources entirely. In those cases IsTraceFiltered returns false and filtered traces are kept and counted in stats, which defeats agent-configured filtering for a real production traffic pattern.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Interesting. We should probably do whatever the Go agent is doing.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

So. This is where things get very interesting 😅 Will discuss this one offline I think 😄

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Mostly fixed this in #8436, though not strictly the same as the Go agent does. Plus this is going to bork with partial flushing (both sides)

@pr-commenter
Copy link
Copy Markdown

pr-commenter bot commented Apr 7, 2026

Benchmarks

Benchmark execution time: 2026-04-16 09:05:29

Comparing candidate commit e9ee48d in PR branch andrew/client-side-stats/client-side-stats-1-2 with baseline commit b176976 in branch master.

Found 0 performance improvements and 1 performance regressions! Performance is the same for 26 metrics, 0 unstable metrics, 87 known flaky benchmarks.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

scenario:Benchmarks.Trace.HttpClientBenchmark.SendAsync net472

  • 🟥 throughput [-5139.024op/s; -4940.670op/s] or [-5.867%; -5.640%]

Known flaky benchmarks

These benchmarks are marked as flaky and will not trigger a failure. Modify FLAKY_BENCHMARKS_REGEX to control which benchmarks are marked as flaky.

scenario:Benchmarks.Trace.ActivityBenchmark.StartStopWithChild net472

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.008%; +0.005%]
  • ignore execution_time [-1224.873µs; -272.358µs] or [-0.609%; -0.135%]
  • ignore throughput [-1513.353op/s; -901.947op/s] or [-1.794%; -1.069%]

scenario:Benchmarks.Trace.ActivityBenchmark.StartStopWithChild net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.006%; +0.007%]
  • ignore execution_time [-1825.930µs; +1678.206µs] or [-0.911%; +0.837%]
  • 🟩 throughput [+8136.061op/s; +10402.524op/s] or [+6.839%; +8.744%]

scenario:Benchmarks.Trace.ActivityBenchmark.StartStopWithChild netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.003%; +0.007%]
  • ignore execution_time [+0.838ms; +3.000ms] or [+0.421%; +1.509%]
  • ignore throughput [-518.493op/s; +589.143op/s] or [-0.527%; +0.599%]

scenario:Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces net472

  • ignore allocated_mem [-20 bytes; -19 bytes] or [-0.613%; -0.600%]
  • 🟥 execution_time [+309.845ms; +311.867ms] or [+153.756%; +154.760%]
  • ignore throughput [+4.375op/s; +8.266op/s] or [+0.787%; +1.487%]

scenario:Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.009%; +0.002%]
  • 🟥 execution_time [+381.729ms; +384.167ms] or [+301.589%; +303.516%]
  • ignore throughput [+12.155op/s; +15.824op/s] or [+1.603%; +2.086%]

scenario:Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.009%; +0.002%]
  • 🟥 execution_time [+395.526ms; +398.934ms] or [+350.025%; +353.041%]
  • ignore throughput [+2.414op/s; +5.265op/s] or [+0.341%; +0.744%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleMoreComplexBody net472

  • 🟥 allocated_mem [+1.308KB; +1.308KB] or [+27.529%; +27.541%]
  • ignore execution_time [+178.896µs; +805.792µs] or [+0.089%; +0.403%]
  • ignore throughput [-4592.248op/s; -4139.921op/s] or [-3.573%; -3.221%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleMoreComplexBody net6.0

  • 🟥 allocated_mem [+471 bytes; +472 bytes] or [+9.977%; +9.987%]
  • 🟩 execution_time [-16.349ms; -12.187ms] or [-7.636%; -5.692%]
  • ignore throughput [+5463.907op/s; +8244.610op/s] or [+3.988%; +6.018%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleMoreComplexBody netcoreapp3.1

  • 🟥 allocated_mem [+1.272KB; +1.272KB] or [+27.502%; +27.510%]
  • ignore execution_time [-12.300ms; -8.103ms] or [-5.857%; -3.859%]
  • ignore throughput [-1291.524op/s; +1027.675op/s] or [-1.168%; +0.929%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleSimpleBody net472

  • 🟥 allocated_mem [+1.307KB; +1.307KB] or [+105.746%; +105.759%]
  • ignore execution_time [-926.194µs; -368.057µs] or [-0.461%; -0.183%]
  • 🟥 throughput [-251248.764op/s; -248218.475op/s] or [-25.654%; -25.344%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleSimpleBody net6.0

  • 🟥 allocated_mem [+471 bytes; +472 bytes] or [+38.558%; +38.566%]
  • 🟩 execution_time [-26.882ms; -21.983ms] or [-11.988%; -9.803%]
  • ignore throughput [-55506.009op/s; -32740.824op/s] or [-5.930%; -3.498%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleSimpleBody netcoreapp3.1

  • 🟥 allocated_mem [+1.272KB; +1.272KB] or [+105.292%; +105.304%]
  • ignore execution_time [-3.572ms; +0.766ms] or [-1.783%; +0.382%]
  • 🟥 throughput [-135846.227op/s; -119671.669op/s] or [-19.518%; -17.195%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorMoreComplexBody net472

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.007%; +0.003%]
  • ignore execution_time [-1334.578µs; -304.312µs] or [-0.665%; -0.152%]
  • ignore throughput [-164.317op/s; +689.581op/s] or [-0.111%; +0.464%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorMoreComplexBody net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.004%; +0.003%]
  • ignore execution_time [-0.133ms; +3.340ms] or [-0.067%; +1.685%]
  • 🟩 throughput [+10326.218op/s; +13260.766op/s] or [+6.570%; +8.438%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorMoreComplexBody netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.007%; +0.003%]
  • ignore execution_time [-9.141ms; +0.727ms] or [-4.660%; +0.370%]
  • 🟩 throughput [+8340.703op/s; +11026.376op/s] or [+6.644%; +8.784%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorSimpleBody net472

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.008%; +0.004%]
  • ignore execution_time [-147.000µs; +337.751µs] or [-0.073%; +0.169%]
  • ignore throughput [+75127.443op/s; +83906.988op/s] or [+2.285%; +2.553%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorSimpleBody net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.006%; +0.008%]
  • ignore execution_time [-2.673ms; -2.002ms] or [-1.321%; -0.990%]
  • 🟩 throughput [+496010.835op/s; +512933.539op/s] or [+16.539%; +17.103%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorSimpleBody netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.004%; +0.004%]
  • 🟩 execution_time [-18.999ms; -14.621ms] or [-8.758%; -6.740%]
  • 🟩 throughput [+210575.538op/s; +264672.637op/s] or [+8.358%; +10.506%]

scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeArgs net472

  • ignore allocated_mem [+0 bytes; +2 bytes] or [-0.001%; +0.007%]
  • 🟥 execution_time [+299.579ms; +300.103ms] or [+149.690%; +149.951%]
  • ignore throughput [+97.778op/s; +124.973op/s] or [+1.080%; +1.380%]

scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeArgs net6.0

  • ignore allocated_mem [-1 bytes; +2 bytes] or [-0.004%; +0.008%]
  • 🟥 execution_time [+299.096ms; +302.406ms] or [+150.835%; +152.504%]
  • ignore throughput [+227.962op/s; +446.088op/s] or [+1.744%; +3.412%]

scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeArgs netcoreapp3.1

  • ignore allocated_mem [-1 bytes; +2 bytes] or [-0.004%; +0.008%]
  • 🟥 execution_time [+299.753ms; +302.258ms] or [+150.992%; +152.254%]
  • ignore throughput [+177.170op/s; +305.090op/s] or [+1.710%; +2.945%]

scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeLegacyArgs net472

  • ignore allocated_mem [+3 bytes; +4 bytes] or [+0.186%; +0.199%]
  • 🟥 execution_time [+297.175ms; +297.787ms] or [+145.961%; +146.261%]
  • ignore throughput [+8.702op/s; +14.740op/s] or [+0.231%; +0.391%]

scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeLegacyArgs net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.004%; +0.009%]
  • 🟥 execution_time [+294.038ms; +296.845ms] or [+143.744%; +145.116%]
  • ignore throughput [+156.447op/s; +204.213op/s] or [+2.273%; +2.967%]

scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeLegacyArgs netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.004%; +0.009%]
  • 🟥 execution_time [+299.539ms; +300.795ms] or [+149.709%; +150.337%]
  • ignore throughput [+55.466op/s; +78.063op/s] or [+1.101%; +1.549%]

scenario:Benchmarks.Trace.Asm.AppSecWafBenchmark.RunWafRealisticBenchmark net472

  • ignore allocated_mem [+0 bytes; +0 bytes] or [+nan%; +nan%]
  • ignore execution_time [+2.358µs; +5.970µs] or [+0.484%; +1.226%]
  • ignore throughput [-24.676op/s; -9.714op/s] or [-1.202%; -0.473%]

scenario:Benchmarks.Trace.Asm.AppSecWafBenchmark.RunWafRealisticBenchmark net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.000%; +0.010%]
  • ignore execution_time [+13.510µs; +40.089µs] or [+3.099%; +9.194%]
  • ignore throughput [-199.064op/s; -79.467op/s] or [-8.655%; -3.455%]

scenario:Benchmarks.Trace.Asm.AppSecWafBenchmark.RunWafRealisticBenchmark netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.003%; +0.003%]
  • ignore execution_time [+7.717µs; +29.697µs] or [+1.653%; +6.363%]
  • ignore throughput [-146.487op/s; -65.644op/s] or [-6.762%; -3.030%]

scenario:Benchmarks.Trace.Asm.AppSecWafBenchmark.RunWafRealisticBenchmarkWithAttack net472

  • ignore allocated_mem [+0 bytes; +0 bytes] or [+nan%; +nan%]
  • ignore execution_time [-7.055µs; -3.339µs] or [-1.905%; -0.901%]
  • ignore throughput [+25.183op/s; +52.010op/s] or [+0.933%; +1.926%]

scenario:Benchmarks.Trace.Asm.AppSecWafBenchmark.RunWafRealisticBenchmarkWithAttack net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.003%; +0.007%]
  • 🟥 execution_time [+20.994µs; +44.556µs] or [+6.702%; +14.224%]
  • 🟥 throughput [-416.622op/s; -218.321op/s] or [-12.987%; -6.806%]

scenario:Benchmarks.Trace.Asm.AppSecWafBenchmark.RunWafRealisticBenchmarkWithAttack netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.003%; +0.003%]
  • ignore execution_time [-14.799µs; +7.597µs] or [-4.048%; +2.078%]
  • ignore throughput [-90.107op/s; +43.639op/s] or [-3.234%; +1.566%]

scenario:Benchmarks.Trace.AspNetCoreBenchmark.SendRequest net472

  • ignore allocated_mem [+0 bytes; +0 bytes] or [+nan%; +nan%]
  • 🟥 execution_time [+299.569ms; +300.224ms] or [+149.516%; +149.842%]
  • ignore throughput [-1476927.735op/s; -901777.785op/s] or [-0.740%; -0.452%]

scenario:Benchmarks.Trace.AspNetCoreBenchmark.SendRequest net6.0

  • ignore allocated_mem [+75 bytes; +77 bytes] or [+0.419%; +0.431%]
  • unstable execution_time [+300.305ms; +346.208ms] or [+326.294%; +376.169%]
  • 🟩 throughput [+1169.646op/s; +1315.147op/s] or [+9.611%; +10.807%]

scenario:Benchmarks.Trace.AspNetCoreBenchmark.SendRequest netcoreapp3.1

  • ignore allocated_mem [+20 bytes; +22 bytes] or [+0.099%; +0.110%]
  • unstable execution_time [+339.059ms; +361.169ms] or [+257.444%; +274.232%]
  • 🟩 throughput [+681.280op/s; +884.688op/s] or [+6.595%; +8.564%]

scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces net472

  • 🟥 allocated_mem [+2.837KB; +2.842KB] or [+5.040%; +5.049%]
  • unstable execution_time [+344.347ms; +411.898ms] or [+158.328%; +189.387%]
  • 🟥 throughput [-542.396op/s; -472.771op/s] or [-49.146%; -42.838%]

scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces net6.0

  • ignore allocated_mem [-1.270KB; -1.268KB] or [-2.995%; -2.990%]
  • unstable execution_time [+199.739ms; +332.938ms] or [+85.121%; +141.884%]
  • 🟥 throughput [-742.512op/s; -659.099op/s] or [-49.526%; -43.962%]

scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces netcoreapp3.1

  • 🟥 allocated_mem [+2.226KB; +2.229KB] or [+5.255%; +5.262%]
  • 🟥 execution_time [+346.868ms; +355.414ms] or [+207.468%; +212.579%]
  • 🟥 throughput [-415.903op/s; -380.034op/s] or [-28.959%; -26.461%]

scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSlice net472

  • ignore allocated_mem [+0 bytes; +0 bytes] or [+nan%; +nan%]
  • ignore execution_time [-67.298µs; -33.799µs] or [-3.387%; -1.701%]
  • ignore throughput [+9.877op/s; +18.223op/s] or [+1.963%; +3.621%]

scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSlice net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [+nan%; +nan%]
  • ignore execution_time [-58.664µs; -45.988µs] or [-4.030%; -3.159%]
  • ignore throughput [+22.760op/s; +29.092op/s] or [+3.313%; +4.235%]

scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSlice netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [+nan%; +nan%]
  • ignore execution_time [-260.842µs; -143.156µs] or [-9.074%; -4.980%]
  • 🟩 throughput [+20.760op/s; +46.156op/s] or [+5.967%; +13.267%]

scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSliceWithPool net472

  • ignore allocated_mem [+0 bytes; +0 bytes] or [+nan%; +nan%]
  • ignore execution_time [-4766.738ns; +3441.404ns] or [-0.412%; +0.297%]
  • ignore throughput [-2.305op/s; +3.761op/s] or [-0.267%; +0.435%]

scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSliceWithPool net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [+nan%; +nan%]
  • ignore execution_time [-18.545µs; -5.202µs] or [-1.720%; -0.482%]
  • ignore throughput [+5.409op/s; +16.847op/s] or [+0.583%; +1.816%]

scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSliceWithPool netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [+nan%; +nan%]
  • ignore execution_time [+14.470µs; +24.443µs] or [+0.775%; +1.310%]
  • ignore throughput [-6.897op/s; -4.071op/s] or [-1.287%; -0.760%]

scenario:Benchmarks.Trace.CharSliceBenchmark.OriginalCharSlice net472

  • ignore allocated_mem [-43 bytes; +21 bytes] or [-0.007%; +0.003%]
  • ignore execution_time [+45.618µs; +55.941µs] or [+1.782%; +2.185%]
  • ignore throughput [-8.317op/s; -6.808op/s] or [-2.129%; -1.743%]

scenario:Benchmarks.Trace.CharSliceBenchmark.OriginalCharSlice net6.0

  • ignore allocated_mem [-38 bytes; +46 bytes] or [-0.006%; +0.007%]
  • 🟩 execution_time [-159.264µs; -125.125µs] or [-8.068%; -6.338%]
  • 🟩 throughput [+35.883op/s; +44.902op/s] or [+7.084%; +8.864%]

scenario:Benchmarks.Trace.CharSliceBenchmark.OriginalCharSlice netcoreapp3.1

  • ignore allocated_mem [-42 bytes; +23 bytes] or [-0.007%; +0.004%]
  • ignore execution_time [-128.336µs; -88.792µs] or [-3.254%; -2.252%]
  • ignore throughput [+6.091op/s; +8.596op/s] or [+2.402%; +3.390%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearch net472

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.001%; +0.008%]
  • 🟥 execution_time [+303.574ms; +305.356ms] or [+152.874%; +153.772%]
  • ignore throughput [-706.655op/s; +708.505op/s] or [-0.227%; +0.228%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearch net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.006%; +0.004%]
  • 🟥 execution_time [+300.431ms; +302.510ms] or [+150.547%; +151.589%]
  • ignore throughput [+23222.063op/s; +27212.326op/s] or [+3.661%; +4.290%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearch netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.006%; +0.004%]
  • 🟥 execution_time [+302.338ms; +305.655ms] or [+151.882%; +153.548%]
  • ignore throughput [+9211.011op/s; +16929.252op/s] or [+1.940%; +3.566%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearchAsync net472

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.008%; +0.004%]
  • 🟥 execution_time [+300.724ms; +302.512ms] or [+151.013%; +151.911%]
  • ignore throughput [+10113.287op/s; +11847.433op/s] or [+3.388%; +3.969%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearchAsync net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.007%; +0.006%]
  • 🟥 execution_time [+296.264ms; +298.141ms] or [+146.489%; +147.417%]
  • ignore throughput [+16004.256op/s; +22114.776op/s] or [+2.579%; +3.563%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearchAsync netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.006%; +0.004%]
  • 🟥 execution_time [+304.254ms; +308.110ms] or [+154.209%; +156.163%]
  • ignore throughput [-7247.874op/s; +1563.413op/s] or [-1.565%; +0.338%]

scenario:Benchmarks.Trace.GraphQLBenchmark.ExecuteAsync net472

  • ignore allocated_mem [+0 bytes; +1 bytes] or [+0.108%; +0.119%]
  • 🟥 execution_time [+299.949ms; +301.492ms] or [+150.548%; +151.322%]
  • ignore throughput [-15339.539op/s; -13711.619op/s] or [-3.979%; -3.557%]

scenario:Benchmarks.Trace.GraphQLBenchmark.ExecuteAsync net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.006%; +0.006%]
  • 🟥 execution_time [+301.220ms; +303.170ms] or [+150.131%; +151.102%]
  • 🟩 throughput [+54957.216op/s; +59848.885op/s] or [+10.913%; +11.884%]

scenario:Benchmarks.Trace.GraphQLBenchmark.ExecuteAsync netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.006%; +0.006%]
  • 🟥 execution_time [+298.575ms; +301.339ms] or [+148.538%; +149.914%]
  • ignore throughput [-8696.390op/s; -3173.996op/s] or [-2.058%; -0.751%]

scenario:Benchmarks.Trace.ILoggerBenchmark.EnrichedLog net472

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.003%; +0.006%]
  • ignore execution_time [-1095.587µs; -217.875µs] or [-0.545%; -0.108%]
  • ignore throughput [-8834.711op/s; -7651.443op/s] or [-3.553%; -3.077%]

scenario:Benchmarks.Trace.ILoggerBenchmark.EnrichedLog net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.005%; +0.003%]
  • 🟩 execution_time [-16.023ms; -12.222ms] or [-7.451%; -5.683%]
  • 🟩 throughput [+23371.572op/s; +30173.947op/s] or [+6.411%; +8.278%]

scenario:Benchmarks.Trace.ILoggerBenchmark.EnrichedLog netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.004%; +0.008%]
  • ignore execution_time [-0.498ms; +3.503ms] or [-0.250%; +1.757%]
  • ignore throughput [+6178.349op/s; +11892.885op/s] or [+2.255%; +4.341%]

scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatAspectBenchmark net472

  • ignore allocated_mem [-4.459KB; -4.431KB] or [-1.623%; -1.613%]
  • ignore execution_time [+6.768µs; +47.100µs] or [+1.672%; +11.634%]
  • ignore throughput [-247.689op/s; -42.918op/s] or [-9.967%; -1.727%]

scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatAspectBenchmark net6.0

  • 🟩 allocated_mem [-18.996KB; -18.975KB] or [-6.929%; -6.922%]
  • unstable execution_time [-50.931µs; +1.277µs] or [-10.066%; +0.252%]
  • ignore throughput [+7.903op/s; +190.143op/s] or [+0.394%; +9.488%]

scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatAspectBenchmark netcoreapp3.1

  • 🟩 allocated_mem [-16.471KB; -16.455KB] or [-6.005%; -5.998%]
  • ignore execution_time [-49.480µs; +7.625µs] or [-8.575%; +1.321%]
  • ignore throughput [-8.934op/s; +146.252op/s] or [-0.510%; +8.356%]

scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatBenchmark net472

  • ignore allocated_mem [-2 bytes; +2 bytes] or [-0.005%; +0.006%]
  • ignore execution_time [+177.474ns; +1637.192ns] or [+0.307%; +2.836%]
  • ignore throughput [-460.762op/s; -48.260op/s] or [-2.659%; -0.278%]

scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatBenchmark net6.0

  • ignore allocated_mem [-4 bytes; +0 bytes] or [-0.010%; -0.001%]
  • 🟥 execution_time [+5.687µs; +9.672µs] or [+13.442%; +22.861%]
  • 🟥 throughput [-4452.615op/s; -2702.673op/s] or [-18.744%; -11.377%]

scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatBenchmark netcoreapp3.1

  • ignore allocated_mem [-1 bytes; +1 bytes] or [-0.002%; +0.002%]
  • unstable execution_time [-13.324µs; -5.994µs] or [-20.672%; -9.300%]
  • 🟩 throughput [+1535.782op/s; +3101.702op/s] or [+9.423%; +19.030%]

scenario:Benchmarks.Trace.Log4netBenchmark.EnrichedLog net472

  • ignore allocated_mem [+1 bytes; +2 bytes] or [+0.039%; +0.050%]
  • 🟥 execution_time [+301.975ms; +302.974ms] or [+152.635%; +153.140%]
  • ignore throughput [-120.195op/s; -99.578op/s] or [-2.008%; -1.664%]

scenario:Benchmarks.Trace.Log4netBenchmark.EnrichedLog net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.005%; +0.005%]
  • 🟥 execution_time [+302.891ms; +305.152ms] or [+154.171%; +155.321%]
  • ignore throughput [-101.478op/s; -24.458op/s] or [-1.259%; -0.303%]

scenario:Benchmarks.Trace.Log4netBenchmark.EnrichedLog netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.005%; +0.005%]
  • 🟥 execution_time [+300.784ms; +302.928ms] or [+150.579%; +151.653%]
  • ignore throughput [-204.910op/s; -139.933op/s] or [-2.610%; -1.783%]

scenario:Benchmarks.Trace.RedisBenchmark.SendReceive net472

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.005%; +0.005%]
  • ignore execution_time [-431.040µs; +281.804µs] or [-0.215%; +0.140%]
  • 🟥 throughput [-25216.768op/s; -23803.905op/s] or [-6.981%; -6.590%]

scenario:Benchmarks.Trace.RedisBenchmark.SendReceive net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.004%; +0.007%]
  • ignore execution_time [-339.877µs; +255.923µs] or [-0.170%; +0.128%]
  • 🟩 throughput [+53663.458op/s; +56223.230op/s] or [+10.157%; +10.642%]

scenario:Benchmarks.Trace.RedisBenchmark.SendReceive netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.004%; +0.006%]
  • ignore execution_time [+0.956ms; +4.624ms] or [+0.485%; +2.344%]
  • ignore throughput [-6978.015op/s; +1547.574op/s] or [-1.651%; +0.366%]

scenario:Benchmarks.Trace.SerilogBenchmark.EnrichedLog net472

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.005%; +0.006%]
  • 🟥 execution_time [+300.323ms; +302.014ms] or [+149.684%; +150.527%]
  • ignore throughput [-7924.998op/s; -6948.465op/s] or [-5.233%; -4.588%]

scenario:Benchmarks.Trace.SerilogBenchmark.EnrichedLog net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [+0.000%; +0.009%]
  • 🟥 execution_time [+300.786ms; +301.902ms] or [+151.040%; +151.601%]
  • ignore throughput [+3689.217op/s; +5804.692op/s] or [+1.604%; +2.524%]

scenario:Benchmarks.Trace.SerilogBenchmark.EnrichedLog netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.004%; +0.003%]
  • 🟥 execution_time [+304.109ms; +306.756ms] or [+154.225%; +155.567%]
  • ignore throughput [-1925.090op/s; -11.770op/s] or [-1.084%; -0.007%]

scenario:Benchmarks.Trace.SingleSpanAspNetCoreBenchmark.SingleSpanAspNetCore net472

  • ignore allocated_mem [+0 bytes; +0 bytes] or [+nan%; +nan%]
  • 🟥 execution_time [+300.104ms; +300.823ms] or [+149.694%; +150.052%]
  • 🟩 throughput [+61114833.385op/s; +61379888.185op/s] or [+44.508%; +44.701%]

scenario:Benchmarks.Trace.SingleSpanAspNetCoreBenchmark.SingleSpanAspNetCore net6.0

  • ignore allocated_mem [+80 bytes; +82 bytes] or [+0.472%; +0.481%]
  • unstable execution_time [+346.037ms; +391.157ms] or [+430.359%; +486.473%]
  • 🟩 throughput [+947.577op/s; +1131.212op/s] or [+7.325%; +8.745%]

scenario:Benchmarks.Trace.SingleSpanAspNetCoreBenchmark.SingleSpanAspNetCore netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [+nan%; +nan%]
  • 🟥 execution_time [+299.177ms; +300.070ms] or [+149.223%; +149.668%]
  • ignore throughput [+1699849.421op/s; +2635155.955op/s] or [+0.753%; +1.167%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishScope net472

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.005%; +0.006%]
  • ignore execution_time [-134.360µs; +260.217µs] or [-0.067%; +0.130%]
  • ignore throughput [+18392.851op/s; +21443.877op/s] or [+2.053%; +2.393%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishScope net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.005%; +0.007%]
  • ignore execution_time [-4.813ms; -3.700ms] or [-2.357%; -1.812%]
  • 🟩 throughput [+102064.376op/s; +108826.825op/s] or [+9.529%; +10.161%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishScope netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.003%; +0.006%]
  • ignore execution_time [+2.975ms; +7.199ms] or [+1.506%; +3.643%]
  • 🟩 throughput [+52733.190op/s; +72114.383op/s] or [+6.104%; +8.347%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishSpan net472

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.000%; +0.009%]
  • ignore execution_time [-98.406µs; +420.617µs] or [-0.049%; +0.210%]
  • ignore throughput [-1007.115op/s; +1885.592op/s] or [-0.092%; +0.173%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishSpan net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.004%; +0.004%]
  • ignore execution_time [+5.152ms; +14.362ms] or [+2.684%; +7.482%]
  • ignore throughput [+64354.815op/s; +105074.867op/s] or [+4.981%; +8.133%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishSpan netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.001%; +0.008%]
  • ignore execution_time [-4.663ms; -3.181ms] or [-2.291%; -1.563%]
  • 🟩 throughput [+82822.330op/s; +91335.899op/s] or [+8.226%; +9.071%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishTwoScopes net472

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.008%; +0.002%]
  • ignore execution_time [-2.766ms; -1.240ms] or [-1.377%; -0.617%]
  • ignore throughput [+14572.538op/s; +17702.225op/s] or [+3.247%; +3.944%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishTwoScopes net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.004%; +0.009%]
  • ignore execution_time [-2.088ms; -0.512ms] or [-1.043%; -0.256%]
  • 🟩 throughput [+46839.540op/s; +54973.266op/s] or [+8.505%; +9.982%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishTwoScopes netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.006%; +0.004%]
  • ignore execution_time [-0.933ms; +3.157ms] or [-0.469%; +1.586%]
  • ignore throughput [+18965.750op/s; +28716.847op/s] or [+4.245%; +6.428%]

scenario:Benchmarks.Trace.TraceAnnotationsBenchmark.RunOnMethodBegin net472

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.005%; +0.006%]
  • ignore execution_time [-588.651µs; +565.859µs] or [-0.293%; +0.282%]
  • ignore throughput [-10987.220op/s; -7309.312op/s] or [-1.608%; -1.070%]

scenario:Benchmarks.Trace.TraceAnnotationsBenchmark.RunOnMethodBegin net6.0

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.005%; +0.007%]
  • ignore execution_time [-1133.312µs; +2366.921µs] or [-0.567%; +1.184%]
  • 🟩 throughput [+59284.513op/s; +77724.472op/s] or [+6.624%; +8.684%]

scenario:Benchmarks.Trace.TraceAnnotationsBenchmark.RunOnMethodBegin netcoreapp3.1

  • ignore allocated_mem [+0 bytes; +0 bytes] or [-0.005%; +0.005%]
  • ignore execution_time [+1.372ms; +5.277ms] or [+0.697%; +2.680%]
  • ignore throughput [+28130.356op/s; +43024.488op/s] or [+3.928%; +6.007%]

@dd-trace-dotnet-ci-bot
Copy link
Copy Markdown

dd-trace-dotnet-ci-bot bot commented Apr 7, 2026

Execution-Time Benchmarks Report ⏱️

Execution-time results for samples comparing This PR (8420) and master.

✅ No regressions detected - check the details below

Full Metrics Comparison

FakeDbCommand

Metric Master (Mean ± 95% CI) Current (Mean ± 95% CI) Change Status
.NET Framework 4.8 - Baseline
duration72.00 ± (71.93 - 72.25) ms71.96 ± (71.89 - 72.21) ms-0.0%
.NET Framework 4.8 - Bailout
duration75.72 ± (75.64 - 76.02) ms75.38 ± (75.30 - 75.62) ms-0.4%
.NET Framework 4.8 - CallTarget+Inlining+NGEN
duration1069.15 ± (1071.14 - 1080.01) ms1061.71 ± (1062.61 - 1068.35) ms-0.7%
.NET Core 3.1 - Baseline
process.internal_duration_ms22.29 ± (22.26 - 22.32) ms22.33 ± (22.29 - 22.37) ms+0.2%✅⬆️
process.time_to_main_ms83.82 ± (83.62 - 84.01) ms82.78 ± (82.59 - 82.98) ms-1.2%
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed10.92 ± (10.92 - 10.93) MB10.92 ± (10.91 - 10.92) MB-0.1%
runtime.dotnet.threads.count12 ± (12 - 12)12 ± (12 - 12)+0.0%
.NET Core 3.1 - Bailout
process.internal_duration_ms22.31 ± (22.27 - 22.34) ms22.25 ± (22.22 - 22.28) ms-0.3%
process.time_to_main_ms85.12 ± (84.96 - 85.29) ms84.74 ± (84.55 - 84.93) ms-0.5%
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed10.96 ± (10.96 - 10.97) MB10.94 ± (10.94 - 10.95) MB-0.2%
runtime.dotnet.threads.count13 ± (13 - 13)13 ± (13 - 13)+0.0%
.NET Core 3.1 - CallTarget+Inlining+NGEN
process.internal_duration_ms227.08 ± (225.83 - 228.34) ms226.47 ± (225.16 - 227.77) ms-0.3%
process.time_to_main_ms518.37 ± (517.13 - 519.61) ms520.14 ± (519.08 - 521.19) ms+0.3%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed48.42 ± (48.39 - 48.45) MB48.45 ± (48.41 - 48.48) MB+0.1%✅⬆️
runtime.dotnet.threads.count28 ± (28 - 28)28 ± (28 - 28)+0.0%
.NET 6 - Baseline
process.internal_duration_ms21.04 ± (21.00 - 21.08) ms21.02 ± (20.99 - 21.05) ms-0.1%
process.time_to_main_ms72.40 ± (72.24 - 72.56) ms72.55 ± (72.38 - 72.73) ms+0.2%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed10.63 ± (10.63 - 10.63) MB10.64 ± (10.64 - 10.64) MB+0.1%✅⬆️
runtime.dotnet.threads.count10 ± (10 - 10)10 ± (10 - 10)+0.0%
.NET 6 - Bailout
process.internal_duration_ms21.00 ± (20.97 - 21.04) ms20.81 ± (20.77 - 20.85) ms-0.9%
process.time_to_main_ms73.79 ± (73.63 - 73.95) ms72.78 ± (72.64 - 72.91) ms-1.4%
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed10.68 ± (10.68 - 10.69) MB10.75 ± (10.74 - 10.75) MB+0.6%✅⬆️
runtime.dotnet.threads.count11 ± (11 - 11)11 ± (11 - 11)+0.0%
.NET 6 - CallTarget+Inlining+NGEN
process.internal_duration_ms385.43 ± (383.54 - 387.32) ms386.67 ± (384.07 - 389.26) ms+0.3%✅⬆️
process.time_to_main_ms517.75 ± (516.81 - 518.69) ms517.52 ± (516.61 - 518.43) ms-0.0%
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed50.08 ± (50.05 - 50.11) MB50.12 ± (50.09 - 50.15) MB+0.1%✅⬆️
runtime.dotnet.threads.count28 ± (28 - 28)28 ± (28 - 28)-0.0%
.NET 8 - Baseline
process.internal_duration_ms19.18 ± (19.14 - 19.21) ms19.14 ± (19.11 - 19.16) ms-0.2%
process.time_to_main_ms71.25 ± (71.08 - 71.42) ms71.02 ± (70.86 - 71.18) ms-0.3%
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed7.69 ± (7.68 - 7.70) MB7.67 ± (7.66 - 7.67) MB-0.3%
runtime.dotnet.threads.count10 ± (10 - 10)10 ± (10 - 10)+0.0%
.NET 8 - Bailout
process.internal_duration_ms19.17 ± (19.13 - 19.20) ms19.19 ± (19.15 - 19.22) ms+0.1%✅⬆️
process.time_to_main_ms72.34 ± (72.21 - 72.48) ms72.21 ± (72.06 - 72.36) ms-0.2%
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed7.73 ± (7.72 - 7.74) MB7.74 ± (7.73 - 7.75) MB+0.1%✅⬆️
runtime.dotnet.threads.count11 ± (11 - 11)11 ± (11 - 11)+0.0%
.NET 8 - CallTarget+Inlining+NGEN
process.internal_duration_ms306.20 ± (303.93 - 308.48) ms304.72 ± (302.42 - 307.01) ms-0.5%
process.time_to_main_ms478.84 ± (477.97 - 479.70) ms478.67 ± (477.92 - 479.42) ms-0.0%
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed37.11 ± (37.09 - 37.14) MB37.38 ± (37.32 - 37.45) MB+0.7%✅⬆️
runtime.dotnet.threads.count27 ± (27 - 27)27 ± (27 - 27)-0.5%

HttpMessageHandler

Metric Master (Mean ± 95% CI) Current (Mean ± 95% CI) Change Status
.NET Framework 4.8 - Baseline
duration190.29 ± (190.30 - 190.99) ms190.37 ± (190.24 - 191.07) ms+0.0%✅⬆️
.NET Framework 4.8 - Bailout
duration193.46 ± (193.52 - 193.92) ms193.73 ± (193.60 - 194.06) ms+0.1%✅⬆️
.NET Framework 4.8 - CallTarget+Inlining+NGEN
duration1139.43 ± (1138.81 - 1144.75) ms1139.97 ± (1140.83 - 1147.26) ms+0.0%✅⬆️
.NET Core 3.1 - Baseline
process.internal_duration_ms185.20 ± (184.92 - 185.49) ms184.74 ± (184.40 - 185.08) ms-0.2%
process.time_to_main_ms79.54 ± (79.37 - 79.70) ms79.81 ± (79.64 - 79.98) ms+0.3%✅⬆️
runtime.dotnet.exceptions.count3 ± (3 - 3)3 ± (3 - 3)+0.0%
runtime.dotnet.mem.committed16.18 ± (16.16 - 16.21) MB16.17 ± (16.14 - 16.20) MB-0.1%
runtime.dotnet.threads.count20 ± (20 - 20)20 ± (20 - 20)+0.4%✅⬆️
.NET Core 3.1 - Bailout
process.internal_duration_ms184.57 ± (184.34 - 184.81) ms184.32 ± (184.12 - 184.53) ms-0.1%
process.time_to_main_ms80.97 ± (80.86 - 81.09) ms80.96 ± (80.84 - 81.08) ms-0.0%
runtime.dotnet.exceptions.count3 ± (3 - 3)3 ± (3 - 3)+0.0%
runtime.dotnet.mem.committed16.16 ± (16.07 - 16.26) MB16.13 ± (16.05 - 16.22) MB-0.2%
runtime.dotnet.threads.count21 ± (20 - 21)21 ± (20 - 21)-0.1%
.NET Core 3.1 - CallTarget+Inlining+NGEN
process.internal_duration_ms392.10 ± (390.63 - 393.58) ms392.31 ± (390.96 - 393.66) ms+0.1%✅⬆️
process.time_to_main_ms504.22 ± (503.13 - 505.31) ms506.36 ± (505.17 - 507.55) ms+0.4%✅⬆️
runtime.dotnet.exceptions.count3 ± (3 - 3)3 ± (3 - 3)+0.0%
runtime.dotnet.mem.committed58.64 ± (58.42 - 58.86) MB58.65 ± (58.45 - 58.84) MB+0.0%✅⬆️
runtime.dotnet.threads.count30 ± (30 - 30)30 ± (30 - 30)+0.1%✅⬆️
.NET 6 - Baseline
process.internal_duration_ms189.67 ± (189.36 - 189.98) ms190.29 ± (189.96 - 190.63) ms+0.3%✅⬆️
process.time_to_main_ms69.29 ± (69.14 - 69.45) ms69.85 ± (69.69 - 70.01) ms+0.8%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed15.86 ± (15.68 - 16.03) MB16.17 ± (16.02 - 16.31) MB+1.9%✅⬆️
runtime.dotnet.threads.count18 ± (18 - 18)18 ± (18 - 18)+1.3%✅⬆️
.NET 6 - Bailout
process.internal_duration_ms188.26 ± (188.11 - 188.41) ms192.17 ± (191.73 - 192.61) ms+2.1%✅⬆️
process.time_to_main_ms70.24 ± (70.19 - 70.30) ms71.57 ± (71.45 - 71.69) ms+1.9%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed16.00 ± (15.83 - 16.17) MB16.21 ± (16.06 - 16.35) MB+1.3%✅⬆️
runtime.dotnet.threads.count19 ± (18 - 19)20 ± (20 - 20)+5.4%✅⬆️
.NET 6 - CallTarget+Inlining+NGEN
process.internal_duration_ms594.85 ± (591.84 - 597.87) ms599.09 ± (596.43 - 601.75) ms+0.7%✅⬆️
process.time_to_main_ms508.09 ± (507.18 - 508.99) ms518.16 ± (516.96 - 519.37) ms+2.0%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed61.67 ± (61.58 - 61.77) MB61.69 ± (61.58 - 61.80) MB+0.0%✅⬆️
runtime.dotnet.threads.count30 ± (30 - 30)30 ± (30 - 30)+0.2%✅⬆️
.NET 8 - Baseline
process.internal_duration_ms185.99 ± (185.74 - 186.25) ms198.08 ± (197.66 - 198.51) ms+6.5%✅⬆️
process.time_to_main_ms68.37 ± (68.25 - 68.49) ms73.17 ± (72.93 - 73.40) ms+7.0%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed11.72 ± (11.65 - 11.79) MB11.73 ± (11.71 - 11.75) MB+0.1%✅⬆️
runtime.dotnet.threads.count18 ± (18 - 18)18 ± (18 - 18)+1.7%✅⬆️
.NET 8 - Bailout
process.internal_duration_ms185.71 ± (185.49 - 185.92) ms196.86 ± (196.43 - 197.29) ms+6.0%✅⬆️
process.time_to_main_ms69.62 ± (69.56 - 69.69) ms73.64 ± (73.44 - 73.85) ms+5.8%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed11.67 ± (11.58 - 11.77) MB11.75 ± (11.73 - 11.78) MB+0.7%✅⬆️
runtime.dotnet.threads.count19 ± (18 - 19)19 ± (19 - 19)+4.1%✅⬆️
.NET 8 - CallTarget+Inlining+NGEN
process.internal_duration_ms521.08 ± (518.52 - 523.65) ms522.39 ± (518.94 - 525.84) ms+0.3%✅⬆️
process.time_to_main_ms466.62 ± (465.98 - 467.26) ms490.98 ± (489.59 - 492.38) ms+5.2%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed50.75 ± (50.72 - 50.78) MB50.84 ± (50.79 - 50.89) MB+0.2%✅⬆️
runtime.dotnet.threads.count30 ± (30 - 30)30 ± (30 - 30)+0.4%✅⬆️
Comparison explanation

Execution-time benchmarks measure the whole time it takes to execute a program, and are intended to measure the one-off costs. Cases where the execution time results for the PR are worse than latest master results are highlighted in **red**. The following thresholds were used for comparing the execution times:

  • Welch test with statistical test for significance of 5%
  • Only results indicating a difference greater than 5% and 5 ms are considered.

Note that these results are based on a single point-in-time result for each branch. For full results, see the dashboard.

Graphs show the p99 interval based on the mean and StdDev of the test run, as well as the mean value of the run (shown as a diamond below the graph).

Duration charts
FakeDbCommand (.NET Framework 4.8)
gantt
    title Execution time (ms) FakeDbCommand (.NET Framework 4.8)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8420) - mean (72ms)  : 70, 74
    master - mean (72ms)  : 70, 74

    section Bailout
    This PR (8420) - mean (75ms)  : 74, 77
    master - mean (76ms)  : 74, 78

    section CallTarget+Inlining+NGEN
    This PR (8420) - mean (1,065ms)  : 1024, 1107
    master - mean (1,076ms)  : 1010, 1141

Loading
FakeDbCommand (.NET Core 3.1)
gantt
    title Execution time (ms) FakeDbCommand (.NET Core 3.1)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8420) - mean (112ms)  : 109, 115
    master - mean (114ms)  : 110, 117

    section Bailout
    This PR (8420) - mean (114ms)  : 111, 116
    master - mean (115ms)  : 112, 118

    section CallTarget+Inlining+NGEN
    This PR (8420) - mean (786ms)  : 763, 809
    master - mean (784ms)  : 764, 805

Loading
FakeDbCommand (.NET 6)
gantt
    title Execution time (ms) FakeDbCommand (.NET 6)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8420) - mean (100ms)  : 96, 103
    master - mean (100ms)  : 96, 103

    section Bailout
    This PR (8420) - mean (100ms)  : 98, 102
    master - mean (101ms)  : 98, 104

    section CallTarget+Inlining+NGEN
    This PR (8420) - mean (931ms)  : 893, 969
    master - mean (932ms)  : 899, 964

Loading
FakeDbCommand (.NET 8)
gantt
    title Execution time (ms) FakeDbCommand (.NET 8)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8420) - mean (98ms)  : 95, 100
    master - mean (98ms)  : 94, 103

    section Bailout
    This PR (8420) - mean (99ms)  : 97, 101
    master - mean (99ms)  : 97, 101

    section CallTarget+Inlining+NGEN
    This PR (8420) - mean (813ms)  : 779, 847
    master - mean (814ms)  : 781, 848

Loading
HttpMessageHandler (.NET Framework 4.8)
gantt
    title Execution time (ms) HttpMessageHandler (.NET Framework 4.8)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8420) - mean (191ms)  : 187, 195
    master - mean (191ms)  : 187, 194

    section Bailout
    This PR (8420) - mean (194ms)  : 192, 196
    master - mean (194ms)  : 192, 196

    section CallTarget+Inlining+NGEN
    This PR (8420) - mean (1,144ms)  : 1096, 1192
    master - mean (1,142ms)  : 1100, 1184

Loading
HttpMessageHandler (.NET Core 3.1)
gantt
    title Execution time (ms) HttpMessageHandler (.NET Core 3.1)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8420) - mean (273ms)  : 268, 277
    master - mean (273ms)  : 269, 277

    section Bailout
    This PR (8420) - mean (273ms)  : 271, 276
    master - mean (273ms)  : 270, 276

    section CallTarget+Inlining+NGEN
    This PR (8420) - mean (926ms)  : 906, 946
    master - mean (924ms)  : 895, 953

Loading
HttpMessageHandler (.NET 6)
gantt
    title Execution time (ms) HttpMessageHandler (.NET 6)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8420) - mean (268ms)  : 262, 275
    master - mean (267ms)  : 262, 272

    section Bailout
    This PR (8420) - mean (272ms)  : 267, 277
    master - mean (266ms)  : 264, 268

    section CallTarget+Inlining+NGEN
    This PR (8420) - mean (1,146ms)  : 1115, 1177
    master - mean (1,133ms)  : 1090, 1176

Loading
HttpMessageHandler (.NET 8)
gantt
    title Execution time (ms) HttpMessageHandler (.NET 8)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8420) - mean (281ms)  : 274, 289
    master - mean (264ms)  : 261, 267

    section Bailout
    This PR (8420) - mean (280ms)  : crit, 273, 288
    master - mean (265ms)  : 261, 268

    section CallTarget+Inlining+NGEN
    This PR (8420) - mean (1,045ms)  : 996, 1094
    master - mean (1,019ms)  : 980, 1058

Loading

spanMetaStructs: spanMetaStructs,
spanEvents: spanEvents);
spanEvents: spanEvents,
peerTags: peerTags!,
Copy link
Copy Markdown
Member

@lucaspimentel lucaspimentel Apr 8, 2026

Choose a reason for hiding this comment

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

peerTags is nullable so we shouldn't need the ! nullability suppression here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, except the ! is suppressing the inner non-nullability, because the compiler isn't clever enough to know that it's a List<string> not List<string?>, even though we filter string.IsNullOrEmpty (note that StringUtil also doesn't help here)

            var peerTags = (jObject["peer_tags"] as JArray)?.Values<string>().Where(x => !string.IsNullOrEmpty(x)).Distinct().OrderBy(x => x).ToList();

Removing the dammit gives errors 🙁

image

Comment thread tracer/src/Datadog.Trace/Agent/TraceSamplers/TraceFilter.cs Outdated
{
if (!string.IsNullOrEmpty(pattern))
{
compiled.Add(new Regex(pattern, RegexOptions.Compiled, matchTimeout: TimeSpan.FromSeconds(1)));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Remember these aren't actually compiled until the first time they are used. Not sure if it matters here, but somewhere else we do a dummy Match() to force the compilation early.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, it's a good point, hadn't thought about that. I think this is likely actually the correct behaviour, as it means that compilation is happening at serialization time instead of blocking the discovery service updates, but I could see an argument both ways 🤔 Any preference?

Comment thread tracer/src/Datadog.Trace/Agent/Api.cs Outdated
Comment thread tracer/src/Datadog.Trace/Agent/NullStatsAggregator.cs Outdated
Comment thread tracer/src/Datadog.Trace/Agent/StatsAggregator.cs Outdated
Comment thread tracer/src/Datadog.Trace/Agent/StatsAggregator.cs Outdated
}

// Find the root span (ParentId == null or 0) and apply the filter
foreach (var span in spans)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If this span collection follows our usual ordering, the root span will usually be the last span, so it might be faster to search in reverse (from last span to first span).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Heh, that's actually harder than it seems, because it's a SpanCollection which doesn't expose the root array, but it's a good point. I'll address that when looking into the partial flush issue...

Comment thread tracer/src/Datadog.Trace/Processors/ObfuscatorTraceProcessor.cs
Comment thread tracer/test/Datadog.Trace.Tests/Agent/ApiTests.cs Outdated
@lucaspimentel lucaspimentel requested a review from a team April 8, 2026 04:54
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the tracer’s client-side stats pipeline to better match the “client-side-stats” 1.2.0 wire format/behavior by expanding aggregation dimensions, adjusting serialization semantics, and incorporating agent-driven discovery/filtering signals.

Changes:

  • Extend stats aggregation key + msgpack payload to include additional dimensions (span kind, trace-root flag, HTTP method/endpoint, gRPC status, service source, peer tags) and add stochastic rounding/weighted accumulation.
  • Add agent discovery support for peer tags, span kinds eligibility, obfuscation negotiation, and trace-level filtering (exact/regex/resource-based).
  • Update SQL obfuscation tokenization/normalization and adjust stats sending API surface (obfuscation header, no-retry for agent stats).

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tracer/src/Datadog.Trace/Agent/StatsAggregator.cs Computes stats with new dimensions (peer tags, span kinds), filtering, weighting, and obfuscation negotiation.
tracer/src/Datadog.Trace/Agent/StatsBuffer.cs Serializes updated msgpack wire format and suppresses empty buckets.
tracer/src/Datadog.Trace/Agent/StatsBucket.cs Switch counters to double for weighted accumulation; store peer tags.
tracer/src/Datadog.Trace/Agent/StatsAggregationKey.cs Adds new dimensions to key equality/hash.
tracer/src/Datadog.Trace/Agent/DiscoveryService/DiscoveryService.cs Parses /info additions (peer_tags, span_kinds_stats_computed, obfuscation_version, trace filters).
tracer/src/Datadog.Trace/Agent/DiscoveryService/AgentConfiguration.cs Carries newly discovered config fields through the system.
tracer/src/Datadog.Trace/Agent/TraceSamplers/TraceFilter.cs Implements agent-driven trace-level filtering on root spans.
tracer/src/Datadog.Trace/Processors/ObfuscatorTraceProcessor.cs Expands SQL token splitters and normalizes spaces around operators near placeholders.
tracer/src/Datadog.Trace/Agent/Api.cs Sends stats with obfuscation header and disables retries for agent stats.
tracer/src/Datadog.Trace/Agent/ApiOtlp.cs Updates stats API signature/state for OTLP path.
tracer/src/Datadog.Trace/Agent/TraceSamplers/RareSampler.cs Uses stats aggregator to build keys (signature change).
tracer/test/** Updates/adds tests for new dimensions, filtering, obfuscation behavior, and API signature changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tracer/src/Datadog.Trace/Agent/ApiOtlp.cs
Comment thread tracer/src/Datadog.Trace/Agent/StatsAggregationKey.cs
Comment thread tracer/src/Datadog.Trace/Agent/StatsAggregator.cs Outdated
Comment thread tracer/test/Datadog.Trace.Tests/Agent/ApiTests.cs Outdated
Comment on lines +416 to 421
var key = BuildKey(span, out var peerTags);

var buffer = CurrentBuffer;

if (!buffer.Buckets.TryGetValue(key, out var bucket))
{
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

AddToBuffer() calls BuildKey(span, out var peerTags) for every span before TryGetValue. For client/producer/consumer spans this allocates UTF-8 bytes (and intermediate strings via $"{tagKey}:{tagValue}") even when the bucket already exists and the computed peerTags list is discarded. Consider restructuring so peer-tag materialization only happens when inserting a new bucket (e.g., compute hash first, or defer building utf8PeerTags until after TryGetValue indicates the key is new) to avoid per-span allocation overhead.

Suggested change
var key = BuildKey(span, out var peerTags);
var buffer = CurrentBuffer;
if (!buffer.Buckets.TryGetValue(key, out var bucket))
{
var buffer = CurrentBuffer;
var key = BuildKey(span, out _);
if (!buffer.Buckets.TryGetValue(key, out var bucket))
{
key = BuildKey(span, out var peerTags);

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, this one is a pain, I don't have a great answer to it currently 🙁 Will address in a follow up, when I work out how 😅

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

To Clarify, the suggested approach is much worse, as it allocates twice 😉

Comment thread tracer/src/Datadog.Trace/Agent/StatsBuffer.cs
@andrewlock andrewlock force-pushed the andrew/client-side-stats/client-side-stats-1-2 branch from e52cc41 to 562d9e7 Compare April 8, 2026 10:40
@andrewlock andrewlock requested review from a team as code owners April 8, 2026 10:40
@andrewlock andrewlock force-pushed the andrew/client-side-stats/ip-obfuscation branch from f00d1b6 to 77f3510 Compare April 8, 2026 10:41
@andrewlock andrewlock force-pushed the andrew/client-side-stats/client-side-stats-1-2 branch from 562d9e7 to 0b3eff8 Compare April 8, 2026 10:57
@andrewlock andrewlock force-pushed the andrew/client-side-stats/ip-obfuscation branch from 77f3510 to 536d246 Compare April 8, 2026 10:57
andrewlock and others added 29 commits April 16, 2026 08:54
The ObfuscatorTraceProcessor was running unconditionally, causing
double-obfuscation when the agent also obfuscates (version 0 or
version > tracer). Now it only runs when the tracer has negotiated
obfuscation responsibility (agent version > 0 and <= tracer version).

Re-obfuscating an already-obfuscated query produces incorrect
results (e.g. SELECT ? FROM users would have ? treated as a
literal on the second pass).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The rebase made tracerObfuscationVersion a required parameter on
IApi.SendStatsAsync. Update test call sites to pass the argument.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Lucas Pimentel <lucas.pimentel@datadoghq.com>
@andrewlock andrewlock force-pushed the andrew/client-side-stats/client-side-stats-1-2 branch from 5573211 to e9ee48d Compare April 16, 2026 07:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos area:tracer The core tracer library (Datadog.Trace, does not include OpenTracing, native code, or integrations)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants