1use crate::nanos::Nanos;
2use core::ops::Add;
3use core::time::Duration;
4#[cfg(feature = "jitter")]
5use rand::distr::uniform::{SampleBorrow, SampleUniform, UniformInt, UniformSampler};
6#[cfg(feature = "jitter")]
7use rand::distr::{Distribution, Uniform};
8#[cfg(feature = "jitter")]
9use rand::{rng, Rng};
10
11#[cfg(feature = "std")]
12use std::time::Instant;
13
14#[derive(Debug, PartialEq, Eq, Default, Clone, Copy)]
59pub struct Jitter {
60 min: Nanos,
61 max: Nanos,
62}
63
64impl Jitter {
65 #[cfg(feature = "std")]
66 pub(crate) const NONE: Jitter = Jitter {
68 min: Nanos::new(0),
69 max: Nanos::new(0),
70 };
71
72 #[cfg(any(all(feature = "jitter", not(feature = "no_std")), feature = "std"))]
87 pub fn up_to(max: Duration) -> Jitter {
88 Jitter {
89 min: Nanos::from(0),
90 max: max.into(),
91 }
92 }
93
94 #[cfg(any(all(feature = "jitter", not(feature = "no_std")), feature = "std"))]
96 pub fn new(min: Duration, interval: Duration) -> Jitter {
97 let min: Nanos = min.into();
98 let max: Nanos = min + Nanos::from(interval);
99 Jitter { min, max }
100 }
101
102 #[cfg(feature = "jitter")]
104 pub(crate) fn get(&self) -> Nanos {
105 if self.min == self.max {
106 return self.min;
107 }
108 let uniform =
109 Uniform::new(self.min, self.max).expect("range is large enough for a distribution");
110 uniform.sample(&mut rng())
111 }
112
113 #[cfg(not(feature = "jitter"))]
115 pub(crate) fn get(&self) -> Nanos {
116 self.min
117 }
118}
119
120#[cfg(feature = "jitter")]
122#[derive(Clone, Copy, Debug)]
123pub struct UniformJitter(UniformInt<u64>);
124
125#[cfg(feature = "jitter")]
126impl UniformSampler for UniformJitter {
127 type X = Nanos;
128
129 fn new<B1, B2>(low: B1, high: B2) -> Result<Self, rand::distr::uniform::Error>
130 where
131 B1: SampleBorrow<Self::X> + Sized,
132 B2: SampleBorrow<Self::X> + Sized,
133 {
134 Ok(UniformJitter(UniformInt::new(
135 low.borrow().as_u64(),
136 high.borrow().as_u64(),
137 )?))
138 }
139
140 fn new_inclusive<B1, B2>(low: B1, high: B2) -> Result<Self, rand::distr::uniform::Error>
141 where
142 B1: SampleBorrow<Self::X> + Sized,
143 B2: SampleBorrow<Self::X> + Sized,
144 {
145 Ok(UniformJitter(UniformInt::new(
146 low.borrow().as_u64(),
147 high.borrow().as_u64(),
148 )?))
149 }
150
151 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::X {
152 Nanos::from(self.0.sample(rng))
153 }
154}
155
156#[cfg(feature = "jitter")]
157impl SampleUniform for Nanos {
158 type Sampler = UniformJitter;
159}
160
161impl Add<Duration> for Jitter {
162 type Output = Duration;
163
164 fn add(self, rhs: Duration) -> Duration {
165 let amount: Duration = self.get().into();
166 rhs + amount
167 }
168}
169
170impl Add<Nanos> for Jitter {
171 type Output = Nanos;
172
173 fn add(self, rhs: Nanos) -> Nanos {
174 rhs + self.get()
175 }
176}
177
178#[cfg(feature = "std")]
179impl Add<Instant> for Jitter {
180 type Output = Instant;
181
182 fn add(self, rhs: Instant) -> Instant {
183 let amount: Duration = self.get().into();
184 rhs + amount
185 }
186}
187
188#[cfg(all(feature = "jitter", not(feature = "no_std"), test))]
189mod test {
190 use super::*;
191
192 #[test]
193 fn jitter_impl_coverage() {
194 let basic = Jitter::up_to(Duration::from_secs(20));
195 let verbose = Jitter::new(Duration::from_secs(0), Duration::from_secs(20));
196 assert_eq!(basic, verbose);
197 }
198
199 #[test]
200 fn uniform_sampler_coverage() {
201 let low = Duration::from_secs(0);
202 let high = Duration::from_secs(20);
203 let sampler = UniformJitter::new_inclusive(Nanos::from(low), Nanos::from(high));
204 assert!(!format!("{sampler:?}").is_empty());
205 assert!(!format!("{:?}", sampler.clone()).is_empty());
206 }
207}