grafos_observe/
sampling.rs1use crate::trace::TraceContext;
9
10#[derive(Debug, Clone, PartialEq)]
12pub enum SamplingStrategy {
13 AlwaysOn,
15 AlwaysOff,
17 HeadBased,
20 RateBased {
22 traces_per_sec: u32,
24 },
25}
26
27#[derive(Debug, Clone)]
29pub struct SamplingConfig {
30 pub rate: f64,
32 pub strategy: SamplingStrategy,
34}
35
36impl Default for SamplingConfig {
37 fn default() -> Self {
39 SamplingConfig {
40 rate: 1.0,
41 strategy: SamplingStrategy::AlwaysOn,
42 }
43 }
44}
45
46pub struct Sampler {
48 config: SamplingConfig,
49 rate_count: u32,
51 rate_window_sec: u64,
53}
54
55impl Sampler {
56 pub fn new(config: SamplingConfig) -> Self {
58 Sampler {
59 config,
60 rate_count: 0,
61 rate_window_sec: 0,
62 }
63 }
64
65 pub fn should_sample(&mut self, trace_id_low: u64, now_unix_sec: u64) -> bool {
74 match &self.config.strategy {
75 SamplingStrategy::AlwaysOn => true,
76 SamplingStrategy::AlwaysOff => false,
77 SamplingStrategy::HeadBased => {
78 let hash_f = (trace_id_low as f64) / (u64::MAX as f64);
81 hash_f < self.config.rate
82 }
83 SamplingStrategy::RateBased { traces_per_sec } => {
84 if now_unix_sec != self.rate_window_sec {
85 self.rate_window_sec = now_unix_sec;
86 self.rate_count = 0;
87 }
88 if self.rate_count < *traces_per_sec {
89 self.rate_count += 1;
90 true
91 } else {
92 false
93 }
94 }
95 }
96 }
97
98 pub fn should_sample_child(parent: &TraceContext) -> bool {
102 parent.is_sampled()
103 }
104
105 pub fn config(&self) -> &SamplingConfig {
107 &self.config
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn always_on_samples_everything() {
117 let mut sampler = Sampler::new(SamplingConfig {
118 rate: 1.0,
119 strategy: SamplingStrategy::AlwaysOn,
120 });
121 for i in 0..100 {
122 assert!(sampler.should_sample(i, 1000));
123 }
124 }
125
126 #[test]
127 fn always_off_samples_nothing() {
128 let mut sampler = Sampler::new(SamplingConfig {
129 rate: 0.0,
130 strategy: SamplingStrategy::AlwaysOff,
131 });
132 for i in 0..100 {
133 assert!(!sampler.should_sample(i, 1000));
134 }
135 }
136
137 #[test]
138 fn head_based_rate_zero_samples_none() {
139 let mut sampler = Sampler::new(SamplingConfig {
140 rate: 0.0,
141 strategy: SamplingStrategy::HeadBased,
142 });
143 let mut sampled = 0;
145 for i in 0..1000u64 {
146 if sampler.should_sample(i, 1000) {
147 sampled += 1;
148 }
149 }
150 assert_eq!(sampled, 0);
151 }
152
153 #[test]
154 fn head_based_rate_one_samples_all() {
155 let mut sampler = Sampler::new(SamplingConfig {
156 rate: 1.0,
157 strategy: SamplingStrategy::HeadBased,
158 });
159 let mut sampled = 0;
160 for i in 0..1000u64 {
161 if sampler.should_sample(i, 1000) {
162 sampled += 1;
163 }
164 }
165 assert_eq!(sampled, 1000);
166 }
167
168 #[test]
169 fn head_based_approximately_half() {
170 let mut sampler = Sampler::new(SamplingConfig {
171 rate: 0.5,
172 strategy: SamplingStrategy::HeadBased,
173 });
174 let mut sampled = 0u64;
175 for i in 0..10_000u64 {
177 let trace_id_low = i
178 .wrapping_mul(6364136223846793005)
179 .wrapping_add(1442695040888963407);
180 if sampler.should_sample(trace_id_low, 1000) {
181 sampled += 1;
182 }
183 }
184 assert!(
186 sampled > 4000 && sampled < 6000,
187 "expected ~5000, got {sampled}"
188 );
189 }
190
191 #[test]
192 fn rate_based_caps_per_second() {
193 let mut sampler = Sampler::new(SamplingConfig {
194 rate: 1.0,
195 strategy: SamplingStrategy::RateBased { traces_per_sec: 5 },
196 });
197
198 for i in 0..5 {
200 assert!(sampler.should_sample(i, 100), "trace {i} should be sampled");
201 }
202 assert!(!sampler.should_sample(5, 100));
204
205 assert!(sampler.should_sample(6, 101));
207 }
208
209 #[test]
210 fn child_inherits_parent_sampling() {
211 let mut ctx = TraceContext::default();
212 ctx.set_sampled(true);
213 assert!(Sampler::should_sample_child(&ctx));
214
215 ctx.set_sampled(false);
216 assert!(!Sampler::should_sample_child(&ctx));
217 }
218
219 #[test]
220 fn default_config_is_always_on() {
221 let config = SamplingConfig::default();
222 assert_eq!(config.strategy, SamplingStrategy::AlwaysOn);
223 assert!((config.rate - 1.0).abs() < f64::EPSILON);
224 }
225}