Skip to main content

ConservationChecker

Struct ConservationChecker 

Source
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

Source

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?
examples/basic.rs (line 4)
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
Hide additional examples
examples/budget_tracking.rs (line 4)
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}
examples/cluster_monitoring.rs (line 66)
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}
Source

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?
examples/basic.rs (line 5)
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
Hide additional examples
examples/budget_tracking.rs (line 7)
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}
examples/cluster_monitoring.rs (line 69)
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}
Source

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?
examples/basic.rs (line 9)
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
Hide additional examples
examples/budget_tracking.rs (line 13)
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}
examples/cluster_monitoring.rs (line 90)
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}
Source

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?
examples/basic.rs (line 13)
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
Hide additional examples
examples/budget_tracking.rs (line 33)
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}
examples/cluster_monitoring.rs (line 108)
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}
Source

pub fn violations(&self) -> Vec<String>

Return the names of all quantities that are currently violated.

Examples found in repository?
examples/basic.rs (line 20)
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
Hide additional examples
examples/budget_tracking.rs (line 36)
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}
examples/cluster_monitoring.rs (line 99)
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}
Source

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 downward
Examples found in repository?
examples/basic.rs (line 10)
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
Hide additional examples
examples/budget_tracking.rs (line 14)
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}
examples/cluster_monitoring.rs (line 96)
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}
Source

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?
examples/basic.rs (line 21)
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
Hide additional examples
examples/budget_tracking.rs (line 16)
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}
examples/cluster_monitoring.rs (line 130)
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}
Source

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?
examples/basic.rs (line 22)
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
Hide additional examples
examples/budget_tracking.rs (line 17)
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}
examples/cluster_monitoring.rs (line 184)
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}
Source

pub fn current_value(&self, name: &str) -> f64

Get the current value of a quantity.

§Panics

Panics if name has not been registered.

Examples found in repository?
examples/basic.rs (line 7)
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
Hide additional examples
examples/budget_tracking.rs (line 12)
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}
examples/cluster_monitoring.rs (line 109)
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}
Source

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?
examples/budget_tracking.rs (line 35)
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}
Source

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?
examples/cluster_monitoring.rs (line 185)
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}
Source

pub fn registered(&self) -> Vec<String>

List all registered quantity names in arbitrary order.

Examples found in repository?
examples/cluster_monitoring.rs (line 145)
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}
Source

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.

Source

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

Source§

fn clone(&self) -> ConservationChecker

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConservationChecker

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ConservationChecker

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.