Skip to content

Commit 71a91df

Browse files
committed
reuse shared CalculateThreshold and GetRandomnessFromTraceId, strip stale th on the drop path
1 parent f0d2754 commit 71a91df

2 files changed

Lines changed: 95 additions & 53 deletions

File tree

sdk/src/trace/samplers/probability.cc

Lines changed: 35 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
#include <stddef.h>
55
#include <atomic>
6-
#include <cmath>
76
#include <cstdint>
87
#include <map>
98
#include <memory>
@@ -21,28 +20,21 @@
2120
#include "opentelemetry/trace/trace_state.h"
2221
#include "opentelemetry/version.h"
2322

23+
#include "ot_trace_state.h"
24+
2425
namespace trace_api = opentelemetry::trace;
2526
namespace nostd = opentelemetry::nostd;
2627

2728
namespace
2829
{
29-
constexpr char kOtTraceStateKey[] = "ot";
30-
31-
// Rejection threshold for a ratio of zero, 2^56: drops every span.
32-
constexpr uint64_t kMaxThreshold = 1ULL << 56;
33-
34-
/**
35-
* Converts a ratio in [0, 1] to a 56-bit rejection threshold.
36-
*/
37-
uint64_t CalculateRejectionThreshold(double ratio) noexcept
30+
// Clamps ratio to [0, 1]. NaN and negatives map to 0 (never-sample).
31+
double ClampProbability(double ratio) noexcept
3832
{
39-
// The negated comparison also routes NaN to the never-sample threshold.
4033
if (!(ratio > 0.0))
41-
return kMaxThreshold;
42-
if (ratio >= 1.0)
43-
return 0;
44-
return kMaxThreshold -
45-
static_cast<uint64_t>(std::llround(ratio * static_cast<double>(kMaxThreshold)));
34+
return 0.0;
35+
if (ratio > 1.0)
36+
return 1.0;
37+
return ratio;
4638
}
4739

4840
bool ParseHex56(nostd::string_view hex, uint64_t *value) noexcept
@@ -83,19 +75,6 @@ std::string EncodeThreshold(uint64_t threshold)
8375
return hex;
8476
}
8577

86-
uint64_t RandomnessFromTraceId(const trace_api::TraceId &trace_id) noexcept
87-
{
88-
static_assert(trace_api::TraceId::kSize >= 7, "TraceID must be at least 7 bytes long.");
89-
90-
const uint8_t *data = trace_id.Id().data();
91-
uint64_t result = 0;
92-
for (size_t i = trace_api::TraceId::kSize - 7; i < trace_api::TraceId::kSize; ++i)
93-
{
94-
result = (result << 8) | data[i];
95-
}
96-
return result;
97-
}
98-
9978
// Returns true when the ot value carries a valid rv sub-key.
10079
bool ExplicitRandomness(nostd::string_view ot_value, uint64_t *randomness) noexcept
10180
{
@@ -154,14 +133,9 @@ namespace sdk
154133
namespace trace
155134
{
156135
ProbabilitySampler::ProbabilitySampler(double ratio)
157-
: threshold_(CalculateRejectionThreshold(ratio))
158-
{
159-
if (!(ratio > 0.0))
160-
ratio = 0.0;
161-
if (ratio > 1.0)
162-
ratio = 1.0;
163-
description_ = "ProbabilitySampler{" + std::to_string(ratio) + "}";
164-
}
136+
: description_("ProbabilitySampler{" + std::to_string(ClampProbability(ratio)) + "}"),
137+
threshold_(CalculateThreshold(ClampProbability(ratio)))
138+
{}
165139

166140
SamplingResult ProbabilitySampler::ShouldSample(
167141
const trace_api::SpanContext &parent_context,
@@ -171,31 +145,41 @@ SamplingResult ProbabilitySampler::ShouldSample(
171145
const opentelemetry::common::KeyValueIterable & /*attributes*/,
172146
const trace_api::SpanContextKeyValueIterable & /*links*/) noexcept
173147
{
174-
if (threshold_ == kMaxThreshold)
175-
return {Decision::DROP, nullptr, {}};
176-
177148
auto parent_trace_state = parent_context.trace_state();
178149
std::string ot_value;
179150
bool has_ot = parent_trace_state->Get(kOtTraceStateKey, ot_value);
180151

181-
uint64_t randomness = 0;
182-
if (!ExplicitRandomness(ot_value, &randomness))
152+
bool drop = threshold_ == kMaxThreshold;
153+
if (!drop)
183154
{
184-
if (parent_context.IsValid() && !parent_context.trace_flags().IsRandom())
155+
uint64_t randomness = 0;
156+
if (!ExplicitRandomness(ot_value, &randomness))
185157
{
186-
static std::atomic<bool> warned{false};
187-
if (!warned.exchange(true))
158+
if (parent_context.IsValid() && !parent_context.trace_flags().IsRandom())
188159
{
189-
OTEL_INTERNAL_LOG_WARN(
190-
"ProbabilitySampler presumes TraceID randomness, but the W3C random trace flag is "
191-
"not set. Upgrade the caller to W3C Trace Context Level 2.");
160+
static std::atomic<bool> warned{false};
161+
if (!warned.exchange(true))
162+
{
163+
OTEL_INTERNAL_LOG_WARN(
164+
"ProbabilitySampler presumes TraceID randomness, but the W3C random trace flag is "
165+
"not set. Upgrade the caller to W3C Trace Context Level 2.");
166+
}
192167
}
168+
randomness = GetRandomnessFromTraceId(trace_id);
193169
}
194-
randomness = RandomnessFromTraceId(trace_id);
170+
drop = randomness < threshold_;
195171
}
196172

197-
if (randomness < threshold_)
198-
return {Decision::DROP, nullptr, {}};
173+
if (drop)
174+
{
175+
OtelTraceState ot_state = OtelTraceState::Parse(ot_value);
176+
ot_state.has_threshold = false;
177+
std::string dropped_ot = ot_state.Serialize();
178+
auto trace_state = parent_trace_state->Delete(kOtTraceStateKey);
179+
if (!dropped_ot.empty())
180+
trace_state = trace_state->Set(kOtTraceStateKey, dropped_ot);
181+
return {Decision::DROP, nullptr, trace_state};
182+
}
199183

200184
std::string new_ot_value = SetThresholdSubKey(ot_value, EncodeThreshold(threshold_));
201185
if (!trace_api::TraceState::IsValidValue(new_ot_value))

sdk/test/trace/probability_sampler_test.cc

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@ TEST(ProbabilitySampler, ShouldSampleNever)
131131

132132
ASSERT_EQ(Decision::DROP, sampling_result.decision);
133133
ASSERT_EQ(nullptr, sampling_result.attributes);
134-
ASSERT_EQ(nullptr, sampling_result.trace_state);
134+
ASSERT_NE(nullptr, sampling_result.trace_state);
135+
std::string ot_value;
136+
ASSERT_FALSE(sampling_result.trace_state->Get("ot", ot_value));
135137
}
136138

137139
TEST(ProbabilitySampler, ShouldSampleAtThreshold)
@@ -152,7 +154,9 @@ TEST(ProbabilitySampler, ShouldSampleAtThreshold)
152154
TraceIdWithRandomness(0x7fffffffffffff));
153155

154156
ASSERT_EQ(Decision::DROP, sampling_result.decision);
155-
ASSERT_EQ(nullptr, sampling_result.trace_state);
157+
ASSERT_NE(nullptr, sampling_result.trace_state);
158+
std::string dropped_ot;
159+
ASSERT_FALSE(sampling_result.trace_state->Get("ot", dropped_ot));
156160
}
157161

158162
TEST(ProbabilitySampler, ExplicitRandomnessTakesPrecedence)
@@ -235,6 +239,60 @@ TEST(ProbabilitySampler, ThresholdReplacesExistingValue)
235239
ASSERT_EQ("bar", foo_value);
236240
}
237241

242+
TEST(ProbabilitySampler, DropClearsStaleThreshold)
243+
{
244+
ProbabilitySampler s1(0.5);
245+
246+
uint8_t trace_id_buffer[trace_api::TraceId::kSize] = {1};
247+
trace_api::TraceId trace_id{trace_id_buffer};
248+
uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1};
249+
trace_api::SpanId span_id{span_id_buffer};
250+
251+
// rv of zero is below the 0.5 threshold, so the span is dropped.
252+
auto trace_state =
253+
trace_api::TraceState::FromHeader("ot=th:8;rv:00000000000000;vendor:xyz,foo=bar");
254+
trace_api::SpanContext context(trace_id, span_id, trace_api::TraceFlags{0}, false, trace_state);
255+
256+
auto sampling_result = SampleWithContext(s1, context, TraceIdWithRandomness(0));
257+
258+
ASSERT_EQ(Decision::DROP, sampling_result.decision);
259+
ASSERT_NE(nullptr, sampling_result.trace_state);
260+
261+
std::string ot_value;
262+
ASSERT_TRUE(sampling_result.trace_state->Get("ot", ot_value));
263+
ASSERT_EQ("rv:00000000000000;vendor:xyz", ot_value);
264+
265+
std::string foo_value;
266+
ASSERT_TRUE(sampling_result.trace_state->Get("foo", foo_value));
267+
ASSERT_EQ("bar", foo_value);
268+
}
269+
270+
TEST(ProbabilitySampler, DropDeletesOtWhenOnlyThreshold)
271+
{
272+
// ot=th:8 with no other sub-keys: stripping th leaves an empty value, so the ot key is deleted.
273+
ProbabilitySampler s1(0.0);
274+
275+
uint8_t trace_id_buffer[trace_api::TraceId::kSize] = {1};
276+
trace_api::TraceId trace_id{trace_id_buffer};
277+
uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1};
278+
trace_api::SpanId span_id{span_id_buffer};
279+
280+
auto trace_state = trace_api::TraceState::FromHeader("ot=th:8,foo=bar");
281+
trace_api::SpanContext context(trace_id, span_id, trace_api::TraceFlags{0}, false, trace_state);
282+
283+
auto sampling_result = SampleWithContext(s1, context, TraceIdWithRandomness(0));
284+
285+
ASSERT_EQ(Decision::DROP, sampling_result.decision);
286+
ASSERT_NE(nullptr, sampling_result.trace_state);
287+
288+
std::string ot_value;
289+
ASSERT_FALSE(sampling_result.trace_state->Get("ot", ot_value));
290+
291+
std::string foo_value;
292+
ASSERT_TRUE(sampling_result.trace_state->Get("foo", foo_value));
293+
ASSERT_EQ("bar", foo_value);
294+
}
295+
238296
TEST(ProbabilitySampler, MalformedSubKeysAreDropped)
239297
{
240298
ProbabilitySampler s1(0.5);

0 commit comments

Comments
 (0)