1#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum TimeUnit {
15 Nanos,
16 Micros,
17 Millis,
18 Seconds,
19}
20
21impl TimeUnit {
22 pub fn for_rate(ops_per_sec: f64) -> Self {
24 if ops_per_sec > 1.0 {
25 TimeUnit::Nanos
26 } else if ops_per_sec > 0.001 {
27 TimeUnit::Micros
28 } else if ops_per_sec > 0.000001 {
29 TimeUnit::Millis
30 } else {
31 TimeUnit::Seconds
32 }
33 }
34
35 pub fn nanos_to_ticks(self, nanos: u64) -> u32 {
37 let ticks = match self {
38 TimeUnit::Nanos => nanos,
39 TimeUnit::Micros => nanos / 1_000,
40 TimeUnit::Millis => nanos / 1_000_000,
41 TimeUnit::Seconds => nanos / 1_000_000_000,
42 };
43 ticks.min(u32::MAX as u64) as u32
44 }
45
46 pub fn ticks_to_nanos(self, ticks: u32) -> u64 {
48 match self {
49 TimeUnit::Nanos => ticks as u64,
50 TimeUnit::Micros => ticks as u64 * 1_000,
51 TimeUnit::Millis => ticks as u64 * 1_000_000,
52 TimeUnit::Seconds => ticks as u64 * 1_000_000_000,
53 }
54 }
55
56 pub fn ticks_per_op(self, ops_per_sec: f64) -> u32 {
58 let ticks = match self {
59 TimeUnit::Nanos => 1_000_000_000.0 / ops_per_sec,
60 TimeUnit::Micros => 1_000_000.0 / ops_per_sec,
61 TimeUnit::Millis => 1_000.0 / ops_per_sec,
62 TimeUnit::Seconds => 1.0 / ops_per_sec,
63 };
64 (ticks as u64).min(u32::MAX as u64) as u32
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq)]
70pub enum Verb {
71 Start,
73 Configure,
75 Restart,
77 Stop,
79}
80
81#[derive(Debug, Clone)]
83pub struct RateSpec {
84 pub ops_per_sec: f64,
85 pub burst_ratio: f64,
86 pub verb: Verb,
87 pub unit: TimeUnit,
88}
89
90impl RateSpec {
91 pub fn new(ops_per_sec: f64) -> Self {
93 Self::with_burst(ops_per_sec, 1.1)
94 }
95
96 pub fn with_burst(ops_per_sec: f64, burst_ratio: f64) -> Self {
98 Self {
99 ops_per_sec,
100 burst_ratio,
101 verb: Verb::Start,
102 unit: TimeUnit::for_rate(ops_per_sec),
103 }
104 }
105
106 pub fn parse(spec: &str) -> Result<Self, String> {
113 let parts: Vec<&str> = spec.split(',').map(|s| s.trim()).collect();
114 if parts.is_empty() {
115 return Err("empty rate spec".into());
116 }
117
118 let ops_per_sec: f64 = parts[0].parse().map_err(|e| format!("invalid rate: {e}"))?;
119 if ops_per_sec <= 0.0 {
120 return Err("rate must be positive".into());
121 }
122
123 let burst_ratio = if parts.len() > 1 {
124 parts[1].parse::<f64>().unwrap_or(1.1)
125 } else {
126 1.1
127 };
128
129 let verb = if parts.len() > 2 {
130 match parts[2].to_lowercase().as_str() {
131 "start" => Verb::Start,
132 "configure" => Verb::Configure,
133 "restart" => Verb::Restart,
134 "stop" => Verb::Stop,
135 other => return Err(format!("unknown verb: {other}")),
136 }
137 } else {
138 Verb::Start
139 };
140
141 Ok(Self {
142 ops_per_sec,
143 burst_ratio,
144 verb,
145 unit: TimeUnit::for_rate(ops_per_sec),
146 })
147 }
148
149 pub fn ticks_per_op(&self) -> u32 {
151 self.unit.ticks_per_op(self.ops_per_sec)
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn time_unit_scaling() {
161 assert_eq!(TimeUnit::for_rate(50.0), TimeUnit::Nanos);
162 assert_eq!(TimeUnit::for_rate(0.5), TimeUnit::Micros);
163 assert_eq!(TimeUnit::for_rate(0.0005), TimeUnit::Millis);
164 assert_eq!(TimeUnit::for_rate(0.0000001), TimeUnit::Seconds);
165 }
166
167 #[test]
168 fn ticks_per_op_fits() {
169 let unit = TimeUnit::Nanos;
171 assert_eq!(unit.ticks_per_op(50.0), 20_000_000);
172
173 let unit = TimeUnit::Micros;
175 assert_eq!(unit.ticks_per_op(0.5), 2_000_000);
176 }
177
178 #[test]
179 fn nanos_ticks_roundtrip() {
180 let unit = TimeUnit::Micros;
181 let nanos = 5_000_000u64; let ticks = unit.nanos_to_ticks(nanos);
183 assert_eq!(ticks, 5000);
184 assert_eq!(unit.ticks_to_nanos(ticks), nanos);
185 }
186
187 #[test]
188 fn parse_simple() {
189 let spec = RateSpec::parse("1000").unwrap();
190 assert_eq!(spec.ops_per_sec, 1000.0);
191 assert_eq!(spec.burst_ratio, 1.1);
192 assert_eq!(spec.verb, Verb::Start);
193 }
194
195 #[test]
196 fn parse_with_burst() {
197 let spec = RateSpec::parse("500, 1.5").unwrap();
198 assert_eq!(spec.ops_per_sec, 500.0);
199 assert_eq!(spec.burst_ratio, 1.5);
200 }
201
202 #[test]
203 fn parse_with_verb() {
204 let spec = RateSpec::parse("1000, 1.1, restart").unwrap();
205 assert_eq!(spec.verb, Verb::Restart);
206 }
207
208 #[test]
209 fn parse_errors() {
210 assert!(RateSpec::parse("").is_err());
211 assert!(RateSpec::parse("-1").is_err());
212 assert!(RateSpec::parse("abc").is_err());
213 }
214}