pub struct ConservationChecker { /* private fields */ }Expand description
Tracker for one-sided conservation laws.
A ConservationChecker monitors named quantities that must not decrease
(beyond an optional tolerance). It records snapshots over time so you can
detect drift, phase transitions, and violations.
Implementations§
Source§impl ConservationChecker
impl ConservationChecker
Sourcepub fn new() -> Self
pub fn new() -> Self
Create a new, empty tracker.
§Example
use conservation_checker::ConservationChecker;
let checker = ConservationChecker::new();
assert!(checker.registered().is_empty());Examples found in repository?
3fn main() {
4 let mut checker = ConservationChecker::new();
5 checker.register("energy", 100.0, 5.0);
6
7 println!("Initial: energy = {}", checker.current_value("energy"));
8
9 checker.update("energy", 98.0);
10 checker.snapshot();
11 println!("After use: energy = {} (conserved: {})",
12 checker.current_value("energy"),
13 checker.is_conserved("energy"));
14
15 checker.update("energy", 90.0);
16 checker.snapshot();
17 println!("After heavy use: energy = {} (conserved: {})",
18 checker.current_value("energy"),
19 checker.is_conserved("energy"));
20 println!("Violations: {:?}", checker.violations());
21 println!("Phase: {}", checker.phase("energy"));
22 println!("Drift rate: {:.2}/snapshot", checker.drift_rate("energy"));
23}More examples
3fn main() {
4 let mut budget = ConservationChecker::new();
5
6 // Monthly budget: $5000, we allow going $100 over as a buffer
7 budget.register("monthly_budget", 5000.0, 100.0);
8
9 let expenses = [1200.0, 800.0, 1500.0, 900.0, 700.0, 350.0];
10
11 for (i, expense) in expenses.iter().enumerate() {
12 let remaining = budget.current_value("monthly_budget") - expense;
13 budget.update("monthly_budget", remaining);
14 budget.snapshot();
15
16 let phase = budget.phase("monthly_budget");
17 let drift = budget.drift_rate("monthly_budget");
18
19 println!(
20 "Day {}: spent ${:.0}, remaining ${:.0} | phase={:?}, drift={:.1}/day",
21 i + 1,
22 expense,
23 remaining,
24 phase,
25 drift,
26 );
27
28 if phase == Phase::Transitioning {
29 println!(" ⚠️ Budget is depleting fast!");
30 }
31 }
32
33 if !budget.is_conserved("monthly_budget") {
34 println!("\n❌ Budget violated! Over by ${:.0}",
35 budget.initial_value("monthly_budget") - budget.current_value("monthly_budget"));
36 println!(" Violations: {:?}", budget.violations());
37 } else {
38 println!("\n✅ Budget intact with ${:.0} remaining", budget.current_value("monthly_budget"));
39 }
40}38fn main() {
39 // ── Setup: 10-node cluster ──────────────────────────────────────
40 let node_names = [
41 "web-01", "web-02", "web-03",
42 "db-01", "db-02",
43 "worker-01", "worker-02", "worker-03",
44 "cache-01", "monitor-01",
45 ];
46
47 let mut nodes: Vec<Node> = node_names
48 .iter()
49 .enumerate()
50 .map(|(i, name)| {
51 let seed = i * 7;
52 Node {
53 name: name.to_string(),
54 cpu_percent: (60.0 + (seed % 20) as f64).min(100.0),
55 memory_gb: 16.0 + (seed % 8) as f64,
56 network_mbps: 100.0 + (seed % 50) as f64,
57 }
58 })
59 .collect();
60
61 // ── Conservation laws ────────────────────────────────────────────
62 // CPU must not drop below 30% on any node
63 // Memory must not exceed 48 GB (reverse conservation — this crate
64 // only checks "must not decrease", so we invert: track "memory_remaining")
65 // Network throughput must not drop below 10 Mbps
66 let mut cluster = ConservationChecker::new();
67
68 for node in &nodes {
69 cluster.register(format!("{}.cpu", node.name), node.cpu_percent, 0.0);
70 // Invert memory: track remaining = 64 - used, so low remaining = violation
71 let mem_remaining = 64.0 - node.memory_gb;
72 cluster.register(format!("{}.mem_remaining", node.name), mem_remaining, 0.0);
73 cluster.register(format!("{}.net", node.name), node.network_mbps, 10.0);
74 }
75
76 println!("╔══════════════════════════════════════════════════════════╗");
77 println!("║ SRE Cluster Monitoring Simulation (10 nodes, 20 ticks) ║");
78 println!("╚══════════════════════════════════════════════════════════╝");
79 println!();
80
81 // ── Static CPU minimum threshold ────────────────────────────────
82 const CPU_MIN: f64 = 30.0;
83
84 // ── Run simulation ──────────────────────────────────────────────
85 for tick_id in 0..20 {
86 tick(&mut nodes, tick_id);
87
88 // Update all conservation values
89 for node in &nodes {
90 cluster.update(&format!("{}.cpu", node.name), node.cpu_percent);
91 let mem_remaining = 64.0 - node.memory_gb;
92 cluster.update(&format!("{}.mem_remaining", node.name), mem_remaining);
93 cluster.update(&format!("{}.net", node.name), node.network_mbps);
94 }
95
96 cluster.snapshot(); // Take a snapshot every tick
97
98 // Collect violations for this tick
99 let violations = cluster.violations();
100
101 // Collect additional SRE-relevant stats
102 let mut cpu_alerts = Vec::new();
103 let mut mem_alerts = Vec::new();
104 let mut net_alerts = Vec::new();
105 let mut phases_report = Vec::new();
106
107 for node in &nodes {
108 let cpu_ok = cluster.is_conserved(&format!("{}.cpu", node.name));
109 let cpu_pct = cluster.current_value(&format!("{}.cpu", node.name));
110 if cpu_pct < CPU_MIN {
111 cpu_alerts.push(format!("{}@{}%", node.name, cpu_pct));
112 }
113
114 let mem_ok = cluster.is_conserved(&format!("{}.mem_remaining", node.name));
115 if !mem_ok {
116 let used = 64.0 - cluster.current_value(&format!("{}.mem_remaining", node.name));
117 mem_alerts.push(format!("{}@{:.1}GB", node.name, used));
118 }
119
120 let net_ok = cluster.is_conserved(&format!("{}.net", node.name));
121 if !net_ok {
122 net_alerts.push(format!(
123 "{}@{}Mbps",
124 node.name,
125 cluster.current_value(&format!("{}.net", node.name))
126 ));
127 }
128
129 // Track phases for nodes in transition
130 let cpu_phase = cluster.phase(&format!("{}.cpu", node.name));
131 let mem_phase = cluster.phase(&format!("{}.mem_remaining", node.name));
132 let net_phase = cluster.phase(&format!("{}.net", node.name));
133
134 if cpu_phase != Phase::Stable || mem_phase != Phase::Stable || net_phase != Phase::Stable {
135 phases_report.push(format!(
136 "{}: cpu={}, mem={}, net={}",
137 node.name, cpu_phase, mem_phase, net_phase
138 ));
139 }
140 }
141
142 // Print tick summary
143 if tick_id % 5 == 0 || !violations.is_empty() || !cpu_alerts.is_empty() || !mem_alerts.is_empty() {
144 println!("── Tick {:>2} ─────────────────────", tick_id);
145 println!(" Registered quantities: {}", cluster.registered().len());
146 println!(" Total violations: {}", violations.len());
147
148 if !cpu_alerts.is_empty() {
149 println!(" 🔴 CPU low: {}", cpu_alerts.join(", "));
150 }
151 if !mem_alerts.is_empty() {
152 println!(" 🟠 Memory high: {}", mem_alerts.join(", "));
153 }
154 if !net_alerts.is_empty() {
155 println!(" 🟡 Network low: {}", net_alerts.join(", "));
156 }
157 if !phases_report.is_empty() {
158 for line in &phases_report {
159 println!(" 📊 {}", line);
160 }
161 }
162 println!();
163 }
164 }
165
166 // ── Final summary ────────────────────────────────────────────────
167 println!("═══════════════════════════════════════════════════════════════");
168 println!(" 🏁 FINAL STATE");
169 println!("═══════════════════════════════════════════════════════════════");
170
171 let final_violations = cluster.violations();
172 println!(" Total violations at end: {}", final_violations.len());
173 for v in &final_violations {
174 println!(" ❌ {}", v);
175 }
176
177 // Phase distribution at end
178 println!();
179 println!(" Quantity phases at tick 20:");
180 for name in cluster.registered() {
181 let phase = cluster.phase(&name);
182 let current = cluster.current_value(&name);
183 let conserved = cluster.is_conserved(&name);
184 let drift = cluster.drift_rate(&name);
185 let snaps = cluster.snapshot_count(&name);
186 println!(
187 " {} | value={:.1}, conserved={}, phase={}, drift={:+.2}/tick, {} snapshots",
188 name, current, conserved, phase, drift, snaps
189 );
190 }
191
192 // ── Serde snapshot ───────────────────────────────────────────────
193 #[cfg(feature = "serde")]
194 {
195 println!();
196 println!(" ┌─ Serde JSON snapshot ──────────────────────────┐");
197 let json = cluster.snapshot_json();
198 println!(" {}", json.replace('\n', "\n "));
199 println!(" └────────────────────────────────────────────────┘");
200
201 // Demonstrate deserializing the whole ConservationChecker
202 let json_full = serde_json::to_string_pretty(&cluster).unwrap();
203 println!();
204 println!(" Full state (all history) available for archival:");
205 println!(" {} bytes", json_full.len());
206 }
207}Sourcepub fn register(
&mut self,
name: impl Into<String>,
initial_value: f64,
tolerance: f64,
)
pub fn register( &mut self, name: impl Into<String>, initial_value: f64, tolerance: f64, )
Register a named quantity with an initial value and tolerance.
tolerance is the maximum allowed decrease from the initial value
before the quantity is considered violated. Use 0.0 for strict
conservation.
§Example
use conservation_checker::ConservationChecker;
let mut checker = ConservationChecker::new();
checker.register("energy", 100.0, 5.0); // allow up to 5.0 decrease
checker.register("budget", 1000.0, 0.0); // strict: any decrease violates
assert!(checker.is_conserved("energy"));
assert!(checker.is_conserved("budget"));Examples found in repository?
3fn main() {
4 let mut checker = ConservationChecker::new();
5 checker.register("energy", 100.0, 5.0);
6
7 println!("Initial: energy = {}", checker.current_value("energy"));
8
9 checker.update("energy", 98.0);
10 checker.snapshot();
11 println!("After use: energy = {} (conserved: {})",
12 checker.current_value("energy"),
13 checker.is_conserved("energy"));
14
15 checker.update("energy", 90.0);
16 checker.snapshot();
17 println!("After heavy use: energy = {} (conserved: {})",
18 checker.current_value("energy"),
19 checker.is_conserved("energy"));
20 println!("Violations: {:?}", checker.violations());
21 println!("Phase: {}", checker.phase("energy"));
22 println!("Drift rate: {:.2}/snapshot", checker.drift_rate("energy"));
23}More examples
3fn main() {
4 let mut budget = ConservationChecker::new();
5
6 // Monthly budget: $5000, we allow going $100 over as a buffer
7 budget.register("monthly_budget", 5000.0, 100.0);
8
9 let expenses = [1200.0, 800.0, 1500.0, 900.0, 700.0, 350.0];
10
11 for (i, expense) in expenses.iter().enumerate() {
12 let remaining = budget.current_value("monthly_budget") - expense;
13 budget.update("monthly_budget", remaining);
14 budget.snapshot();
15
16 let phase = budget.phase("monthly_budget");
17 let drift = budget.drift_rate("monthly_budget");
18
19 println!(
20 "Day {}: spent ${:.0}, remaining ${:.0} | phase={:?}, drift={:.1}/day",
21 i + 1,
22 expense,
23 remaining,
24 phase,
25 drift,
26 );
27
28 if phase == Phase::Transitioning {
29 println!(" ⚠️ Budget is depleting fast!");
30 }
31 }
32
33 if !budget.is_conserved("monthly_budget") {
34 println!("\n❌ Budget violated! Over by ${:.0}",
35 budget.initial_value("monthly_budget") - budget.current_value("monthly_budget"));
36 println!(" Violations: {:?}", budget.violations());
37 } else {
38 println!("\n✅ Budget intact with ${:.0} remaining", budget.current_value("monthly_budget"));
39 }
40}38fn main() {
39 // ── Setup: 10-node cluster ──────────────────────────────────────
40 let node_names = [
41 "web-01", "web-02", "web-03",
42 "db-01", "db-02",
43 "worker-01", "worker-02", "worker-03",
44 "cache-01", "monitor-01",
45 ];
46
47 let mut nodes: Vec<Node> = node_names
48 .iter()
49 .enumerate()
50 .map(|(i, name)| {
51 let seed = i * 7;
52 Node {
53 name: name.to_string(),
54 cpu_percent: (60.0 + (seed % 20) as f64).min(100.0),
55 memory_gb: 16.0 + (seed % 8) as f64,
56 network_mbps: 100.0 + (seed % 50) as f64,
57 }
58 })
59 .collect();
60
61 // ── Conservation laws ────────────────────────────────────────────
62 // CPU must not drop below 30% on any node
63 // Memory must not exceed 48 GB (reverse conservation — this crate
64 // only checks "must not decrease", so we invert: track "memory_remaining")
65 // Network throughput must not drop below 10 Mbps
66 let mut cluster = ConservationChecker::new();
67
68 for node in &nodes {
69 cluster.register(format!("{}.cpu", node.name), node.cpu_percent, 0.0);
70 // Invert memory: track remaining = 64 - used, so low remaining = violation
71 let mem_remaining = 64.0 - node.memory_gb;
72 cluster.register(format!("{}.mem_remaining", node.name), mem_remaining, 0.0);
73 cluster.register(format!("{}.net", node.name), node.network_mbps, 10.0);
74 }
75
76 println!("╔══════════════════════════════════════════════════════════╗");
77 println!("║ SRE Cluster Monitoring Simulation (10 nodes, 20 ticks) ║");
78 println!("╚══════════════════════════════════════════════════════════╝");
79 println!();
80
81 // ── Static CPU minimum threshold ────────────────────────────────
82 const CPU_MIN: f64 = 30.0;
83
84 // ── Run simulation ──────────────────────────────────────────────
85 for tick_id in 0..20 {
86 tick(&mut nodes, tick_id);
87
88 // Update all conservation values
89 for node in &nodes {
90 cluster.update(&format!("{}.cpu", node.name), node.cpu_percent);
91 let mem_remaining = 64.0 - node.memory_gb;
92 cluster.update(&format!("{}.mem_remaining", node.name), mem_remaining);
93 cluster.update(&format!("{}.net", node.name), node.network_mbps);
94 }
95
96 cluster.snapshot(); // Take a snapshot every tick
97
98 // Collect violations for this tick
99 let violations = cluster.violations();
100
101 // Collect additional SRE-relevant stats
102 let mut cpu_alerts = Vec::new();
103 let mut mem_alerts = Vec::new();
104 let mut net_alerts = Vec::new();
105 let mut phases_report = Vec::new();
106
107 for node in &nodes {
108 let cpu_ok = cluster.is_conserved(&format!("{}.cpu", node.name));
109 let cpu_pct = cluster.current_value(&format!("{}.cpu", node.name));
110 if cpu_pct < CPU_MIN {
111 cpu_alerts.push(format!("{}@{}%", node.name, cpu_pct));
112 }
113
114 let mem_ok = cluster.is_conserved(&format!("{}.mem_remaining", node.name));
115 if !mem_ok {
116 let used = 64.0 - cluster.current_value(&format!("{}.mem_remaining", node.name));
117 mem_alerts.push(format!("{}@{:.1}GB", node.name, used));
118 }
119
120 let net_ok = cluster.is_conserved(&format!("{}.net", node.name));
121 if !net_ok {
122 net_alerts.push(format!(
123 "{}@{}Mbps",
124 node.name,
125 cluster.current_value(&format!("{}.net", node.name))
126 ));
127 }
128
129 // Track phases for nodes in transition
130 let cpu_phase = cluster.phase(&format!("{}.cpu", node.name));
131 let mem_phase = cluster.phase(&format!("{}.mem_remaining", node.name));
132 let net_phase = cluster.phase(&format!("{}.net", node.name));
133
134 if cpu_phase != Phase::Stable || mem_phase != Phase::Stable || net_phase != Phase::Stable {
135 phases_report.push(format!(
136 "{}: cpu={}, mem={}, net={}",
137 node.name, cpu_phase, mem_phase, net_phase
138 ));
139 }
140 }
141
142 // Print tick summary
143 if tick_id % 5 == 0 || !violations.is_empty() || !cpu_alerts.is_empty() || !mem_alerts.is_empty() {
144 println!("── Tick {:>2} ─────────────────────", tick_id);
145 println!(" Registered quantities: {}", cluster.registered().len());
146 println!(" Total violations: {}", violations.len());
147
148 if !cpu_alerts.is_empty() {
149 println!(" 🔴 CPU low: {}", cpu_alerts.join(", "));
150 }
151 if !mem_alerts.is_empty() {
152 println!(" 🟠 Memory high: {}", mem_alerts.join(", "));
153 }
154 if !net_alerts.is_empty() {
155 println!(" 🟡 Network low: {}", net_alerts.join(", "));
156 }
157 if !phases_report.is_empty() {
158 for line in &phases_report {
159 println!(" 📊 {}", line);
160 }
161 }
162 println!();
163 }
164 }
165
166 // ── Final summary ────────────────────────────────────────────────
167 println!("═══════════════════════════════════════════════════════════════");
168 println!(" 🏁 FINAL STATE");
169 println!("═══════════════════════════════════════════════════════════════");
170
171 let final_violations = cluster.violations();
172 println!(" Total violations at end: {}", final_violations.len());
173 for v in &final_violations {
174 println!(" ❌ {}", v);
175 }
176
177 // Phase distribution at end
178 println!();
179 println!(" Quantity phases at tick 20:");
180 for name in cluster.registered() {
181 let phase = cluster.phase(&name);
182 let current = cluster.current_value(&name);
183 let conserved = cluster.is_conserved(&name);
184 let drift = cluster.drift_rate(&name);
185 let snaps = cluster.snapshot_count(&name);
186 println!(
187 " {} | value={:.1}, conserved={}, phase={}, drift={:+.2}/tick, {} snapshots",
188 name, current, conserved, phase, drift, snaps
189 );
190 }
191
192 // ── Serde snapshot ───────────────────────────────────────────────
193 #[cfg(feature = "serde")]
194 {
195 println!();
196 println!(" ┌─ Serde JSON snapshot ──────────────────────────┐");
197 let json = cluster.snapshot_json();
198 println!(" {}", json.replace('\n', "\n "));
199 println!(" └────────────────────────────────────────────────┘");
200
201 // Demonstrate deserializing the whole ConservationChecker
202 let json_full = serde_json::to_string_pretty(&cluster).unwrap();
203 println!();
204 println!(" Full state (all history) available for archival:");
205 println!(" {} bytes", json_full.len());
206 }
207}Sourcepub fn update(&mut self, name: &str, value: f64)
pub fn update(&mut self, name: &str, value: f64)
Update the current value of a registered quantity.
Call this whenever the tracked quantity changes. After updating, use
is_conserved to check whether the new value
is still within tolerance.
§Panics
Panics if name has not been registered.
§Example
use conservation_checker::ConservationChecker;
let mut checker = ConservationChecker::new();
checker.register("tokens", 100.0, 0.0);
checker.update("tokens", 80.0);
assert!(!checker.is_conserved("tokens"));Examples found in repository?
3fn main() {
4 let mut checker = ConservationChecker::new();
5 checker.register("energy", 100.0, 5.0);
6
7 println!("Initial: energy = {}", checker.current_value("energy"));
8
9 checker.update("energy", 98.0);
10 checker.snapshot();
11 println!("After use: energy = {} (conserved: {})",
12 checker.current_value("energy"),
13 checker.is_conserved("energy"));
14
15 checker.update("energy", 90.0);
16 checker.snapshot();
17 println!("After heavy use: energy = {} (conserved: {})",
18 checker.current_value("energy"),
19 checker.is_conserved("energy"));
20 println!("Violations: {:?}", checker.violations());
21 println!("Phase: {}", checker.phase("energy"));
22 println!("Drift rate: {:.2}/snapshot", checker.drift_rate("energy"));
23}More examples
3fn main() {
4 let mut budget = ConservationChecker::new();
5
6 // Monthly budget: $5000, we allow going $100 over as a buffer
7 budget.register("monthly_budget", 5000.0, 100.0);
8
9 let expenses = [1200.0, 800.0, 1500.0, 900.0, 700.0, 350.0];
10
11 for (i, expense) in expenses.iter().enumerate() {
12 let remaining = budget.current_value("monthly_budget") - expense;
13 budget.update("monthly_budget", remaining);
14 budget.snapshot();
15
16 let phase = budget.phase("monthly_budget");
17 let drift = budget.drift_rate("monthly_budget");
18
19 println!(
20 "Day {}: spent ${:.0}, remaining ${:.0} | phase={:?}, drift={:.1}/day",
21 i + 1,
22 expense,
23 remaining,
24 phase,
25 drift,
26 );
27
28 if phase == Phase::Transitioning {
29 println!(" ⚠️ Budget is depleting fast!");
30 }
31 }
32
33 if !budget.is_conserved("monthly_budget") {
34 println!("\n❌ Budget violated! Over by ${:.0}",
35 budget.initial_value("monthly_budget") - budget.current_value("monthly_budget"));
36 println!(" Violations: {:?}", budget.violations());
37 } else {
38 println!("\n✅ Budget intact with ${:.0} remaining", budget.current_value("monthly_budget"));
39 }
40}38fn main() {
39 // ── Setup: 10-node cluster ──────────────────────────────────────
40 let node_names = [
41 "web-01", "web-02", "web-03",
42 "db-01", "db-02",
43 "worker-01", "worker-02", "worker-03",
44 "cache-01", "monitor-01",
45 ];
46
47 let mut nodes: Vec<Node> = node_names
48 .iter()
49 .enumerate()
50 .map(|(i, name)| {
51 let seed = i * 7;
52 Node {
53 name: name.to_string(),
54 cpu_percent: (60.0 + (seed % 20) as f64).min(100.0),
55 memory_gb: 16.0 + (seed % 8) as f64,
56 network_mbps: 100.0 + (seed % 50) as f64,
57 }
58 })
59 .collect();
60
61 // ── Conservation laws ────────────────────────────────────────────
62 // CPU must not drop below 30% on any node
63 // Memory must not exceed 48 GB (reverse conservation — this crate
64 // only checks "must not decrease", so we invert: track "memory_remaining")
65 // Network throughput must not drop below 10 Mbps
66 let mut cluster = ConservationChecker::new();
67
68 for node in &nodes {
69 cluster.register(format!("{}.cpu", node.name), node.cpu_percent, 0.0);
70 // Invert memory: track remaining = 64 - used, so low remaining = violation
71 let mem_remaining = 64.0 - node.memory_gb;
72 cluster.register(format!("{}.mem_remaining", node.name), mem_remaining, 0.0);
73 cluster.register(format!("{}.net", node.name), node.network_mbps, 10.0);
74 }
75
76 println!("╔══════════════════════════════════════════════════════════╗");
77 println!("║ SRE Cluster Monitoring Simulation (10 nodes, 20 ticks) ║");
78 println!("╚══════════════════════════════════════════════════════════╝");
79 println!();
80
81 // ── Static CPU minimum threshold ────────────────────────────────
82 const CPU_MIN: f64 = 30.0;
83
84 // ── Run simulation ──────────────────────────────────────────────
85 for tick_id in 0..20 {
86 tick(&mut nodes, tick_id);
87
88 // Update all conservation values
89 for node in &nodes {
90 cluster.update(&format!("{}.cpu", node.name), node.cpu_percent);
91 let mem_remaining = 64.0 - node.memory_gb;
92 cluster.update(&format!("{}.mem_remaining", node.name), mem_remaining);
93 cluster.update(&format!("{}.net", node.name), node.network_mbps);
94 }
95
96 cluster.snapshot(); // Take a snapshot every tick
97
98 // Collect violations for this tick
99 let violations = cluster.violations();
100
101 // Collect additional SRE-relevant stats
102 let mut cpu_alerts = Vec::new();
103 let mut mem_alerts = Vec::new();
104 let mut net_alerts = Vec::new();
105 let mut phases_report = Vec::new();
106
107 for node in &nodes {
108 let cpu_ok = cluster.is_conserved(&format!("{}.cpu", node.name));
109 let cpu_pct = cluster.current_value(&format!("{}.cpu", node.name));
110 if cpu_pct < CPU_MIN {
111 cpu_alerts.push(format!("{}@{}%", node.name, cpu_pct));
112 }
113
114 let mem_ok = cluster.is_conserved(&format!("{}.mem_remaining", node.name));
115 if !mem_ok {
116 let used = 64.0 - cluster.current_value(&format!("{}.mem_remaining", node.name));
117 mem_alerts.push(format!("{}@{:.1}GB", node.name, used));
118 }
119
120 let net_ok = cluster.is_conserved(&format!("{}.net", node.name));
121 if !net_ok {
122 net_alerts.push(format!(
123 "{}@{}Mbps",
124 node.name,
125 cluster.current_value(&format!("{}.net", node.name))
126 ));
127 }
128
129 // Track phases for nodes in transition
130 let cpu_phase = cluster.phase(&format!("{}.cpu", node.name));
131 let mem_phase = cluster.phase(&format!("{}.mem_remaining", node.name));
132 let net_phase = cluster.phase(&format!("{}.net", node.name));
133
134 if cpu_phase != Phase::Stable || mem_phase != Phase::Stable || net_phase != Phase::Stable {
135 phases_report.push(format!(
136 "{}: cpu={}, mem={}, net={}",
137 node.name, cpu_phase, mem_phase, net_phase
138 ));
139 }
140 }
141
142 // Print tick summary
143 if tick_id % 5 == 0 || !violations.is_empty() || !cpu_alerts.is_empty() || !mem_alerts.is_empty() {
144 println!("── Tick {:>2} ─────────────────────", tick_id);
145 println!(" Registered quantities: {}", cluster.registered().len());
146 println!(" Total violations: {}", violations.len());
147
148 if !cpu_alerts.is_empty() {
149 println!(" 🔴 CPU low: {}", cpu_alerts.join(", "));
150 }
151 if !mem_alerts.is_empty() {
152 println!(" 🟠 Memory high: {}", mem_alerts.join(", "));
153 }
154 if !net_alerts.is_empty() {
155 println!(" 🟡 Network low: {}", net_alerts.join(", "));
156 }
157 if !phases_report.is_empty() {
158 for line in &phases_report {
159 println!(" 📊 {}", line);
160 }
161 }
162 println!();
163 }
164 }
165
166 // ── Final summary ────────────────────────────────────────────────
167 println!("═══════════════════════════════════════════════════════════════");
168 println!(" 🏁 FINAL STATE");
169 println!("═══════════════════════════════════════════════════════════════");
170
171 let final_violations = cluster.violations();
172 println!(" Total violations at end: {}", final_violations.len());
173 for v in &final_violations {
174 println!(" ❌ {}", v);
175 }
176
177 // Phase distribution at end
178 println!();
179 println!(" Quantity phases at tick 20:");
180 for name in cluster.registered() {
181 let phase = cluster.phase(&name);
182 let current = cluster.current_value(&name);
183 let conserved = cluster.is_conserved(&name);
184 let drift = cluster.drift_rate(&name);
185 let snaps = cluster.snapshot_count(&name);
186 println!(
187 " {} | value={:.1}, conserved={}, phase={}, drift={:+.2}/tick, {} snapshots",
188 name, current, conserved, phase, drift, snaps
189 );
190 }
191
192 // ── Serde snapshot ───────────────────────────────────────────────
193 #[cfg(feature = "serde")]
194 {
195 println!();
196 println!(" ┌─ Serde JSON snapshot ──────────────────────────┐");
197 let json = cluster.snapshot_json();
198 println!(" {}", json.replace('\n', "\n "));
199 println!(" └────────────────────────────────────────────────┘");
200
201 // Demonstrate deserializing the whole ConservationChecker
202 let json_full = serde_json::to_string_pretty(&cluster).unwrap();
203 println!();
204 println!(" Full state (all history) available for archival:");
205 println!(" {} bytes", json_full.len());
206 }
207}Sourcepub fn is_conserved(&self, name: &str) -> bool
pub fn is_conserved(&self, name: &str) -> bool
Check whether a quantity is still conserved.
A quantity is conserved when current >= initial - tolerance.
Increases are always OK (one-sided conservation).
§Panics
Panics if name has not been registered.
Examples found in repository?
3fn main() {
4 let mut checker = ConservationChecker::new();
5 checker.register("energy", 100.0, 5.0);
6
7 println!("Initial: energy = {}", checker.current_value("energy"));
8
9 checker.update("energy", 98.0);
10 checker.snapshot();
11 println!("After use: energy = {} (conserved: {})",
12 checker.current_value("energy"),
13 checker.is_conserved("energy"));
14
15 checker.update("energy", 90.0);
16 checker.snapshot();
17 println!("After heavy use: energy = {} (conserved: {})",
18 checker.current_value("energy"),
19 checker.is_conserved("energy"));
20 println!("Violations: {:?}", checker.violations());
21 println!("Phase: {}", checker.phase("energy"));
22 println!("Drift rate: {:.2}/snapshot", checker.drift_rate("energy"));
23}More examples
3fn main() {
4 let mut budget = ConservationChecker::new();
5
6 // Monthly budget: $5000, we allow going $100 over as a buffer
7 budget.register("monthly_budget", 5000.0, 100.0);
8
9 let expenses = [1200.0, 800.0, 1500.0, 900.0, 700.0, 350.0];
10
11 for (i, expense) in expenses.iter().enumerate() {
12 let remaining = budget.current_value("monthly_budget") - expense;
13 budget.update("monthly_budget", remaining);
14 budget.snapshot();
15
16 let phase = budget.phase("monthly_budget");
17 let drift = budget.drift_rate("monthly_budget");
18
19 println!(
20 "Day {}: spent ${:.0}, remaining ${:.0} | phase={:?}, drift={:.1}/day",
21 i + 1,
22 expense,
23 remaining,
24 phase,
25 drift,
26 );
27
28 if phase == Phase::Transitioning {
29 println!(" ⚠️ Budget is depleting fast!");
30 }
31 }
32
33 if !budget.is_conserved("monthly_budget") {
34 println!("\n❌ Budget violated! Over by ${:.0}",
35 budget.initial_value("monthly_budget") - budget.current_value("monthly_budget"));
36 println!(" Violations: {:?}", budget.violations());
37 } else {
38 println!("\n✅ Budget intact with ${:.0} remaining", budget.current_value("monthly_budget"));
39 }
40}38fn main() {
39 // ── Setup: 10-node cluster ──────────────────────────────────────
40 let node_names = [
41 "web-01", "web-02", "web-03",
42 "db-01", "db-02",
43 "worker-01", "worker-02", "worker-03",
44 "cache-01", "monitor-01",
45 ];
46
47 let mut nodes: Vec<Node> = node_names
48 .iter()
49 .enumerate()
50 .map(|(i, name)| {
51 let seed = i * 7;
52 Node {
53 name: name.to_string(),
54 cpu_percent: (60.0 + (seed % 20) as f64).min(100.0),
55 memory_gb: 16.0 + (seed % 8) as f64,
56 network_mbps: 100.0 + (seed % 50) as f64,
57 }
58 })
59 .collect();
60
61 // ── Conservation laws ────────────────────────────────────────────
62 // CPU must not drop below 30% on any node
63 // Memory must not exceed 48 GB (reverse conservation — this crate
64 // only checks "must not decrease", so we invert: track "memory_remaining")
65 // Network throughput must not drop below 10 Mbps
66 let mut cluster = ConservationChecker::new();
67
68 for node in &nodes {
69 cluster.register(format!("{}.cpu", node.name), node.cpu_percent, 0.0);
70 // Invert memory: track remaining = 64 - used, so low remaining = violation
71 let mem_remaining = 64.0 - node.memory_gb;
72 cluster.register(format!("{}.mem_remaining", node.name), mem_remaining, 0.0);
73 cluster.register(format!("{}.net", node.name), node.network_mbps, 10.0);
74 }
75
76 println!("╔══════════════════════════════════════════════════════════╗");
77 println!("║ SRE Cluster Monitoring Simulation (10 nodes, 20 ticks) ║");
78 println!("╚══════════════════════════════════════════════════════════╝");
79 println!();
80
81 // ── Static CPU minimum threshold ────────────────────────────────
82 const CPU_MIN: f64 = 30.0;
83
84 // ── Run simulation ──────────────────────────────────────────────
85 for tick_id in 0..20 {
86 tick(&mut nodes, tick_id);
87
88 // Update all conservation values
89 for node in &nodes {
90 cluster.update(&format!("{}.cpu", node.name), node.cpu_percent);
91 let mem_remaining = 64.0 - node.memory_gb;
92 cluster.update(&format!("{}.mem_remaining", node.name), mem_remaining);
93 cluster.update(&format!("{}.net", node.name), node.network_mbps);
94 }
95
96 cluster.snapshot(); // Take a snapshot every tick
97
98 // Collect violations for this tick
99 let violations = cluster.violations();
100
101 // Collect additional SRE-relevant stats
102 let mut cpu_alerts = Vec::new();
103 let mut mem_alerts = Vec::new();
104 let mut net_alerts = Vec::new();
105 let mut phases_report = Vec::new();
106
107 for node in &nodes {
108 let cpu_ok = cluster.is_conserved(&format!("{}.cpu", node.name));
109 let cpu_pct = cluster.current_value(&format!("{}.cpu", node.name));
110 if cpu_pct < CPU_MIN {
111 cpu_alerts.push(format!("{}@{}%", node.name, cpu_pct));
112 }
113
114 let mem_ok = cluster.is_conserved(&format!("{}.mem_remaining", node.name));
115 if !mem_ok {
116 let used = 64.0 - cluster.current_value(&format!("{}.mem_remaining", node.name));
117 mem_alerts.push(format!("{}@{:.1}GB", node.name, used));
118 }
119
120 let net_ok = cluster.is_conserved(&format!("{}.net", node.name));
121 if !net_ok {
122 net_alerts.push(format!(
123 "{}@{}Mbps",
124 node.name,
125 cluster.current_value(&format!("{}.net", node.name))
126 ));
127 }
128
129 // Track phases for nodes in transition
130 let cpu_phase = cluster.phase(&format!("{}.cpu", node.name));
131 let mem_phase = cluster.phase(&format!("{}.mem_remaining", node.name));
132 let net_phase = cluster.phase(&format!("{}.net", node.name));
133
134 if cpu_phase != Phase::Stable || mem_phase != Phase::Stable || net_phase != Phase::Stable {
135 phases_report.push(format!(
136 "{}: cpu={}, mem={}, net={}",
137 node.name, cpu_phase, mem_phase, net_phase
138 ));
139 }
140 }
141
142 // Print tick summary
143 if tick_id % 5 == 0 || !violations.is_empty() || !cpu_alerts.is_empty() || !mem_alerts.is_empty() {
144 println!("── Tick {:>2} ─────────────────────", tick_id);
145 println!(" Registered quantities: {}", cluster.registered().len());
146 println!(" Total violations: {}", violations.len());
147
148 if !cpu_alerts.is_empty() {
149 println!(" 🔴 CPU low: {}", cpu_alerts.join(", "));
150 }
151 if !mem_alerts.is_empty() {
152 println!(" 🟠 Memory high: {}", mem_alerts.join(", "));
153 }
154 if !net_alerts.is_empty() {
155 println!(" 🟡 Network low: {}", net_alerts.join(", "));
156 }
157 if !phases_report.is_empty() {
158 for line in &phases_report {
159 println!(" 📊 {}", line);
160 }
161 }
162 println!();
163 }
164 }
165
166 // ── Final summary ────────────────────────────────────────────────
167 println!("═══════════════════════════════════════════════════════════════");
168 println!(" 🏁 FINAL STATE");
169 println!("═══════════════════════════════════════════════════════════════");
170
171 let final_violations = cluster.violations();
172 println!(" Total violations at end: {}", final_violations.len());
173 for v in &final_violations {
174 println!(" ❌ {}", v);
175 }
176
177 // Phase distribution at end
178 println!();
179 println!(" Quantity phases at tick 20:");
180 for name in cluster.registered() {
181 let phase = cluster.phase(&name);
182 let current = cluster.current_value(&name);
183 let conserved = cluster.is_conserved(&name);
184 let drift = cluster.drift_rate(&name);
185 let snaps = cluster.snapshot_count(&name);
186 println!(
187 " {} | value={:.1}, conserved={}, phase={}, drift={:+.2}/tick, {} snapshots",
188 name, current, conserved, phase, drift, snaps
189 );
190 }
191
192 // ── Serde snapshot ───────────────────────────────────────────────
193 #[cfg(feature = "serde")]
194 {
195 println!();
196 println!(" ┌─ Serde JSON snapshot ──────────────────────────┐");
197 let json = cluster.snapshot_json();
198 println!(" {}", json.replace('\n', "\n "));
199 println!(" └────────────────────────────────────────────────┘");
200
201 // Demonstrate deserializing the whole ConservationChecker
202 let json_full = serde_json::to_string_pretty(&cluster).unwrap();
203 println!();
204 println!(" Full state (all history) available for archival:");
205 println!(" {} bytes", json_full.len());
206 }
207}Sourcepub fn violations(&self) -> Vec<String>
pub fn violations(&self) -> Vec<String>
Return the names of all quantities that are currently violated.
Examples found in repository?
3fn main() {
4 let mut checker = ConservationChecker::new();
5 checker.register("energy", 100.0, 5.0);
6
7 println!("Initial: energy = {}", checker.current_value("energy"));
8
9 checker.update("energy", 98.0);
10 checker.snapshot();
11 println!("After use: energy = {} (conserved: {})",
12 checker.current_value("energy"),
13 checker.is_conserved("energy"));
14
15 checker.update("energy", 90.0);
16 checker.snapshot();
17 println!("After heavy use: energy = {} (conserved: {})",
18 checker.current_value("energy"),
19 checker.is_conserved("energy"));
20 println!("Violations: {:?}", checker.violations());
21 println!("Phase: {}", checker.phase("energy"));
22 println!("Drift rate: {:.2}/snapshot", checker.drift_rate("energy"));
23}More examples
3fn main() {
4 let mut budget = ConservationChecker::new();
5
6 // Monthly budget: $5000, we allow going $100 over as a buffer
7 budget.register("monthly_budget", 5000.0, 100.0);
8
9 let expenses = [1200.0, 800.0, 1500.0, 900.0, 700.0, 350.0];
10
11 for (i, expense) in expenses.iter().enumerate() {
12 let remaining = budget.current_value("monthly_budget") - expense;
13 budget.update("monthly_budget", remaining);
14 budget.snapshot();
15
16 let phase = budget.phase("monthly_budget");
17 let drift = budget.drift_rate("monthly_budget");
18
19 println!(
20 "Day {}: spent ${:.0}, remaining ${:.0} | phase={:?}, drift={:.1}/day",
21 i + 1,
22 expense,
23 remaining,
24 phase,
25 drift,
26 );
27
28 if phase == Phase::Transitioning {
29 println!(" ⚠️ Budget is depleting fast!");
30 }
31 }
32
33 if !budget.is_conserved("monthly_budget") {
34 println!("\n❌ Budget violated! Over by ${:.0}",
35 budget.initial_value("monthly_budget") - budget.current_value("monthly_budget"));
36 println!(" Violations: {:?}", budget.violations());
37 } else {
38 println!("\n✅ Budget intact with ${:.0} remaining", budget.current_value("monthly_budget"));
39 }
40}38fn main() {
39 // ── Setup: 10-node cluster ──────────────────────────────────────
40 let node_names = [
41 "web-01", "web-02", "web-03",
42 "db-01", "db-02",
43 "worker-01", "worker-02", "worker-03",
44 "cache-01", "monitor-01",
45 ];
46
47 let mut nodes: Vec<Node> = node_names
48 .iter()
49 .enumerate()
50 .map(|(i, name)| {
51 let seed = i * 7;
52 Node {
53 name: name.to_string(),
54 cpu_percent: (60.0 + (seed % 20) as f64).min(100.0),
55 memory_gb: 16.0 + (seed % 8) as f64,
56 network_mbps: 100.0 + (seed % 50) as f64,
57 }
58 })
59 .collect();
60
61 // ── Conservation laws ────────────────────────────────────────────
62 // CPU must not drop below 30% on any node
63 // Memory must not exceed 48 GB (reverse conservation — this crate
64 // only checks "must not decrease", so we invert: track "memory_remaining")
65 // Network throughput must not drop below 10 Mbps
66 let mut cluster = ConservationChecker::new();
67
68 for node in &nodes {
69 cluster.register(format!("{}.cpu", node.name), node.cpu_percent, 0.0);
70 // Invert memory: track remaining = 64 - used, so low remaining = violation
71 let mem_remaining = 64.0 - node.memory_gb;
72 cluster.register(format!("{}.mem_remaining", node.name), mem_remaining, 0.0);
73 cluster.register(format!("{}.net", node.name), node.network_mbps, 10.0);
74 }
75
76 println!("╔══════════════════════════════════════════════════════════╗");
77 println!("║ SRE Cluster Monitoring Simulation (10 nodes, 20 ticks) ║");
78 println!("╚══════════════════════════════════════════════════════════╝");
79 println!();
80
81 // ── Static CPU minimum threshold ────────────────────────────────
82 const CPU_MIN: f64 = 30.0;
83
84 // ── Run simulation ──────────────────────────────────────────────
85 for tick_id in 0..20 {
86 tick(&mut nodes, tick_id);
87
88 // Update all conservation values
89 for node in &nodes {
90 cluster.update(&format!("{}.cpu", node.name), node.cpu_percent);
91 let mem_remaining = 64.0 - node.memory_gb;
92 cluster.update(&format!("{}.mem_remaining", node.name), mem_remaining);
93 cluster.update(&format!("{}.net", node.name), node.network_mbps);
94 }
95
96 cluster.snapshot(); // Take a snapshot every tick
97
98 // Collect violations for this tick
99 let violations = cluster.violations();
100
101 // Collect additional SRE-relevant stats
102 let mut cpu_alerts = Vec::new();
103 let mut mem_alerts = Vec::new();
104 let mut net_alerts = Vec::new();
105 let mut phases_report = Vec::new();
106
107 for node in &nodes {
108 let cpu_ok = cluster.is_conserved(&format!("{}.cpu", node.name));
109 let cpu_pct = cluster.current_value(&format!("{}.cpu", node.name));
110 if cpu_pct < CPU_MIN {
111 cpu_alerts.push(format!("{}@{}%", node.name, cpu_pct));
112 }
113
114 let mem_ok = cluster.is_conserved(&format!("{}.mem_remaining", node.name));
115 if !mem_ok {
116 let used = 64.0 - cluster.current_value(&format!("{}.mem_remaining", node.name));
117 mem_alerts.push(format!("{}@{:.1}GB", node.name, used));
118 }
119
120 let net_ok = cluster.is_conserved(&format!("{}.net", node.name));
121 if !net_ok {
122 net_alerts.push(format!(
123 "{}@{}Mbps",
124 node.name,
125 cluster.current_value(&format!("{}.net", node.name))
126 ));
127 }
128
129 // Track phases for nodes in transition
130 let cpu_phase = cluster.phase(&format!("{}.cpu", node.name));
131 let mem_phase = cluster.phase(&format!("{}.mem_remaining", node.name));
132 let net_phase = cluster.phase(&format!("{}.net", node.name));
133
134 if cpu_phase != Phase::Stable || mem_phase != Phase::Stable || net_phase != Phase::Stable {
135 phases_report.push(format!(
136 "{}: cpu={}, mem={}, net={}",
137 node.name, cpu_phase, mem_phase, net_phase
138 ));
139 }
140 }
141
142 // Print tick summary
143 if tick_id % 5 == 0 || !violations.is_empty() || !cpu_alerts.is_empty() || !mem_alerts.is_empty() {
144 println!("── Tick {:>2} ─────────────────────", tick_id);
145 println!(" Registered quantities: {}", cluster.registered().len());
146 println!(" Total violations: {}", violations.len());
147
148 if !cpu_alerts.is_empty() {
149 println!(" 🔴 CPU low: {}", cpu_alerts.join(", "));
150 }
151 if !mem_alerts.is_empty() {
152 println!(" 🟠 Memory high: {}", mem_alerts.join(", "));
153 }
154 if !net_alerts.is_empty() {
155 println!(" 🟡 Network low: {}", net_alerts.join(", "));
156 }
157 if !phases_report.is_empty() {
158 for line in &phases_report {
159 println!(" 📊 {}", line);
160 }
161 }
162 println!();
163 }
164 }
165
166 // ── Final summary ────────────────────────────────────────────────
167 println!("═══════════════════════════════════════════════════════════════");
168 println!(" 🏁 FINAL STATE");
169 println!("═══════════════════════════════════════════════════════════════");
170
171 let final_violations = cluster.violations();
172 println!(" Total violations at end: {}", final_violations.len());
173 for v in &final_violations {
174 println!(" ❌ {}", v);
175 }
176
177 // Phase distribution at end
178 println!();
179 println!(" Quantity phases at tick 20:");
180 for name in cluster.registered() {
181 let phase = cluster.phase(&name);
182 let current = cluster.current_value(&name);
183 let conserved = cluster.is_conserved(&name);
184 let drift = cluster.drift_rate(&name);
185 let snaps = cluster.snapshot_count(&name);
186 println!(
187 " {} | value={:.1}, conserved={}, phase={}, drift={:+.2}/tick, {} snapshots",
188 name, current, conserved, phase, drift, snaps
189 );
190 }
191
192 // ── Serde snapshot ───────────────────────────────────────────────
193 #[cfg(feature = "serde")]
194 {
195 println!();
196 println!(" ┌─ Serde JSON snapshot ──────────────────────────┐");
197 let json = cluster.snapshot_json();
198 println!(" {}", json.replace('\n', "\n "));
199 println!(" └────────────────────────────────────────────────┘");
200
201 // Demonstrate deserializing the whole ConservationChecker
202 let json_full = serde_json::to_string_pretty(&cluster).unwrap();
203 println!();
204 println!(" Full state (all history) available for archival:");
205 println!(" {} bytes", json_full.len());
206 }
207}Sourcepub fn snapshot(&mut self)
pub fn snapshot(&mut self)
Record a snapshot of every quantity’s current value into its history.
Call this periodically (e.g. once per tick, request, or batch) to
build a time-series that phase and
drift_rate can analyse.
§Example
use conservation_checker::ConservationChecker;
let mut checker = ConservationChecker::new();
checker.register("budget", 500.0, 100.0);
checker.update("budget", 450.0);
checker.snapshot();
checker.update("budget", 420.0);
checker.snapshot();
// drift_rate now compares first and last snapshot
let drift = checker.drift_rate("budget");
assert!(drift < 0.0); // budget is drifting downwardExamples found in repository?
3fn main() {
4 let mut checker = ConservationChecker::new();
5 checker.register("energy", 100.0, 5.0);
6
7 println!("Initial: energy = {}", checker.current_value("energy"));
8
9 checker.update("energy", 98.0);
10 checker.snapshot();
11 println!("After use: energy = {} (conserved: {})",
12 checker.current_value("energy"),
13 checker.is_conserved("energy"));
14
15 checker.update("energy", 90.0);
16 checker.snapshot();
17 println!("After heavy use: energy = {} (conserved: {})",
18 checker.current_value("energy"),
19 checker.is_conserved("energy"));
20 println!("Violations: {:?}", checker.violations());
21 println!("Phase: {}", checker.phase("energy"));
22 println!("Drift rate: {:.2}/snapshot", checker.drift_rate("energy"));
23}More examples
3fn main() {
4 let mut budget = ConservationChecker::new();
5
6 // Monthly budget: $5000, we allow going $100 over as a buffer
7 budget.register("monthly_budget", 5000.0, 100.0);
8
9 let expenses = [1200.0, 800.0, 1500.0, 900.0, 700.0, 350.0];
10
11 for (i, expense) in expenses.iter().enumerate() {
12 let remaining = budget.current_value("monthly_budget") - expense;
13 budget.update("monthly_budget", remaining);
14 budget.snapshot();
15
16 let phase = budget.phase("monthly_budget");
17 let drift = budget.drift_rate("monthly_budget");
18
19 println!(
20 "Day {}: spent ${:.0}, remaining ${:.0} | phase={:?}, drift={:.1}/day",
21 i + 1,
22 expense,
23 remaining,
24 phase,
25 drift,
26 );
27
28 if phase == Phase::Transitioning {
29 println!(" ⚠️ Budget is depleting fast!");
30 }
31 }
32
33 if !budget.is_conserved("monthly_budget") {
34 println!("\n❌ Budget violated! Over by ${:.0}",
35 budget.initial_value("monthly_budget") - budget.current_value("monthly_budget"));
36 println!(" Violations: {:?}", budget.violations());
37 } else {
38 println!("\n✅ Budget intact with ${:.0} remaining", budget.current_value("monthly_budget"));
39 }
40}38fn main() {
39 // ── Setup: 10-node cluster ──────────────────────────────────────
40 let node_names = [
41 "web-01", "web-02", "web-03",
42 "db-01", "db-02",
43 "worker-01", "worker-02", "worker-03",
44 "cache-01", "monitor-01",
45 ];
46
47 let mut nodes: Vec<Node> = node_names
48 .iter()
49 .enumerate()
50 .map(|(i, name)| {
51 let seed = i * 7;
52 Node {
53 name: name.to_string(),
54 cpu_percent: (60.0 + (seed % 20) as f64).min(100.0),
55 memory_gb: 16.0 + (seed % 8) as f64,
56 network_mbps: 100.0 + (seed % 50) as f64,
57 }
58 })
59 .collect();
60
61 // ── Conservation laws ────────────────────────────────────────────
62 // CPU must not drop below 30% on any node
63 // Memory must not exceed 48 GB (reverse conservation — this crate
64 // only checks "must not decrease", so we invert: track "memory_remaining")
65 // Network throughput must not drop below 10 Mbps
66 let mut cluster = ConservationChecker::new();
67
68 for node in &nodes {
69 cluster.register(format!("{}.cpu", node.name), node.cpu_percent, 0.0);
70 // Invert memory: track remaining = 64 - used, so low remaining = violation
71 let mem_remaining = 64.0 - node.memory_gb;
72 cluster.register(format!("{}.mem_remaining", node.name), mem_remaining, 0.0);
73 cluster.register(format!("{}.net", node.name), node.network_mbps, 10.0);
74 }
75
76 println!("╔══════════════════════════════════════════════════════════╗");
77 println!("║ SRE Cluster Monitoring Simulation (10 nodes, 20 ticks) ║");
78 println!("╚══════════════════════════════════════════════════════════╝");
79 println!();
80
81 // ── Static CPU minimum threshold ────────────────────────────────
82 const CPU_MIN: f64 = 30.0;
83
84 // ── Run simulation ──────────────────────────────────────────────
85 for tick_id in 0..20 {
86 tick(&mut nodes, tick_id);
87
88 // Update all conservation values
89 for node in &nodes {
90 cluster.update(&format!("{}.cpu", node.name), node.cpu_percent);
91 let mem_remaining = 64.0 - node.memory_gb;
92 cluster.update(&format!("{}.mem_remaining", node.name), mem_remaining);
93 cluster.update(&format!("{}.net", node.name), node.network_mbps);
94 }
95
96 cluster.snapshot(); // Take a snapshot every tick
97
98 // Collect violations for this tick
99 let violations = cluster.violations();
100
101 // Collect additional SRE-relevant stats
102 let mut cpu_alerts = Vec::new();
103 let mut mem_alerts = Vec::new();
104 let mut net_alerts = Vec::new();
105 let mut phases_report = Vec::new();
106
107 for node in &nodes {
108 let cpu_ok = cluster.is_conserved(&format!("{}.cpu", node.name));
109 let cpu_pct = cluster.current_value(&format!("{}.cpu", node.name));
110 if cpu_pct < CPU_MIN {
111 cpu_alerts.push(format!("{}@{}%", node.name, cpu_pct));
112 }
113
114 let mem_ok = cluster.is_conserved(&format!("{}.mem_remaining", node.name));
115 if !mem_ok {
116 let used = 64.0 - cluster.current_value(&format!("{}.mem_remaining", node.name));
117 mem_alerts.push(format!("{}@{:.1}GB", node.name, used));
118 }
119
120 let net_ok = cluster.is_conserved(&format!("{}.net", node.name));
121 if !net_ok {
122 net_alerts.push(format!(
123 "{}@{}Mbps",
124 node.name,
125 cluster.current_value(&format!("{}.net", node.name))
126 ));
127 }
128
129 // Track phases for nodes in transition
130 let cpu_phase = cluster.phase(&format!("{}.cpu", node.name));
131 let mem_phase = cluster.phase(&format!("{}.mem_remaining", node.name));
132 let net_phase = cluster.phase(&format!("{}.net", node.name));
133
134 if cpu_phase != Phase::Stable || mem_phase != Phase::Stable || net_phase != Phase::Stable {
135 phases_report.push(format!(
136 "{}: cpu={}, mem={}, net={}",
137 node.name, cpu_phase, mem_phase, net_phase
138 ));
139 }
140 }
141
142 // Print tick summary
143 if tick_id % 5 == 0 || !violations.is_empty() || !cpu_alerts.is_empty() || !mem_alerts.is_empty() {
144 println!("── Tick {:>2} ─────────────────────", tick_id);
145 println!(" Registered quantities: {}", cluster.registered().len());
146 println!(" Total violations: {}", violations.len());
147
148 if !cpu_alerts.is_empty() {
149 println!(" 🔴 CPU low: {}", cpu_alerts.join(", "));
150 }
151 if !mem_alerts.is_empty() {
152 println!(" 🟠 Memory high: {}", mem_alerts.join(", "));
153 }
154 if !net_alerts.is_empty() {
155 println!(" 🟡 Network low: {}", net_alerts.join(", "));
156 }
157 if !phases_report.is_empty() {
158 for line in &phases_report {
159 println!(" 📊 {}", line);
160 }
161 }
162 println!();
163 }
164 }
165
166 // ── Final summary ────────────────────────────────────────────────
167 println!("═══════════════════════════════════════════════════════════════");
168 println!(" 🏁 FINAL STATE");
169 println!("═══════════════════════════════════════════════════════════════");
170
171 let final_violations = cluster.violations();
172 println!(" Total violations at end: {}", final_violations.len());
173 for v in &final_violations {
174 println!(" ❌ {}", v);
175 }
176
177 // Phase distribution at end
178 println!();
179 println!(" Quantity phases at tick 20:");
180 for name in cluster.registered() {
181 let phase = cluster.phase(&name);
182 let current = cluster.current_value(&name);
183 let conserved = cluster.is_conserved(&name);
184 let drift = cluster.drift_rate(&name);
185 let snaps = cluster.snapshot_count(&name);
186 println!(
187 " {} | value={:.1}, conserved={}, phase={}, drift={:+.2}/tick, {} snapshots",
188 name, current, conserved, phase, drift, snaps
189 );
190 }
191
192 // ── Serde snapshot ───────────────────────────────────────────────
193 #[cfg(feature = "serde")]
194 {
195 println!();
196 println!(" ┌─ Serde JSON snapshot ──────────────────────────┐");
197 let json = cluster.snapshot_json();
198 println!(" {}", json.replace('\n', "\n "));
199 println!(" └────────────────────────────────────────────────┘");
200
201 // Demonstrate deserializing the whole ConservationChecker
202 let json_full = serde_json::to_string_pretty(&cluster).unwrap();
203 println!();
204 println!(" Full state (all history) available for archival:");
205 println!(" {} bytes", json_full.len());
206 }
207}Sourcepub fn phase(&self, name: &str) -> Phase
pub fn phase(&self, name: &str) -> Phase
Detect the current phase of a quantity based on its history.
Uses the most recent snapshots to compute a short-term rate of change
and classifies the trajectory as Phase::Stable, Phase::PreTransition,
Phase::Transitioning, or Phase::Resolving.
Requires at least 3 snapshots to return anything other than Stable.
§Panics
Panics if name has not been registered.
§Example
use conservation_checker::{ConservationChecker, Phase};
let mut checker = ConservationChecker::new();
checker.register("energy", 100.0, 0.0);
// Drain it down
checker.update("energy", 90.0);
checker.snapshot();
checker.update("energy", 80.0);
checker.snapshot();
assert_eq!(checker.phase("energy"), Phase::Transitioning);Examples found in repository?
3fn main() {
4 let mut checker = ConservationChecker::new();
5 checker.register("energy", 100.0, 5.0);
6
7 println!("Initial: energy = {}", checker.current_value("energy"));
8
9 checker.update("energy", 98.0);
10 checker.snapshot();
11 println!("After use: energy = {} (conserved: {})",
12 checker.current_value("energy"),
13 checker.is_conserved("energy"));
14
15 checker.update("energy", 90.0);
16 checker.snapshot();
17 println!("After heavy use: energy = {} (conserved: {})",
18 checker.current_value("energy"),
19 checker.is_conserved("energy"));
20 println!("Violations: {:?}", checker.violations());
21 println!("Phase: {}", checker.phase("energy"));
22 println!("Drift rate: {:.2}/snapshot", checker.drift_rate("energy"));
23}More examples
3fn main() {
4 let mut budget = ConservationChecker::new();
5
6 // Monthly budget: $5000, we allow going $100 over as a buffer
7 budget.register("monthly_budget", 5000.0, 100.0);
8
9 let expenses = [1200.0, 800.0, 1500.0, 900.0, 700.0, 350.0];
10
11 for (i, expense) in expenses.iter().enumerate() {
12 let remaining = budget.current_value("monthly_budget") - expense;
13 budget.update("monthly_budget", remaining);
14 budget.snapshot();
15
16 let phase = budget.phase("monthly_budget");
17 let drift = budget.drift_rate("monthly_budget");
18
19 println!(
20 "Day {}: spent ${:.0}, remaining ${:.0} | phase={:?}, drift={:.1}/day",
21 i + 1,
22 expense,
23 remaining,
24 phase,
25 drift,
26 );
27
28 if phase == Phase::Transitioning {
29 println!(" ⚠️ Budget is depleting fast!");
30 }
31 }
32
33 if !budget.is_conserved("monthly_budget") {
34 println!("\n❌ Budget violated! Over by ${:.0}",
35 budget.initial_value("monthly_budget") - budget.current_value("monthly_budget"));
36 println!(" Violations: {:?}", budget.violations());
37 } else {
38 println!("\n✅ Budget intact with ${:.0} remaining", budget.current_value("monthly_budget"));
39 }
40}38fn main() {
39 // ── Setup: 10-node cluster ──────────────────────────────────────
40 let node_names = [
41 "web-01", "web-02", "web-03",
42 "db-01", "db-02",
43 "worker-01", "worker-02", "worker-03",
44 "cache-01", "monitor-01",
45 ];
46
47 let mut nodes: Vec<Node> = node_names
48 .iter()
49 .enumerate()
50 .map(|(i, name)| {
51 let seed = i * 7;
52 Node {
53 name: name.to_string(),
54 cpu_percent: (60.0 + (seed % 20) as f64).min(100.0),
55 memory_gb: 16.0 + (seed % 8) as f64,
56 network_mbps: 100.0 + (seed % 50) as f64,
57 }
58 })
59 .collect();
60
61 // ── Conservation laws ────────────────────────────────────────────
62 // CPU must not drop below 30% on any node
63 // Memory must not exceed 48 GB (reverse conservation — this crate
64 // only checks "must not decrease", so we invert: track "memory_remaining")
65 // Network throughput must not drop below 10 Mbps
66 let mut cluster = ConservationChecker::new();
67
68 for node in &nodes {
69 cluster.register(format!("{}.cpu", node.name), node.cpu_percent, 0.0);
70 // Invert memory: track remaining = 64 - used, so low remaining = violation
71 let mem_remaining = 64.0 - node.memory_gb;
72 cluster.register(format!("{}.mem_remaining", node.name), mem_remaining, 0.0);
73 cluster.register(format!("{}.net", node.name), node.network_mbps, 10.0);
74 }
75
76 println!("╔══════════════════════════════════════════════════════════╗");
77 println!("║ SRE Cluster Monitoring Simulation (10 nodes, 20 ticks) ║");
78 println!("╚══════════════════════════════════════════════════════════╝");
79 println!();
80
81 // ── Static CPU minimum threshold ────────────────────────────────
82 const CPU_MIN: f64 = 30.0;
83
84 // ── Run simulation ──────────────────────────────────────────────
85 for tick_id in 0..20 {
86 tick(&mut nodes, tick_id);
87
88 // Update all conservation values
89 for node in &nodes {
90 cluster.update(&format!("{}.cpu", node.name), node.cpu_percent);
91 let mem_remaining = 64.0 - node.memory_gb;
92 cluster.update(&format!("{}.mem_remaining", node.name), mem_remaining);
93 cluster.update(&format!("{}.net", node.name), node.network_mbps);
94 }
95
96 cluster.snapshot(); // Take a snapshot every tick
97
98 // Collect violations for this tick
99 let violations = cluster.violations();
100
101 // Collect additional SRE-relevant stats
102 let mut cpu_alerts = Vec::new();
103 let mut mem_alerts = Vec::new();
104 let mut net_alerts = Vec::new();
105 let mut phases_report = Vec::new();
106
107 for node in &nodes {
108 let cpu_ok = cluster.is_conserved(&format!("{}.cpu", node.name));
109 let cpu_pct = cluster.current_value(&format!("{}.cpu", node.name));
110 if cpu_pct < CPU_MIN {
111 cpu_alerts.push(format!("{}@{}%", node.name, cpu_pct));
112 }
113
114 let mem_ok = cluster.is_conserved(&format!("{}.mem_remaining", node.name));
115 if !mem_ok {
116 let used = 64.0 - cluster.current_value(&format!("{}.mem_remaining", node.name));
117 mem_alerts.push(format!("{}@{:.1}GB", node.name, used));
118 }
119
120 let net_ok = cluster.is_conserved(&format!("{}.net", node.name));
121 if !net_ok {
122 net_alerts.push(format!(
123 "{}@{}Mbps",
124 node.name,
125 cluster.current_value(&format!("{}.net", node.name))
126 ));
127 }
128
129 // Track phases for nodes in transition
130 let cpu_phase = cluster.phase(&format!("{}.cpu", node.name));
131 let mem_phase = cluster.phase(&format!("{}.mem_remaining", node.name));
132 let net_phase = cluster.phase(&format!("{}.net", node.name));
133
134 if cpu_phase != Phase::Stable || mem_phase != Phase::Stable || net_phase != Phase::Stable {
135 phases_report.push(format!(
136 "{}: cpu={}, mem={}, net={}",
137 node.name, cpu_phase, mem_phase, net_phase
138 ));
139 }
140 }
141
142 // Print tick summary
143 if tick_id % 5 == 0 || !violations.is_empty() || !cpu_alerts.is_empty() || !mem_alerts.is_empty() {
144 println!("── Tick {:>2} ─────────────────────", tick_id);
145 println!(" Registered quantities: {}", cluster.registered().len());
146 println!(" Total violations: {}", violations.len());
147
148 if !cpu_alerts.is_empty() {
149 println!(" 🔴 CPU low: {}", cpu_alerts.join(", "));
150 }
151 if !mem_alerts.is_empty() {
152 println!(" 🟠 Memory high: {}", mem_alerts.join(", "));
153 }
154 if !net_alerts.is_empty() {
155 println!(" 🟡 Network low: {}", net_alerts.join(", "));
156 }
157 if !phases_report.is_empty() {
158 for line in &phases_report {
159 println!(" 📊 {}", line);
160 }
161 }
162 println!();
163 }
164 }
165
166 // ── Final summary ────────────────────────────────────────────────
167 println!("═══════════════════════════════════════════════════════════════");
168 println!(" 🏁 FINAL STATE");
169 println!("═══════════════════════════════════════════════════════════════");
170
171 let final_violations = cluster.violations();
172 println!(" Total violations at end: {}", final_violations.len());
173 for v in &final_violations {
174 println!(" ❌ {}", v);
175 }
176
177 // Phase distribution at end
178 println!();
179 println!(" Quantity phases at tick 20:");
180 for name in cluster.registered() {
181 let phase = cluster.phase(&name);
182 let current = cluster.current_value(&name);
183 let conserved = cluster.is_conserved(&name);
184 let drift = cluster.drift_rate(&name);
185 let snaps = cluster.snapshot_count(&name);
186 println!(
187 " {} | value={:.1}, conserved={}, phase={}, drift={:+.2}/tick, {} snapshots",
188 name, current, conserved, phase, drift, snaps
189 );
190 }
191
192 // ── Serde snapshot ───────────────────────────────────────────────
193 #[cfg(feature = "serde")]
194 {
195 println!();
196 println!(" ┌─ Serde JSON snapshot ──────────────────────────┐");
197 let json = cluster.snapshot_json();
198 println!(" {}", json.replace('\n', "\n "));
199 println!(" └────────────────────────────────────────────────┘");
200
201 // Demonstrate deserializing the whole ConservationChecker
202 let json_full = serde_json::to_string_pretty(&cluster).unwrap();
203 println!();
204 println!(" Full state (all history) available for archival:");
205 println!(" {} bytes", json_full.len());
206 }
207}Sourcepub fn drift_rate(&self, name: &str) -> f64
pub fn drift_rate(&self, name: &str) -> f64
Compute the average rate of change per snapshot for a quantity.
Computed as (last_value - first_value) / (snapshot_count - 1).
Returns 0.0 when there are fewer than two snapshots.
§Panics
Panics if name has not been registered.
Examples found in repository?
3fn main() {
4 let mut checker = ConservationChecker::new();
5 checker.register("energy", 100.0, 5.0);
6
7 println!("Initial: energy = {}", checker.current_value("energy"));
8
9 checker.update("energy", 98.0);
10 checker.snapshot();
11 println!("After use: energy = {} (conserved: {})",
12 checker.current_value("energy"),
13 checker.is_conserved("energy"));
14
15 checker.update("energy", 90.0);
16 checker.snapshot();
17 println!("After heavy use: energy = {} (conserved: {})",
18 checker.current_value("energy"),
19 checker.is_conserved("energy"));
20 println!("Violations: {:?}", checker.violations());
21 println!("Phase: {}", checker.phase("energy"));
22 println!("Drift rate: {:.2}/snapshot", checker.drift_rate("energy"));
23}More examples
3fn main() {
4 let mut budget = ConservationChecker::new();
5
6 // Monthly budget: $5000, we allow going $100 over as a buffer
7 budget.register("monthly_budget", 5000.0, 100.0);
8
9 let expenses = [1200.0, 800.0, 1500.0, 900.0, 700.0, 350.0];
10
11 for (i, expense) in expenses.iter().enumerate() {
12 let remaining = budget.current_value("monthly_budget") - expense;
13 budget.update("monthly_budget", remaining);
14 budget.snapshot();
15
16 let phase = budget.phase("monthly_budget");
17 let drift = budget.drift_rate("monthly_budget");
18
19 println!(
20 "Day {}: spent ${:.0}, remaining ${:.0} | phase={:?}, drift={:.1}/day",
21 i + 1,
22 expense,
23 remaining,
24 phase,
25 drift,
26 );
27
28 if phase == Phase::Transitioning {
29 println!(" ⚠️ Budget is depleting fast!");
30 }
31 }
32
33 if !budget.is_conserved("monthly_budget") {
34 println!("\n❌ Budget violated! Over by ${:.0}",
35 budget.initial_value("monthly_budget") - budget.current_value("monthly_budget"));
36 println!(" Violations: {:?}", budget.violations());
37 } else {
38 println!("\n✅ Budget intact with ${:.0} remaining", budget.current_value("monthly_budget"));
39 }
40}38fn main() {
39 // ── Setup: 10-node cluster ──────────────────────────────────────
40 let node_names = [
41 "web-01", "web-02", "web-03",
42 "db-01", "db-02",
43 "worker-01", "worker-02", "worker-03",
44 "cache-01", "monitor-01",
45 ];
46
47 let mut nodes: Vec<Node> = node_names
48 .iter()
49 .enumerate()
50 .map(|(i, name)| {
51 let seed = i * 7;
52 Node {
53 name: name.to_string(),
54 cpu_percent: (60.0 + (seed % 20) as f64).min(100.0),
55 memory_gb: 16.0 + (seed % 8) as f64,
56 network_mbps: 100.0 + (seed % 50) as f64,
57 }
58 })
59 .collect();
60
61 // ── Conservation laws ────────────────────────────────────────────
62 // CPU must not drop below 30% on any node
63 // Memory must not exceed 48 GB (reverse conservation — this crate
64 // only checks "must not decrease", so we invert: track "memory_remaining")
65 // Network throughput must not drop below 10 Mbps
66 let mut cluster = ConservationChecker::new();
67
68 for node in &nodes {
69 cluster.register(format!("{}.cpu", node.name), node.cpu_percent, 0.0);
70 // Invert memory: track remaining = 64 - used, so low remaining = violation
71 let mem_remaining = 64.0 - node.memory_gb;
72 cluster.register(format!("{}.mem_remaining", node.name), mem_remaining, 0.0);
73 cluster.register(format!("{}.net", node.name), node.network_mbps, 10.0);
74 }
75
76 println!("╔══════════════════════════════════════════════════════════╗");
77 println!("║ SRE Cluster Monitoring Simulation (10 nodes, 20 ticks) ║");
78 println!("╚══════════════════════════════════════════════════════════╝");
79 println!();
80
81 // ── Static CPU minimum threshold ────────────────────────────────
82 const CPU_MIN: f64 = 30.0;
83
84 // ── Run simulation ──────────────────────────────────────────────
85 for tick_id in 0..20 {
86 tick(&mut nodes, tick_id);
87
88 // Update all conservation values
89 for node in &nodes {
90 cluster.update(&format!("{}.cpu", node.name), node.cpu_percent);
91 let mem_remaining = 64.0 - node.memory_gb;
92 cluster.update(&format!("{}.mem_remaining", node.name), mem_remaining);
93 cluster.update(&format!("{}.net", node.name), node.network_mbps);
94 }
95
96 cluster.snapshot(); // Take a snapshot every tick
97
98 // Collect violations for this tick
99 let violations = cluster.violations();
100
101 // Collect additional SRE-relevant stats
102 let mut cpu_alerts = Vec::new();
103 let mut mem_alerts = Vec::new();
104 let mut net_alerts = Vec::new();
105 let mut phases_report = Vec::new();
106
107 for node in &nodes {
108 let cpu_ok = cluster.is_conserved(&format!("{}.cpu", node.name));
109 let cpu_pct = cluster.current_value(&format!("{}.cpu", node.name));
110 if cpu_pct < CPU_MIN {
111 cpu_alerts.push(format!("{}@{}%", node.name, cpu_pct));
112 }
113
114 let mem_ok = cluster.is_conserved(&format!("{}.mem_remaining", node.name));
115 if !mem_ok {
116 let used = 64.0 - cluster.current_value(&format!("{}.mem_remaining", node.name));
117 mem_alerts.push(format!("{}@{:.1}GB", node.name, used));
118 }
119
120 let net_ok = cluster.is_conserved(&format!("{}.net", node.name));
121 if !net_ok {
122 net_alerts.push(format!(
123 "{}@{}Mbps",
124 node.name,
125 cluster.current_value(&format!("{}.net", node.name))
126 ));
127 }
128
129 // Track phases for nodes in transition
130 let cpu_phase = cluster.phase(&format!("{}.cpu", node.name));
131 let mem_phase = cluster.phase(&format!("{}.mem_remaining", node.name));
132 let net_phase = cluster.phase(&format!("{}.net", node.name));
133
134 if cpu_phase != Phase::Stable || mem_phase != Phase::Stable || net_phase != Phase::Stable {
135 phases_report.push(format!(
136 "{}: cpu={}, mem={}, net={}",
137 node.name, cpu_phase, mem_phase, net_phase
138 ));
139 }
140 }
141
142 // Print tick summary
143 if tick_id % 5 == 0 || !violations.is_empty() || !cpu_alerts.is_empty() || !mem_alerts.is_empty() {
144 println!("── Tick {:>2} ─────────────────────", tick_id);
145 println!(" Registered quantities: {}", cluster.registered().len());
146 println!(" Total violations: {}", violations.len());
147
148 if !cpu_alerts.is_empty() {
149 println!(" 🔴 CPU low: {}", cpu_alerts.join(", "));
150 }
151 if !mem_alerts.is_empty() {
152 println!(" 🟠 Memory high: {}", mem_alerts.join(", "));
153 }
154 if !net_alerts.is_empty() {
155 println!(" 🟡 Network low: {}", net_alerts.join(", "));
156 }
157 if !phases_report.is_empty() {
158 for line in &phases_report {
159 println!(" 📊 {}", line);
160 }
161 }
162 println!();
163 }
164 }
165
166 // ── Final summary ────────────────────────────────────────────────
167 println!("═══════════════════════════════════════════════════════════════");
168 println!(" 🏁 FINAL STATE");
169 println!("═══════════════════════════════════════════════════════════════");
170
171 let final_violations = cluster.violations();
172 println!(" Total violations at end: {}", final_violations.len());
173 for v in &final_violations {
174 println!(" ❌ {}", v);
175 }
176
177 // Phase distribution at end
178 println!();
179 println!(" Quantity phases at tick 20:");
180 for name in cluster.registered() {
181 let phase = cluster.phase(&name);
182 let current = cluster.current_value(&name);
183 let conserved = cluster.is_conserved(&name);
184 let drift = cluster.drift_rate(&name);
185 let snaps = cluster.snapshot_count(&name);
186 println!(
187 " {} | value={:.1}, conserved={}, phase={}, drift={:+.2}/tick, {} snapshots",
188 name, current, conserved, phase, drift, snaps
189 );
190 }
191
192 // ── Serde snapshot ───────────────────────────────────────────────
193 #[cfg(feature = "serde")]
194 {
195 println!();
196 println!(" ┌─ Serde JSON snapshot ──────────────────────────┐");
197 let json = cluster.snapshot_json();
198 println!(" {}", json.replace('\n', "\n "));
199 println!(" └────────────────────────────────────────────────┘");
200
201 // Demonstrate deserializing the whole ConservationChecker
202 let json_full = serde_json::to_string_pretty(&cluster).unwrap();
203 println!();
204 println!(" Full state (all history) available for archival:");
205 println!(" {} bytes", json_full.len());
206 }
207}Sourcepub fn current_value(&self, name: &str) -> f64
pub fn current_value(&self, name: &str) -> f64
Examples found in repository?
3fn main() {
4 let mut checker = ConservationChecker::new();
5 checker.register("energy", 100.0, 5.0);
6
7 println!("Initial: energy = {}", checker.current_value("energy"));
8
9 checker.update("energy", 98.0);
10 checker.snapshot();
11 println!("After use: energy = {} (conserved: {})",
12 checker.current_value("energy"),
13 checker.is_conserved("energy"));
14
15 checker.update("energy", 90.0);
16 checker.snapshot();
17 println!("After heavy use: energy = {} (conserved: {})",
18 checker.current_value("energy"),
19 checker.is_conserved("energy"));
20 println!("Violations: {:?}", checker.violations());
21 println!("Phase: {}", checker.phase("energy"));
22 println!("Drift rate: {:.2}/snapshot", checker.drift_rate("energy"));
23}More examples
3fn main() {
4 let mut budget = ConservationChecker::new();
5
6 // Monthly budget: $5000, we allow going $100 over as a buffer
7 budget.register("monthly_budget", 5000.0, 100.0);
8
9 let expenses = [1200.0, 800.0, 1500.0, 900.0, 700.0, 350.0];
10
11 for (i, expense) in expenses.iter().enumerate() {
12 let remaining = budget.current_value("monthly_budget") - expense;
13 budget.update("monthly_budget", remaining);
14 budget.snapshot();
15
16 let phase = budget.phase("monthly_budget");
17 let drift = budget.drift_rate("monthly_budget");
18
19 println!(
20 "Day {}: spent ${:.0}, remaining ${:.0} | phase={:?}, drift={:.1}/day",
21 i + 1,
22 expense,
23 remaining,
24 phase,
25 drift,
26 );
27
28 if phase == Phase::Transitioning {
29 println!(" ⚠️ Budget is depleting fast!");
30 }
31 }
32
33 if !budget.is_conserved("monthly_budget") {
34 println!("\n❌ Budget violated! Over by ${:.0}",
35 budget.initial_value("monthly_budget") - budget.current_value("monthly_budget"));
36 println!(" Violations: {:?}", budget.violations());
37 } else {
38 println!("\n✅ Budget intact with ${:.0} remaining", budget.current_value("monthly_budget"));
39 }
40}38fn main() {
39 // ── Setup: 10-node cluster ──────────────────────────────────────
40 let node_names = [
41 "web-01", "web-02", "web-03",
42 "db-01", "db-02",
43 "worker-01", "worker-02", "worker-03",
44 "cache-01", "monitor-01",
45 ];
46
47 let mut nodes: Vec<Node> = node_names
48 .iter()
49 .enumerate()
50 .map(|(i, name)| {
51 let seed = i * 7;
52 Node {
53 name: name.to_string(),
54 cpu_percent: (60.0 + (seed % 20) as f64).min(100.0),
55 memory_gb: 16.0 + (seed % 8) as f64,
56 network_mbps: 100.0 + (seed % 50) as f64,
57 }
58 })
59 .collect();
60
61 // ── Conservation laws ────────────────────────────────────────────
62 // CPU must not drop below 30% on any node
63 // Memory must not exceed 48 GB (reverse conservation — this crate
64 // only checks "must not decrease", so we invert: track "memory_remaining")
65 // Network throughput must not drop below 10 Mbps
66 let mut cluster = ConservationChecker::new();
67
68 for node in &nodes {
69 cluster.register(format!("{}.cpu", node.name), node.cpu_percent, 0.0);
70 // Invert memory: track remaining = 64 - used, so low remaining = violation
71 let mem_remaining = 64.0 - node.memory_gb;
72 cluster.register(format!("{}.mem_remaining", node.name), mem_remaining, 0.0);
73 cluster.register(format!("{}.net", node.name), node.network_mbps, 10.0);
74 }
75
76 println!("╔══════════════════════════════════════════════════════════╗");
77 println!("║ SRE Cluster Monitoring Simulation (10 nodes, 20 ticks) ║");
78 println!("╚══════════════════════════════════════════════════════════╝");
79 println!();
80
81 // ── Static CPU minimum threshold ────────────────────────────────
82 const CPU_MIN: f64 = 30.0;
83
84 // ── Run simulation ──────────────────────────────────────────────
85 for tick_id in 0..20 {
86 tick(&mut nodes, tick_id);
87
88 // Update all conservation values
89 for node in &nodes {
90 cluster.update(&format!("{}.cpu", node.name), node.cpu_percent);
91 let mem_remaining = 64.0 - node.memory_gb;
92 cluster.update(&format!("{}.mem_remaining", node.name), mem_remaining);
93 cluster.update(&format!("{}.net", node.name), node.network_mbps);
94 }
95
96 cluster.snapshot(); // Take a snapshot every tick
97
98 // Collect violations for this tick
99 let violations = cluster.violations();
100
101 // Collect additional SRE-relevant stats
102 let mut cpu_alerts = Vec::new();
103 let mut mem_alerts = Vec::new();
104 let mut net_alerts = Vec::new();
105 let mut phases_report = Vec::new();
106
107 for node in &nodes {
108 let cpu_ok = cluster.is_conserved(&format!("{}.cpu", node.name));
109 let cpu_pct = cluster.current_value(&format!("{}.cpu", node.name));
110 if cpu_pct < CPU_MIN {
111 cpu_alerts.push(format!("{}@{}%", node.name, cpu_pct));
112 }
113
114 let mem_ok = cluster.is_conserved(&format!("{}.mem_remaining", node.name));
115 if !mem_ok {
116 let used = 64.0 - cluster.current_value(&format!("{}.mem_remaining", node.name));
117 mem_alerts.push(format!("{}@{:.1}GB", node.name, used));
118 }
119
120 let net_ok = cluster.is_conserved(&format!("{}.net", node.name));
121 if !net_ok {
122 net_alerts.push(format!(
123 "{}@{}Mbps",
124 node.name,
125 cluster.current_value(&format!("{}.net", node.name))
126 ));
127 }
128
129 // Track phases for nodes in transition
130 let cpu_phase = cluster.phase(&format!("{}.cpu", node.name));
131 let mem_phase = cluster.phase(&format!("{}.mem_remaining", node.name));
132 let net_phase = cluster.phase(&format!("{}.net", node.name));
133
134 if cpu_phase != Phase::Stable || mem_phase != Phase::Stable || net_phase != Phase::Stable {
135 phases_report.push(format!(
136 "{}: cpu={}, mem={}, net={}",
137 node.name, cpu_phase, mem_phase, net_phase
138 ));
139 }
140 }
141
142 // Print tick summary
143 if tick_id % 5 == 0 || !violations.is_empty() || !cpu_alerts.is_empty() || !mem_alerts.is_empty() {
144 println!("── Tick {:>2} ─────────────────────", tick_id);
145 println!(" Registered quantities: {}", cluster.registered().len());
146 println!(" Total violations: {}", violations.len());
147
148 if !cpu_alerts.is_empty() {
149 println!(" 🔴 CPU low: {}", cpu_alerts.join(", "));
150 }
151 if !mem_alerts.is_empty() {
152 println!(" 🟠 Memory high: {}", mem_alerts.join(", "));
153 }
154 if !net_alerts.is_empty() {
155 println!(" 🟡 Network low: {}", net_alerts.join(", "));
156 }
157 if !phases_report.is_empty() {
158 for line in &phases_report {
159 println!(" 📊 {}", line);
160 }
161 }
162 println!();
163 }
164 }
165
166 // ── Final summary ────────────────────────────────────────────────
167 println!("═══════════════════════════════════════════════════════════════");
168 println!(" 🏁 FINAL STATE");
169 println!("═══════════════════════════════════════════════════════════════");
170
171 let final_violations = cluster.violations();
172 println!(" Total violations at end: {}", final_violations.len());
173 for v in &final_violations {
174 println!(" ❌ {}", v);
175 }
176
177 // Phase distribution at end
178 println!();
179 println!(" Quantity phases at tick 20:");
180 for name in cluster.registered() {
181 let phase = cluster.phase(&name);
182 let current = cluster.current_value(&name);
183 let conserved = cluster.is_conserved(&name);
184 let drift = cluster.drift_rate(&name);
185 let snaps = cluster.snapshot_count(&name);
186 println!(
187 " {} | value={:.1}, conserved={}, phase={}, drift={:+.2}/tick, {} snapshots",
188 name, current, conserved, phase, drift, snaps
189 );
190 }
191
192 // ── Serde snapshot ───────────────────────────────────────────────
193 #[cfg(feature = "serde")]
194 {
195 println!();
196 println!(" ┌─ Serde JSON snapshot ──────────────────────────┐");
197 let json = cluster.snapshot_json();
198 println!(" {}", json.replace('\n', "\n "));
199 println!(" └────────────────────────────────────────────────┘");
200
201 // Demonstrate deserializing the whole ConservationChecker
202 let json_full = serde_json::to_string_pretty(&cluster).unwrap();
203 println!();
204 println!(" Full state (all history) available for archival:");
205 println!(" {} bytes", json_full.len());
206 }
207}Sourcepub fn initial_value(&self, name: &str) -> f64
pub fn initial_value(&self, name: &str) -> f64
Get the initial value the quantity was registered with.
§Panics
Panics if name has not been registered.
Examples found in repository?
3fn main() {
4 let mut budget = ConservationChecker::new();
5
6 // Monthly budget: $5000, we allow going $100 over as a buffer
7 budget.register("monthly_budget", 5000.0, 100.0);
8
9 let expenses = [1200.0, 800.0, 1500.0, 900.0, 700.0, 350.0];
10
11 for (i, expense) in expenses.iter().enumerate() {
12 let remaining = budget.current_value("monthly_budget") - expense;
13 budget.update("monthly_budget", remaining);
14 budget.snapshot();
15
16 let phase = budget.phase("monthly_budget");
17 let drift = budget.drift_rate("monthly_budget");
18
19 println!(
20 "Day {}: spent ${:.0}, remaining ${:.0} | phase={:?}, drift={:.1}/day",
21 i + 1,
22 expense,
23 remaining,
24 phase,
25 drift,
26 );
27
28 if phase == Phase::Transitioning {
29 println!(" ⚠️ Budget is depleting fast!");
30 }
31 }
32
33 if !budget.is_conserved("monthly_budget") {
34 println!("\n❌ Budget violated! Over by ${:.0}",
35 budget.initial_value("monthly_budget") - budget.current_value("monthly_budget"));
36 println!(" Violations: {:?}", budget.violations());
37 } else {
38 println!("\n✅ Budget intact with ${:.0} remaining", budget.current_value("monthly_budget"));
39 }
40}Sourcepub fn snapshot_count(&self, name: &str) -> usize
pub fn snapshot_count(&self, name: &str) -> usize
Get the number of snapshots recorded for a quantity (including the initial value).
§Panics
Panics if name has not been registered.
Examples found in repository?
38fn main() {
39 // ── Setup: 10-node cluster ──────────────────────────────────────
40 let node_names = [
41 "web-01", "web-02", "web-03",
42 "db-01", "db-02",
43 "worker-01", "worker-02", "worker-03",
44 "cache-01", "monitor-01",
45 ];
46
47 let mut nodes: Vec<Node> = node_names
48 .iter()
49 .enumerate()
50 .map(|(i, name)| {
51 let seed = i * 7;
52 Node {
53 name: name.to_string(),
54 cpu_percent: (60.0 + (seed % 20) as f64).min(100.0),
55 memory_gb: 16.0 + (seed % 8) as f64,
56 network_mbps: 100.0 + (seed % 50) as f64,
57 }
58 })
59 .collect();
60
61 // ── Conservation laws ────────────────────────────────────────────
62 // CPU must not drop below 30% on any node
63 // Memory must not exceed 48 GB (reverse conservation — this crate
64 // only checks "must not decrease", so we invert: track "memory_remaining")
65 // Network throughput must not drop below 10 Mbps
66 let mut cluster = ConservationChecker::new();
67
68 for node in &nodes {
69 cluster.register(format!("{}.cpu", node.name), node.cpu_percent, 0.0);
70 // Invert memory: track remaining = 64 - used, so low remaining = violation
71 let mem_remaining = 64.0 - node.memory_gb;
72 cluster.register(format!("{}.mem_remaining", node.name), mem_remaining, 0.0);
73 cluster.register(format!("{}.net", node.name), node.network_mbps, 10.0);
74 }
75
76 println!("╔══════════════════════════════════════════════════════════╗");
77 println!("║ SRE Cluster Monitoring Simulation (10 nodes, 20 ticks) ║");
78 println!("╚══════════════════════════════════════════════════════════╝");
79 println!();
80
81 // ── Static CPU minimum threshold ────────────────────────────────
82 const CPU_MIN: f64 = 30.0;
83
84 // ── Run simulation ──────────────────────────────────────────────
85 for tick_id in 0..20 {
86 tick(&mut nodes, tick_id);
87
88 // Update all conservation values
89 for node in &nodes {
90 cluster.update(&format!("{}.cpu", node.name), node.cpu_percent);
91 let mem_remaining = 64.0 - node.memory_gb;
92 cluster.update(&format!("{}.mem_remaining", node.name), mem_remaining);
93 cluster.update(&format!("{}.net", node.name), node.network_mbps);
94 }
95
96 cluster.snapshot(); // Take a snapshot every tick
97
98 // Collect violations for this tick
99 let violations = cluster.violations();
100
101 // Collect additional SRE-relevant stats
102 let mut cpu_alerts = Vec::new();
103 let mut mem_alerts = Vec::new();
104 let mut net_alerts = Vec::new();
105 let mut phases_report = Vec::new();
106
107 for node in &nodes {
108 let cpu_ok = cluster.is_conserved(&format!("{}.cpu", node.name));
109 let cpu_pct = cluster.current_value(&format!("{}.cpu", node.name));
110 if cpu_pct < CPU_MIN {
111 cpu_alerts.push(format!("{}@{}%", node.name, cpu_pct));
112 }
113
114 let mem_ok = cluster.is_conserved(&format!("{}.mem_remaining", node.name));
115 if !mem_ok {
116 let used = 64.0 - cluster.current_value(&format!("{}.mem_remaining", node.name));
117 mem_alerts.push(format!("{}@{:.1}GB", node.name, used));
118 }
119
120 let net_ok = cluster.is_conserved(&format!("{}.net", node.name));
121 if !net_ok {
122 net_alerts.push(format!(
123 "{}@{}Mbps",
124 node.name,
125 cluster.current_value(&format!("{}.net", node.name))
126 ));
127 }
128
129 // Track phases for nodes in transition
130 let cpu_phase = cluster.phase(&format!("{}.cpu", node.name));
131 let mem_phase = cluster.phase(&format!("{}.mem_remaining", node.name));
132 let net_phase = cluster.phase(&format!("{}.net", node.name));
133
134 if cpu_phase != Phase::Stable || mem_phase != Phase::Stable || net_phase != Phase::Stable {
135 phases_report.push(format!(
136 "{}: cpu={}, mem={}, net={}",
137 node.name, cpu_phase, mem_phase, net_phase
138 ));
139 }
140 }
141
142 // Print tick summary
143 if tick_id % 5 == 0 || !violations.is_empty() || !cpu_alerts.is_empty() || !mem_alerts.is_empty() {
144 println!("── Tick {:>2} ─────────────────────", tick_id);
145 println!(" Registered quantities: {}", cluster.registered().len());
146 println!(" Total violations: {}", violations.len());
147
148 if !cpu_alerts.is_empty() {
149 println!(" 🔴 CPU low: {}", cpu_alerts.join(", "));
150 }
151 if !mem_alerts.is_empty() {
152 println!(" 🟠 Memory high: {}", mem_alerts.join(", "));
153 }
154 if !net_alerts.is_empty() {
155 println!(" 🟡 Network low: {}", net_alerts.join(", "));
156 }
157 if !phases_report.is_empty() {
158 for line in &phases_report {
159 println!(" 📊 {}", line);
160 }
161 }
162 println!();
163 }
164 }
165
166 // ── Final summary ────────────────────────────────────────────────
167 println!("═══════════════════════════════════════════════════════════════");
168 println!(" 🏁 FINAL STATE");
169 println!("═══════════════════════════════════════════════════════════════");
170
171 let final_violations = cluster.violations();
172 println!(" Total violations at end: {}", final_violations.len());
173 for v in &final_violations {
174 println!(" ❌ {}", v);
175 }
176
177 // Phase distribution at end
178 println!();
179 println!(" Quantity phases at tick 20:");
180 for name in cluster.registered() {
181 let phase = cluster.phase(&name);
182 let current = cluster.current_value(&name);
183 let conserved = cluster.is_conserved(&name);
184 let drift = cluster.drift_rate(&name);
185 let snaps = cluster.snapshot_count(&name);
186 println!(
187 " {} | value={:.1}, conserved={}, phase={}, drift={:+.2}/tick, {} snapshots",
188 name, current, conserved, phase, drift, snaps
189 );
190 }
191
192 // ── Serde snapshot ───────────────────────────────────────────────
193 #[cfg(feature = "serde")]
194 {
195 println!();
196 println!(" ┌─ Serde JSON snapshot ──────────────────────────┐");
197 let json = cluster.snapshot_json();
198 println!(" {}", json.replace('\n', "\n "));
199 println!(" └────────────────────────────────────────────────┘");
200
201 // Demonstrate deserializing the whole ConservationChecker
202 let json_full = serde_json::to_string_pretty(&cluster).unwrap();
203 println!();
204 println!(" Full state (all history) available for archival:");
205 println!(" {} bytes", json_full.len());
206 }
207}Sourcepub fn registered(&self) -> Vec<String>
pub fn registered(&self) -> Vec<String>
List all registered quantity names in arbitrary order.
Examples found in repository?
38fn main() {
39 // ── Setup: 10-node cluster ──────────────────────────────────────
40 let node_names = [
41 "web-01", "web-02", "web-03",
42 "db-01", "db-02",
43 "worker-01", "worker-02", "worker-03",
44 "cache-01", "monitor-01",
45 ];
46
47 let mut nodes: Vec<Node> = node_names
48 .iter()
49 .enumerate()
50 .map(|(i, name)| {
51 let seed = i * 7;
52 Node {
53 name: name.to_string(),
54 cpu_percent: (60.0 + (seed % 20) as f64).min(100.0),
55 memory_gb: 16.0 + (seed % 8) as f64,
56 network_mbps: 100.0 + (seed % 50) as f64,
57 }
58 })
59 .collect();
60
61 // ── Conservation laws ────────────────────────────────────────────
62 // CPU must not drop below 30% on any node
63 // Memory must not exceed 48 GB (reverse conservation — this crate
64 // only checks "must not decrease", so we invert: track "memory_remaining")
65 // Network throughput must not drop below 10 Mbps
66 let mut cluster = ConservationChecker::new();
67
68 for node in &nodes {
69 cluster.register(format!("{}.cpu", node.name), node.cpu_percent, 0.0);
70 // Invert memory: track remaining = 64 - used, so low remaining = violation
71 let mem_remaining = 64.0 - node.memory_gb;
72 cluster.register(format!("{}.mem_remaining", node.name), mem_remaining, 0.0);
73 cluster.register(format!("{}.net", node.name), node.network_mbps, 10.0);
74 }
75
76 println!("╔══════════════════════════════════════════════════════════╗");
77 println!("║ SRE Cluster Monitoring Simulation (10 nodes, 20 ticks) ║");
78 println!("╚══════════════════════════════════════════════════════════╝");
79 println!();
80
81 // ── Static CPU minimum threshold ────────────────────────────────
82 const CPU_MIN: f64 = 30.0;
83
84 // ── Run simulation ──────────────────────────────────────────────
85 for tick_id in 0..20 {
86 tick(&mut nodes, tick_id);
87
88 // Update all conservation values
89 for node in &nodes {
90 cluster.update(&format!("{}.cpu", node.name), node.cpu_percent);
91 let mem_remaining = 64.0 - node.memory_gb;
92 cluster.update(&format!("{}.mem_remaining", node.name), mem_remaining);
93 cluster.update(&format!("{}.net", node.name), node.network_mbps);
94 }
95
96 cluster.snapshot(); // Take a snapshot every tick
97
98 // Collect violations for this tick
99 let violations = cluster.violations();
100
101 // Collect additional SRE-relevant stats
102 let mut cpu_alerts = Vec::new();
103 let mut mem_alerts = Vec::new();
104 let mut net_alerts = Vec::new();
105 let mut phases_report = Vec::new();
106
107 for node in &nodes {
108 let cpu_ok = cluster.is_conserved(&format!("{}.cpu", node.name));
109 let cpu_pct = cluster.current_value(&format!("{}.cpu", node.name));
110 if cpu_pct < CPU_MIN {
111 cpu_alerts.push(format!("{}@{}%", node.name, cpu_pct));
112 }
113
114 let mem_ok = cluster.is_conserved(&format!("{}.mem_remaining", node.name));
115 if !mem_ok {
116 let used = 64.0 - cluster.current_value(&format!("{}.mem_remaining", node.name));
117 mem_alerts.push(format!("{}@{:.1}GB", node.name, used));
118 }
119
120 let net_ok = cluster.is_conserved(&format!("{}.net", node.name));
121 if !net_ok {
122 net_alerts.push(format!(
123 "{}@{}Mbps",
124 node.name,
125 cluster.current_value(&format!("{}.net", node.name))
126 ));
127 }
128
129 // Track phases for nodes in transition
130 let cpu_phase = cluster.phase(&format!("{}.cpu", node.name));
131 let mem_phase = cluster.phase(&format!("{}.mem_remaining", node.name));
132 let net_phase = cluster.phase(&format!("{}.net", node.name));
133
134 if cpu_phase != Phase::Stable || mem_phase != Phase::Stable || net_phase != Phase::Stable {
135 phases_report.push(format!(
136 "{}: cpu={}, mem={}, net={}",
137 node.name, cpu_phase, mem_phase, net_phase
138 ));
139 }
140 }
141
142 // Print tick summary
143 if tick_id % 5 == 0 || !violations.is_empty() || !cpu_alerts.is_empty() || !mem_alerts.is_empty() {
144 println!("── Tick {:>2} ─────────────────────", tick_id);
145 println!(" Registered quantities: {}", cluster.registered().len());
146 println!(" Total violations: {}", violations.len());
147
148 if !cpu_alerts.is_empty() {
149 println!(" 🔴 CPU low: {}", cpu_alerts.join(", "));
150 }
151 if !mem_alerts.is_empty() {
152 println!(" 🟠 Memory high: {}", mem_alerts.join(", "));
153 }
154 if !net_alerts.is_empty() {
155 println!(" 🟡 Network low: {}", net_alerts.join(", "));
156 }
157 if !phases_report.is_empty() {
158 for line in &phases_report {
159 println!(" 📊 {}", line);
160 }
161 }
162 println!();
163 }
164 }
165
166 // ── Final summary ────────────────────────────────────────────────
167 println!("═══════════════════════════════════════════════════════════════");
168 println!(" 🏁 FINAL STATE");
169 println!("═══════════════════════════════════════════════════════════════");
170
171 let final_violations = cluster.violations();
172 println!(" Total violations at end: {}", final_violations.len());
173 for v in &final_violations {
174 println!(" ❌ {}", v);
175 }
176
177 // Phase distribution at end
178 println!();
179 println!(" Quantity phases at tick 20:");
180 for name in cluster.registered() {
181 let phase = cluster.phase(&name);
182 let current = cluster.current_value(&name);
183 let conserved = cluster.is_conserved(&name);
184 let drift = cluster.drift_rate(&name);
185 let snaps = cluster.snapshot_count(&name);
186 println!(
187 " {} | value={:.1}, conserved={}, phase={}, drift={:+.2}/tick, {} snapshots",
188 name, current, conserved, phase, drift, snaps
189 );
190 }
191
192 // ── Serde snapshot ───────────────────────────────────────────────
193 #[cfg(feature = "serde")]
194 {
195 println!();
196 println!(" ┌─ Serde JSON snapshot ──────────────────────────┐");
197 let json = cluster.snapshot_json();
198 println!(" {}", json.replace('\n', "\n "));
199 println!(" └────────────────────────────────────────────────┘");
200
201 // Demonstrate deserializing the whole ConservationChecker
202 let json_full = serde_json::to_string_pretty(&cluster).unwrap();
203 println!();
204 println!(" Full state (all history) available for archival:");
205 println!(" {} bytes", json_full.len());
206 }
207}Sourcepub fn deregister(&mut self, name: &str) -> bool
pub fn deregister(&mut self, name: &str) -> bool
Remove a quantity from the tracker.
Returns true if the quantity existed and was removed, false otherwise.
Sourcepub fn reset_baseline(&mut self, name: &str)
pub fn reset_baseline(&mut self, name: &str)
Reset a quantity’s initial value to its current value, clearing any violations.
Useful after resolving a violation to establish a new baseline without re-registering the quantity.
§Panics
Panics if name has not been registered.
§Example
use conservation_checker::ConservationChecker;
let mut checker = ConservationChecker::new();
checker.register("budget", 100.0, 0.0);
checker.update("budget", 50.0);
assert!(!checker.is_conserved("budget"));
checker.reset_baseline("budget");
assert!(checker.is_conserved("budget"));
assert_eq!(checker.initial_value("budget"), 50.0);Trait Implementations§
Source§impl Clone for ConservationChecker
impl Clone for ConservationChecker
Source§fn clone(&self) -> ConservationChecker
fn clone(&self) -> ConservationChecker
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more