1use std::collections::HashMap;
9use std::sync::{Arc, RwLock};
10use std::time::Instant;
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub enum ComponentType {
17 Software,
19 Hardware,
21 Network,
23 Storage,
25 Custom(String),
27}
28
29#[derive(Debug, Clone)]
31pub struct Component {
32 pub id: String,
33 pub name: String,
34 pub component_type: ComponentType,
35 pub capacity: HashMap<String, f64>,
37 pub load: f64,
39 pub error_rate: f64,
41 pub latency_ms: f64,
43}
44
45#[derive(Debug, Clone)]
47pub struct Connection {
48 pub from: String,
49 pub to: String,
50 pub bandwidth: f64,
52 pub protocol: String,
54 pub latency_ms: f64,
56 pub packet_loss: f64,
58}
59
60#[derive(Debug, Clone)]
62pub struct Architecture {
63 pub id: String,
64 pub name: String,
65 pub description: String,
66 pub components: HashMap<String, Component>,
67 pub connections: Vec<Connection>,
68 pub version: u32,
70 pub cohesion_score: f64,
72}
73
74#[derive(Debug, Clone)]
76pub struct SimulationConfig {
77 pub duration_secs: f64,
79 pub time_step_ms: f64,
81 pub max_concurrent_requests: u32,
83 pub load_pattern: String,
85 pub peak_load: f64,
87 pub enable_fault_injection: bool,
89 pub fault_rate: f64,
91}
92
93impl Default for SimulationConfig {
94 fn default() -> Self {
95 Self {
96 duration_secs: 60.0,
97 time_step_ms: 100.0,
98 max_concurrent_requests: 100,
99 load_pattern: "linear".to_string(),
100 peak_load: 0.8,
101 enable_fault_injection: false,
102 fault_rate: 0.01,
103 }
104 }
105}
106
107#[derive(Debug, Clone)]
109pub struct SimulationMetrics {
110 pub architecture_id: String,
111 pub avg_latency_ms: f64,
112 pub p95_latency_ms: f64,
113 pub p99_latency_ms: f64,
114 pub throughput_req_per_sec: f64,
115 pub error_rate: f64,
116 pub cpu_utilization: f64,
117 pub memory_utilization: f64,
118 pub network_utilization: f64,
119 pub bottleneck_components: Vec<String>,
120 pub stability_score: f64,
121 pub resilience_score: f64,
122 pub total_requests: u64,
123 pub failed_requests: u64,
124 pub simulation_time_secs: f64,
125}
126
127#[derive(Debug, Clone)]
129pub struct StressTestResult {
130 pub architecture_id: String,
131 pub breaking_point_load: f64,
132 pub recovery_time_ms: f64,
133 pub graceful_degradation: bool,
134 pub cascade_failures: Vec<String>,
135 pub recommendations: Vec<String>,
136}
137
138#[derive(Debug, Clone)]
140pub struct SimulationEvent {
141 pub time_ms: f64,
142 pub event_type: String,
143 pub component_id: String,
144 pub description: String,
145 pub severity: u8, }
147
148pub struct ArchitectureSimulator {
152 architectures: Arc<RwLock<HashMap<String, Architecture>>>,
153 simulation_results: Arc<RwLock<HashMap<String, Vec<SimulationMetrics>>>>,
154 stress_test_results: Arc<RwLock<HashMap<String, Vec<StressTestResult>>>>,
155 events: Arc<RwLock<Vec<SimulationEvent>>>,
156 learned_patterns: Arc<RwLock<HashMap<String, f64>>>,
158}
159
160impl Default for ArchitectureSimulator {
161 fn default() -> Self {
162 Self::new()
163 }
164}
165
166impl ArchitectureSimulator {
167 pub fn new() -> Self {
169 Self {
170 architectures: Arc::new(RwLock::new(HashMap::new())),
171 simulation_results: Arc::new(RwLock::new(HashMap::new())),
172 stress_test_results: Arc::new(RwLock::new(HashMap::new())),
173 events: Arc::new(RwLock::new(Vec::new())),
174 learned_patterns: Arc::new(RwLock::new(HashMap::new())),
175 }
176 }
177
178 pub fn register_architecture(&self, arch: Architecture) {
180 let mut architectures = crate::sync_guard::write_lock(&self.architectures);
181 architectures.insert(arch.id.clone(), arch);
182 }
183
184 pub fn get_architecture(&self, id: &str) -> Option<Architecture> {
186 let architectures = crate::sync_guard::read_lock(&self.architectures);
187 architectures.get(id).cloned()
188 }
189
190 pub fn list_architectures(&self) -> Vec<Architecture> {
192 let architectures = crate::sync_guard::read_lock(&self.architectures);
193 architectures.values().cloned().collect()
194 }
195
196 pub fn run_simulation(
198 &self,
199 arch_id: &str,
200 config: &SimulationConfig,
201 ) -> Option<SimulationMetrics> {
202 let arch = {
203 let architectures = crate::sync_guard::read_lock(&self.architectures);
204 architectures.get(arch_id).cloned()?
205 };
206
207 let mut events = crate::sync_guard::write_lock(&self.events);
208 let _start_time = Instant::now();
209 let mut metrics = SimulationMetrics {
210 architecture_id: arch_id.to_string(),
211 avg_latency_ms: 0.0,
212 p95_latency_ms: 0.0,
213 p99_latency_ms: 0.0,
214 throughput_req_per_sec: 0.0,
215 error_rate: 0.0,
216 cpu_utilization: 0.0,
217 memory_utilization: 0.0,
218 network_utilization: 0.0,
219 bottleneck_components: Vec::new(),
220 stability_score: 1.0,
221 resilience_score: 1.0,
222 total_requests: 0,
223 failed_requests: 0,
224 simulation_time_secs: config.duration_secs,
225 };
226
227 let steps = (config.duration_secs * 1000.0 / config.time_step_ms) as u64;
228 let mut latencies: Vec<f64> = Vec::new();
229 let mut current_load: f64;
230 let mut component_loads: HashMap<String, Vec<f64>> = HashMap::new();
231
232 for comp_id in arch.components.keys() {
234 component_loads.insert(comp_id.clone(), Vec::new());
235 }
236
237 for step in 0..steps {
238 let current_time = step as f64 * config.time_step_ms;
239
240 current_load = match config.load_pattern.as_str() {
242 "linear" => (current_time / (config.duration_secs * 1000.0)) * config.peak_load,
243 "spike" => {
244 if current_time > config.duration_secs * 500.0
245 && current_time < config.duration_secs * 600.0
246 {
247 config.peak_load
248 } else {
249 config.peak_load * 0.3
250 }
251 }
252 "sine" => {
253 let phase = (current_time / (config.duration_secs * 1000.0))
254 * std::f64::consts::PI
255 * 4.0;
256 (phase.sin() * 0.5 + 0.5) * config.peak_load
257 }
258 "random" => rand::random::<f64>() * config.peak_load,
259 _ => config.peak_load * 0.5,
260 };
261
262 let requests_this_step = (current_load * config.max_concurrent_requests as f64) as u64;
264 metrics.total_requests += requests_this_step;
265
266 for (comp_id, comp) in &arch.components {
268 let base_latency = comp.latency_ms;
269 let load_factor = current_load * (1.0 + comp.error_rate);
270 let effective_latency = base_latency * (1.0 + load_factor * 2.0);
271
272 latencies.push(effective_latency);
273 component_loads.get_mut(comp_id).unwrap().push(load_factor);
274
275 if config.enable_fault_injection && rand::random::<f64>() < config.fault_rate {
277 metrics.failed_requests += 1;
278 events.push(SimulationEvent {
279 time_ms: current_time,
280 event_type: "fault".to_string(),
281 component_id: comp_id.clone(),
282 description: format!(
283 "Fault injected in {} at load {:.2}",
284 comp.name, current_load
285 ),
286 severity: 2,
287 });
288 }
289
290 for conn in &arch.connections {
292 if conn.from == *comp_id || conn.to == *comp_id {
293 let network_delay = conn.latency_ms * (1.0 + current_load);
294 latencies.push(network_delay);
295
296 if rand::random::<f64>() < conn.packet_loss {
297 metrics.failed_requests += 1;
298 }
299 }
300 }
301 }
302
303 if current_load > 0.9 {
305 metrics.stability_score *= 0.99;
306 }
307 if metrics.failed_requests as f64 / metrics.total_requests.max(1) as f64 > 0.1 {
308 metrics.resilience_score *= 0.95;
309 }
310 }
311
312 if !latencies.is_empty() {
314 latencies.sort_by(|a, b| a.total_cmp(b));
315 let len = latencies.len();
316 metrics.avg_latency_ms = latencies.iter().sum::<f64>() / len as f64;
317 metrics.p95_latency_ms = latencies[(len as f64 * 0.95) as usize];
318 metrics.p99_latency_ms = latencies[(len as f64 * 0.99) as usize];
319 }
320
321 metrics.throughput_req_per_sec = metrics.total_requests as f64 / config.duration_secs;
322 metrics.error_rate = metrics.failed_requests as f64 / metrics.total_requests.max(1) as f64;
323
324 let avg_loads: Vec<f64> = component_loads
326 .values()
327 .map(|loads| loads.iter().sum::<f64>() / loads.len() as f64)
328 .collect();
329
330 if avg_loads.len() >= 3 {
331 metrics.cpu_utilization = avg_loads[0];
332 metrics.memory_utilization = avg_loads[1 % avg_loads.len()];
333 metrics.network_utilization = avg_loads[2 % avg_loads.len()];
334 }
335
336 for (comp_id, loads) in &component_loads {
338 let avg_load = loads.iter().sum::<f64>() / loads.len() as f64;
339 if avg_load > 0.8 {
340 metrics.bottleneck_components.push(comp_id.clone());
341 }
342 }
343
344 let mut results = crate::sync_guard::write_lock(&self.simulation_results);
346 results
347 .entry(arch_id.to_string())
348 .or_default()
349 .push(metrics.clone());
350
351 self.learn_from_simulation(&metrics);
353
354 Some(metrics)
355 }
356
357 pub fn run_stress_test(&self, arch_id: &str) -> Option<StressTestResult> {
359 let _arch = {
360 let architectures = crate::sync_guard::read_lock(&self.architectures);
361 architectures.get(arch_id).cloned()?
362 };
363
364 let mut result = StressTestResult {
365 architecture_id: arch_id.to_string(),
366 breaking_point_load: 0.0,
367 recovery_time_ms: 0.0,
368 graceful_degradation: true,
369 cascade_failures: Vec::new(),
370 recommendations: Vec::new(),
371 };
372
373 let mut load = 0.1;
374 let mut previous_error_rate = 0.0;
375 let mut cascade_started = false;
376
377 while load <= 1.0 {
378 let config = SimulationConfig {
379 duration_secs: 10.0,
380 time_step_ms: 50.0,
381 max_concurrent_requests: 1000,
382 load_pattern: "linear".to_string(),
383 peak_load: load,
384 enable_fault_injection: true,
385 fault_rate: 0.001,
386 };
387
388 if let Some(metrics) = self.run_simulation(arch_id, &config) {
389 if metrics.error_rate > 0.5 && result.breaking_point_load == 0.0 {
391 result.breaking_point_load = load;
392
393 if metrics.error_rate > previous_error_rate * 2.0 {
395 cascade_started = true;
396 result.cascade_failures.push(format!(
397 "Cascade failure at load {:.2}: error rate jumped from {:.2} to {:.2}",
398 load, previous_error_rate, metrics.error_rate
399 ));
400 }
401 }
402
403 if metrics.error_rate > 0.8 {
405 result.graceful_degradation = false;
406 }
407
408 previous_error_rate = metrics.error_rate;
409 }
410
411 load += 0.1;
412 }
413
414 if result.breaking_point_load == 0.0 {
416 result.breaking_point_load = 1.0;
417 result
418 .recommendations
419 .push("Architecture is stable up to maximum load".to_string());
420 } else {
421 result.recommendations.push(format!(
422 "Breaking point at {:.0}% load — consider scaling or optimization",
423 result.breaking_point_load * 100.0
424 ));
425 }
426
427 if !result.graceful_degradation {
428 result
429 .recommendations
430 .push("Implement circuit breaker pattern for graceful degradation".to_string());
431 }
432
433 if cascade_started {
434 result
435 .recommendations
436 .push("Add bulkhead isolation to prevent cascade failures".to_string());
437 }
438
439 let mut stress_results = crate::sync_guard::write_lock(&self.stress_test_results);
441 stress_results
442 .entry(arch_id.to_string())
443 .or_default()
444 .push(result.clone());
445
446 Some(result)
447 }
448
449 pub fn compare_architectures(
451 &self,
452 arch_id_a: &str,
453 arch_id_b: &str,
454 ) -> Option<ComparisonResult> {
455 let results = crate::sync_guard::read_lock(&self.simulation_results);
456 let results_a = results.get(arch_id_a)?;
457 let results_b = results.get(arch_id_b)?;
458
459 let latest_a = results_a.last()?;
460 let latest_b = results_b.last()?;
461
462 Some(ComparisonResult {
463 architecture_a: arch_id_a.to_string(),
464 architecture_b: arch_id_b.to_string(),
465 latency_winner: if latest_a.avg_latency_ms < latest_b.avg_latency_ms {
466 arch_id_a.to_string()
467 } else {
468 arch_id_b.to_string()
469 },
470 throughput_winner: if latest_a.throughput_req_per_sec > latest_b.throughput_req_per_sec
471 {
472 arch_id_a.to_string()
473 } else {
474 arch_id_b.to_string()
475 },
476 stability_winner: if latest_a.stability_score > latest_b.stability_score {
477 arch_id_a.to_string()
478 } else {
479 arch_id_b.to_string()
480 },
481 resilience_winner: if latest_a.resilience_score > latest_b.resilience_score {
482 arch_id_a.to_string()
483 } else {
484 arch_id_b.to_string()
485 },
486 recommendations: self.generate_recommendations(latest_a, latest_b),
487 })
488 }
489
490 fn learn_from_simulation(&self, metrics: &SimulationMetrics) {
492 let mut patterns = crate::sync_guard::write_lock(&self.learned_patterns);
493
494 if metrics.error_rate > 0.1 {
496 let key = format!("high_error_rate_{:.2}", metrics.error_rate);
497 *patterns.entry(key).or_insert(0.0) -= 0.1;
498 }
499
500 if metrics.avg_latency_ms < 10.0 {
502 let key = format!("low_latency_{:.2}", metrics.avg_latency_ms);
503 *patterns.entry(key).or_insert(0.0) += 0.1;
504 }
505
506 for bottleneck in &metrics.bottleneck_components {
508 let key = format!("bottleneck_{}", bottleneck);
509 *patterns.entry(key).or_insert(0.0) -= 0.05;
510 }
511 }
512
513 fn generate_recommendations(
515 &self,
516 a: &SimulationMetrics,
517 b: &SimulationMetrics,
518 ) -> Vec<String> {
519 let mut recs = Vec::new();
520
521 if a.avg_latency_ms < b.avg_latency_ms * 0.8 {
522 recs.push(format!(
523 "Architecture A has {:.0}% better latency — consider adopting its component layout",
524 (1.0 - a.avg_latency_ms / b.avg_latency_ms) * 100.0
525 ));
526 }
527
528 if a.throughput_req_per_sec > b.throughput_req_per_sec * 1.2 {
529 recs.push(format!(
530 "Architecture A handles {:.0}% more throughput — analyze its parallelization strategy",
531 (a.throughput_req_per_sec / b.throughput_req_per_sec - 1.0) * 100.0
532 ));
533 }
534
535 if a.stability_score > b.stability_score {
536 recs.push("Architecture A is more stable under load".to_string());
537 }
538
539 recs
540 }
541
542 pub fn get_learned_patterns(&self) -> HashMap<String, f64> {
544 let patterns = crate::sync_guard::read_lock(&self.learned_patterns);
545 patterns.clone()
546 }
547
548 pub fn get_events(&self) -> Vec<SimulationEvent> {
550 let events = crate::sync_guard::read_lock(&self.events);
551 events.clone()
552 }
553
554 pub fn clear_results(&self) {
556 crate::sync_guard::write_lock(&self.simulation_results).clear();
557 crate::sync_guard::write_lock(&self.stress_test_results).clear();
558 crate::sync_guard::write_lock(&self.events).clear();
559 }
560}
561
562#[derive(Debug, Clone)]
564pub struct ComparisonResult {
565 pub architecture_a: String,
566 pub architecture_b: String,
567 pub latency_winner: String,
568 pub throughput_winner: String,
569 pub stability_winner: String,
570 pub resilience_winner: String,
571 pub recommendations: Vec<String>,
572}
573
574pub fn create_architecture(
578 name: &str,
579 description: &str,
580 components: Vec<(&str, ComponentType, f64, f64)>, connections: Vec<(&str, &str, f64, &str)>, ) -> Architecture {
583 let id = format!("arch_{}", rand::random::<u32>());
584 let mut comp_map = HashMap::new();
585
586 for (i, (name, comp_type, latency, error_rate)) in components.iter().enumerate() {
587 let comp_id = format!("comp_{}", i);
588 comp_map.insert(
589 comp_id.clone(),
590 Component {
591 id: comp_id,
592 name: name.to_string(),
593 component_type: comp_type.clone(),
594 capacity: HashMap::new(),
595 load: 0.0,
596 error_rate: *error_rate,
597 latency_ms: *latency,
598 },
599 );
600 }
601
602 let conns: Vec<Connection> = connections
603 .iter()
604 .enumerate()
605 .map(|(i, (_from, _to, bw, proto))| Connection {
606 from: format!("comp_{}", i),
607 to: format!("comp_{}", i + 1),
608 bandwidth: *bw,
609 protocol: proto.to_string(),
610 latency_ms: 1.0,
611 packet_loss: 0.001,
612 })
613 .collect();
614
615 Architecture {
616 id,
617 name: name.to_string(),
618 description: description.to_string(),
619 components: comp_map,
620 connections: conns,
621 version: 1,
622 cohesion_score: 0.7,
623 }
624}
625
626pub fn run_parallel_simulations(
628 simulator: &ArchitectureSimulator,
629 arch_ids: &[&str],
630 config: &SimulationConfig,
631) -> Vec<(String, Option<SimulationMetrics>)> {
632 arch_ids
633 .iter()
634 .map(|id| {
635 let result = simulator.run_simulation(id, config);
636 (id.to_string(), result)
637 })
638 .collect()
639}
640
641#[cfg(test)]
642mod tests {
643 use super::*;
644
645 #[test]
646 fn test_create_and_simulate() {
647 let simulator = ArchitectureSimulator::new();
648
649 let arch = create_architecture(
650 "Test Microservice",
651 "A simple test architecture",
652 vec![
653 ("API Gateway", ComponentType::Software, 5.0, 0.01),
654 ("Auth Service", ComponentType::Software, 10.0, 0.02),
655 ("Database", ComponentType::Storage, 20.0, 0.005),
656 ],
657 vec![
658 ("API Gateway", "Auth Service", 1000.0, "HTTP/2"),
659 ("Auth Service", "Database", 5000.0, "TCP"),
660 ],
661 );
662
663 let arch_id = arch.id.clone();
664 simulator.register_architecture(arch);
665
666 let config = SimulationConfig::default();
667 let result = simulator.run_simulation(&arch_id, &config);
668
669 assert!(result.is_some());
670 let metrics = result.unwrap();
671 assert!(metrics.total_requests > 0);
672 assert!(metrics.avg_latency_ms > 0.0);
673 }
674
675 #[test]
676 fn test_stress_test() {
677 let simulator = ArchitectureSimulator::new();
678
679 let arch = create_architecture(
680 "Stress Test Target",
681 "Architecture for stress testing",
682 vec![
683 ("Load Balancer", ComponentType::Software, 2.0, 0.001),
684 ("Web Server", ComponentType::Software, 15.0, 0.01),
685 ("Cache", ComponentType::Storage, 1.0, 0.001),
686 ],
687 vec![
688 ("Load Balancer", "Web Server", 10000.0, "HTTP/1.1"),
689 ("Web Server", "Cache", 20000.0, "Redis"),
690 ],
691 );
692
693 let arch_id = arch.id.clone();
694 simulator.register_architecture(arch);
695 let result = simulator.run_stress_test(&arch_id);
696
697 assert!(result.is_some());
698 let stress = result.unwrap();
699 assert!(stress.breaking_point_load > 0.0);
700 }
701}