pub struct Kiln { /* private fields */ }Expand description
The kiln — the runtime that fires tasks and cools them to detect patterns.
Like a pottery kiln, this runtime has two distinct phases:
-
Firing: Tasks execute (
fire()), producing outputs and metrics. This is the hot phase — the work gets done. -
Cooling: After all tasks have fired, the runtime examines the completed tasks for emergent patterns. The crackle glaze forms in the cooling, not the firing.
§Example
use crackle_runtime::{CrackleTask, Kiln, ThermalProfile, TaskOutput};
struct MyTask { x: f64 }
impl CrackleTask for MyTask {
type Output = f64;
fn fire(&self) -> TaskOutput<Self::Output> {
TaskOutput::new(self.x, vec![("value".into(), self.x)])
}
}
let mut kiln = Kiln::new(ThermalProfile::default());
kiln.fire_task(MyTask { x: 1.0 })?;
kiln.fire_task(MyTask { x: 2.0 })?;
kiln.fire_task(MyTask { x: 3.0 })?;
let patterns = kiln.cool();Implementations§
Source§impl Kiln
impl Kiln
Sourcepub fn new(profile: ThermalProfile) -> Self
pub fn new(profile: ThermalProfile) -> Self
Create a new kiln with the given thermal profile.
Examples found in repository?
29fn main() {
30 let mut kiln = Kiln::new(ThermalProfile::fast_cooling());
31
32 kiln.fire_and_record(NumberTask { value: 2.0, name: "a".into() }).unwrap();
33 kiln.fire_and_record(NumberTask { value: 2.1, name: "b".into() }).unwrap();
34 kiln.fire_and_record(NumberTask { value: 9.8, name: "c".into() }).unwrap();
35 kiln.fire_and_record(NumberTask { value: 10.0, name: "d".into() }).unwrap();
36 kiln.fire_and_record(NumberTask { value: 2.2, name: "e".into() }).unwrap();
37
38 println!("Tasks in kiln: {}", kiln.task_count());
39
40 let patterns = kiln.cool();
41 println!("\nDetected {} pattern(s):\n", patterns.len());
42
43 for p in &patterns {
44 println!("[{}] {}", p.kind(), p.description());
45 println!(" confidence: {:.2}", p.confidence());
46 println!(" tasks: {:?}\n", p.involved_tasks());
47 }
48}More examples
30fn main() {
31 let mut kiln = Kiln::new(ThermalProfile::default());
32
33 // Normal builds
34 kiln.fire_and_record(CiBuild { duration_secs: 45.0, test_count: 200.0, branch: "feature/auth".into() }).unwrap();
35 kiln.fire_and_record(CiBuild { duration_secs: 42.0, test_count: 195.0, branch: "fix/typo".into() }).unwrap();
36 kiln.fire_and_record(CiBuild { duration_secs: 48.0, test_count: 210.0, branch: "feature/ui".into() }).unwrap();
37
38 // Something changed — builds got slower
39 kiln.fire_and_record(CiBuild { duration_secs: 95.0, test_count: 200.0, branch: "feature/cache".into() }).unwrap();
40 kiln.fire_and_record(CiBuild { duration_secs: 102.0, test_count: 198.0, branch: "chore/deps".into() }).unwrap();
41
42 let patterns = kiln.cool();
43 println!("CI Build Pattern Analysis");
44 println!("=========================\n");
45
46 for p in &patterns {
47 println!("[{}] {}", p.kind().to_string().to_uppercase(), p.description());
48 println!(" confidence: {:.2}", p.confidence());
49 println!(" branches: {:?}\n", p.involved_tasks());
50 }
51
52 // Check for phase transition in build duration
53 let phase_shifts: Vec<_> = patterns.iter()
54 .filter(|p| format!("{}", p.kind()) == "phase transition")
55 .collect();
56
57 if !phase_shifts.is_empty() {
58 println!("⚠️ Build duration shifted significantly — investigate recent changes!");
59 }
60}32fn main() {
33 let mut kiln = Kiln::new(ThermalProfile::default());
34
35 // /api/users — fast, small responses
36 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/users".into(), latency_ms: 45.0, status: 200, response_bytes: 1200.0 }).unwrap();
37 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/users".into(), latency_ms: 48.0, status: 200, response_bytes: 1150.0 }).unwrap();
38 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/users".into(), latency_ms: 52.0, status: 200, response_bytes: 1300.0 }).unwrap();
39
40 // /api/reports — slow, large responses (correlated with bytes)
41 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/reports".into(), latency_ms: 350.0, status: 200, response_bytes: 55000.0 }).unwrap();
42 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/reports".into(), latency_ms: 380.0, status: 200, response_bytes: 62000.0 }).unwrap();
43
44 // /api/health — tiny, always fast (conservation candidate)
45 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/health".into(), latency_ms: 3.0, status: 200, response_bytes: 15.0 }).unwrap();
46 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/health".into(), latency_ms: 4.0, status: 200, response_bytes: 15.0 }).unwrap();
47
48 let patterns = kiln.cool();
49 println!("API Monitoring — Pattern Report");
50 println!("================================\n");
51 println!("{} patterns detected:\n", patterns.len());
52
53 for p in &patterns {
54 println!("[{}] {}", p.kind(), p.description());
55 println!(" confidence: {:.2}", p.confidence());
56 if !p.involved_tasks().is_empty() {
57 println!(" endpoints: {:?}", p.involved_tasks());
58 }
59 println!();
60 }
61
62 // Highlight correlations between latency and response size
63 let correlations: Vec<_> = patterns.iter()
64 .filter(|p| format!("{}", p.kind()) == "correlation")
65 .collect();
66
67 if !correlations.is_empty() {
68 println!("📊 Latency and response size are correlated — consider pagination or caching.");
69 }
70}98fn main() {
99 let total_events = 1000;
100 let batch_size = 100;
101 let num_batches = total_events / batch_size;
102
103 println!("═══════════════════════════════════════════════════════════════");
104 println!(" CRACKLE-RUNTIME: EVENT STREAM PROCESSOR TEST");
105 println!("═══════════════════════════════════════════════════════════════\n");
106 println!("Processing {} events across {} batches of {}...\n",
107 total_events, num_batches, batch_size);
108
109 let start = Instant::now();
110
111 // Phase 1: Healthy system (batches 1-4)
112 println!("─── Phase 1: Healthy System ───");
113 let mut healthy_kiln = Kiln::new(
114 ThermalProfile::fast_cooling()
115 );
116
117 // Batch 1-4: all healthy, 10ms baseline latency
118 for batch_id in 1..=4 {
119 let events = simulate_batch(batch_id, batch_size as u32, 10.0, 0.05);
120 for event in events {
121 healthy_kiln.fire_and_record(event).unwrap();
122 }
123
124 let elapsed = start.elapsed();
125 println!(" Batch {}/{} fired ({} events, {:.0}ms)",
126 batch_id, num_batches, batch_size, elapsed.as_millis());
127 }
128
129 // Cool healthy phase
130 let healthy_patterns = healthy_kiln.cool();
131 println!("\n Healthy system patterns ({} detected):", healthy_patterns.len());
132 for p in &healthy_patterns {
133 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
134 }
135 // Also check conservation on records_in/records_out
136 let conservation = healthy_kiln.distribution_shift(10);
137 println!(" Distribution shifts:");
138 for (name, shift) in &conservation[..conservation.len().min(3)] {
139 println!(" {} → KL = {:.4}", name, shift);
140 }
141
142 println!();
143
144 // Phase 2: Degrading system (batches 5-8)
145 println!("─── Phase 2: Degrading System ───");
146
147 let mut degrading_kiln = Kiln::new(
148 ThermalProfile::fast_cooling()
149 );
150
151 // Batches where degradation ramps up
152 let degradation_levels = [0.2, 0.5, 0.9, 1.5];
153 for (i, °radation) in degradation_levels.iter().enumerate() {
154 let batch_id = (5 + i) as u32;
155 // latency increases with degradation
156 let base_latency = 10.0 + degradation * 50.0;
157
158 let events = simulate_batch(batch_id, batch_size as u32, base_latency, degradation);
159 for event in events {
160 degrading_kiln.fire_and_record(event).unwrap();
161 }
162
163 let elapsed = start.elapsed();
164 println!(" Batch {}/{} [degradation={:.1}x] fired (latency baseline: {:.0}ms, {:.0}ms total)",
165 batch_id, num_batches, degradation, base_latency, elapsed.as_millis());
166 }
167
168 let degrading_patterns = degrading_kiln.cool();
169 println!("\n Degrading system patterns ({} detected):", degrading_patterns.len());
170 for p in °rading_patterns {
171 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
172 }
173
174 // JSD shift (distribution shape changes)
175 let jsd_shifts = degrading_kiln.jsd_shift(10);
176 println!(" JSD shifts (distribution shape changes):");
177 for (name, shift) in &jsd_shifts[..jsd_shifts.len().min(5)] {
178 println!(" {} → JSD = {:.4}", name, shift);
179 }
180
181 // Permutation entropy (temporal structure)
182 let pes = degrading_kiln.permutation_entropies(4);
183 println!(" Permutation entropies (temporal structure):");
184 for (name, pe) in &pes {
185 println!(" {} → PE = {:.4}", name, pe);
186 }
187
188 println!();
189
190 // Phase 3: Conservation law test — records in = records out should be violated
191 // as errors increase
192 println!("─── Phase 3: Conservation Law Verification ───");
193 let mut conservation_kiln = Kiln::new(
194 ThermalProfile::fast_cooling()
195 );
196
197 // Force records_in = records_out for first 3 batches (healthy conservation)
198 for batch_id in 1..=3 {
199 let event = ProcessedEvent {
200 event_id: batch_id as u64,
201 batch_id: batch_id as u32,
202 processing_time_ms: 10.0,
203 throughput: 100.0,
204 memory_mb: 128.0,
205 error_rate: 0.01,
206 records_in: 1000.0,
207 records_out: 995.0, // nearly conserved
208 cpu_temp_c: 65.0,
209 latency_p99_ms: 25.0,
210 consumer_lag: 10.0,
211 gc_pause_ms: 5.0,
212 };
213 conservation_kiln.fire_and_record(event).unwrap();
214 }
215
216 // Force violation of records conservation (errors spike)
217 for batch_id in 4..=6 {
218 let event = ProcessedEvent {
219 event_id: batch_id as u64,
220 batch_id: batch_id as u32,
221 processing_time_ms: 10.0 + batch_id as f64 * 10.0,
222 throughput: 50.0,
223 memory_mb: 256.0,
224 error_rate: 0.3,
225 records_in: 1000.0,
226 records_out: 700.0, // 30% loss — conservation violated
227 cpu_temp_c: 80.0,
228 latency_p99_ms: 100.0,
229 consumer_lag: 500.0,
230 gc_pause_ms: 20.0,
231 };
232 conservation_kiln.fire_and_record(event).unwrap();
233 }
234
235 let conservation_patterns = conservation_kiln.cool();
236 println!(" Conservation-specific patterns ({} detected):", conservation_patterns.len());
237 for p in &conservation_patterns {
238 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
239 }
240
241 // MI matrix to show nonlinear dependencies
242 let mi = conservation_kiln.mi_matrix(8);
243 println!(" Mutual Information matrix (8 bins):");
244 let metric_names = ["processing_time_ms", "error_rate", "records_in", "records_out", "memory_mb"];
245 for (i, name_i) in metric_names.iter().enumerate() {
246 for (j, name_j) in metric_names.iter().enumerate() {
247 if i == j {
248 println!(" I({:25}; {:25}) = H({})", name_i, name_j, name_i);
249 } else {
250 println!(" I({:25}; {:25}) = {:.4} bits", name_i, name_j, mi[i][j]);
251 }
252 }
253 }
254
255 println!();
256 println!("───────────────────────────────────────────────────────────────");
257 println!(" SUMMARY");
258 println!("───────────────────────────────────────────────────────────────\n");
259
260 // Degradation detection analysis
261 println!(" Degradation Metrics:");
262 let pt = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::PhaseTransition));
263 let pt_count = pt.count();
264 println!(" Phase transitions detected in degraded system: {} alerts",
265 pt_count);
266
267 let corr = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Correlation));
268 let corr_count = corr.count();
269 println!(" Correlations in degraded system: {} pairs", corr_count);
270
271 let cons = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Conservation));
272 let cons_count = cons.count();
273 println!(" Conservation laws in degraded system: {} found", cons_count);
274
275 let clust = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Clustering));
276 let clust_count = clust.count();
277 println!(" Clusters in degraded system: {} groups", clust_count);
278
279 println!();
280 let total_duration = start.elapsed();
281 println!(" Total test duration: {:.2}s", total_duration.as_secs_f64());
282 println!(" Events processed: {}", total_events);
283 println!(" Avg throughput: {:.0} events/s",
284 total_events as f64 / total_duration.as_secs_f64());
285}Sourcepub fn default_profile() -> Self
pub fn default_profile() -> Self
Create a kiln with default thermal profile.
Sourcepub fn fire_task<T: CrackleTask>(
&self,
task: T,
) -> Result<TaskOutput<T::Output>>
pub fn fire_task<T: CrackleTask>( &self, task: T, ) -> Result<TaskOutput<T::Output>>
Fire a single task and return its output (without recording).
Returns the task’s output value.
§Errors
Returns CrackleError::KilnCooled if called after cool().
Sourcepub fn fire_and_record<T: CrackleTask>(
&mut self,
task: T,
) -> Result<TaskOutput<T::Output>>
pub fn fire_and_record<T: CrackleTask>( &mut self, task: T, ) -> Result<TaskOutput<T::Output>>
Fire a task and record it in the kiln for later cooling.
This stores the task’s metrics internally so patterns can be detected during the cooling phase.
§Errors
Returns CrackleError::KilnCooled if called after cool().
Examples found in repository?
29fn main() {
30 let mut kiln = Kiln::new(ThermalProfile::fast_cooling());
31
32 kiln.fire_and_record(NumberTask { value: 2.0, name: "a".into() }).unwrap();
33 kiln.fire_and_record(NumberTask { value: 2.1, name: "b".into() }).unwrap();
34 kiln.fire_and_record(NumberTask { value: 9.8, name: "c".into() }).unwrap();
35 kiln.fire_and_record(NumberTask { value: 10.0, name: "d".into() }).unwrap();
36 kiln.fire_and_record(NumberTask { value: 2.2, name: "e".into() }).unwrap();
37
38 println!("Tasks in kiln: {}", kiln.task_count());
39
40 let patterns = kiln.cool();
41 println!("\nDetected {} pattern(s):\n", patterns.len());
42
43 for p in &patterns {
44 println!("[{}] {}", p.kind(), p.description());
45 println!(" confidence: {:.2}", p.confidence());
46 println!(" tasks: {:?}\n", p.involved_tasks());
47 }
48}More examples
30fn main() {
31 let mut kiln = Kiln::new(ThermalProfile::default());
32
33 // Normal builds
34 kiln.fire_and_record(CiBuild { duration_secs: 45.0, test_count: 200.0, branch: "feature/auth".into() }).unwrap();
35 kiln.fire_and_record(CiBuild { duration_secs: 42.0, test_count: 195.0, branch: "fix/typo".into() }).unwrap();
36 kiln.fire_and_record(CiBuild { duration_secs: 48.0, test_count: 210.0, branch: "feature/ui".into() }).unwrap();
37
38 // Something changed — builds got slower
39 kiln.fire_and_record(CiBuild { duration_secs: 95.0, test_count: 200.0, branch: "feature/cache".into() }).unwrap();
40 kiln.fire_and_record(CiBuild { duration_secs: 102.0, test_count: 198.0, branch: "chore/deps".into() }).unwrap();
41
42 let patterns = kiln.cool();
43 println!("CI Build Pattern Analysis");
44 println!("=========================\n");
45
46 for p in &patterns {
47 println!("[{}] {}", p.kind().to_string().to_uppercase(), p.description());
48 println!(" confidence: {:.2}", p.confidence());
49 println!(" branches: {:?}\n", p.involved_tasks());
50 }
51
52 // Check for phase transition in build duration
53 let phase_shifts: Vec<_> = patterns.iter()
54 .filter(|p| format!("{}", p.kind()) == "phase transition")
55 .collect();
56
57 if !phase_shifts.is_empty() {
58 println!("⚠️ Build duration shifted significantly — investigate recent changes!");
59 }
60}32fn main() {
33 let mut kiln = Kiln::new(ThermalProfile::default());
34
35 // /api/users — fast, small responses
36 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/users".into(), latency_ms: 45.0, status: 200, response_bytes: 1200.0 }).unwrap();
37 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/users".into(), latency_ms: 48.0, status: 200, response_bytes: 1150.0 }).unwrap();
38 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/users".into(), latency_ms: 52.0, status: 200, response_bytes: 1300.0 }).unwrap();
39
40 // /api/reports — slow, large responses (correlated with bytes)
41 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/reports".into(), latency_ms: 350.0, status: 200, response_bytes: 55000.0 }).unwrap();
42 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/reports".into(), latency_ms: 380.0, status: 200, response_bytes: 62000.0 }).unwrap();
43
44 // /api/health — tiny, always fast (conservation candidate)
45 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/health".into(), latency_ms: 3.0, status: 200, response_bytes: 15.0 }).unwrap();
46 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/health".into(), latency_ms: 4.0, status: 200, response_bytes: 15.0 }).unwrap();
47
48 let patterns = kiln.cool();
49 println!("API Monitoring — Pattern Report");
50 println!("================================\n");
51 println!("{} patterns detected:\n", patterns.len());
52
53 for p in &patterns {
54 println!("[{}] {}", p.kind(), p.description());
55 println!(" confidence: {:.2}", p.confidence());
56 if !p.involved_tasks().is_empty() {
57 println!(" endpoints: {:?}", p.involved_tasks());
58 }
59 println!();
60 }
61
62 // Highlight correlations between latency and response size
63 let correlations: Vec<_> = patterns.iter()
64 .filter(|p| format!("{}", p.kind()) == "correlation")
65 .collect();
66
67 if !correlations.is_empty() {
68 println!("📊 Latency and response size are correlated — consider pagination or caching.");
69 }
70}98fn main() {
99 let total_events = 1000;
100 let batch_size = 100;
101 let num_batches = total_events / batch_size;
102
103 println!("═══════════════════════════════════════════════════════════════");
104 println!(" CRACKLE-RUNTIME: EVENT STREAM PROCESSOR TEST");
105 println!("═══════════════════════════════════════════════════════════════\n");
106 println!("Processing {} events across {} batches of {}...\n",
107 total_events, num_batches, batch_size);
108
109 let start = Instant::now();
110
111 // Phase 1: Healthy system (batches 1-4)
112 println!("─── Phase 1: Healthy System ───");
113 let mut healthy_kiln = Kiln::new(
114 ThermalProfile::fast_cooling()
115 );
116
117 // Batch 1-4: all healthy, 10ms baseline latency
118 for batch_id in 1..=4 {
119 let events = simulate_batch(batch_id, batch_size as u32, 10.0, 0.05);
120 for event in events {
121 healthy_kiln.fire_and_record(event).unwrap();
122 }
123
124 let elapsed = start.elapsed();
125 println!(" Batch {}/{} fired ({} events, {:.0}ms)",
126 batch_id, num_batches, batch_size, elapsed.as_millis());
127 }
128
129 // Cool healthy phase
130 let healthy_patterns = healthy_kiln.cool();
131 println!("\n Healthy system patterns ({} detected):", healthy_patterns.len());
132 for p in &healthy_patterns {
133 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
134 }
135 // Also check conservation on records_in/records_out
136 let conservation = healthy_kiln.distribution_shift(10);
137 println!(" Distribution shifts:");
138 for (name, shift) in &conservation[..conservation.len().min(3)] {
139 println!(" {} → KL = {:.4}", name, shift);
140 }
141
142 println!();
143
144 // Phase 2: Degrading system (batches 5-8)
145 println!("─── Phase 2: Degrading System ───");
146
147 let mut degrading_kiln = Kiln::new(
148 ThermalProfile::fast_cooling()
149 );
150
151 // Batches where degradation ramps up
152 let degradation_levels = [0.2, 0.5, 0.9, 1.5];
153 for (i, °radation) in degradation_levels.iter().enumerate() {
154 let batch_id = (5 + i) as u32;
155 // latency increases with degradation
156 let base_latency = 10.0 + degradation * 50.0;
157
158 let events = simulate_batch(batch_id, batch_size as u32, base_latency, degradation);
159 for event in events {
160 degrading_kiln.fire_and_record(event).unwrap();
161 }
162
163 let elapsed = start.elapsed();
164 println!(" Batch {}/{} [degradation={:.1}x] fired (latency baseline: {:.0}ms, {:.0}ms total)",
165 batch_id, num_batches, degradation, base_latency, elapsed.as_millis());
166 }
167
168 let degrading_patterns = degrading_kiln.cool();
169 println!("\n Degrading system patterns ({} detected):", degrading_patterns.len());
170 for p in °rading_patterns {
171 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
172 }
173
174 // JSD shift (distribution shape changes)
175 let jsd_shifts = degrading_kiln.jsd_shift(10);
176 println!(" JSD shifts (distribution shape changes):");
177 for (name, shift) in &jsd_shifts[..jsd_shifts.len().min(5)] {
178 println!(" {} → JSD = {:.4}", name, shift);
179 }
180
181 // Permutation entropy (temporal structure)
182 let pes = degrading_kiln.permutation_entropies(4);
183 println!(" Permutation entropies (temporal structure):");
184 for (name, pe) in &pes {
185 println!(" {} → PE = {:.4}", name, pe);
186 }
187
188 println!();
189
190 // Phase 3: Conservation law test — records in = records out should be violated
191 // as errors increase
192 println!("─── Phase 3: Conservation Law Verification ───");
193 let mut conservation_kiln = Kiln::new(
194 ThermalProfile::fast_cooling()
195 );
196
197 // Force records_in = records_out for first 3 batches (healthy conservation)
198 for batch_id in 1..=3 {
199 let event = ProcessedEvent {
200 event_id: batch_id as u64,
201 batch_id: batch_id as u32,
202 processing_time_ms: 10.0,
203 throughput: 100.0,
204 memory_mb: 128.0,
205 error_rate: 0.01,
206 records_in: 1000.0,
207 records_out: 995.0, // nearly conserved
208 cpu_temp_c: 65.0,
209 latency_p99_ms: 25.0,
210 consumer_lag: 10.0,
211 gc_pause_ms: 5.0,
212 };
213 conservation_kiln.fire_and_record(event).unwrap();
214 }
215
216 // Force violation of records conservation (errors spike)
217 for batch_id in 4..=6 {
218 let event = ProcessedEvent {
219 event_id: batch_id as u64,
220 batch_id: batch_id as u32,
221 processing_time_ms: 10.0 + batch_id as f64 * 10.0,
222 throughput: 50.0,
223 memory_mb: 256.0,
224 error_rate: 0.3,
225 records_in: 1000.0,
226 records_out: 700.0, // 30% loss — conservation violated
227 cpu_temp_c: 80.0,
228 latency_p99_ms: 100.0,
229 consumer_lag: 500.0,
230 gc_pause_ms: 20.0,
231 };
232 conservation_kiln.fire_and_record(event).unwrap();
233 }
234
235 let conservation_patterns = conservation_kiln.cool();
236 println!(" Conservation-specific patterns ({} detected):", conservation_patterns.len());
237 for p in &conservation_patterns {
238 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
239 }
240
241 // MI matrix to show nonlinear dependencies
242 let mi = conservation_kiln.mi_matrix(8);
243 println!(" Mutual Information matrix (8 bins):");
244 let metric_names = ["processing_time_ms", "error_rate", "records_in", "records_out", "memory_mb"];
245 for (i, name_i) in metric_names.iter().enumerate() {
246 for (j, name_j) in metric_names.iter().enumerate() {
247 if i == j {
248 println!(" I({:25}; {:25}) = H({})", name_i, name_j, name_i);
249 } else {
250 println!(" I({:25}; {:25}) = {:.4} bits", name_i, name_j, mi[i][j]);
251 }
252 }
253 }
254
255 println!();
256 println!("───────────────────────────────────────────────────────────────");
257 println!(" SUMMARY");
258 println!("───────────────────────────────────────────────────────────────\n");
259
260 // Degradation detection analysis
261 println!(" Degradation Metrics:");
262 let pt = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::PhaseTransition));
263 let pt_count = pt.count();
264 println!(" Phase transitions detected in degraded system: {} alerts",
265 pt_count);
266
267 let corr = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Correlation));
268 let corr_count = corr.count();
269 println!(" Correlations in degraded system: {} pairs", corr_count);
270
271 let cons = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Conservation));
272 let cons_count = cons.count();
273 println!(" Conservation laws in degraded system: {} found", cons_count);
274
275 let clust = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Clustering));
276 let clust_count = clust.count();
277 println!(" Clusters in degraded system: {} groups", clust_count);
278
279 println!();
280 let total_duration = start.elapsed();
281 println!(" Total test duration: {:.2}s", total_duration.as_secs_f64());
282 println!(" Events processed: {}", total_events);
283 println!(" Avg throughput: {:.0} events/s",
284 total_events as f64 / total_duration.as_secs_f64());
285}Sourcepub fn fire_all<T: CrackleTask>(
&mut self,
tasks: Vec<T>,
) -> Result<Vec<TaskOutput<T::Output>>>
pub fn fire_all<T: CrackleTask>( &mut self, tasks: Vec<T>, ) -> Result<Vec<TaskOutput<T::Output>>>
Fire multiple tasks in sequence and record them all.
§Errors
Returns the first error encountered. Already-fired tasks are still recorded.
Sourcepub fn add_entry(
&mut self,
label: impl Into<String>,
metrics: Vec<(String, f64)>,
)
pub fn add_entry( &mut self, label: impl Into<String>, metrics: Vec<(String, f64)>, )
Add a pre-computed task entry directly (useful for testing).
Sourcepub fn task_count(&self) -> usize
pub fn task_count(&self) -> usize
The number of tasks currently in the kiln.
Examples found in repository?
29fn main() {
30 let mut kiln = Kiln::new(ThermalProfile::fast_cooling());
31
32 kiln.fire_and_record(NumberTask { value: 2.0, name: "a".into() }).unwrap();
33 kiln.fire_and_record(NumberTask { value: 2.1, name: "b".into() }).unwrap();
34 kiln.fire_and_record(NumberTask { value: 9.8, name: "c".into() }).unwrap();
35 kiln.fire_and_record(NumberTask { value: 10.0, name: "d".into() }).unwrap();
36 kiln.fire_and_record(NumberTask { value: 2.2, name: "e".into() }).unwrap();
37
38 println!("Tasks in kiln: {}", kiln.task_count());
39
40 let patterns = kiln.cool();
41 println!("\nDetected {} pattern(s):\n", patterns.len());
42
43 for p in &patterns {
44 println!("[{}] {}", p.kind(), p.description());
45 println!(" confidence: {:.2}", p.confidence());
46 println!(" tasks: {:?}\n", p.involved_tasks());
47 }
48}Sourcepub fn cool(&mut self) -> Vec<CracklePattern>
pub fn cool(&mut self) -> Vec<CracklePattern>
Cool the kiln: run pattern detection across all completed tasks.
This is where the beauty emerges. Just as a pottery kiln’s crackle glaze forms during cooling, the patterns that crackle-runtime detects are only visible after the heat of execution has passed.
Returns all detected patterns.
Examples found in repository?
29fn main() {
30 let mut kiln = Kiln::new(ThermalProfile::fast_cooling());
31
32 kiln.fire_and_record(NumberTask { value: 2.0, name: "a".into() }).unwrap();
33 kiln.fire_and_record(NumberTask { value: 2.1, name: "b".into() }).unwrap();
34 kiln.fire_and_record(NumberTask { value: 9.8, name: "c".into() }).unwrap();
35 kiln.fire_and_record(NumberTask { value: 10.0, name: "d".into() }).unwrap();
36 kiln.fire_and_record(NumberTask { value: 2.2, name: "e".into() }).unwrap();
37
38 println!("Tasks in kiln: {}", kiln.task_count());
39
40 let patterns = kiln.cool();
41 println!("\nDetected {} pattern(s):\n", patterns.len());
42
43 for p in &patterns {
44 println!("[{}] {}", p.kind(), p.description());
45 println!(" confidence: {:.2}", p.confidence());
46 println!(" tasks: {:?}\n", p.involved_tasks());
47 }
48}More examples
30fn main() {
31 let mut kiln = Kiln::new(ThermalProfile::default());
32
33 // Normal builds
34 kiln.fire_and_record(CiBuild { duration_secs: 45.0, test_count: 200.0, branch: "feature/auth".into() }).unwrap();
35 kiln.fire_and_record(CiBuild { duration_secs: 42.0, test_count: 195.0, branch: "fix/typo".into() }).unwrap();
36 kiln.fire_and_record(CiBuild { duration_secs: 48.0, test_count: 210.0, branch: "feature/ui".into() }).unwrap();
37
38 // Something changed — builds got slower
39 kiln.fire_and_record(CiBuild { duration_secs: 95.0, test_count: 200.0, branch: "feature/cache".into() }).unwrap();
40 kiln.fire_and_record(CiBuild { duration_secs: 102.0, test_count: 198.0, branch: "chore/deps".into() }).unwrap();
41
42 let patterns = kiln.cool();
43 println!("CI Build Pattern Analysis");
44 println!("=========================\n");
45
46 for p in &patterns {
47 println!("[{}] {}", p.kind().to_string().to_uppercase(), p.description());
48 println!(" confidence: {:.2}", p.confidence());
49 println!(" branches: {:?}\n", p.involved_tasks());
50 }
51
52 // Check for phase transition in build duration
53 let phase_shifts: Vec<_> = patterns.iter()
54 .filter(|p| format!("{}", p.kind()) == "phase transition")
55 .collect();
56
57 if !phase_shifts.is_empty() {
58 println!("⚠️ Build duration shifted significantly — investigate recent changes!");
59 }
60}32fn main() {
33 let mut kiln = Kiln::new(ThermalProfile::default());
34
35 // /api/users — fast, small responses
36 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/users".into(), latency_ms: 45.0, status: 200, response_bytes: 1200.0 }).unwrap();
37 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/users".into(), latency_ms: 48.0, status: 200, response_bytes: 1150.0 }).unwrap();
38 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/users".into(), latency_ms: 52.0, status: 200, response_bytes: 1300.0 }).unwrap();
39
40 // /api/reports — slow, large responses (correlated with bytes)
41 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/reports".into(), latency_ms: 350.0, status: 200, response_bytes: 55000.0 }).unwrap();
42 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/reports".into(), latency_ms: 380.0, status: 200, response_bytes: 62000.0 }).unwrap();
43
44 // /api/health — tiny, always fast (conservation candidate)
45 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/health".into(), latency_ms: 3.0, status: 200, response_bytes: 15.0 }).unwrap();
46 kiln.fire_and_record(ApiRequest { endpoint: "GET /api/health".into(), latency_ms: 4.0, status: 200, response_bytes: 15.0 }).unwrap();
47
48 let patterns = kiln.cool();
49 println!("API Monitoring — Pattern Report");
50 println!("================================\n");
51 println!("{} patterns detected:\n", patterns.len());
52
53 for p in &patterns {
54 println!("[{}] {}", p.kind(), p.description());
55 println!(" confidence: {:.2}", p.confidence());
56 if !p.involved_tasks().is_empty() {
57 println!(" endpoints: {:?}", p.involved_tasks());
58 }
59 println!();
60 }
61
62 // Highlight correlations between latency and response size
63 let correlations: Vec<_> = patterns.iter()
64 .filter(|p| format!("{}", p.kind()) == "correlation")
65 .collect();
66
67 if !correlations.is_empty() {
68 println!("📊 Latency and response size are correlated — consider pagination or caching.");
69 }
70}98fn main() {
99 let total_events = 1000;
100 let batch_size = 100;
101 let num_batches = total_events / batch_size;
102
103 println!("═══════════════════════════════════════════════════════════════");
104 println!(" CRACKLE-RUNTIME: EVENT STREAM PROCESSOR TEST");
105 println!("═══════════════════════════════════════════════════════════════\n");
106 println!("Processing {} events across {} batches of {}...\n",
107 total_events, num_batches, batch_size);
108
109 let start = Instant::now();
110
111 // Phase 1: Healthy system (batches 1-4)
112 println!("─── Phase 1: Healthy System ───");
113 let mut healthy_kiln = Kiln::new(
114 ThermalProfile::fast_cooling()
115 );
116
117 // Batch 1-4: all healthy, 10ms baseline latency
118 for batch_id in 1..=4 {
119 let events = simulate_batch(batch_id, batch_size as u32, 10.0, 0.05);
120 for event in events {
121 healthy_kiln.fire_and_record(event).unwrap();
122 }
123
124 let elapsed = start.elapsed();
125 println!(" Batch {}/{} fired ({} events, {:.0}ms)",
126 batch_id, num_batches, batch_size, elapsed.as_millis());
127 }
128
129 // Cool healthy phase
130 let healthy_patterns = healthy_kiln.cool();
131 println!("\n Healthy system patterns ({} detected):", healthy_patterns.len());
132 for p in &healthy_patterns {
133 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
134 }
135 // Also check conservation on records_in/records_out
136 let conservation = healthy_kiln.distribution_shift(10);
137 println!(" Distribution shifts:");
138 for (name, shift) in &conservation[..conservation.len().min(3)] {
139 println!(" {} → KL = {:.4}", name, shift);
140 }
141
142 println!();
143
144 // Phase 2: Degrading system (batches 5-8)
145 println!("─── Phase 2: Degrading System ───");
146
147 let mut degrading_kiln = Kiln::new(
148 ThermalProfile::fast_cooling()
149 );
150
151 // Batches where degradation ramps up
152 let degradation_levels = [0.2, 0.5, 0.9, 1.5];
153 for (i, °radation) in degradation_levels.iter().enumerate() {
154 let batch_id = (5 + i) as u32;
155 // latency increases with degradation
156 let base_latency = 10.0 + degradation * 50.0;
157
158 let events = simulate_batch(batch_id, batch_size as u32, base_latency, degradation);
159 for event in events {
160 degrading_kiln.fire_and_record(event).unwrap();
161 }
162
163 let elapsed = start.elapsed();
164 println!(" Batch {}/{} [degradation={:.1}x] fired (latency baseline: {:.0}ms, {:.0}ms total)",
165 batch_id, num_batches, degradation, base_latency, elapsed.as_millis());
166 }
167
168 let degrading_patterns = degrading_kiln.cool();
169 println!("\n Degrading system patterns ({} detected):", degrading_patterns.len());
170 for p in °rading_patterns {
171 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
172 }
173
174 // JSD shift (distribution shape changes)
175 let jsd_shifts = degrading_kiln.jsd_shift(10);
176 println!(" JSD shifts (distribution shape changes):");
177 for (name, shift) in &jsd_shifts[..jsd_shifts.len().min(5)] {
178 println!(" {} → JSD = {:.4}", name, shift);
179 }
180
181 // Permutation entropy (temporal structure)
182 let pes = degrading_kiln.permutation_entropies(4);
183 println!(" Permutation entropies (temporal structure):");
184 for (name, pe) in &pes {
185 println!(" {} → PE = {:.4}", name, pe);
186 }
187
188 println!();
189
190 // Phase 3: Conservation law test — records in = records out should be violated
191 // as errors increase
192 println!("─── Phase 3: Conservation Law Verification ───");
193 let mut conservation_kiln = Kiln::new(
194 ThermalProfile::fast_cooling()
195 );
196
197 // Force records_in = records_out for first 3 batches (healthy conservation)
198 for batch_id in 1..=3 {
199 let event = ProcessedEvent {
200 event_id: batch_id as u64,
201 batch_id: batch_id as u32,
202 processing_time_ms: 10.0,
203 throughput: 100.0,
204 memory_mb: 128.0,
205 error_rate: 0.01,
206 records_in: 1000.0,
207 records_out: 995.0, // nearly conserved
208 cpu_temp_c: 65.0,
209 latency_p99_ms: 25.0,
210 consumer_lag: 10.0,
211 gc_pause_ms: 5.0,
212 };
213 conservation_kiln.fire_and_record(event).unwrap();
214 }
215
216 // Force violation of records conservation (errors spike)
217 for batch_id in 4..=6 {
218 let event = ProcessedEvent {
219 event_id: batch_id as u64,
220 batch_id: batch_id as u32,
221 processing_time_ms: 10.0 + batch_id as f64 * 10.0,
222 throughput: 50.0,
223 memory_mb: 256.0,
224 error_rate: 0.3,
225 records_in: 1000.0,
226 records_out: 700.0, // 30% loss — conservation violated
227 cpu_temp_c: 80.0,
228 latency_p99_ms: 100.0,
229 consumer_lag: 500.0,
230 gc_pause_ms: 20.0,
231 };
232 conservation_kiln.fire_and_record(event).unwrap();
233 }
234
235 let conservation_patterns = conservation_kiln.cool();
236 println!(" Conservation-specific patterns ({} detected):", conservation_patterns.len());
237 for p in &conservation_patterns {
238 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
239 }
240
241 // MI matrix to show nonlinear dependencies
242 let mi = conservation_kiln.mi_matrix(8);
243 println!(" Mutual Information matrix (8 bins):");
244 let metric_names = ["processing_time_ms", "error_rate", "records_in", "records_out", "memory_mb"];
245 for (i, name_i) in metric_names.iter().enumerate() {
246 for (j, name_j) in metric_names.iter().enumerate() {
247 if i == j {
248 println!(" I({:25}; {:25}) = H({})", name_i, name_j, name_i);
249 } else {
250 println!(" I({:25}; {:25}) = {:.4} bits", name_i, name_j, mi[i][j]);
251 }
252 }
253 }
254
255 println!();
256 println!("───────────────────────────────────────────────────────────────");
257 println!(" SUMMARY");
258 println!("───────────────────────────────────────────────────────────────\n");
259
260 // Degradation detection analysis
261 println!(" Degradation Metrics:");
262 let pt = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::PhaseTransition));
263 let pt_count = pt.count();
264 println!(" Phase transitions detected in degraded system: {} alerts",
265 pt_count);
266
267 let corr = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Correlation));
268 let corr_count = corr.count();
269 println!(" Correlations in degraded system: {} pairs", corr_count);
270
271 let cons = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Conservation));
272 let cons_count = cons.count();
273 println!(" Conservation laws in degraded system: {} found", cons_count);
274
275 let clust = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Clustering));
276 let clust_count = clust.count();
277 println!(" Clusters in degraded system: {} groups", clust_count);
278
279 println!();
280 let total_duration = start.elapsed();
281 println!(" Total test duration: {:.2}s", total_duration.as_secs_f64());
282 println!(" Events processed: {}", total_events);
283 println!(" Avg throughput: {:.0} events/s",
284 total_events as f64 / total_duration.as_secs_f64());
285}Sourcepub fn profile(&self) -> &ThermalProfile
pub fn profile(&self) -> &ThermalProfile
Get the thermal profile.
Sourcepub fn mi_matrix(&self, bins: usize) -> Vec<Vec<f64>>
pub fn mi_matrix(&self, bins: usize) -> Vec<Vec<f64>>
Compute the full mutual information matrix for all metric pairs.
Returns a symmetric matrix where entry (i,j) is the mutual information between metric i and metric j. Captures non-linear dependencies that Pearson correlation misses.
§Arguments
bins- Number of bins for discretization (typically 10)
§Panics
Panics if there are no metric names (empty kiln).
Examples found in repository?
98fn main() {
99 let total_events = 1000;
100 let batch_size = 100;
101 let num_batches = total_events / batch_size;
102
103 println!("═══════════════════════════════════════════════════════════════");
104 println!(" CRACKLE-RUNTIME: EVENT STREAM PROCESSOR TEST");
105 println!("═══════════════════════════════════════════════════════════════\n");
106 println!("Processing {} events across {} batches of {}...\n",
107 total_events, num_batches, batch_size);
108
109 let start = Instant::now();
110
111 // Phase 1: Healthy system (batches 1-4)
112 println!("─── Phase 1: Healthy System ───");
113 let mut healthy_kiln = Kiln::new(
114 ThermalProfile::fast_cooling()
115 );
116
117 // Batch 1-4: all healthy, 10ms baseline latency
118 for batch_id in 1..=4 {
119 let events = simulate_batch(batch_id, batch_size as u32, 10.0, 0.05);
120 for event in events {
121 healthy_kiln.fire_and_record(event).unwrap();
122 }
123
124 let elapsed = start.elapsed();
125 println!(" Batch {}/{} fired ({} events, {:.0}ms)",
126 batch_id, num_batches, batch_size, elapsed.as_millis());
127 }
128
129 // Cool healthy phase
130 let healthy_patterns = healthy_kiln.cool();
131 println!("\n Healthy system patterns ({} detected):", healthy_patterns.len());
132 for p in &healthy_patterns {
133 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
134 }
135 // Also check conservation on records_in/records_out
136 let conservation = healthy_kiln.distribution_shift(10);
137 println!(" Distribution shifts:");
138 for (name, shift) in &conservation[..conservation.len().min(3)] {
139 println!(" {} → KL = {:.4}", name, shift);
140 }
141
142 println!();
143
144 // Phase 2: Degrading system (batches 5-8)
145 println!("─── Phase 2: Degrading System ───");
146
147 let mut degrading_kiln = Kiln::new(
148 ThermalProfile::fast_cooling()
149 );
150
151 // Batches where degradation ramps up
152 let degradation_levels = [0.2, 0.5, 0.9, 1.5];
153 for (i, °radation) in degradation_levels.iter().enumerate() {
154 let batch_id = (5 + i) as u32;
155 // latency increases with degradation
156 let base_latency = 10.0 + degradation * 50.0;
157
158 let events = simulate_batch(batch_id, batch_size as u32, base_latency, degradation);
159 for event in events {
160 degrading_kiln.fire_and_record(event).unwrap();
161 }
162
163 let elapsed = start.elapsed();
164 println!(" Batch {}/{} [degradation={:.1}x] fired (latency baseline: {:.0}ms, {:.0}ms total)",
165 batch_id, num_batches, degradation, base_latency, elapsed.as_millis());
166 }
167
168 let degrading_patterns = degrading_kiln.cool();
169 println!("\n Degrading system patterns ({} detected):", degrading_patterns.len());
170 for p in °rading_patterns {
171 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
172 }
173
174 // JSD shift (distribution shape changes)
175 let jsd_shifts = degrading_kiln.jsd_shift(10);
176 println!(" JSD shifts (distribution shape changes):");
177 for (name, shift) in &jsd_shifts[..jsd_shifts.len().min(5)] {
178 println!(" {} → JSD = {:.4}", name, shift);
179 }
180
181 // Permutation entropy (temporal structure)
182 let pes = degrading_kiln.permutation_entropies(4);
183 println!(" Permutation entropies (temporal structure):");
184 for (name, pe) in &pes {
185 println!(" {} → PE = {:.4}", name, pe);
186 }
187
188 println!();
189
190 // Phase 3: Conservation law test — records in = records out should be violated
191 // as errors increase
192 println!("─── Phase 3: Conservation Law Verification ───");
193 let mut conservation_kiln = Kiln::new(
194 ThermalProfile::fast_cooling()
195 );
196
197 // Force records_in = records_out for first 3 batches (healthy conservation)
198 for batch_id in 1..=3 {
199 let event = ProcessedEvent {
200 event_id: batch_id as u64,
201 batch_id: batch_id as u32,
202 processing_time_ms: 10.0,
203 throughput: 100.0,
204 memory_mb: 128.0,
205 error_rate: 0.01,
206 records_in: 1000.0,
207 records_out: 995.0, // nearly conserved
208 cpu_temp_c: 65.0,
209 latency_p99_ms: 25.0,
210 consumer_lag: 10.0,
211 gc_pause_ms: 5.0,
212 };
213 conservation_kiln.fire_and_record(event).unwrap();
214 }
215
216 // Force violation of records conservation (errors spike)
217 for batch_id in 4..=6 {
218 let event = ProcessedEvent {
219 event_id: batch_id as u64,
220 batch_id: batch_id as u32,
221 processing_time_ms: 10.0 + batch_id as f64 * 10.0,
222 throughput: 50.0,
223 memory_mb: 256.0,
224 error_rate: 0.3,
225 records_in: 1000.0,
226 records_out: 700.0, // 30% loss — conservation violated
227 cpu_temp_c: 80.0,
228 latency_p99_ms: 100.0,
229 consumer_lag: 500.0,
230 gc_pause_ms: 20.0,
231 };
232 conservation_kiln.fire_and_record(event).unwrap();
233 }
234
235 let conservation_patterns = conservation_kiln.cool();
236 println!(" Conservation-specific patterns ({} detected):", conservation_patterns.len());
237 for p in &conservation_patterns {
238 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
239 }
240
241 // MI matrix to show nonlinear dependencies
242 let mi = conservation_kiln.mi_matrix(8);
243 println!(" Mutual Information matrix (8 bins):");
244 let metric_names = ["processing_time_ms", "error_rate", "records_in", "records_out", "memory_mb"];
245 for (i, name_i) in metric_names.iter().enumerate() {
246 for (j, name_j) in metric_names.iter().enumerate() {
247 if i == j {
248 println!(" I({:25}; {:25}) = H({})", name_i, name_j, name_i);
249 } else {
250 println!(" I({:25}; {:25}) = {:.4} bits", name_i, name_j, mi[i][j]);
251 }
252 }
253 }
254
255 println!();
256 println!("───────────────────────────────────────────────────────────────");
257 println!(" SUMMARY");
258 println!("───────────────────────────────────────────────────────────────\n");
259
260 // Degradation detection analysis
261 println!(" Degradation Metrics:");
262 let pt = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::PhaseTransition));
263 let pt_count = pt.count();
264 println!(" Phase transitions detected in degraded system: {} alerts",
265 pt_count);
266
267 let corr = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Correlation));
268 let corr_count = corr.count();
269 println!(" Correlations in degraded system: {} pairs", corr_count);
270
271 let cons = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Conservation));
272 let cons_count = cons.count();
273 println!(" Conservation laws in degraded system: {} found", cons_count);
274
275 let clust = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Clustering));
276 let clust_count = clust.count();
277 println!(" Clusters in degraded system: {} groups", clust_count);
278
279 println!();
280 let total_duration = start.elapsed();
281 println!(" Total test duration: {:.2}s", total_duration.as_secs_f64());
282 println!(" Events processed: {}", total_events);
283 println!(" Avg throughput: {:.0} events/s",
284 total_events as f64 / total_duration.as_secs_f64());
285}Sourcepub fn distribution_shift(&self, bins: usize) -> Vec<(String, f64)>
pub fn distribution_shift(&self, bins: usize) -> Vec<(String, f64)>
Compute KL divergence between first-half and second-half metric distributions.
Principled replacement for the old “phase transition” heuristic. Returns the KL divergence for each metric name.
Examples found in repository?
98fn main() {
99 let total_events = 1000;
100 let batch_size = 100;
101 let num_batches = total_events / batch_size;
102
103 println!("═══════════════════════════════════════════════════════════════");
104 println!(" CRACKLE-RUNTIME: EVENT STREAM PROCESSOR TEST");
105 println!("═══════════════════════════════════════════════════════════════\n");
106 println!("Processing {} events across {} batches of {}...\n",
107 total_events, num_batches, batch_size);
108
109 let start = Instant::now();
110
111 // Phase 1: Healthy system (batches 1-4)
112 println!("─── Phase 1: Healthy System ───");
113 let mut healthy_kiln = Kiln::new(
114 ThermalProfile::fast_cooling()
115 );
116
117 // Batch 1-4: all healthy, 10ms baseline latency
118 for batch_id in 1..=4 {
119 let events = simulate_batch(batch_id, batch_size as u32, 10.0, 0.05);
120 for event in events {
121 healthy_kiln.fire_and_record(event).unwrap();
122 }
123
124 let elapsed = start.elapsed();
125 println!(" Batch {}/{} fired ({} events, {:.0}ms)",
126 batch_id, num_batches, batch_size, elapsed.as_millis());
127 }
128
129 // Cool healthy phase
130 let healthy_patterns = healthy_kiln.cool();
131 println!("\n Healthy system patterns ({} detected):", healthy_patterns.len());
132 for p in &healthy_patterns {
133 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
134 }
135 // Also check conservation on records_in/records_out
136 let conservation = healthy_kiln.distribution_shift(10);
137 println!(" Distribution shifts:");
138 for (name, shift) in &conservation[..conservation.len().min(3)] {
139 println!(" {} → KL = {:.4}", name, shift);
140 }
141
142 println!();
143
144 // Phase 2: Degrading system (batches 5-8)
145 println!("─── Phase 2: Degrading System ───");
146
147 let mut degrading_kiln = Kiln::new(
148 ThermalProfile::fast_cooling()
149 );
150
151 // Batches where degradation ramps up
152 let degradation_levels = [0.2, 0.5, 0.9, 1.5];
153 for (i, °radation) in degradation_levels.iter().enumerate() {
154 let batch_id = (5 + i) as u32;
155 // latency increases with degradation
156 let base_latency = 10.0 + degradation * 50.0;
157
158 let events = simulate_batch(batch_id, batch_size as u32, base_latency, degradation);
159 for event in events {
160 degrading_kiln.fire_and_record(event).unwrap();
161 }
162
163 let elapsed = start.elapsed();
164 println!(" Batch {}/{} [degradation={:.1}x] fired (latency baseline: {:.0}ms, {:.0}ms total)",
165 batch_id, num_batches, degradation, base_latency, elapsed.as_millis());
166 }
167
168 let degrading_patterns = degrading_kiln.cool();
169 println!("\n Degrading system patterns ({} detected):", degrading_patterns.len());
170 for p in °rading_patterns {
171 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
172 }
173
174 // JSD shift (distribution shape changes)
175 let jsd_shifts = degrading_kiln.jsd_shift(10);
176 println!(" JSD shifts (distribution shape changes):");
177 for (name, shift) in &jsd_shifts[..jsd_shifts.len().min(5)] {
178 println!(" {} → JSD = {:.4}", name, shift);
179 }
180
181 // Permutation entropy (temporal structure)
182 let pes = degrading_kiln.permutation_entropies(4);
183 println!(" Permutation entropies (temporal structure):");
184 for (name, pe) in &pes {
185 println!(" {} → PE = {:.4}", name, pe);
186 }
187
188 println!();
189
190 // Phase 3: Conservation law test — records in = records out should be violated
191 // as errors increase
192 println!("─── Phase 3: Conservation Law Verification ───");
193 let mut conservation_kiln = Kiln::new(
194 ThermalProfile::fast_cooling()
195 );
196
197 // Force records_in = records_out for first 3 batches (healthy conservation)
198 for batch_id in 1..=3 {
199 let event = ProcessedEvent {
200 event_id: batch_id as u64,
201 batch_id: batch_id as u32,
202 processing_time_ms: 10.0,
203 throughput: 100.0,
204 memory_mb: 128.0,
205 error_rate: 0.01,
206 records_in: 1000.0,
207 records_out: 995.0, // nearly conserved
208 cpu_temp_c: 65.0,
209 latency_p99_ms: 25.0,
210 consumer_lag: 10.0,
211 gc_pause_ms: 5.0,
212 };
213 conservation_kiln.fire_and_record(event).unwrap();
214 }
215
216 // Force violation of records conservation (errors spike)
217 for batch_id in 4..=6 {
218 let event = ProcessedEvent {
219 event_id: batch_id as u64,
220 batch_id: batch_id as u32,
221 processing_time_ms: 10.0 + batch_id as f64 * 10.0,
222 throughput: 50.0,
223 memory_mb: 256.0,
224 error_rate: 0.3,
225 records_in: 1000.0,
226 records_out: 700.0, // 30% loss — conservation violated
227 cpu_temp_c: 80.0,
228 latency_p99_ms: 100.0,
229 consumer_lag: 500.0,
230 gc_pause_ms: 20.0,
231 };
232 conservation_kiln.fire_and_record(event).unwrap();
233 }
234
235 let conservation_patterns = conservation_kiln.cool();
236 println!(" Conservation-specific patterns ({} detected):", conservation_patterns.len());
237 for p in &conservation_patterns {
238 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
239 }
240
241 // MI matrix to show nonlinear dependencies
242 let mi = conservation_kiln.mi_matrix(8);
243 println!(" Mutual Information matrix (8 bins):");
244 let metric_names = ["processing_time_ms", "error_rate", "records_in", "records_out", "memory_mb"];
245 for (i, name_i) in metric_names.iter().enumerate() {
246 for (j, name_j) in metric_names.iter().enumerate() {
247 if i == j {
248 println!(" I({:25}; {:25}) = H({})", name_i, name_j, name_i);
249 } else {
250 println!(" I({:25}; {:25}) = {:.4} bits", name_i, name_j, mi[i][j]);
251 }
252 }
253 }
254
255 println!();
256 println!("───────────────────────────────────────────────────────────────");
257 println!(" SUMMARY");
258 println!("───────────────────────────────────────────────────────────────\n");
259
260 // Degradation detection analysis
261 println!(" Degradation Metrics:");
262 let pt = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::PhaseTransition));
263 let pt_count = pt.count();
264 println!(" Phase transitions detected in degraded system: {} alerts",
265 pt_count);
266
267 let corr = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Correlation));
268 let corr_count = corr.count();
269 println!(" Correlations in degraded system: {} pairs", corr_count);
270
271 let cons = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Conservation));
272 let cons_count = cons.count();
273 println!(" Conservation laws in degraded system: {} found", cons_count);
274
275 let clust = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Clustering));
276 let clust_count = clust.count();
277 println!(" Clusters in degraded system: {} groups", clust_count);
278
279 println!();
280 let total_duration = start.elapsed();
281 println!(" Total test duration: {:.2}s", total_duration.as_secs_f64());
282 println!(" Events processed: {}", total_events);
283 println!(" Avg throughput: {:.0} events/s",
284 total_events as f64 / total_duration.as_secs_f64());
285}Sourcepub fn jsd_shift(&self, bins: usize) -> Vec<(String, f64)>
pub fn jsd_shift(&self, bins: usize) -> Vec<(String, f64)>
Compute Jensen-Shannon divergence between first-half and second-half metric distributions.
Symmetric version of KL divergence. Returns JSD for each metric name.
Examples found in repository?
98fn main() {
99 let total_events = 1000;
100 let batch_size = 100;
101 let num_batches = total_events / batch_size;
102
103 println!("═══════════════════════════════════════════════════════════════");
104 println!(" CRACKLE-RUNTIME: EVENT STREAM PROCESSOR TEST");
105 println!("═══════════════════════════════════════════════════════════════\n");
106 println!("Processing {} events across {} batches of {}...\n",
107 total_events, num_batches, batch_size);
108
109 let start = Instant::now();
110
111 // Phase 1: Healthy system (batches 1-4)
112 println!("─── Phase 1: Healthy System ───");
113 let mut healthy_kiln = Kiln::new(
114 ThermalProfile::fast_cooling()
115 );
116
117 // Batch 1-4: all healthy, 10ms baseline latency
118 for batch_id in 1..=4 {
119 let events = simulate_batch(batch_id, batch_size as u32, 10.0, 0.05);
120 for event in events {
121 healthy_kiln.fire_and_record(event).unwrap();
122 }
123
124 let elapsed = start.elapsed();
125 println!(" Batch {}/{} fired ({} events, {:.0}ms)",
126 batch_id, num_batches, batch_size, elapsed.as_millis());
127 }
128
129 // Cool healthy phase
130 let healthy_patterns = healthy_kiln.cool();
131 println!("\n Healthy system patterns ({} detected):", healthy_patterns.len());
132 for p in &healthy_patterns {
133 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
134 }
135 // Also check conservation on records_in/records_out
136 let conservation = healthy_kiln.distribution_shift(10);
137 println!(" Distribution shifts:");
138 for (name, shift) in &conservation[..conservation.len().min(3)] {
139 println!(" {} → KL = {:.4}", name, shift);
140 }
141
142 println!();
143
144 // Phase 2: Degrading system (batches 5-8)
145 println!("─── Phase 2: Degrading System ───");
146
147 let mut degrading_kiln = Kiln::new(
148 ThermalProfile::fast_cooling()
149 );
150
151 // Batches where degradation ramps up
152 let degradation_levels = [0.2, 0.5, 0.9, 1.5];
153 for (i, °radation) in degradation_levels.iter().enumerate() {
154 let batch_id = (5 + i) as u32;
155 // latency increases with degradation
156 let base_latency = 10.0 + degradation * 50.0;
157
158 let events = simulate_batch(batch_id, batch_size as u32, base_latency, degradation);
159 for event in events {
160 degrading_kiln.fire_and_record(event).unwrap();
161 }
162
163 let elapsed = start.elapsed();
164 println!(" Batch {}/{} [degradation={:.1}x] fired (latency baseline: {:.0}ms, {:.0}ms total)",
165 batch_id, num_batches, degradation, base_latency, elapsed.as_millis());
166 }
167
168 let degrading_patterns = degrading_kiln.cool();
169 println!("\n Degrading system patterns ({} detected):", degrading_patterns.len());
170 for p in °rading_patterns {
171 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
172 }
173
174 // JSD shift (distribution shape changes)
175 let jsd_shifts = degrading_kiln.jsd_shift(10);
176 println!(" JSD shifts (distribution shape changes):");
177 for (name, shift) in &jsd_shifts[..jsd_shifts.len().min(5)] {
178 println!(" {} → JSD = {:.4}", name, shift);
179 }
180
181 // Permutation entropy (temporal structure)
182 let pes = degrading_kiln.permutation_entropies(4);
183 println!(" Permutation entropies (temporal structure):");
184 for (name, pe) in &pes {
185 println!(" {} → PE = {:.4}", name, pe);
186 }
187
188 println!();
189
190 // Phase 3: Conservation law test — records in = records out should be violated
191 // as errors increase
192 println!("─── Phase 3: Conservation Law Verification ───");
193 let mut conservation_kiln = Kiln::new(
194 ThermalProfile::fast_cooling()
195 );
196
197 // Force records_in = records_out for first 3 batches (healthy conservation)
198 for batch_id in 1..=3 {
199 let event = ProcessedEvent {
200 event_id: batch_id as u64,
201 batch_id: batch_id as u32,
202 processing_time_ms: 10.0,
203 throughput: 100.0,
204 memory_mb: 128.0,
205 error_rate: 0.01,
206 records_in: 1000.0,
207 records_out: 995.0, // nearly conserved
208 cpu_temp_c: 65.0,
209 latency_p99_ms: 25.0,
210 consumer_lag: 10.0,
211 gc_pause_ms: 5.0,
212 };
213 conservation_kiln.fire_and_record(event).unwrap();
214 }
215
216 // Force violation of records conservation (errors spike)
217 for batch_id in 4..=6 {
218 let event = ProcessedEvent {
219 event_id: batch_id as u64,
220 batch_id: batch_id as u32,
221 processing_time_ms: 10.0 + batch_id as f64 * 10.0,
222 throughput: 50.0,
223 memory_mb: 256.0,
224 error_rate: 0.3,
225 records_in: 1000.0,
226 records_out: 700.0, // 30% loss — conservation violated
227 cpu_temp_c: 80.0,
228 latency_p99_ms: 100.0,
229 consumer_lag: 500.0,
230 gc_pause_ms: 20.0,
231 };
232 conservation_kiln.fire_and_record(event).unwrap();
233 }
234
235 let conservation_patterns = conservation_kiln.cool();
236 println!(" Conservation-specific patterns ({} detected):", conservation_patterns.len());
237 for p in &conservation_patterns {
238 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
239 }
240
241 // MI matrix to show nonlinear dependencies
242 let mi = conservation_kiln.mi_matrix(8);
243 println!(" Mutual Information matrix (8 bins):");
244 let metric_names = ["processing_time_ms", "error_rate", "records_in", "records_out", "memory_mb"];
245 for (i, name_i) in metric_names.iter().enumerate() {
246 for (j, name_j) in metric_names.iter().enumerate() {
247 if i == j {
248 println!(" I({:25}; {:25}) = H({})", name_i, name_j, name_i);
249 } else {
250 println!(" I({:25}; {:25}) = {:.4} bits", name_i, name_j, mi[i][j]);
251 }
252 }
253 }
254
255 println!();
256 println!("───────────────────────────────────────────────────────────────");
257 println!(" SUMMARY");
258 println!("───────────────────────────────────────────────────────────────\n");
259
260 // Degradation detection analysis
261 println!(" Degradation Metrics:");
262 let pt = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::PhaseTransition));
263 let pt_count = pt.count();
264 println!(" Phase transitions detected in degraded system: {} alerts",
265 pt_count);
266
267 let corr = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Correlation));
268 let corr_count = corr.count();
269 println!(" Correlations in degraded system: {} pairs", corr_count);
270
271 let cons = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Conservation));
272 let cons_count = cons.count();
273 println!(" Conservation laws in degraded system: {} found", cons_count);
274
275 let clust = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Clustering));
276 let clust_count = clust.count();
277 println!(" Clusters in degraded system: {} groups", clust_count);
278
279 println!();
280 let total_duration = start.elapsed();
281 println!(" Total test duration: {:.2}s", total_duration.as_secs_f64());
282 println!(" Events processed: {}", total_events);
283 println!(" Avg throughput: {:.0} events/s",
284 total_events as f64 / total_duration.as_secs_f64());
285}Sourcepub fn permutation_entropies(&self, order: usize) -> Vec<(String, f64)>
pub fn permutation_entropies(&self, order: usize) -> Vec<(String, f64)>
Compute permutation entropy for each metric’s time series.
Captures temporal structure in metric values.
Examples found in repository?
98fn main() {
99 let total_events = 1000;
100 let batch_size = 100;
101 let num_batches = total_events / batch_size;
102
103 println!("═══════════════════════════════════════════════════════════════");
104 println!(" CRACKLE-RUNTIME: EVENT STREAM PROCESSOR TEST");
105 println!("═══════════════════════════════════════════════════════════════\n");
106 println!("Processing {} events across {} batches of {}...\n",
107 total_events, num_batches, batch_size);
108
109 let start = Instant::now();
110
111 // Phase 1: Healthy system (batches 1-4)
112 println!("─── Phase 1: Healthy System ───");
113 let mut healthy_kiln = Kiln::new(
114 ThermalProfile::fast_cooling()
115 );
116
117 // Batch 1-4: all healthy, 10ms baseline latency
118 for batch_id in 1..=4 {
119 let events = simulate_batch(batch_id, batch_size as u32, 10.0, 0.05);
120 for event in events {
121 healthy_kiln.fire_and_record(event).unwrap();
122 }
123
124 let elapsed = start.elapsed();
125 println!(" Batch {}/{} fired ({} events, {:.0}ms)",
126 batch_id, num_batches, batch_size, elapsed.as_millis());
127 }
128
129 // Cool healthy phase
130 let healthy_patterns = healthy_kiln.cool();
131 println!("\n Healthy system patterns ({} detected):", healthy_patterns.len());
132 for p in &healthy_patterns {
133 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
134 }
135 // Also check conservation on records_in/records_out
136 let conservation = healthy_kiln.distribution_shift(10);
137 println!(" Distribution shifts:");
138 for (name, shift) in &conservation[..conservation.len().min(3)] {
139 println!(" {} → KL = {:.4}", name, shift);
140 }
141
142 println!();
143
144 // Phase 2: Degrading system (batches 5-8)
145 println!("─── Phase 2: Degrading System ───");
146
147 let mut degrading_kiln = Kiln::new(
148 ThermalProfile::fast_cooling()
149 );
150
151 // Batches where degradation ramps up
152 let degradation_levels = [0.2, 0.5, 0.9, 1.5];
153 for (i, °radation) in degradation_levels.iter().enumerate() {
154 let batch_id = (5 + i) as u32;
155 // latency increases with degradation
156 let base_latency = 10.0 + degradation * 50.0;
157
158 let events = simulate_batch(batch_id, batch_size as u32, base_latency, degradation);
159 for event in events {
160 degrading_kiln.fire_and_record(event).unwrap();
161 }
162
163 let elapsed = start.elapsed();
164 println!(" Batch {}/{} [degradation={:.1}x] fired (latency baseline: {:.0}ms, {:.0}ms total)",
165 batch_id, num_batches, degradation, base_latency, elapsed.as_millis());
166 }
167
168 let degrading_patterns = degrading_kiln.cool();
169 println!("\n Degrading system patterns ({} detected):", degrading_patterns.len());
170 for p in °rading_patterns {
171 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
172 }
173
174 // JSD shift (distribution shape changes)
175 let jsd_shifts = degrading_kiln.jsd_shift(10);
176 println!(" JSD shifts (distribution shape changes):");
177 for (name, shift) in &jsd_shifts[..jsd_shifts.len().min(5)] {
178 println!(" {} → JSD = {:.4}", name, shift);
179 }
180
181 // Permutation entropy (temporal structure)
182 let pes = degrading_kiln.permutation_entropies(4);
183 println!(" Permutation entropies (temporal structure):");
184 for (name, pe) in &pes {
185 println!(" {} → PE = {:.4}", name, pe);
186 }
187
188 println!();
189
190 // Phase 3: Conservation law test — records in = records out should be violated
191 // as errors increase
192 println!("─── Phase 3: Conservation Law Verification ───");
193 let mut conservation_kiln = Kiln::new(
194 ThermalProfile::fast_cooling()
195 );
196
197 // Force records_in = records_out for first 3 batches (healthy conservation)
198 for batch_id in 1..=3 {
199 let event = ProcessedEvent {
200 event_id: batch_id as u64,
201 batch_id: batch_id as u32,
202 processing_time_ms: 10.0,
203 throughput: 100.0,
204 memory_mb: 128.0,
205 error_rate: 0.01,
206 records_in: 1000.0,
207 records_out: 995.0, // nearly conserved
208 cpu_temp_c: 65.0,
209 latency_p99_ms: 25.0,
210 consumer_lag: 10.0,
211 gc_pause_ms: 5.0,
212 };
213 conservation_kiln.fire_and_record(event).unwrap();
214 }
215
216 // Force violation of records conservation (errors spike)
217 for batch_id in 4..=6 {
218 let event = ProcessedEvent {
219 event_id: batch_id as u64,
220 batch_id: batch_id as u32,
221 processing_time_ms: 10.0 + batch_id as f64 * 10.0,
222 throughput: 50.0,
223 memory_mb: 256.0,
224 error_rate: 0.3,
225 records_in: 1000.0,
226 records_out: 700.0, // 30% loss — conservation violated
227 cpu_temp_c: 80.0,
228 latency_p99_ms: 100.0,
229 consumer_lag: 500.0,
230 gc_pause_ms: 20.0,
231 };
232 conservation_kiln.fire_and_record(event).unwrap();
233 }
234
235 let conservation_patterns = conservation_kiln.cool();
236 println!(" Conservation-specific patterns ({} detected):", conservation_patterns.len());
237 for p in &conservation_patterns {
238 println!(" [{:?}] {} (conf: {:.2})", p.kind(), p.description(), p.confidence());
239 }
240
241 // MI matrix to show nonlinear dependencies
242 let mi = conservation_kiln.mi_matrix(8);
243 println!(" Mutual Information matrix (8 bins):");
244 let metric_names = ["processing_time_ms", "error_rate", "records_in", "records_out", "memory_mb"];
245 for (i, name_i) in metric_names.iter().enumerate() {
246 for (j, name_j) in metric_names.iter().enumerate() {
247 if i == j {
248 println!(" I({:25}; {:25}) = H({})", name_i, name_j, name_i);
249 } else {
250 println!(" I({:25}; {:25}) = {:.4} bits", name_i, name_j, mi[i][j]);
251 }
252 }
253 }
254
255 println!();
256 println!("───────────────────────────────────────────────────────────────");
257 println!(" SUMMARY");
258 println!("───────────────────────────────────────────────────────────────\n");
259
260 // Degradation detection analysis
261 println!(" Degradation Metrics:");
262 let pt = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::PhaseTransition));
263 let pt_count = pt.count();
264 println!(" Phase transitions detected in degraded system: {} alerts",
265 pt_count);
266
267 let corr = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Correlation));
268 let corr_count = corr.count();
269 println!(" Correlations in degraded system: {} pairs", corr_count);
270
271 let cons = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Conservation));
272 let cons_count = cons.count();
273 println!(" Conservation laws in degraded system: {} found", cons_count);
274
275 let clust = degrading_patterns.iter().filter(|p| matches!(p.kind(), crackle_runtime::PatternKind::Clustering));
276 let clust_count = clust.count();
277 println!(" Clusters in degraded system: {} groups", clust_count);
278
279 println!();
280 let total_duration = start.elapsed();
281 println!(" Total test duration: {:.2}s", total_duration.as_secs_f64());
282 println!(" Events processed: {}", total_events);
283 println!(" Avg throughput: {:.0} events/s",
284 total_events as f64 / total_duration.as_secs_f64());
285}