@@ -63,6 +63,7 @@ def advance_time(self, nanoseconds_increment: int):
63
63
def get_current_time_nanoseconds (self ) -> int :
64
64
return self .current_time
65
65
66
+ # Test sampling behavior with a high target rate and moderate span frequency
66
67
def test_constant_rate_sampling (self ):
67
68
target_rate = 1000.0
68
69
sampler = RateLimitedSampler (target_rate )
@@ -95,6 +96,7 @@ def test_constant_rate_sampling(self):
95
96
96
97
self .assertGreater (sampled_count , 0 , "Should sample some spans with high target rate" )
97
98
99
+ # Test throttling behavior under high volume of spans with low target rate
98
100
def test_high_volume_sampling (self ):
99
101
target_rate = 5.0
100
102
sampler = RateLimitedSampler (target_rate )
@@ -127,6 +129,7 @@ def test_high_volume_sampling(self):
127
129
self .assertGreater (sampled_count , 0 , "Should sample at least some spans even under high load" )
128
130
self .assertLess (sampled_count , num_spans * 0.1 , "Should throttle significantly under high load" )
129
131
132
+ # Test adaptive sampling when span arrival rate increases over time
130
133
def test_rate_adaptation_increasing_load (self ):
131
134
target_rate = 20.0
132
135
sampler = RateLimitedSampler (target_rate )
@@ -171,6 +174,7 @@ def test_rate_adaptation_increasing_load(self):
171
174
self .assertLess (phase2_percentage , phase1_percentage ,
172
175
"Sampling percentage should decrease under high load" )
173
176
177
+ # Test sampler instantiation with various target rates and description format
174
178
def test_sampler_creation (self ):
175
179
for target_rate in [0.1 , 0.5 , 1.0 , 5.0 , 100.0 ]:
176
180
sampler = RateLimitedSampler (target_rate )
@@ -180,10 +184,12 @@ def test_sampler_creation(self):
180
184
f"RateLimitedSampler{{{ target_rate } }}"
181
185
)
182
186
187
+ # Test that negative target rates raise a ValueError
183
188
def test_negative_rate_raises_error (self ):
184
189
with self .assertRaises (ValueError ):
185
190
RateLimitedSampler (- 1.0 )
186
191
192
+ # Test sampling behavior with zero target rate
187
193
def test_zero_rate_sampling (self ):
188
194
sampler = RateLimitedSampler (0.0 )
189
195
@@ -198,6 +204,7 @@ def test_zero_rate_sampling(self):
198
204
self .assertIn (result .decision , [Decision .RECORD_AND_SAMPLE , Decision .DROP ])
199
205
self .assertIn (_SAMPLE_RATE_KEY , result .attributes )
200
206
207
+ # Test that the same trace ID produces consistent sampling decisions
201
208
def test_sampling_decision_consistency (self ):
202
209
sampler = RateLimitedSampler (50.0 )
203
210
@@ -217,6 +224,7 @@ def test_sampling_decision_consistency(self):
217
224
self .assertEqual (result .decision , first_decision ,
218
225
"Sampling decision should be consistent for same trace ID" )
219
226
227
+ # Test that sampling results include valid sample rate attributes
220
228
def test_sampling_attributes (self ):
221
229
sampler = RateLimitedSampler (25.0 )
222
230
@@ -235,6 +243,7 @@ def test_sampling_attributes(self):
235
243
self .assertGreaterEqual (float (sample_rate ), 0.0 )
236
244
self .assertLessEqual (float (sample_rate ), 100.0 )
237
245
246
+ # Test sampling behavior with edge case trace ID values
238
247
def test_sampler_with_extreme_trace_ids (self ):
239
248
sampler = RateLimitedSampler (1.0 )
240
249
@@ -264,6 +273,7 @@ def test_sampler_with_extreme_trace_ids(self):
264
273
self .assertGreaterEqual (float (sample_rate ), 0.0 )
265
274
self .assertLessEqual (float (sample_rate ), 100.0 )
266
275
276
+ # Test that sampler is thread-safe under concurrent access
267
277
def test_thread_safety (self ):
268
278
sampler = RateLimitedSampler (10.0 )
269
279
results = []
@@ -293,6 +303,7 @@ def worker():
293
303
self .assertIn (result .decision , [Decision .RECORD_AND_SAMPLE , Decision .DROP ])
294
304
self .assertIn (_SAMPLE_RATE_KEY , result .attributes )
295
305
306
+ # Test inheriting sampling decision from sampled parent span with sample rate
296
307
def test_parent_span_sampled_with_sample_rate (self ):
297
308
sampler = RateLimitedSampler (10.0 )
298
309
@@ -310,6 +321,7 @@ def test_parent_span_sampled_with_sample_rate(self):
310
321
self .assertEqual (result .decision , Decision .RECORD_AND_SAMPLE )
311
322
self .assertEqual (result .attributes [_SAMPLE_RATE_KEY ], 75.0 )
312
323
324
+ # Test inheriting sampling decision from non-sampled parent span with sample rate
313
325
def test_parent_span_not_sampled_with_sample_rate (self ):
314
326
sampler = RateLimitedSampler (10.0 )
315
327
@@ -327,6 +339,7 @@ def test_parent_span_not_sampled_with_sample_rate(self):
327
339
self .assertEqual (result .decision , Decision .DROP )
328
340
self .assertEqual (result .attributes [_SAMPLE_RATE_KEY ], 0.0 )
329
341
342
+ # Test parent span with 100% sample rate maintains decision
330
343
def test_parent_span_sampled_with_100_percent_sample_rate (self ):
331
344
sampler = RateLimitedSampler (5.0 )
332
345
@@ -344,6 +357,7 @@ def test_parent_span_sampled_with_100_percent_sample_rate(self):
344
357
self .assertEqual (result .decision , Decision .RECORD_AND_SAMPLE )
345
358
self .assertEqual (result .attributes [_SAMPLE_RATE_KEY ], 100.0 )
346
359
360
+ # Test that remote parent spans are ignored for sampling decisions
347
361
def test_parent_span_remote_ignored (self ):
348
362
sampler = RateLimitedSampler (5.0 )
349
363
@@ -366,6 +380,7 @@ def test_parent_span_remote_ignored(self):
366
380
367
381
self .assertNotEqual (result .attributes [_SAMPLE_RATE_KEY ], 80.0 )
368
382
383
+ # Test parent span without sample rate attribute uses local sampling
369
384
def test_parent_span_no_sample_rate_attribute (self ):
370
385
sampler = RateLimitedSampler (5.0 )
371
386
@@ -390,6 +405,7 @@ def test_parent_span_no_sample_rate_attribute(self):
390
405
sample_rate = result .attributes [_SAMPLE_RATE_KEY ]
391
406
self .assertIsInstance (sample_rate , (int , float ))
392
407
408
+ # Test handling parent span with invalid span context
393
409
def test_parent_span_invalid_context (self ):
394
410
sampler = RateLimitedSampler (5.0 )
395
411
@@ -418,6 +434,7 @@ def test_parent_span_invalid_context(self):
418
434
sample_rate = result .attributes [_SAMPLE_RATE_KEY ]
419
435
self .assertIsInstance (sample_rate , (int , float ))
420
436
437
+ # Test sampling behavior when no parent context is provided
421
438
def test_no_parent_context_uses_local_sampling (self ):
422
439
sampler = RateLimitedSampler (5.0 )
423
440
@@ -437,6 +454,7 @@ def test_no_parent_context_uses_local_sampling(self):
437
454
sample_rate = result .attributes [_SAMPLE_RATE_KEY ]
438
455
self .assertIsInstance (sample_rate , (int , float ))
439
456
457
+ # Test that original span attributes are preserved in sampling result
440
458
def test_parent_context_preserves_original_attributes (self ):
441
459
sampler = RateLimitedSampler (10.0 )
442
460
@@ -464,6 +482,7 @@ def test_parent_context_preserves_original_attributes(self):
464
482
465
483
class TestUtilityFunctions (unittest .TestCase ):
466
484
485
+ # Test that DJB2 hash produces consistent results for the same input
467
486
def test_djb2_hash_consistency (self ):
468
487
from azure .monitor .opentelemetry .exporter .export .trace ._utils import _get_DJB2_sample_score
469
488
@@ -473,6 +492,7 @@ def test_djb2_hash_consistency(self):
473
492
474
493
self .assertTrue (all (score == scores [0 ] for score in scores ))
475
494
495
+ # Test DJB2 hash function with edge case inputs
476
496
def test_djb2_hash_edge_cases (self ):
477
497
from azure .monitor .opentelemetry .exporter .export .trace ._utils import _get_DJB2_sample_score
478
498
0 commit comments