Line data Source code
1 : // Licensed under the Apache License, Version 2.0
2 : // Copyright 2025, Michael Bushe, All rights reserved.
3 :
4 : import 'dart:math' as math;
5 :
6 : import '../../../dartastic_opentelemetry.dart';
7 :
8 : /// A sampler that samples traces based on a probability defined by the ratio of
9 : /// traces that should be sampled. The ratio must be in the range [0.0, 1.0].
10 : ///
11 : /// Uses the lowest 8 bytes of the trace ID to make a sampling decision.
12 : class TraceIdRatioSampler implements Sampler {
13 : /// The sampling ratio, a value between 0.0 and 1.0 that determines
14 : /// the probability that a trace will be sampled.
15 : final double ratio;
16 :
17 2 : @override
18 4 : String get description => 'TraceIdRatioSampler{$ratio}';
19 :
20 : /// Creates a TraceIdRatioSampler with the given ratio.
21 : /// [ratio] must be in the range [0.0, 1.0].
22 3 : TraceIdRatioSampler(this.ratio) {
23 12 : if (ratio < 0.0 || ratio > 1.0) {
24 1 : throw ArgumentError('ratio must be in range [0.0, 1.0]');
25 : }
26 : }
27 :
28 : /// Converts a trace ID to a value between 0.0 and 1.0 for sampling decisions.
29 : /// Uses the lowest 8 bytes (16 hex characters) of the trace ID as per the specification.
30 : ///
31 : /// @param traceId The trace ID as a hex string
32 : /// @return A value between 0.0 and 1.0 based on the trace ID
33 3 : double _traceIdToValue(String traceId) {
34 : // Get the last 16 hex chars (8 bytes) of trace ID
35 12 : final lastBytes = traceId.substring(math.max(0, traceId.length - 16));
36 :
37 : // Parse hex string to integer with BigInt to avoid precision issues
38 3 : final value = BigInt.parse(lastBytes, radix: 16);
39 :
40 : // Maximum possible value for 8 bytes (64 bits) is 2^64 - 1
41 3 : final maxValue = BigInt.parse('ffffffffffffffff', radix: 16);
42 :
43 : // Convert to a double between 0.0 and 1.0
44 9 : return value.toDouble() / maxValue.toDouble();
45 : }
46 :
47 3 : @override
48 : SamplingResult shouldSample({
49 : required Context parentContext,
50 : required String traceId,
51 : required String name,
52 : required SpanKind spanKind,
53 : required Attributes? attributes,
54 : required List<SpanLink>? links,
55 : }) {
56 : // If ratio is 0, never sample
57 6 : if (ratio == 0.0) {
58 : return const SamplingResult(
59 : decision: SamplingDecision.drop,
60 : source: SamplingDecisionSource.tracerConfig,
61 : );
62 : }
63 :
64 : // If ratio is 1, always sample
65 6 : if (ratio == 1.0) {
66 : return const SamplingResult(
67 : decision: SamplingDecision.recordAndSample,
68 : source: SamplingDecisionSource.tracerConfig,
69 : );
70 : }
71 :
72 : // Convert trace ID to a value between 0.0 and 1.0
73 3 : final value = _traceIdToValue(traceId);
74 :
75 : // If the value is less than our ratio, we should sample
76 6 : final shouldSample = value < ratio;
77 :
78 3 : return SamplingResult(
79 : decision: shouldSample
80 : ? SamplingDecision.recordAndSample
81 : : SamplingDecision.drop,
82 : source: SamplingDecisionSource.tracerConfig,
83 : );
84 : }
85 : }
|