1use std::collections::HashSet;
33use std::time::SystemTime;
34
35#[derive(Debug, Clone)]
40#[non_exhaustive]
41pub enum DeploymentTarget {
42 Docker {
44 image_tag: String,
46 memory_limit_mb: Option<u64>,
48 },
49 Kubernetes {
51 namespace: String,
53 replicas: u32,
55 cpu_request_millicores: Option<u32>,
57 memory_request_mib: Option<u32>,
59 },
60 AwsLambda {
62 memory_mb: u32,
64 timeout_secs: u32,
66 },
67 AzureFunctions {
69 plan_tier: String,
71 max_instances: u32,
73 },
74 CloudRun {
76 max_concurrency: u32,
78 cpu: u32,
80 memory_mib: u32,
82 },
83 BareMetal {
85 host: String,
87 },
88}
89
90impl DeploymentTarget {
91 pub fn description(&self) -> String {
93 match self {
94 Self::Docker { image_tag, .. } => format!("Docker container: {image_tag}"),
95 Self::Kubernetes {
96 namespace,
97 replicas,
98 ..
99 } => format!("Kubernetes: {namespace} ({replicas} replicas)"),
100 Self::AwsLambda {
101 memory_mb,
102 timeout_secs,
103 } => format!("AWS Lambda: {memory_mb}MB, {timeout_secs}s timeout"),
104 Self::AzureFunctions {
105 plan_tier,
106 max_instances,
107 } => format!("Azure Functions: {plan_tier} (max {max_instances} instances)"),
108 Self::CloudRun {
109 max_concurrency,
110 cpu,
111 memory_mib,
112 } => format!("Cloud Run: {cpu} CPU, {memory_mib}MiB, concurrency {max_concurrency}"),
113 Self::BareMetal { host } => format!("Bare metal: {host}"),
114 #[allow(unreachable_patterns)]
115 _ => "Unknown deployment target".to_string(),
116 }
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Hash)]
122#[non_exhaustive]
123pub enum SlaCategory {
124 LinearAlgebra,
126 Fft,
128 Statistics,
130 Signal,
132 Sparse,
134 Integration,
136 Interpolation,
138 Optimization,
140}
141
142impl core::fmt::Display for SlaCategory {
143 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
144 match self {
145 Self::LinearAlgebra => write!(f, "linalg"),
146 Self::Fft => write!(f, "fft"),
147 Self::Statistics => write!(f, "stats"),
148 Self::Signal => write!(f, "signal"),
149 Self::Sparse => write!(f, "sparse"),
150 Self::Integration => write!(f, "integrate"),
151 Self::Interpolation => write!(f, "interpolate"),
152 Self::Optimization => write!(f, "optimize"),
153 #[allow(unreachable_patterns)]
154 _ => write!(f, "unknown"),
155 }
156 }
157}
158
159#[derive(Debug, Clone)]
165pub struct PerformanceSla {
166 pub category: SlaCategory,
168 pub operation: String,
170 pub max_latency_ms: u64,
172 pub throughput_ops_per_sec: Option<f64>,
174 pub p99_latency_ms: Option<u64>,
176 pub description: String,
178}
179
180impl Default for PerformanceSla {
181 fn default() -> Self {
182 Self {
183 category: SlaCategory::LinearAlgebra,
184 operation: String::new(),
185 max_latency_ms: 0,
186 throughput_ops_per_sec: None,
187 p99_latency_ms: None,
188 description: String::new(),
189 }
190 }
191}
192
193pub fn default_sla_baselines() -> Vec<PerformanceSla> {
203 vec![
204 PerformanceSla {
206 category: SlaCategory::LinearAlgebra,
207 operation: "matmul_1000x1000".into(),
208 max_latency_ms: 500,
209 throughput_ops_per_sec: Some(2.0),
210 p99_latency_ms: Some(600),
211 description: "Dense matrix multiply (1000x1000 f64)".into(),
212 },
213 PerformanceSla {
214 category: SlaCategory::LinearAlgebra,
215 operation: "det_100x100".into(),
216 max_latency_ms: 10,
217 throughput_ops_per_sec: Some(100.0),
218 p99_latency_ms: Some(15),
219 description: "Determinant of 100x100 f64 matrix".into(),
220 },
221 PerformanceSla {
222 category: SlaCategory::LinearAlgebra,
223 operation: "svd_500x500".into(),
224 max_latency_ms: 2000,
225 throughput_ops_per_sec: Some(0.5),
226 p99_latency_ms: Some(2500),
227 description: "Full SVD of 500x500 f64 matrix".into(),
228 },
229 PerformanceSla {
230 category: SlaCategory::LinearAlgebra,
231 operation: "solve_1000x1000".into(),
232 max_latency_ms: 500,
233 throughput_ops_per_sec: Some(2.0),
234 p99_latency_ms: Some(600),
235 description: "Dense linear solve (1000x1000 f64)".into(),
236 },
237 PerformanceSla {
238 category: SlaCategory::LinearAlgebra,
239 operation: "cholesky_1000x1000".into(),
240 max_latency_ms: 300,
241 throughput_ops_per_sec: Some(3.0),
242 p99_latency_ms: Some(400),
243 description: "Cholesky decomposition (1000x1000 SPD f64)".into(),
244 },
245 PerformanceSla {
247 category: SlaCategory::Fft,
248 operation: "fft_1m_points".into(),
249 max_latency_ms: 100,
250 throughput_ops_per_sec: Some(10.0),
251 p99_latency_ms: Some(130),
252 description: "Complex FFT of 2^20 (1M) points".into(),
253 },
254 PerformanceSla {
255 category: SlaCategory::Fft,
256 operation: "fft_64k_points".into(),
257 max_latency_ms: 10,
258 throughput_ops_per_sec: Some(100.0),
259 p99_latency_ms: Some(15),
260 description: "Complex FFT of 2^16 (64K) points".into(),
261 },
262 PerformanceSla {
263 category: SlaCategory::Fft,
264 operation: "batch_fft_1000x1024".into(),
265 max_latency_ms: 200,
266 throughput_ops_per_sec: Some(5.0),
267 p99_latency_ms: Some(250),
268 description: "Batch FFT: 1000 transforms of length 1024".into(),
269 },
270 PerformanceSla {
271 category: SlaCategory::Fft,
272 operation: "rfft_1m_points".into(),
273 max_latency_ms: 60,
274 throughput_ops_per_sec: Some(15.0),
275 p99_latency_ms: Some(80),
276 description: "Real-valued FFT of 2^20 (1M) points".into(),
277 },
278 PerformanceSla {
280 category: SlaCategory::Statistics,
281 operation: "normal_pdf_1m".into(),
282 max_latency_ms: 50,
283 throughput_ops_per_sec: Some(20.0),
284 p99_latency_ms: Some(65),
285 description: "Normal PDF evaluated at 1M points".into(),
286 },
287 PerformanceSla {
288 category: SlaCategory::Statistics,
289 operation: "linreg_10k_100feat".into(),
290 max_latency_ms: 500,
291 throughput_ops_per_sec: Some(2.0),
292 p99_latency_ms: Some(600),
293 description: "Linear regression: 10K samples, 100 features".into(),
294 },
295 PerformanceSla {
296 category: SlaCategory::Statistics,
297 operation: "kde_10k_points".into(),
298 max_latency_ms: 200,
299 throughput_ops_per_sec: Some(5.0),
300 p99_latency_ms: Some(250),
301 description: "Kernel density estimation on 10K points".into(),
302 },
303 PerformanceSla {
305 category: SlaCategory::Signal,
306 operation: "fir_64tap_1m".into(),
307 max_latency_ms: 100,
308 throughput_ops_per_sec: Some(10.0),
309 p99_latency_ms: Some(130),
310 description: "FIR filter: 64 taps, 1M samples".into(),
311 },
312 PerformanceSla {
313 category: SlaCategory::Signal,
314 operation: "stft_1m_1024win".into(),
315 max_latency_ms: 500,
316 throughput_ops_per_sec: Some(2.0),
317 p99_latency_ms: Some(600),
318 description: "STFT: 1M samples, 1024-sample window".into(),
319 },
320 PerformanceSla {
321 category: SlaCategory::Signal,
322 operation: "iir_8pole_1m".into(),
323 max_latency_ms: 50,
324 throughput_ops_per_sec: Some(20.0),
325 p99_latency_ms: Some(65),
326 description: "IIR filter: 8-pole Butterworth, 1M samples".into(),
327 },
328 PerformanceSla {
330 category: SlaCategory::Sparse,
331 operation: "spmv_100k_1m".into(),
332 max_latency_ms: 10,
333 throughput_ops_per_sec: Some(100.0),
334 p99_latency_ms: Some(15),
335 description: "Sparse matrix-vector multiply: 100K x 100K, 1M nnz".into(),
336 },
337 PerformanceSla {
338 category: SlaCategory::Sparse,
339 operation: "cg_10k".into(),
340 max_latency_ms: 1000,
341 throughput_ops_per_sec: Some(1.0),
342 p99_latency_ms: Some(1200),
343 description: "Conjugate gradient solve: 10K x 10K sparse SPD".into(),
344 },
345 PerformanceSla {
346 category: SlaCategory::Sparse,
347 operation: "sparse_lu_10k".into(),
348 max_latency_ms: 2000,
349 throughput_ops_per_sec: Some(0.5),
350 p99_latency_ms: Some(2500),
351 description: "Sparse LU factorization: 10K x 10K".into(),
352 },
353 PerformanceSla {
355 category: SlaCategory::Integration,
356 operation: "quad_1k_points".into(),
357 max_latency_ms: 5,
358 throughput_ops_per_sec: Some(200.0),
359 p99_latency_ms: Some(8),
360 description: "Adaptive quadrature with 1K evaluation points".into(),
361 },
362 PerformanceSla {
363 category: SlaCategory::Integration,
364 operation: "ode_rk45_10k_steps".into(),
365 max_latency_ms: 100,
366 throughput_ops_per_sec: Some(10.0),
367 p99_latency_ms: Some(130),
368 description: "RK45 ODE solver: 10K adaptive steps".into(),
369 },
370 PerformanceSla {
372 category: SlaCategory::Interpolation,
373 operation: "cubic_spline_10k".into(),
374 max_latency_ms: 20,
375 throughput_ops_per_sec: Some(50.0),
376 p99_latency_ms: Some(30),
377 description: "Cubic spline interpolation: 10K knots".into(),
378 },
379 PerformanceSla {
381 category: SlaCategory::Optimization,
382 operation: "lbfgs_100d".into(),
383 max_latency_ms: 200,
384 throughput_ops_per_sec: Some(5.0),
385 p99_latency_ms: Some(250),
386 description: "L-BFGS optimization: 100 dimensions, Rosenbrock".into(),
387 },
388 PerformanceSla {
389 category: SlaCategory::Optimization,
390 operation: "nelder_mead_50d".into(),
391 max_latency_ms: 500,
392 throughput_ops_per_sec: Some(2.0),
393 p99_latency_ms: Some(600),
394 description: "Nelder-Mead: 50 dimensions".into(),
395 },
396 ]
397}
398
399pub fn validate_sla_uniqueness(baselines: &[PerformanceSla]) -> Result<(), String> {
405 let mut seen = HashSet::new();
406 for sla in baselines {
407 if !seen.insert(&sla.operation) {
408 return Err(format!("Duplicate SLA operation: {}", sla.operation));
409 }
410 }
411 Ok(())
412}
413
414#[derive(Debug, Clone)]
416pub struct DeploymentHealth {
417 pub version: String,
419 pub crates_available: Vec<String>,
421 pub uptime_secs: u64,
423 pub memory_usage_bytes: usize,
425 pub timestamp_epoch_secs: u64,
427}
428
429pub fn health_check() -> DeploymentHealth {
434 use std::sync::OnceLock;
435
436 static START_TIME: OnceLock<SystemTime> = OnceLock::new();
437 let start = START_TIME.get_or_init(SystemTime::now);
438
439 let uptime = SystemTime::now()
440 .duration_since(*start)
441 .unwrap_or_default()
442 .as_secs();
443
444 let now_epoch = SystemTime::now()
445 .duration_since(SystemTime::UNIX_EPOCH)
446 .unwrap_or_default()
447 .as_secs();
448
449 let mut crates_available = vec![
453 "scirs2-core".to_string(),
454 "scirs2-linalg".to_string(),
455 "scirs2-stats".to_string(),
456 "scirs2-signal".to_string(),
457 "scirs2-fft".to_string(),
458 "scirs2-sparse".to_string(),
459 "scirs2-optimize".to_string(),
460 "scirs2-integrate".to_string(),
461 "scirs2-interpolate".to_string(),
462 "scirs2-special".to_string(),
463 "scirs2-cluster".to_string(),
464 "scirs2-io".to_string(),
465 "scirs2-graph".to_string(),
466 "scirs2-neural".to_string(),
467 "scirs2-series".to_string(),
468 "scirs2-text".to_string(),
469 "scirs2-vision".to_string(),
470 "scirs2-metrics".to_string(),
471 "scirs2-ndimage".to_string(),
472 "scirs2-transform".to_string(),
473 "scirs2-datasets".to_string(),
474 "scirs2-wasm".to_string(),
475 ];
476 crates_available.sort();
477
478 DeploymentHealth {
479 version: env!("CARGO_PKG_VERSION").to_string(),
480 crates_available,
481 uptime_secs: uptime,
482 memory_usage_bytes: 0, timestamp_epoch_secs: now_epoch,
484 }
485}
486
487#[derive(Debug, Clone)]
489pub struct ResourceRecommendation {
490 pub profile: String,
492 pub cpu_cores: u32,
494 pub memory_mib: u32,
496 pub disk_mib: u32,
498 pub description: String,
500}
501
502pub fn resource_recommendations() -> Vec<ResourceRecommendation> {
504 vec![
505 ResourceRecommendation {
506 profile: "lightweight".into(),
507 cpu_cores: 2,
508 memory_mib: 2048,
509 disk_mib: 512,
510 description: "Statistical computations, small-scale signal processing".into(),
511 },
512 ResourceRecommendation {
513 profile: "standard".into(),
514 cpu_cores: 4,
515 memory_mib: 8192,
516 disk_mib: 2048,
517 description: "General scientific computing, moderate linear algebra".into(),
518 },
519 ResourceRecommendation {
520 profile: "compute_intensive".into(),
521 cpu_cores: 8,
522 memory_mib: 32768,
523 disk_mib: 8192,
524 description: "Large-scale linalg, neural network training, optimization".into(),
525 },
526 ResourceRecommendation {
527 profile: "memory_intensive".into(),
528 cpu_cores: 4,
529 memory_mib: 65536,
530 disk_mib: 16384,
531 description: "Large sparse systems, out-of-core processing, big datasets".into(),
532 },
533 ]
534}
535
536#[cfg(test)]
537mod tests {
538 use super::*;
539
540 #[test]
541 fn test_default_sla_baselines_not_empty() {
542 let baselines = default_sla_baselines();
543 assert!(!baselines.is_empty(), "SLA baselines must not be empty");
544 assert!(
545 baselines.len() >= 15,
546 "Expected at least 15 SLA entries, got {}",
547 baselines.len()
548 );
549 }
550
551 #[test]
552 fn test_sla_values_positive() {
553 for sla in default_sla_baselines() {
554 assert!(
555 sla.max_latency_ms > 0,
556 "SLA {} has zero max_latency_ms",
557 sla.operation
558 );
559 if let Some(throughput) = sla.throughput_ops_per_sec {
560 assert!(
561 throughput > 0.0,
562 "SLA {} has non-positive throughput",
563 sla.operation
564 );
565 }
566 if let Some(p99) = sla.p99_latency_ms {
567 assert!(
568 p99 >= sla.max_latency_ms,
569 "SLA {} has p99 ({}) < max_latency ({})",
570 sla.operation,
571 p99,
572 sla.max_latency_ms
573 );
574 }
575 }
576 }
577
578 #[test]
579 fn test_sla_operation_names_unique() {
580 let baselines = default_sla_baselines();
581 let result = validate_sla_uniqueness(&baselines);
582 assert!(
583 result.is_ok(),
584 "Duplicate SLA operation: {:?}",
585 result.err()
586 );
587 }
588
589 #[test]
590 fn test_sla_all_categories_covered() {
591 let baselines = default_sla_baselines();
592 let categories: HashSet<_> = baselines.iter().map(|s| s.category.clone()).collect();
593 assert!(categories.contains(&SlaCategory::LinearAlgebra));
594 assert!(categories.contains(&SlaCategory::Fft));
595 assert!(categories.contains(&SlaCategory::Statistics));
596 assert!(categories.contains(&SlaCategory::Signal));
597 assert!(categories.contains(&SlaCategory::Sparse));
598 assert!(categories.contains(&SlaCategory::Integration));
599 assert!(categories.contains(&SlaCategory::Interpolation));
600 assert!(categories.contains(&SlaCategory::Optimization));
601 }
602
603 #[test]
604 fn test_health_check() {
605 let health = health_check();
606 assert!(!health.version.is_empty(), "Version must not be empty");
607 assert!(
608 !health.crates_available.is_empty(),
609 "Crates list must not be empty"
610 );
611 assert!(
612 health.crates_available.contains(&"scirs2-core".to_string()),
613 "scirs2-core must be in crates list"
614 );
615 assert!(
616 health.timestamp_epoch_secs > 0,
617 "Timestamp must be positive"
618 );
619 }
620
621 #[test]
622 fn test_deployment_target_variants() {
623 let targets = vec![
624 DeploymentTarget::Docker {
625 image_tag: "scirs2:0.4.0".into(),
626 memory_limit_mb: Some(4096),
627 },
628 DeploymentTarget::Kubernetes {
629 namespace: "production".into(),
630 replicas: 3,
631 cpu_request_millicores: Some(2000),
632 memory_request_mib: Some(8192),
633 },
634 DeploymentTarget::AwsLambda {
635 memory_mb: 1024,
636 timeout_secs: 300,
637 },
638 DeploymentTarget::AzureFunctions {
639 plan_tier: "Premium".into(),
640 max_instances: 10,
641 },
642 DeploymentTarget::CloudRun {
643 max_concurrency: 80,
644 cpu: 4,
645 memory_mib: 8192,
646 },
647 DeploymentTarget::BareMetal {
648 host: "compute-01.example.com".into(),
649 },
650 ];
651 for target in &targets {
652 let desc = target.description();
653 assert!(!desc.is_empty(), "Description must not be empty");
654 }
655 }
656
657 #[test]
658 fn test_deployment_target_description_content() {
659 let docker = DeploymentTarget::Docker {
660 image_tag: "myimg:latest".into(),
661 memory_limit_mb: None,
662 };
663 assert!(docker.description().contains("myimg:latest"));
664
665 let k8s = DeploymentTarget::Kubernetes {
666 namespace: "ml-prod".into(),
667 replicas: 5,
668 cpu_request_millicores: None,
669 memory_request_mib: None,
670 };
671 assert!(k8s.description().contains("ml-prod"));
672 assert!(k8s.description().contains("5"));
673 }
674
675 #[test]
676 fn test_resource_recommendations() {
677 let recs = resource_recommendations();
678 assert!(recs.len() >= 3, "Expected at least 3 resource profiles");
679 for rec in &recs {
680 assert!(!rec.profile.is_empty());
681 assert!(rec.cpu_cores > 0);
682 assert!(rec.memory_mib > 0);
683 }
684 }
685
686 #[test]
687 fn test_sla_category_display() {
688 assert_eq!(SlaCategory::LinearAlgebra.to_string(), "linalg");
689 assert_eq!(SlaCategory::Fft.to_string(), "fft");
690 assert_eq!(SlaCategory::Statistics.to_string(), "stats");
691 assert_eq!(SlaCategory::Signal.to_string(), "signal");
692 assert_eq!(SlaCategory::Sparse.to_string(), "sparse");
693 assert_eq!(SlaCategory::Integration.to_string(), "integrate");
694 assert_eq!(SlaCategory::Interpolation.to_string(), "interpolate");
695 assert_eq!(SlaCategory::Optimization.to_string(), "optimize");
696 }
697
698 #[test]
699 fn test_performance_sla_default() {
700 let sla = PerformanceSla::default();
701 assert!(sla.operation.is_empty());
702 assert_eq!(sla.max_latency_ms, 0);
703 assert!(sla.throughput_ops_per_sec.is_none());
704 assert!(sla.p99_latency_ms.is_none());
705 }
706}