ringkernel_procint/fabric/
anomaly_injection.rs1#[derive(Debug, Clone)]
7pub struct AnomalyConfig {
8 pub bottleneck_rate: f32,
10 pub rework_rate: f32,
12 pub long_running_rate: f32,
14 pub skip_rate: f32,
16 pub bottleneck_multiplier: f32,
18 pub long_running_multiplier: f32,
20 pub max_rework_iterations: u32,
22}
23
24impl Default for AnomalyConfig {
25 fn default() -> Self {
26 Self {
27 bottleneck_rate: 0.05,
28 rework_rate: 0.03,
29 long_running_rate: 0.02,
30 skip_rate: 0.01,
31 bottleneck_multiplier: 5.0,
32 long_running_multiplier: 10.0,
33 max_rework_iterations: 3,
34 }
35 }
36}
37
38impl AnomalyConfig {
39 pub fn none() -> Self {
41 Self {
42 bottleneck_rate: 0.0,
43 rework_rate: 0.0,
44 long_running_rate: 0.0,
45 skip_rate: 0.0,
46 ..Default::default()
47 }
48 }
49
50 pub fn high() -> Self {
52 Self {
53 bottleneck_rate: 0.15,
54 rework_rate: 0.10,
55 long_running_rate: 0.08,
56 skip_rate: 0.05,
57 ..Default::default()
58 }
59 }
60
61 pub fn total_rate(&self) -> f32 {
63 self.bottleneck_rate + self.rework_rate + self.long_running_rate + self.skip_rate
64 }
65
66 pub fn with_bottleneck_rate(mut self, rate: f32) -> Self {
68 self.bottleneck_rate = rate.clamp(0.0, 1.0);
69 self
70 }
71
72 pub fn with_rework_rate(mut self, rate: f32) -> Self {
74 self.rework_rate = rate.clamp(0.0, 1.0);
75 self
76 }
77
78 pub fn with_long_running_rate(mut self, rate: f32) -> Self {
80 self.long_running_rate = rate.clamp(0.0, 1.0);
81 self
82 }
83
84 pub fn with_skip_rate(mut self, rate: f32) -> Self {
86 self.skip_rate = rate.clamp(0.0, 1.0);
87 self
88 }
89}
90
91#[derive(Debug, Clone)]
93pub struct AnomalyInjector {
94 config: AnomalyConfig,
95}
96
97impl AnomalyInjector {
98 pub fn new(config: AnomalyConfig) -> Self {
100 Self { config }
101 }
102
103 pub fn config(&self) -> &AnomalyConfig {
105 &self.config
106 }
107
108 pub fn should_inject_bottleneck(&self, random: f32) -> bool {
110 random < self.config.bottleneck_rate
111 }
112
113 pub fn should_inject_rework(&self, random: f32) -> bool {
115 random < self.config.bottleneck_rate + self.config.rework_rate
116 && random >= self.config.bottleneck_rate
117 }
118
119 pub fn should_inject_long_running(&self, random: f32) -> bool {
121 let threshold = self.config.bottleneck_rate + self.config.rework_rate;
122 random < threshold + self.config.long_running_rate && random >= threshold
123 }
124
125 pub fn should_inject_skip(&self, random: f32) -> bool {
127 let threshold =
128 self.config.bottleneck_rate + self.config.rework_rate + self.config.long_running_rate;
129 random < threshold + self.config.skip_rate && random >= threshold
130 }
131
132 pub fn apply_bottleneck(&self, base_duration: u32) -> u32 {
134 (base_duration as f32 * self.config.bottleneck_multiplier) as u32
135 }
136
137 pub fn apply_long_running(&self, base_duration: u32) -> u32 {
139 (base_duration as f32 * self.config.long_running_multiplier) as u32
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_default_config() {
149 let config = AnomalyConfig::default();
150 assert!(config.total_rate() > 0.0);
151 assert!(config.total_rate() < 0.2);
152 }
153
154 #[test]
155 fn test_no_anomalies() {
156 let config = AnomalyConfig::none();
157 assert_eq!(config.total_rate(), 0.0);
158 }
159
160 #[test]
161 fn test_injector() {
162 let injector = AnomalyInjector::new(AnomalyConfig::default());
163
164 assert!(injector.should_inject_bottleneck(0.01));
166 assert!(!injector.should_inject_bottleneck(0.99));
167
168 let base = 1000;
170 let bottleneck = injector.apply_bottleneck(base);
171 assert!(bottleneck > base);
172 }
173}