11// Copyright The OpenTelemetry Authors
22// SPDX-License-Identifier: Apache-2.0
33
4- #include < stddef.h>
54#include < atomic>
65#include < cstdint>
76#include < map>
8- #include < memory>
97#include < string>
108
11- #include " opentelemetry/nostd/function_ref.h"
129#include " opentelemetry/nostd/shared_ptr.h"
1310#include " opentelemetry/nostd/string_view.h"
1411#include " opentelemetry/sdk/common/global_log_handler.h"
2320#include " ot_trace_state.h"
2421
2522namespace trace_api = opentelemetry::trace;
26- namespace nostd = opentelemetry::nostd;
2723
2824namespace
2925{
@@ -36,118 +32,13 @@ double ClampProbability(double ratio) noexcept
3632 return 1.0 ;
3733 return ratio;
3834}
39-
40- bool ParseHex56 (nostd::string_view hex, uint64_t *value) noexcept
41- {
42- if (hex.size () != 14 )
43- return false ;
44- uint64_t result = 0 ;
45- for (char c : hex)
46- {
47- result <<= 4 ;
48- if (c >= ' 0' && c <= ' 9' )
49- result |= static_cast <uint64_t >(c - ' 0' );
50- else if (c >= ' a' && c <= ' f' )
51- result |= static_cast <uint64_t >(c - ' a' + 10 );
52- else
53- return false ;
54- }
55- *value = result;
56- return true ;
57- }
58-
59- /* *
60- * Encodes a 56-bit threshold as lowercase hex with trailing zeros removed,
61- * e.g. 2^55 becomes "8" and 0 becomes "0".
62- */
63- std::string EncodeThreshold (uint64_t threshold)
64- {
65- if (threshold == 0 )
66- return " 0" ;
67- static const char kHex [] = " 0123456789abcdef" ;
68- std::string hex (14 , ' 0' );
69- for (int i = 13 ; i >= 0 ; --i)
70- {
71- hex[i] = kHex [threshold & 0xf ];
72- threshold >>= 4 ;
73- }
74- hex.erase (hex.find_last_not_of (' 0' ) + 1 );
75- return hex;
76- }
77-
78- // Returns true when the ot value carries a valid rv sub-key.
79- bool ExplicitRandomness (nostd::string_view ot_value, uint64_t *randomness) noexcept
80- {
81- while (!ot_value.empty ())
82- {
83- size_t end = ot_value.find (' ;' );
84- nostd::string_view sub_key = ot_value.substr (0 , end);
85- ot_value = end == nostd::string_view::npos ? " " : ot_value.substr (end + 1 );
86-
87- if (sub_key.substr (0 , 3 ) == " rv:" )
88- return ParseHex56 (sub_key.substr (3 ), randomness);
89- }
90- return false ;
91- }
92-
93- // Sub-keys that are empty, lack a key:value separator, or duplicate th are dropped.
94- std::string SetThresholdSubKey (nostd::string_view ot_value, const std::string &threshold)
95- {
96- std::string result;
97- bool replaced = false ;
98- while (!ot_value.empty ())
99- {
100- size_t end = ot_value.find (' ;' );
101- nostd::string_view sub_key = ot_value.substr (0 , end);
102- ot_value = end == nostd::string_view::npos ? " " : ot_value.substr (end + 1 );
103-
104- if (sub_key.empty () || sub_key.find (' :' ) == nostd::string_view::npos)
105- continue ;
106- if (sub_key.substr (0 , 3 ) == " th:" )
107- {
108- if (replaced)
109- continue ;
110- replaced = true ;
111- if (!result.empty ())
112- result += ' ;' ;
113- result += " th:" + threshold;
114- continue ;
115- }
116- if (!result.empty ())
117- result += ' ;' ;
118- result.append (sub_key.data (), sub_key.size ());
119- }
120- if (!replaced)
121- {
122- if (!result.empty ())
123- result += ' ;' ;
124- result += " th:" + threshold;
125- }
126- return result;
127- }
12835} // namespace
12936
13037OPENTELEMETRY_BEGIN_NAMESPACE
13138namespace sdk
13239{
13340namespace trace
13441{
135- namespace
136- {
137- // Removes the inherited (now stale) "th" sub-key, keeping the other ot sub-keys.
138- nostd::shared_ptr<trace_api::TraceState> EraseThreshold (
139- const nostd::shared_ptr<trace_api::TraceState> &parent_trace_state,
140- const std::string &ot_value)
141- {
142- OtelTraceState ot_state = OtelTraceState::Parse (ot_value);
143- ot_state.has_threshold = false ;
144- std::string stripped = ot_state.Serialize ();
145- auto trace_state = parent_trace_state->Delete (kOtTraceStateKey );
146- if (!stripped.empty ())
147- trace_state = trace_state->Set (kOtTraceStateKey , stripped);
148- return trace_state;
149- }
150- } // namespace
15142
15243ProbabilitySampler::ProbabilitySampler (double ratio)
15344 : description_(" ProbabilitySampler{" + std::to_string(ClampProbability(ratio)) + " }" ),
@@ -163,14 +54,26 @@ SamplingResult ProbabilitySampler::ShouldSample(
16354 const trace_api::SpanContextKeyValueIterable & /* links*/ ) noexcept
16455{
16556 auto parent_trace_state = parent_context.trace_state ();
57+ if (!parent_trace_state)
58+ {
59+ parent_trace_state = trace_api::TraceState::GetDefault ();
60+ }
61+
16662 std::string ot_value;
167- bool has_ot = parent_trace_state->Get (kOtTraceStateKey , ot_value);
63+ parent_trace_state->Get (kOtTraceStateKey , ot_value);
64+ OtelTraceState ot_state = OtelTraceState::Parse (ot_value);
16865
16966 bool drop = threshold_ == kMaxThreshold ;
17067 if (!drop)
17168 {
17269 uint64_t randomness = 0 ;
173- if (!ExplicitRandomness (ot_value, &randomness))
70+ if (ot_state.has_random_value )
71+ {
72+ // An explicit "rv" from an upstream Level 2 participant wins over the
73+ // trace id, keeping the sampling decision consistent across the trace.
74+ randomness = ot_state.random_value ;
75+ }
76+ else
17477 {
17578 if (parent_context.IsValid () && !parent_context.trace_flags ().IsRandom ())
17679 {
@@ -187,31 +90,27 @@ SamplingResult ProbabilitySampler::ShouldSample(
18790 drop = randomness < threshold_;
18891 }
18992
93+ // Record the effective threshold when sampling; a dropped span carries no
94+ // probability, so its inherited (now stale) "th" must be erased. The "rv"
95+ // sub-key and any other "ot" sub-keys are preserved by OtelTraceState.
19096 if (drop)
19197 {
192- return {Decision:: DROP , nullptr , EraseThreshold (parent_trace_state, ot_value)} ;
98+ ot_state. has_threshold = false ;
19399 }
194-
195- std::string new_ot_value = SetThresholdSubKey (ot_value, EncodeThreshold (threshold_));
196- if (!trace_api::TraceState::IsValidValue (new_ot_value))
197- return {Decision::RECORD_AND_SAMPLE , nullptr , EraseThreshold (parent_trace_state, ot_value)};
198-
199- if (!has_ot)
100+ else
200101 {
201- size_t entry_count = 0 ;
202- parent_trace_state->GetAllEntries ([&entry_count](nostd::string_view, nostd::string_view) {
203- ++entry_count;
204- return true ;
205- });
206- if (entry_count >= trace_api::TraceState::kMaxKeyValuePairs )
207- return {Decision::RECORD_AND_SAMPLE , nullptr , {}};
102+ ot_state.has_threshold = true ;
103+ ot_state.threshold = threshold_;
208104 }
209105
210- auto trace_state =
211- has_ot ? parent_trace_state->Delete (kOtTraceStateKey )->Set (kOtTraceStateKey , new_ot_value)
212- : parent_trace_state->Set (kOtTraceStateKey , new_ot_value);
106+ std::string new_ot_value = ot_state.Serialize ();
107+ auto trace_state = parent_trace_state->Delete (kOtTraceStateKey );
108+ if (!new_ot_value.empty ())
109+ {
110+ trace_state = trace_state->Set (kOtTraceStateKey , new_ot_value);
111+ }
213112
214- return {Decision::RECORD_AND_SAMPLE , nullptr , trace_state};
113+ return {drop ? Decision:: DROP : Decision::RECORD_AND_SAMPLE , nullptr , trace_state};
215114}
216115
217116nostd::string_view ProbabilitySampler::GetDescription () const noexcept
0 commit comments