1use anyhow::Result;
7use clap::Args;
8use serde::{Deserialize, Serialize};
9use std::path::PathBuf;
10
11#[derive(Args, Debug, Clone)]
13pub struct DoctorArgs {
14 #[arg(long, help = "Automatically apply safe fixes")]
16 pub fix: bool,
17
18 #[arg(long, short, help = "Show detailed diagnostic information")]
20 pub verbose: bool,
21
22 #[arg(long, help = "Output results as JSON")]
24 pub json: bool,
25
26 #[arg(long, default_value = "all", help = "Category to check")]
28 pub check: CheckCategory,
29
30 #[arg(long, help = "Path to configuration file to validate")]
32 pub config_path: Option<PathBuf>,
33}
34
35#[derive(Debug, Clone, Copy, clap::ValueEnum, Default)]
36pub enum CheckCategory {
37 #[default]
38 All,
39 Cpu,
40 Memory,
41 Config,
42 Deps,
43 Network,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48pub enum CheckStatus {
49 Pass,
50 Warning,
51 Fail,
52 Info,
53}
54
55impl std::fmt::Display for CheckStatus {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 match self {
58 CheckStatus::Pass => write!(f, "ā"),
59 CheckStatus::Warning => write!(f, "ā "),
60 CheckStatus::Fail => write!(f, "ā"),
61 CheckStatus::Info => write!(f, "ā¹"),
62 }
63 }
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct DiagnosticCheck {
69 pub name: String,
70 pub category: String,
71 pub status: CheckStatus,
72 pub message: String,
73 pub recommendation: Option<String>,
74 pub auto_fixable: bool,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct DiagnosticReport {
80 pub timestamp: String,
81 pub system_info: SystemInfo,
82 pub checks: Vec<DiagnosticCheck>,
83 pub recommendations: Vec<String>,
84 pub optimal_config: OptimalConfig,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct SystemInfo {
90 pub os: String,
91 pub arch: String,
92 pub cpu_count: usize,
93 pub cpu_brand: String,
94 pub total_memory_mb: u64,
95 pub available_memory_mb: u64,
96 pub simd_features: SimdFeatures,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct SimdFeatures {
102 pub sse2: bool,
103 pub sse4_1: bool,
104 pub sse4_2: bool,
105 pub avx: bool,
106 pub avx2: bool,
107 pub avx512f: bool,
108 pub neon: bool,
109 pub best_available: String,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct OptimalConfig {
115 pub batch_size: usize,
116 pub worker_threads: usize,
117 pub simd_backend: String,
118 pub memory_limit_mb: u64,
119 pub preprocessing_mode: String,
120 pub cache_enabled: bool,
121 pub cache_size_mb: u64,
122}
123
124pub async fn execute(args: DoctorArgs) -> Result<()> {
126 if !args.json {
127 println!("𩺠SciPix Doctor - Environment Analysis\n");
128 println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n");
129 }
130
131 let mut checks = Vec::new();
132
133 let system_info = gather_system_info();
135
136 match args.check {
138 CheckCategory::All => {
139 checks.extend(check_cpu(&system_info, args.verbose));
140 checks.extend(check_memory(&system_info, args.verbose));
141 checks.extend(check_dependencies(args.verbose));
142 checks.extend(check_config(&args.config_path, args.verbose));
143 checks.extend(check_network(args.verbose).await);
144 }
145 CheckCategory::Cpu => {
146 checks.extend(check_cpu(&system_info, args.verbose));
147 }
148 CheckCategory::Memory => {
149 checks.extend(check_memory(&system_info, args.verbose));
150 }
151 CheckCategory::Config => {
152 checks.extend(check_config(&args.config_path, args.verbose));
153 }
154 CheckCategory::Deps => {
155 checks.extend(check_dependencies(args.verbose));
156 }
157 CheckCategory::Network => {
158 checks.extend(check_network(args.verbose).await);
159 }
160 }
161
162 let optimal_config = generate_optimal_config(&system_info);
164
165 let recommendations: Vec<String> = checks
167 .iter()
168 .filter_map(|c| c.recommendation.clone())
169 .collect();
170
171 let report = DiagnosticReport {
173 timestamp: chrono::Utc::now().to_rfc3339(),
174 system_info: system_info.clone(),
175 checks: checks.clone(),
176 recommendations: recommendations.clone(),
177 optimal_config: optimal_config.clone(),
178 };
179
180 if args.json {
181 println!("{}", serde_json::to_string_pretty(&report)?);
182 return Ok(());
183 }
184
185 print_system_info(&system_info);
187
188 print_check_results(&checks);
190
191 if !recommendations.is_empty() {
193 println!("\nš Recommendations:");
194 println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
195 for (i, rec) in recommendations.iter().enumerate() {
196 println!(" {}. {}", i + 1, rec);
197 }
198 }
199
200 print_optimal_config(&optimal_config);
202
203 if args.fix {
205 apply_fixes(&checks).await?;
206 }
207
208 print_summary(&checks);
210
211 Ok(())
212}
213
214fn gather_system_info() -> SystemInfo {
215 let cpu_count = num_cpus::get();
216
217 let cpu_brand = get_cpu_brand();
219
220 let (total_memory_mb, available_memory_mb) = get_memory_info();
222
223 let simd_features = detect_simd_features();
225
226 SystemInfo {
227 os: std::env::consts::OS.to_string(),
228 arch: std::env::consts::ARCH.to_string(),
229 cpu_count,
230 cpu_brand,
231 total_memory_mb,
232 available_memory_mb,
233 simd_features,
234 }
235}
236
237fn get_cpu_brand() -> String {
238 #[cfg(target_arch = "x86_64")]
239 {
240 if let Some(brand) = get_x86_cpu_brand() {
241 return brand;
242 }
243 }
244
245 format!("{} processor", std::env::consts::ARCH)
247}
248
249#[cfg(target_arch = "x86_64")]
250fn get_x86_cpu_brand() -> Option<String> {
251 if let Ok(cpuinfo) = std::fs::read_to_string("/proc/cpuinfo") {
253 for line in cpuinfo.lines() {
254 if line.starts_with("model name") {
255 if let Some(brand) = line.split(':').nth(1) {
256 return Some(brand.trim().to_string());
257 }
258 }
259 }
260 }
261 None
262}
263
264#[cfg(not(target_arch = "x86_64"))]
265fn get_x86_cpu_brand() -> Option<String> {
266 None
267}
268
269fn get_memory_info() -> (u64, u64) {
270 if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") {
272 let mut total = 0u64;
273 let mut available = 0u64;
274
275 for line in meminfo.lines() {
276 if line.starts_with("MemTotal:") {
277 if let Some(kb) = parse_meminfo_value(line) {
278 total = kb / 1024; }
280 } else if line.starts_with("MemAvailable:") {
281 if let Some(kb) = parse_meminfo_value(line) {
282 available = kb / 1024; }
284 }
285 }
286
287 if total > 0 {
288 return (total, available);
289 }
290 }
291
292 (8192, 4096)
294}
295
296fn parse_meminfo_value(line: &str) -> Option<u64> {
297 line.split_whitespace()
298 .nth(1)
299 .and_then(|s| s.parse().ok())
300}
301
302fn detect_simd_features() -> SimdFeatures {
303 let mut features = SimdFeatures {
304 sse2: false,
305 sse4_1: false,
306 sse4_2: false,
307 avx: false,
308 avx2: false,
309 avx512f: false,
310 neon: false,
311 best_available: "scalar".to_string(),
312 };
313
314 #[cfg(target_arch = "x86_64")]
315 {
316 features.sse2 = is_x86_feature_detected!("sse2");
317 features.sse4_1 = is_x86_feature_detected!("sse4.1");
318 features.sse4_2 = is_x86_feature_detected!("sse4.2");
319 features.avx = is_x86_feature_detected!("avx");
320 features.avx2 = is_x86_feature_detected!("avx2");
321 features.avx512f = is_x86_feature_detected!("avx512f");
322
323 if features.avx512f {
324 features.best_available = "AVX-512".to_string();
325 } else if features.avx2 {
326 features.best_available = "AVX2".to_string();
327 } else if features.avx {
328 features.best_available = "AVX".to_string();
329 } else if features.sse4_2 {
330 features.best_available = "SSE4.2".to_string();
331 } else if features.sse2 {
332 features.best_available = "SSE2".to_string();
333 }
334 }
335
336 #[cfg(target_arch = "aarch64")]
337 {
338 features.neon = true; features.best_available = "NEON".to_string();
340 }
341
342 features
343}
344
345fn check_cpu(system_info: &SystemInfo, verbose: bool) -> Vec<DiagnosticCheck> {
346 let mut checks = Vec::new();
347
348 let cpu_status = if system_info.cpu_count >= 8 {
350 CheckStatus::Pass
351 } else if system_info.cpu_count >= 4 {
352 CheckStatus::Warning
353 } else {
354 CheckStatus::Fail
355 };
356
357 checks.push(DiagnosticCheck {
358 name: "CPU Cores".to_string(),
359 category: "CPU".to_string(),
360 status: cpu_status,
361 message: format!("{} cores detected", system_info.cpu_count),
362 recommendation: if system_info.cpu_count < 4 {
363 Some("Consider running on a machine with more CPU cores for better batch processing".to_string())
364 } else {
365 None
366 },
367 auto_fixable: false,
368 });
369
370 let simd_status = match system_info.simd_features.best_available.as_str() {
372 "AVX-512" | "AVX2" => CheckStatus::Pass,
373 "AVX" | "SSE4.2" | "NEON" => CheckStatus::Warning,
374 _ => CheckStatus::Fail,
375 };
376
377 checks.push(DiagnosticCheck {
378 name: "SIMD Support".to_string(),
379 category: "CPU".to_string(),
380 status: simd_status,
381 message: format!(
382 "Best SIMD: {} (SSE2: {}, AVX: {}, AVX2: {}, AVX-512: {})",
383 system_info.simd_features.best_available,
384 if system_info.simd_features.sse2 { "ā" } else { "ā" },
385 if system_info.simd_features.avx { "ā" } else { "ā" },
386 if system_info.simd_features.avx2 { "ā" } else { "ā" },
387 if system_info.simd_features.avx512f { "ā" } else { "ā" },
388 ),
389 recommendation: if simd_status == CheckStatus::Fail {
390 Some("Upgrade to a CPU with AVX2 support for 4x faster preprocessing".to_string())
391 } else {
392 None
393 },
394 auto_fixable: false,
395 });
396
397 if verbose {
398 checks.push(DiagnosticCheck {
399 name: "CPU Brand".to_string(),
400 category: "CPU".to_string(),
401 status: CheckStatus::Info,
402 message: system_info.cpu_brand.clone(),
403 recommendation: None,
404 auto_fixable: false,
405 });
406 }
407
408 checks
409}
410
411fn check_memory(system_info: &SystemInfo, verbose: bool) -> Vec<DiagnosticCheck> {
412 let mut checks = Vec::new();
413
414 let mem_status = if system_info.total_memory_mb >= 16384 {
416 CheckStatus::Pass
417 } else if system_info.total_memory_mb >= 8192 {
418 CheckStatus::Warning
419 } else {
420 CheckStatus::Fail
421 };
422
423 checks.push(DiagnosticCheck {
424 name: "Total Memory".to_string(),
425 category: "Memory".to_string(),
426 status: mem_status,
427 message: format!("{} MB total", system_info.total_memory_mb),
428 recommendation: if system_info.total_memory_mb < 8192 {
429 Some("Consider upgrading to at least 8GB RAM for optimal batch processing".to_string())
430 } else {
431 None
432 },
433 auto_fixable: false,
434 });
435
436 let avail_ratio = system_info.available_memory_mb as f64 / system_info.total_memory_mb as f64;
438 let avail_status = if avail_ratio >= 0.5 {
439 CheckStatus::Pass
440 } else if avail_ratio >= 0.25 {
441 CheckStatus::Warning
442 } else {
443 CheckStatus::Fail
444 };
445
446 checks.push(DiagnosticCheck {
447 name: "Available Memory".to_string(),
448 category: "Memory".to_string(),
449 status: avail_status,
450 message: format!(
451 "{} MB available ({:.1}%)",
452 system_info.available_memory_mb,
453 avail_ratio * 100.0
454 ),
455 recommendation: if avail_status == CheckStatus::Fail {
456 Some("Close some applications to free up memory before batch processing".to_string())
457 } else {
458 None
459 },
460 auto_fixable: false,
461 });
462
463 if verbose {
464 let mem_per_core = system_info.total_memory_mb / system_info.cpu_count as u64;
466 checks.push(DiagnosticCheck {
467 name: "Memory per Core".to_string(),
468 category: "Memory".to_string(),
469 status: CheckStatus::Info,
470 message: format!("{} MB/core", mem_per_core),
471 recommendation: None,
472 auto_fixable: false,
473 });
474 }
475
476 checks
477}
478
479fn check_dependencies(verbose: bool) -> Vec<DiagnosticCheck> {
480 let mut checks = Vec::new();
481
482 let onnx_status = check_onnx_runtime();
484 checks.push(DiagnosticCheck {
485 name: "ONNX Runtime".to_string(),
486 category: "Dependencies".to_string(),
487 status: if onnx_status.0 { CheckStatus::Pass } else { CheckStatus::Warning },
488 message: onnx_status.1.clone(),
489 recommendation: if !onnx_status.0 {
490 Some("Install ONNX Runtime for neural network acceleration: https://onnxruntime.ai/".to_string())
491 } else {
492 None
493 },
494 auto_fixable: false,
495 });
496
497 checks.push(DiagnosticCheck {
499 name: "Image Processing".to_string(),
500 category: "Dependencies".to_string(),
501 status: CheckStatus::Pass,
502 message: "image crate available (built-in)".to_string(),
503 recommendation: None,
504 auto_fixable: false,
505 });
506
507 let openssl_available = std::process::Command::new("openssl")
509 .arg("version")
510 .output()
511 .map(|o| o.status.success())
512 .unwrap_or(false);
513
514 checks.push(DiagnosticCheck {
515 name: "OpenSSL".to_string(),
516 category: "Dependencies".to_string(),
517 status: if openssl_available { CheckStatus::Pass } else { CheckStatus::Warning },
518 message: if openssl_available {
519 "OpenSSL available for HTTPS".to_string()
520 } else {
521 "OpenSSL not found".to_string()
522 },
523 recommendation: if !openssl_available {
524 Some("Install OpenSSL for secure API communication".to_string())
525 } else {
526 None
527 },
528 auto_fixable: false,
529 });
530
531 if verbose {
532 if let Ok(output) = std::process::Command::new("rustc").arg("--version").output() {
534 let version = String::from_utf8_lossy(&output.stdout);
535 checks.push(DiagnosticCheck {
536 name: "Rust Compiler".to_string(),
537 category: "Dependencies".to_string(),
538 status: CheckStatus::Info,
539 message: version.trim().to_string(),
540 recommendation: None,
541 auto_fixable: false,
542 });
543 }
544 }
545
546 checks
547}
548
549fn check_onnx_runtime() -> (bool, String) {
550 let lib_paths = [
552 "/usr/lib/libonnxruntime.so",
553 "/usr/local/lib/libonnxruntime.so",
554 "/opt/onnxruntime/lib/libonnxruntime.so",
555 ];
556
557 for path in &lib_paths {
558 if std::path::Path::new(path).exists() {
559 return (true, format!("Found at {}", path));
560 }
561 }
562
563 if std::env::var("ORT_DYLIB_PATH").is_ok() {
565 return (true, "Configured via ORT_DYLIB_PATH".to_string());
566 }
567
568 (false, "Not found (optional for ONNX acceleration)".to_string())
569}
570
571fn check_config(config_path: &Option<PathBuf>, verbose: bool) -> Vec<DiagnosticCheck> {
572 let mut checks = Vec::new();
573
574 let config_locations = [
576 config_path.clone(),
577 Some(PathBuf::from("scipix.toml")),
578 Some(PathBuf::from("config/scipix.toml")),
579 dirs::config_dir().map(|p| p.join("scipix/config.toml")),
580 ];
581
582 let mut found_config = false;
583 for loc in config_locations.iter().flatten() {
584 if loc.exists() {
585 checks.push(DiagnosticCheck {
586 name: "Configuration File".to_string(),
587 category: "Config".to_string(),
588 status: CheckStatus::Pass,
589 message: format!("Found at {}", loc.display()),
590 recommendation: None,
591 auto_fixable: false,
592 });
593 found_config = true;
594
595 if let Ok(content) = std::fs::read_to_string(loc) {
597 if content.contains("[api]") || content.contains("[processing]") {
598 checks.push(DiagnosticCheck {
599 name: "Config Validity".to_string(),
600 category: "Config".to_string(),
601 status: CheckStatus::Pass,
602 message: "Configuration file is valid".to_string(),
603 recommendation: None,
604 auto_fixable: false,
605 });
606 }
607 }
608 break;
609 }
610 }
611
612 if !found_config {
613 checks.push(DiagnosticCheck {
614 name: "Configuration File".to_string(),
615 category: "Config".to_string(),
616 status: CheckStatus::Info,
617 message: "No configuration file found (using defaults)".to_string(),
618 recommendation: Some("Create a scipix.toml for custom settings".to_string()),
619 auto_fixable: true,
620 });
621 }
622
623 let env_vars = [
625 ("SCIPIX_API_KEY", "API authentication"),
626 ("SCIPIX_MODEL_PATH", "Custom model path"),
627 ("SCIPIX_CACHE_DIR", "Cache directory"),
628 ];
629
630 for (var, desc) in &env_vars {
631 let status = if std::env::var(var).is_ok() {
632 CheckStatus::Pass
633 } else {
634 CheckStatus::Info
635 };
636
637 if verbose || status == CheckStatus::Pass {
638 checks.push(DiagnosticCheck {
639 name: format!("Env: {}", var),
640 category: "Config".to_string(),
641 status,
642 message: if status == CheckStatus::Pass {
643 format!("{} configured", desc)
644 } else {
645 format!("{} not set (optional)", desc)
646 },
647 recommendation: None,
648 auto_fixable: false,
649 });
650 }
651 }
652
653 checks
654}
655
656async fn check_network(verbose: bool) -> Vec<DiagnosticCheck> {
657 let mut checks = Vec::new();
658
659 let localhost_available = tokio::net::TcpListener::bind("127.0.0.1:0")
661 .await
662 .is_ok();
663
664 checks.push(DiagnosticCheck {
665 name: "Localhost Binding".to_string(),
666 category: "Network".to_string(),
667 status: if localhost_available { CheckStatus::Pass } else { CheckStatus::Fail },
668 message: if localhost_available {
669 "Can bind to localhost".to_string()
670 } else {
671 "Cannot bind to localhost".to_string()
672 },
673 recommendation: if !localhost_available {
674 Some("Check firewall settings and port availability".to_string())
675 } else {
676 None
677 },
678 auto_fixable: false,
679 });
680
681 let ports_to_check = [(8080, "API server"), (3000, "Alternative API")];
683
684 for (port, desc) in &ports_to_check {
685 let available = tokio::net::TcpListener::bind(format!("127.0.0.1:{}", port))
686 .await
687 .is_ok();
688
689 if verbose || !available {
690 checks.push(DiagnosticCheck {
691 name: format!("Port {}", port),
692 category: "Network".to_string(),
693 status: if available { CheckStatus::Pass } else { CheckStatus::Warning },
694 message: if available {
695 format!("Port {} ({}) available", port, desc)
696 } else {
697 format!("Port {} ({}) in use", port, desc)
698 },
699 recommendation: if !available {
700 Some(format!("Free port {} or use --port to specify alternative", port))
701 } else {
702 None
703 },
704 auto_fixable: false,
705 });
706 }
707 }
708
709 checks
710}
711
712fn generate_optimal_config(system_info: &SystemInfo) -> OptimalConfig {
713 let batch_size = if system_info.available_memory_mb >= 8192 {
715 32
716 } else if system_info.available_memory_mb >= 4096 {
717 16
718 } else if system_info.available_memory_mb >= 2048 {
719 8
720 } else {
721 4
722 };
723
724 let worker_threads = (system_info.cpu_count as f64 * 0.75).ceil() as usize;
726 let worker_threads = worker_threads.max(2);
727
728 let simd_backend = system_info.simd_features.best_available.clone();
730
731 let memory_limit_mb = (system_info.available_memory_mb as f64 * 0.6) as u64;
733
734 let preprocessing_mode = if system_info.simd_features.avx2 || system_info.simd_features.neon {
736 "simd_optimized".to_string()
737 } else if system_info.simd_features.sse4_2 {
738 "simd_basic".to_string()
739 } else {
740 "scalar".to_string()
741 };
742
743 let cache_enabled = system_info.available_memory_mb >= 2048;
745 let cache_size_mb = if cache_enabled {
746 (system_info.available_memory_mb as f64 * 0.1) as u64
747 } else {
748 0
749 };
750
751 OptimalConfig {
752 batch_size,
753 worker_threads,
754 simd_backend,
755 memory_limit_mb,
756 preprocessing_mode,
757 cache_enabled,
758 cache_size_mb,
759 }
760}
761
762fn print_system_info(info: &SystemInfo) {
763 println!("š System Information:");
764 println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
765 println!(" OS: {} ({})", info.os, info.arch);
766 println!(" CPU: {}", info.cpu_brand);
767 println!(" Cores: {}", info.cpu_count);
768 println!(" Memory: {} MB total, {} MB available",
769 info.total_memory_mb, info.available_memory_mb);
770 println!(" Best SIMD: {}", info.simd_features.best_available);
771 println!();
772}
773
774fn print_check_results(checks: &[DiagnosticCheck]) {
775 println!("š Diagnostic Checks:");
776 println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
777
778 let mut current_category = String::new();
779 for check in checks {
780 if check.category != current_category {
781 if !current_category.is_empty() {
782 println!();
783 }
784 println!(" [{}]", check.category);
785 current_category = check.category.clone();
786 }
787
788 let status_color = match check.status {
789 CheckStatus::Pass => "\x1b[32m", CheckStatus::Warning => "\x1b[33m", CheckStatus::Fail => "\x1b[31m", CheckStatus::Info => "\x1b[36m", };
794
795 println!(
796 " {}{}\x1b[0m {} - {}",
797 status_color, check.status, check.name, check.message
798 );
799 }
800 println!();
801}
802
803fn print_optimal_config(config: &OptimalConfig) {
804 println!("\nāļø Optimal Configuration:");
805 println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
806 println!(" batch_size: {}", config.batch_size);
807 println!(" worker_threads: {}", config.worker_threads);
808 println!(" simd_backend: {}", config.simd_backend);
809 println!(" memory_limit: {} MB", config.memory_limit_mb);
810 println!(" preprocessing: {}", config.preprocessing_mode);
811 println!(" cache_enabled: {}", config.cache_enabled);
812 if config.cache_enabled {
813 println!(" cache_size: {} MB", config.cache_size_mb);
814 }
815
816 println!("\n š Example configuration (scipix.toml):");
817 println!(" āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
818 println!(" [processing]");
819 println!(" batch_size = {}", config.batch_size);
820 println!(" worker_threads = {}", config.worker_threads);
821 println!(" simd_backend = \"{}\"", config.simd_backend);
822 println!(" memory_limit_mb = {}", config.memory_limit_mb);
823 println!();
824 println!(" [cache]");
825 println!(" enabled = {}", config.cache_enabled);
826 println!(" size_mb = {}", config.cache_size_mb);
827}
828
829fn print_summary(checks: &[DiagnosticCheck]) {
830 let pass_count = checks.iter().filter(|c| c.status == CheckStatus::Pass).count();
831 let warn_count = checks.iter().filter(|c| c.status == CheckStatus::Warning).count();
832 let fail_count = checks.iter().filter(|c| c.status == CheckStatus::Fail).count();
833
834 println!("\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
835 println!(
836 "š Summary: {} passed, {} warnings, {} failed",
837 pass_count, warn_count, fail_count
838 );
839
840 if fail_count > 0 {
841 println!("\nā ļø Some checks failed. Review recommendations above.");
842 } else if warn_count > 0 {
843 println!("\nā System is functional with some areas for improvement.");
844 } else {
845 println!("\nā
System is optimally configured for SciPix!");
846 }
847}
848
849async fn apply_fixes(checks: &[DiagnosticCheck]) -> Result<()> {
850 println!("\nš§ Applying automatic fixes...");
851 println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
852
853 let fixable: Vec<_> = checks.iter().filter(|c| c.auto_fixable).collect();
854
855 if fixable.is_empty() {
856 println!(" No automatic fixes available.");
857 return Ok(());
858 }
859
860 for check in fixable {
861 println!(" Fixing: {}", check.name);
862
863 if check.name == "Configuration File" {
864 let config_content = r#"# SciPix Configuration
866# Generated by scipix doctor --fix
867
868[processing]
869batch_size = 16
870worker_threads = 4
871simd_backend = "auto"
872memory_limit_mb = 4096
873
874[cache]
875enabled = true
876size_mb = 256
877
878[api]
879host = "127.0.0.1"
880port = 8080
881timeout_seconds = 30
882
883[logging]
884level = "info"
885format = "pretty"
886"#;
887
888 let config_path = PathBuf::from("config");
890 if !config_path.exists() {
891 std::fs::create_dir_all(&config_path)?;
892 }
893
894 let config_file = config_path.join("scipix.toml");
895 std::fs::write(&config_file, config_content)?;
896 println!(" ā Created {}", config_file.display());
897 }
898 }
899
900 Ok(())
901}