// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
using System.Globalization;
using OpenTelemetry.Internal;
namespace OpenTelemetry.Trace;
///
/// Samples traces according to the specified probability.
///
public sealed class TraceIdRatioBasedSampler
: Sampler
{
private readonly long idUpperBound;
private readonly double probability;
///
/// Initializes a new instance of the class.
///
/// The desired probability of sampling. This must be between 0.0 and 1.0.
/// Higher the value, higher is the probability of a given Activity to be sampled in.
///
public TraceIdRatioBasedSampler(double probability)
{
Guard.ThrowIfOutOfRange(probability, min: 0.0, max: 1.0);
this.probability = probability;
// The expected description is like TraceIdRatioBasedSampler{0.000100}
this.Description = "TraceIdRatioBasedSampler{" + this.probability.ToString("F6", CultureInfo.InvariantCulture) + "}";
// Special case the limits, to avoid any possible issues with lack of precision across
// double/long boundaries. For probability == 0.0, we use long.MinValue as this guarantees
// that we will never sample a trace: (anyValue & long.MaxValue) is always >= 0,
// and 0 < long.MinValue is false for every possible trace ID.
if (this.probability == 0.0)
{
this.idUpperBound = long.MinValue;
}
else if (this.probability == 1.0)
{
this.idUpperBound = long.MaxValue;
}
else
{
this.idUpperBound = (long)(probability * long.MaxValue);
}
}
///
public override SamplingResult ShouldSample(in SamplingParameters samplingParameters)
{
// Always sample if we are within probability range. This is true even for child activities (that
// may have had a different sampling decision made) to allow for different sampling policies,
// and dynamic increases to sampling probabilities for debugging purposes.
// Masking with long.MaxValue clears the sign bit, mapping every possible long value uniformly
// into [0, long.MaxValue] without any branching or overflow risk.
// Note use of '<' for comparison. This ensures that we never sample for probability == 0.0,
// while allowing for a (very) small chance of *not* sampling if the id == Long.MAX_VALUE.
// This is considered a reasonable trade-off for the simplicity/performance requirements (this
// code is executed in-line for every Activity creation).
Span traceIdBytes = stackalloc byte[16];
samplingParameters.TraceId.CopyTo(traceIdBytes);
return new SamplingResult((GetLowerLong(traceIdBytes) & long.MaxValue) < this.idUpperBound);
}
private static long GetLowerLong(ReadOnlySpan bytes)
{
long result = 0;
for (var i = 0; i < 8; i++)
{
result <<= 8;
#pragma warning disable CS0675 // Bitwise-or operator used on a sign-extended operand
result |= bytes[i] & 0xff;
#pragma warning restore CS0675 // Bitwise-or operator used on a sign-extended operand
}
return result;
}
}