use std::num::NonZeroU32;
use std::time::Duration;
use crate::BackoffInterval;
pub type MillisNonZero = NonZeroU32;
#[inline]
pub(crate) const fn millis(ms: u32) -> MillisNonZero {
match NonZeroU32::new(ms) {
Some(v) => v,
None => panic!("millis value must be non-zero"),
}
}
pub(crate) const SIGNIFICANT_VALUE_DIGITS: u8 = 2;
#[derive(Debug, Clone, Copy)]
pub struct TrackerConfig {
pub window_ms: NonZeroU32,
pub min_samples: u32,
pub max_trackable_latency_ms: u32,
}
impl TrackerConfig {
#[inline]
pub fn window(&self) -> Duration {
Duration::from_millis(self.window_ms.get() as u64)
}
}
impl Default for TrackerConfig {
fn default() -> Self {
Self {
window_ms: millis(60_000),
min_samples: 3,
max_trackable_latency_ms: 60_000,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct TimeoutConfig {
pub backoff: BackoffInterval,
pub quantile: f64,
pub safety_factor: f64,
}
impl TimeoutConfig {
#[inline]
pub fn min_timeout(&self) -> Duration {
Duration::from_millis(self.backoff.min_ms.get() as u64)
}
#[inline]
pub fn max_timeout(&self) -> Duration {
Duration::from_millis(self.backoff.max_ms.get() as u64)
}
}
impl Default for TimeoutConfig {
fn default() -> Self {
Self {
backoff: BackoffInterval::default(),
quantile: 0.9999,
safety_factor: 2.0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem;
#[test]
fn timeout_config_is_compact() {
assert_eq!(mem::size_of::<TimeoutConfig>(), 24);
}
#[test]
fn tracker_config_is_compact() {
assert!(mem::size_of::<TrackerConfig>() <= 12);
}
#[test]
fn millis_helper() {
let m = millis(42);
assert_eq!(m.get(), 42);
}
#[test]
#[should_panic(expected = "non-zero")]
fn millis_zero_panics() {
let _ = millis(0);
}
#[test]
fn timeout_config_conversions() {
let cfg = TimeoutConfig::default();
assert_eq!(cfg.min_timeout(), Duration::from_millis(250));
assert_eq!(cfg.max_timeout(), Duration::from_millis(60_000));
}
#[test]
fn tracker_config_window_conversion() {
let cfg = TrackerConfig::default();
assert_eq!(cfg.window(), Duration::from_secs(60));
}
#[test]
fn timeout_config_from_str() {
let cfg: TimeoutConfig = "10ms..60s".parse().unwrap();
assert_eq!(cfg.backoff.min_ms.get(), 10);
assert_eq!(cfg.backoff.max_ms.get(), 60_000);
assert_eq!(cfg.quantile, 0.9999);
assert_eq!(cfg.safety_factor, 2.0);
}
#[test]
fn timeout_config_from_str_error() {
assert!("not-a-range".parse::<TimeoutConfig>().is_err());
}
#[cfg(feature = "serde")]
mod serde_tests {
use super::*;
#[test]
fn serialize_as_backoff_string() {
let cfg: TimeoutConfig = "10ms..60s".parse().unwrap();
let json = serde_json::to_string(&cfg).unwrap();
assert_eq!(json, r#""10ms..1m""#);
}
#[test]
fn deserialize_from_string() {
let cfg: serde_json::Result<TimeoutConfig> = serde_json::from_str(r#""250ms..1m""#);
let cfg = cfg.unwrap();
assert_eq!(cfg.backoff.min_ms.get(), 250);
assert_eq!(cfg.backoff.max_ms.get(), 60_000);
assert_eq!(cfg.quantile, 0.9999);
assert_eq!(cfg.safety_factor, 2.0);
}
#[test]
fn serde_round_trip() {
let original: TimeoutConfig = "100ms..30s".parse().unwrap();
let json = serde_json::to_string(&original).unwrap();
let restored: TimeoutConfig = serde_json::from_str(&json).unwrap();
assert_eq!(original.backoff, restored.backoff);
assert_eq!(original.quantile, restored.quantile);
assert_eq!(original.safety_factor, restored.safety_factor);
}
#[test]
fn deserialize_error_propagated() {
assert!(serde_json::from_str::<TimeoutConfig>(r#""bad""#).is_err());
}
}
}