21
21
from azure .monitor .opentelemetry .exporter .export .trace ._utils import _get_djb2_sample_score , _round_down_to_nearest
22
22
23
23
class _State :
24
- def __init__ (self , effective_window_count : float , effective_window_nanos : float , last_nano_time : int ):
24
+ def __init__ (self , effective_window_count : float , effective_window_nanoseconds : float , last_nano_time : int ):
25
25
self .effective_window_count = effective_window_count
26
- self .effective_window_nanos = effective_window_nanos
26
+ self .effective_window_nanoseconds = effective_window_nanoseconds
27
27
self .last_nano_time = last_nano_time
28
28
29
29
class RateLimitedSamplingPercentage :
@@ -34,7 +34,7 @@ def __init__(self, target_spans_per_second_limit: float,
34
34
self ._nano_time_supplier = nano_time_supplier or (lambda : int (time .time_ns ()))
35
35
# Hardcoded adaptation time of 0.1 seconds for adjusting to sudden changes in telemetry volumes
36
36
adaptation_time_seconds = 0.1
37
- self ._inverse_adaptation_time_nanos = 1e-9 / adaptation_time_seconds
37
+ self ._inverse_adaptation_time_nanoseconds = 1e-9 / adaptation_time_seconds
38
38
self ._target_spans_per_nanosecond_limit = 1e-9 * target_spans_per_second_limit
39
39
initial_nano_time = self ._nano_time_supplier ()
40
40
self ._state = _State (0.0 , 0.0 , initial_nano_time )
@@ -45,39 +45,38 @@ def _update_state(self, old_state: _State, current_nano_time: int) -> _State:
45
45
if current_nano_time <= old_state .last_nano_time :
46
46
return _State (
47
47
old_state .effective_window_count + 1 ,
48
- old_state .effective_window_nanos ,
48
+ old_state .effective_window_nanoseconds ,
49
49
old_state .last_nano_time
50
50
)
51
51
nano_time_delta = current_nano_time - old_state .last_nano_time
52
- decay_factor = math .exp (- nano_time_delta * self ._inverse_adaptation_time_nanos )
52
+ decay_factor = math .exp (- nano_time_delta * self ._inverse_adaptation_time_nanoseconds )
53
53
current_effective_window_count = old_state .effective_window_count * decay_factor + 1
54
- current_effective_window_nanos = old_state .effective_window_nanos * decay_factor + nano_time_delta
55
-
56
- return _State (current_effective_window_count , current_effective_window_nanos , current_nano_time )
54
+ current_effective_window_nanoseconds = old_state .effective_window_nanoseconds * decay_factor + nano_time_delta
55
+
56
+ return _State (current_effective_window_count , current_effective_window_nanoseconds , current_nano_time )
57
57
58
58
def get (self ) -> float :
59
- """Get the current sampling percentage (0.0 to 100.0)."""
60
59
current_nano_time = self ._nano_time_supplier ()
61
-
60
+
62
61
with self ._lock :
63
62
old_state = self ._state
64
- self ._state = self ._update_state (self . _state , current_nano_time )
63
+ self ._state = self ._update_state (old_state , current_nano_time )
65
64
current_state = self ._state
66
-
65
+
67
66
# Calculate sampling probability based on current state
68
67
if current_state .effective_window_count == 0 :
69
68
return 100.0
70
-
69
+
71
70
sampling_probability = (
72
- (current_state .effective_window_nanos * self ._target_spans_per_nanosecond_limit ) /
71
+ (current_state .effective_window_nanoseconds * self ._target_spans_per_nanosecond_limit ) /
73
72
current_state .effective_window_count
74
73
)
75
-
74
+
76
75
sampling_percentage = 100 * min (sampling_probability , 1.0 )
77
-
76
+
78
77
if self ._round_to_nearest :
79
78
sampling_percentage = _round_down_to_nearest (sampling_percentage )
80
-
79
+
81
80
return sampling_percentage
82
81
83
82
@@ -96,11 +95,11 @@ def should_sample(
96
95
links : Optional [Sequence ["Link" ]] = None ,
97
96
trace_state : Optional ["TraceState" ] = None ,
98
97
) -> "SamplingResult" :
99
-
98
+
100
99
if parent_context is not None :
101
100
parent_span = get_current_span (parent_context )
102
101
parent_span_context = parent_span .get_span_context ()
103
-
102
+
104
103
# Check if parent is valid and local (not remote)
105
104
if parent_span_context .is_valid and not parent_span_context .is_remote :
106
105
# Check if parent was dropped/record-only first
@@ -111,50 +110,50 @@ def should_sample(
111
110
else :
112
111
new_attributes = dict (attributes )
113
112
new_attributes [_SAMPLE_RATE_KEY ] = 0.0
114
-
113
+
115
114
return SamplingResult (
116
115
Decision .DROP ,
117
116
new_attributes ,
118
117
_get_parent_trace_state (parent_context ),
119
118
)
120
-
119
+
121
120
# Parent is recording, check for sample rate attribute
122
121
parent_attributes = getattr (parent_span , 'attributes' , {})
123
122
parent_sample_rate = parent_attributes .get (_SAMPLE_RATE_KEY )
124
-
123
+
125
124
if parent_sample_rate is not None :
126
125
# Honor parent's sampling rate
127
126
if attributes is None :
128
127
new_attributes = {}
129
128
else :
130
129
new_attributes = dict (attributes )
131
130
new_attributes [_SAMPLE_RATE_KEY ] = parent_sample_rate
132
-
131
+
133
132
return SamplingResult (
134
133
Decision .RECORD_AND_SAMPLE ,
135
134
new_attributes ,
136
135
_get_parent_trace_state (parent_context ),
137
136
)
138
-
137
+
139
138
sampling_percentage = self ._sampling_percentage_generator .get ()
140
139
sampling_score = _get_djb2_sample_score (format_trace_id (trace_id ).lower ())
141
-
140
+
142
141
if sampling_score < sampling_percentage :
143
142
decision = Decision .RECORD_AND_SAMPLE
144
143
else :
145
144
decision = Decision .DROP
146
-
145
+
147
146
if attributes is None :
148
147
new_attributes = {}
149
148
else :
150
149
new_attributes = dict (attributes )
151
150
new_attributes [_SAMPLE_RATE_KEY ] = sampling_percentage
152
-
151
+
153
152
return SamplingResult (
154
153
decision ,
155
154
new_attributes ,
156
155
_get_parent_trace_state (parent_context ),
157
156
)
158
157
159
158
def get_description (self ) -> str :
160
- return self ._description
159
+ return self ._description
0 commit comments