Skip to main content

oxirs_core/sla/
class.rs

1//! SLA class definitions for per-tenant resource allocation.
2//!
3//! Defines four service tiers (Bronze → Platinum) with associated latency,
4//! concurrency, and token-bucket parameters.  Higher tiers receive more
5//! tokens, tighter latency budgets, and higher dispatch priority.
6
7use serde::{Deserialize, Serialize};
8
9use super::thresholds::SlaThresholds;
10
11// ─────────────────────────────────────────────────────────────────────────────
12// SlaClass
13// ─────────────────────────────────────────────────────────────────────────────
14
15/// Ordered SLA service tier.
16///
17/// `PartialOrd` / `Ord` are derived so that `Bronze < Silver < Gold < Platinum`
18/// — useful for comparisons and min/max in priority logic.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
20pub enum SlaClass {
21    /// Lowest tier: shared resources, best-effort latency.
22    Bronze,
23    /// Standard tier: guaranteed baseline throughput.
24    Silver,
25    /// Premium tier: low latency, high concurrency.
26    Gold,
27    /// Highest tier: near-real-time, maximum concurrency.
28    Platinum,
29}
30
31impl SlaClass {
32    /// Return the resource thresholds for this tier.
33    pub fn thresholds(&self) -> SlaThresholds {
34        match self {
35            SlaClass::Bronze => SlaThresholds {
36                max_latency_p99_ms: 5_000,
37                max_concurrent_queries: 2,
38                bandwidth_mb_per_sec: 10.0,
39                token_refill_rate: 1.0,
40                token_bucket_capacity: 5.0,
41            },
42            SlaClass::Silver => SlaThresholds {
43                max_latency_p99_ms: 2_000,
44                max_concurrent_queries: 5,
45                bandwidth_mb_per_sec: 50.0,
46                token_refill_rate: 5.0,
47                token_bucket_capacity: 20.0,
48            },
49            SlaClass::Gold => SlaThresholds {
50                max_latency_p99_ms: 500,
51                max_concurrent_queries: 20,
52                bandwidth_mb_per_sec: 200.0,
53                token_refill_rate: 20.0,
54                token_bucket_capacity: 50.0,
55            },
56            SlaClass::Platinum => SlaThresholds {
57                max_latency_p99_ms: 100,
58                max_concurrent_queries: 100,
59                bandwidth_mb_per_sec: 1_000.0,
60                token_refill_rate: 100.0,
61                token_bucket_capacity: 200.0,
62            },
63        }
64    }
65
66    /// Dispatch priority used by [`crate::sla::priority_dispatcher::PriorityDispatcher`].
67    ///
68    /// Higher value ⇒ dequeued first by the max-heap.
69    pub fn dispatch_priority(&self) -> u8 {
70        match self {
71            SlaClass::Bronze => 1,
72            SlaClass::Silver => 2,
73            SlaClass::Gold => 3,
74            SlaClass::Platinum => 4,
75        }
76    }
77
78    /// Human-readable name for the tier.
79    pub fn name(&self) -> &'static str {
80        match self {
81            SlaClass::Bronze => "bronze",
82            SlaClass::Silver => "silver",
83            SlaClass::Gold => "gold",
84            SlaClass::Platinum => "platinum",
85        }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_thresholds_monotonically_increasing() {
95        let bronze = SlaClass::Bronze.thresholds();
96        let silver = SlaClass::Silver.thresholds();
97        let gold = SlaClass::Gold.thresholds();
98        let platinum = SlaClass::Platinum.thresholds();
99
100        // Latency budget tightens as tier increases
101        assert!(bronze.max_latency_p99_ms > silver.max_latency_p99_ms);
102        assert!(silver.max_latency_p99_ms > gold.max_latency_p99_ms);
103        assert!(gold.max_latency_p99_ms > platinum.max_latency_p99_ms);
104
105        // Concurrency, bandwidth, and token capacity all grow
106        assert!(bronze.max_concurrent_queries < silver.max_concurrent_queries);
107        assert!(silver.max_concurrent_queries < gold.max_concurrent_queries);
108        assert!(gold.max_concurrent_queries < platinum.max_concurrent_queries);
109
110        assert!(bronze.token_refill_rate < silver.token_refill_rate);
111        assert!(silver.token_refill_rate < gold.token_refill_rate);
112        assert!(gold.token_refill_rate < platinum.token_refill_rate);
113
114        assert!(bronze.token_bucket_capacity < silver.token_bucket_capacity);
115        assert!(silver.token_bucket_capacity < gold.token_bucket_capacity);
116        assert!(gold.token_bucket_capacity < platinum.token_bucket_capacity);
117    }
118
119    #[test]
120    fn test_dispatch_priority_ordered() {
121        assert!(SlaClass::Bronze.dispatch_priority() < SlaClass::Silver.dispatch_priority());
122        assert!(SlaClass::Silver.dispatch_priority() < SlaClass::Gold.dispatch_priority());
123        assert!(SlaClass::Gold.dispatch_priority() < SlaClass::Platinum.dispatch_priority());
124    }
125
126    #[test]
127    fn test_ord_derives_correctly() {
128        assert!(SlaClass::Bronze < SlaClass::Silver);
129        assert!(SlaClass::Silver < SlaClass::Gold);
130        assert!(SlaClass::Gold < SlaClass::Platinum);
131        assert!(SlaClass::Platinum > SlaClass::Bronze);
132    }
133
134    #[test]
135    fn test_names() {
136        assert_eq!(SlaClass::Bronze.name(), "bronze");
137        assert_eq!(SlaClass::Silver.name(), "silver");
138        assert_eq!(SlaClass::Gold.name(), "gold");
139        assert_eq!(SlaClass::Platinum.name(), "platinum");
140    }
141
142    #[test]
143    fn test_roundtrip_serialization() {
144        let original = SlaClass::Gold;
145        let json = serde_json::to_string(&original).expect("serialize");
146        let decoded: SlaClass = serde_json::from_str(&json).expect("deserialize");
147        assert_eq!(original, decoded);
148    }
149}