Skip to content

Commit f6c4461

Browse files
bm1549claude
andauthored
Add _dd.p.ksr propagated tag for Knuth sampling rate (#8287)
## Summary of changes Add `_dd.p.ksr` (Knuth Sampling Rate) propagated tag to spans when sampling is applied via agent rates or trace sampling rules, per the [Transmit Knuth Sampling Rate to Backend RFC](https://docs.google.com/document/d/1Po3qtJb6PGheFeKFSUMv2pVY_y-HFAxTzNLuacCbCXY/edit). ## Reason for change The backend needs to know the exact sampling rate applied by the tracer to correctly compute effective rates during resampling (e.g., tracer 0.5 × backend 0.5 = effective 0.25). This tag enables that by propagating the rate via `x-datadog-tags` and W3C `tracestate`. ## Implementation details - Set `_dd.p.ksr` in `TraceContext.SetSamplingPriority()` for `AgentRate`, `LocalTraceSamplingRule`, `RemoteAdaptiveSamplingRule`, and `RemoteUserSamplingRule` mechanisms - Use `TryAddTag` to preserve the original rate (consistent with `AppliedSamplingRate ??= rate` semantics) - Format with `"0.######"` (up to 6 decimal digits, no trailing zeros, no scientific notation) per RFC spec - Added `.IsOptional("_dd.p.ksr")` to `SpanTagAssertion.cs` so integration test tag validators accept the new tag ## Test coverage - Unit tests in `TraceContextTests_KnuthSamplingRate.cs`: - KSR set for agent rate sampling - KSR set for trace sampling rules (local, remote adaptive, remote user) - KSR NOT set for manual, AppSec, rate limiter, or single span mechanisms - KSR preserved on subsequent sampling calls (TryAddTag semantics) - Formatting with up to 6 decimal digits (boundary values including small rates like 0.00001) - System tests in [system-tests #6466](DataDog/system-tests#6466) ## Other details Related PRs across tracers: - Java: DataDog/dd-trace-java#10802 - Ruby: DataDog/dd-trace-rb#5436 - Node.js: DataDog/dd-trace-js#7741 - PHP: DataDog/dd-trace-php#3701 - Rust: DataDog/dd-trace-rs#180 - C++: DataDog/dd-trace-cpp#288 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f3f9047 commit f6c4461

4 files changed

Lines changed: 130 additions & 0 deletions

File tree

tracer/src/Datadog.Trace/Tags.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,13 @@ internal static class Propagated
864864
/// A two char hex string with the product being the trace source
865865
/// </summary>
866866
internal const string TraceSource = "_dd.p.ts";
867+
868+
/// <summary>
869+
/// Tag used to propagate the Knuth sampling rate applied to the trace.
870+
/// Set when a sampling decision is made using agent-based or rule-based sampling.
871+
/// The value is the applied sampling rate formatted as a string with up to 6 significant digits.
872+
/// </summary>
873+
internal const string KnuthSamplingRate = "_dd.p.ksr";
867874
}
868875
}
869876
}

tracer/src/Datadog.Trace/TraceContext.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System;
99
using System.Collections;
1010
using System.Collections.Generic;
11+
using System.Globalization;
1112
using System.Runtime.CompilerServices;
1213
using System.Threading;
1314
using Datadog.Trace.Agent;
@@ -329,6 +330,17 @@ public void SetSamplingPriority(
329330
Tags.RemoveTag(Trace.Tags.Propagated.DecisionMaker);
330331
}
331332

333+
// set Knuth sampling rate as a propagated tag for agent and rule-based sampling.
334+
// use TryAddTag to preserve the original rate, consistent with AppliedSamplingRate ??= rate above.
335+
if (rate is { } samplingRate && mechanism is Sampling.SamplingMechanism.AgentRate
336+
or Sampling.SamplingMechanism.LocalTraceSamplingRule
337+
or Sampling.SamplingMechanism.RemoteAdaptiveSamplingRule
338+
or Sampling.SamplingMechanism.RemoteUserSamplingRule)
339+
{
340+
// format with up to 6 decimal digits, no trailing zeros (per RFC)
341+
Tags.TryAddTag(Trace.Tags.Propagated.KnuthSamplingRate, samplingRate.ToString("0.######", CultureInfo.InvariantCulture));
342+
}
343+
332344
if (notifyDistributedTracer)
333345
{
334346
DistributedTracer.Instance.SetSamplingPriority(priority);

tracer/test/Datadog.Trace.TestHelpers/SpanTagAssertion.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public static void DefaultTagAssertions(SpanTagAssertion<T> s) => s
2828
.IsOptional("version")
2929
.IsOptional("_dd.p.dm") // "decision maker", but contains the sampling mechanism
3030
.IsOptional("_dd.p.tid") // contains the upper 64 bits of a 128-bit trace id
31+
.IsOptional("_dd.p.ksr") // Knuth sampling rate, propagated tag added by the tracer
3132
.IsOptional("_dd.parent_id") // Contains the 16 length hex decoded last found parent_id found set from either exising `p` tag on tracestate or headers
3233
.IsOptional("error.msg")
3334
.IsOptional("error.type")
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// <copyright file="TraceContextTests_KnuthSamplingRate.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
using Datadog.Trace.Sampling;
7+
using Datadog.Trace.Tests.Util;
8+
using FluentAssertions;
9+
using Xunit;
10+
11+
namespace Datadog.Trace.Tests;
12+
13+
public class TraceContextTests_KnuthSamplingRate
14+
{
15+
[Theory]
16+
[InlineData(SamplingPriorityValues.AutoKeep, SamplingMechanism.AgentRate, 0.5f, "0.5")]
17+
[InlineData(SamplingPriorityValues.AutoKeep, SamplingMechanism.AgentRate, 1.0f, "1")]
18+
[InlineData(SamplingPriorityValues.AutoKeep, SamplingMechanism.AgentRate, 0.1f, "0.1")]
19+
[InlineData(SamplingPriorityValues.AutoKeep, SamplingMechanism.AgentRate, 0.7654321f, "0.765432")]
20+
[InlineData(SamplingPriorityValues.UserKeep, SamplingMechanism.LocalTraceSamplingRule, 0.25f, "0.25")]
21+
[InlineData(SamplingPriorityValues.UserKeep, SamplingMechanism.RemoteUserSamplingRule, 0.75f, "0.75")]
22+
[InlineData(SamplingPriorityValues.UserKeep, SamplingMechanism.RemoteAdaptiveSamplingRule, 0.333333f, "0.333333")]
23+
public void SetSamplingPriority_SetsKsrTag_ForApplicableMechanisms(
24+
int samplingPriority, string samplingMechanism, float rate, string expectedKsr)
25+
{
26+
var tracer = new StubDatadogTracer();
27+
var traceContext = new TraceContext(tracer);
28+
29+
traceContext.SetSamplingPriority(samplingPriority, samplingMechanism, rate);
30+
31+
traceContext.Tags.GetTag(Tags.Propagated.KnuthSamplingRate).Should().Be(expectedKsr);
32+
}
33+
34+
[Theory]
35+
[InlineData(SamplingPriorityValues.AutoReject, SamplingMechanism.AgentRate, 0.5f)]
36+
[InlineData(SamplingPriorityValues.UserReject, SamplingMechanism.LocalTraceSamplingRule, 0.25f)]
37+
[InlineData(SamplingPriorityValues.UserReject, SamplingMechanism.RemoteUserSamplingRule, 0.75f)]
38+
[InlineData(SamplingPriorityValues.UserReject, SamplingMechanism.RemoteAdaptiveSamplingRule, 0.1f)]
39+
public void SetSamplingPriority_SetsKsrTag_EvenForDropDecisions(
40+
int samplingPriority, string samplingMechanism, float rate)
41+
{
42+
var tracer = new StubDatadogTracer();
43+
var traceContext = new TraceContext(tracer);
44+
45+
traceContext.SetSamplingPriority(samplingPriority, samplingMechanism, rate);
46+
47+
// KSR should be set regardless of keep/drop since it uses TryAddTag
48+
// and records the original sampling rate
49+
traceContext.Tags.GetTag(Tags.Propagated.KnuthSamplingRate).Should().NotBeNull();
50+
}
51+
52+
[Theory]
53+
[InlineData(SamplingPriorityValues.UserKeep, SamplingMechanism.Manual, 0.5f)]
54+
[InlineData(SamplingPriorityValues.UserKeep, SamplingMechanism.Asm, 0.5f)]
55+
[InlineData(SamplingPriorityValues.AutoKeep, SamplingMechanism.Default, 0.5f)]
56+
public void SetSamplingPriority_DoesNotSetKsrTag_ForNonApplicableMechanisms(
57+
int samplingPriority, string samplingMechanism, float rate)
58+
{
59+
var tracer = new StubDatadogTracer();
60+
var traceContext = new TraceContext(tracer);
61+
62+
traceContext.SetSamplingPriority(samplingPriority, samplingMechanism, rate);
63+
64+
traceContext.Tags.GetTag(Tags.Propagated.KnuthSamplingRate).Should().BeNull();
65+
}
66+
67+
[Fact]
68+
public void SetSamplingPriority_DoesNotSetKsrTag_WhenRateIsNull()
69+
{
70+
var tracer = new StubDatadogTracer();
71+
var traceContext = new TraceContext(tracer);
72+
73+
traceContext.SetSamplingPriority(SamplingPriorityValues.AutoKeep, SamplingMechanism.AgentRate, rate: null);
74+
75+
traceContext.Tags.GetTag(Tags.Propagated.KnuthSamplingRate).Should().BeNull();
76+
}
77+
78+
[Fact]
79+
public void SetSamplingPriority_PreservesOriginalKsrTag()
80+
{
81+
var tracer = new StubDatadogTracer();
82+
var traceContext = new TraceContext(tracer);
83+
84+
traceContext.SetSamplingPriority(SamplingPriorityValues.AutoKeep, SamplingMechanism.AgentRate, 0.5f);
85+
traceContext.Tags.GetTag(Tags.Propagated.KnuthSamplingRate).Should().Be("0.5");
86+
87+
// second call should NOT overwrite — TryAddTag preserves the original rate,
88+
// consistent with AppliedSamplingRate ??= rate semantics
89+
traceContext.SetSamplingPriority(SamplingPriorityValues.UserKeep, SamplingMechanism.LocalTraceSamplingRule, 0.75f);
90+
traceContext.Tags.GetTag(Tags.Propagated.KnuthSamplingRate).Should().Be("0.5");
91+
}
92+
93+
[Theory]
94+
[InlineData(0.0f, "0")]
95+
[InlineData(1.0f, "1")]
96+
[InlineData(0.5f, "0.5")]
97+
[InlineData(0.1f, "0.1")]
98+
[InlineData(0.123456f, "0.123456")]
99+
[InlineData(0.1234567f, "0.123457")]
100+
[InlineData(0.00001f, "0.00001")]
101+
public void KsrTag_FormattedWithUpToSixDecimalDigits(float rate, string expected)
102+
{
103+
var tracer = new StubDatadogTracer();
104+
var traceContext = new TraceContext(tracer);
105+
106+
traceContext.SetSamplingPriority(SamplingPriorityValues.AutoKeep, SamplingMechanism.AgentRate, rate);
107+
108+
traceContext.Tags.GetTag(Tags.Propagated.KnuthSamplingRate).Should().Be(expected);
109+
}
110+
}

0 commit comments

Comments
 (0)