Skip to content
Merged
7 changes: 7 additions & 0 deletions tracer/src/Datadog.Trace/Tags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,13 @@ internal static class Propagated
/// A two char hex string with the product being the trace source
/// </summary>
internal const string TraceSource = "_dd.p.ts";

/// <summary>
/// Tag used to propagate the Knuth sampling rate applied to the trace.
/// Set when a sampling decision is made using agent-based or rule-based sampling.
/// The value is the applied sampling rate formatted as a string with up to 6 significant digits.
/// </summary>
internal const string KnuthSamplingRate = "_dd.p.ksr";
}
}
}
12 changes: 12 additions & 0 deletions tracer/src/Datadog.Trace/TraceContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Threading;
using Datadog.Trace.Agent;
Expand Down Expand Up @@ -329,6 +330,17 @@ public void SetSamplingPriority(
Tags.RemoveTag(Trace.Tags.Propagated.DecisionMaker);
}

// set Knuth sampling rate as a propagated tag for agent and rule-based sampling.
// use TryAddTag to preserve the original rate, consistent with AppliedSamplingRate ??= rate above.
if (rate is { } samplingRate && mechanism is Sampling.SamplingMechanism.AgentRate
Comment thread
lucaspimentel marked this conversation as resolved.
or Sampling.SamplingMechanism.LocalTraceSamplingRule
or Sampling.SamplingMechanism.RemoteAdaptiveSamplingRule
or Sampling.SamplingMechanism.RemoteUserSamplingRule)
{
// format with up to 6 decimal digits, no trailing zeros (per RFC)
Tags.TryAddTag(Trace.Tags.Propagated.KnuthSamplingRate, samplingRate.ToString("0.######", CultureInfo.InvariantCulture));
Copy link
Copy Markdown
Member

@lucaspimentel lucaspimentel Mar 24, 2026

Choose a reason for hiding this comment

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

I wonder if it's worth hard-coding the string for common sampling rates like "1" to avoid these heap allocations for every trace. Or caching the string in the rule itself (which allows caching the string for different rates, for example if one rules has "1" and another has "0.5").

Maybe something we can do in a follow-up PR. Thoughts, @andrewlock?

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.

Yeah, nice idea 🤔 Although I wonder if we should even be adding this here, and shouldn't be just adding it at serialization time. We should avoid adding tags like this wherever possible 😬

}

if (notifyDistributedTracer)
{
DistributedTracer.Instance.SetSamplingPriority(priority);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static void DefaultTagAssertions(SpanTagAssertion<T> s) => s
.IsOptional("version")
.IsOptional("_dd.p.dm") // "decision maker", but contains the sampling mechanism
.IsOptional("_dd.p.tid") // contains the upper 64 bits of a 128-bit trace id
.IsOptional("_dd.p.ksr") // Knuth sampling rate, propagated tag added by the tracer
.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
.IsOptional("error.msg")
.IsOptional("error.type")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// <copyright file="TraceContextTests_KnuthSamplingRate.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

using Datadog.Trace.Sampling;
using Datadog.Trace.Tests.Util;
using FluentAssertions;
using Xunit;

namespace Datadog.Trace.Tests;

public class TraceContextTests_KnuthSamplingRate
{
[Theory]
[InlineData(SamplingPriorityValues.AutoKeep, SamplingMechanism.AgentRate, 0.5f, "0.5")]
[InlineData(SamplingPriorityValues.AutoKeep, SamplingMechanism.AgentRate, 1.0f, "1")]
[InlineData(SamplingPriorityValues.AutoKeep, SamplingMechanism.AgentRate, 0.1f, "0.1")]
[InlineData(SamplingPriorityValues.AutoKeep, SamplingMechanism.AgentRate, 0.7654321f, "0.765432")]
[InlineData(SamplingPriorityValues.UserKeep, SamplingMechanism.LocalTraceSamplingRule, 0.25f, "0.25")]
[InlineData(SamplingPriorityValues.UserKeep, SamplingMechanism.RemoteUserSamplingRule, 0.75f, "0.75")]
[InlineData(SamplingPriorityValues.UserKeep, SamplingMechanism.RemoteAdaptiveSamplingRule, 0.333333f, "0.333333")]
public void SetSamplingPriority_SetsKsrTag_ForApplicableMechanisms(
int samplingPriority, string samplingMechanism, float rate, string expectedKsr)
{
var tracer = new StubDatadogTracer();
var traceContext = new TraceContext(tracer);

traceContext.SetSamplingPriority(samplingPriority, samplingMechanism, rate);

traceContext.Tags.GetTag(Tags.Propagated.KnuthSamplingRate).Should().Be(expectedKsr);
}

[Theory]
[InlineData(SamplingPriorityValues.AutoReject, SamplingMechanism.AgentRate, 0.5f)]
[InlineData(SamplingPriorityValues.UserReject, SamplingMechanism.LocalTraceSamplingRule, 0.25f)]
[InlineData(SamplingPriorityValues.UserReject, SamplingMechanism.RemoteUserSamplingRule, 0.75f)]
[InlineData(SamplingPriorityValues.UserReject, SamplingMechanism.RemoteAdaptiveSamplingRule, 0.1f)]
public void SetSamplingPriority_SetsKsrTag_EvenForDropDecisions(
int samplingPriority, string samplingMechanism, float rate)
{
var tracer = new StubDatadogTracer();
var traceContext = new TraceContext(tracer);

traceContext.SetSamplingPriority(samplingPriority, samplingMechanism, rate);

// KSR should be set regardless of keep/drop since it uses TryAddTag
// and records the original sampling rate
traceContext.Tags.GetTag(Tags.Propagated.KnuthSamplingRate).Should().NotBeNull();
}

[Theory]
[InlineData(SamplingPriorityValues.UserKeep, SamplingMechanism.Manual, 0.5f)]
[InlineData(SamplingPriorityValues.UserKeep, SamplingMechanism.Asm, 0.5f)]
[InlineData(SamplingPriorityValues.AutoKeep, SamplingMechanism.Default, 0.5f)]
public void SetSamplingPriority_DoesNotSetKsrTag_ForNonApplicableMechanisms(
int samplingPriority, string samplingMechanism, float rate)
{
var tracer = new StubDatadogTracer();
var traceContext = new TraceContext(tracer);

traceContext.SetSamplingPriority(samplingPriority, samplingMechanism, rate);

traceContext.Tags.GetTag(Tags.Propagated.KnuthSamplingRate).Should().BeNull();
}

[Fact]
public void SetSamplingPriority_DoesNotSetKsrTag_WhenRateIsNull()
{
var tracer = new StubDatadogTracer();
var traceContext = new TraceContext(tracer);

traceContext.SetSamplingPriority(SamplingPriorityValues.AutoKeep, SamplingMechanism.AgentRate, rate: null);

traceContext.Tags.GetTag(Tags.Propagated.KnuthSamplingRate).Should().BeNull();
}

[Fact]
public void SetSamplingPriority_PreservesOriginalKsrTag()
{
var tracer = new StubDatadogTracer();
var traceContext = new TraceContext(tracer);

traceContext.SetSamplingPriority(SamplingPriorityValues.AutoKeep, SamplingMechanism.AgentRate, 0.5f);
traceContext.Tags.GetTag(Tags.Propagated.KnuthSamplingRate).Should().Be("0.5");

// second call should NOT overwrite — TryAddTag preserves the original rate,
// consistent with AppliedSamplingRate ??= rate semantics
traceContext.SetSamplingPriority(SamplingPriorityValues.UserKeep, SamplingMechanism.LocalTraceSamplingRule, 0.75f);
traceContext.Tags.GetTag(Tags.Propagated.KnuthSamplingRate).Should().Be("0.5");
}

[Theory]
[InlineData(0.0f, "0")]
[InlineData(1.0f, "1")]
[InlineData(0.5f, "0.5")]
[InlineData(0.1f, "0.1")]
[InlineData(0.123456f, "0.123456")]
[InlineData(0.1234567f, "0.123457")]
[InlineData(0.00001f, "0.00001")]
public void KsrTag_FormattedWithUpToSixDecimalDigits(float rate, string expected)
{
var tracer = new StubDatadogTracer();
var traceContext = new TraceContext(tracer);

traceContext.SetSamplingPriority(SamplingPriorityValues.AutoKeep, SamplingMechanism.AgentRate, rate);

traceContext.Tags.GetTag(Tags.Propagated.KnuthSamplingRate).Should().Be(expected);
}
}
Loading