1use std::path::Path;
7
8#[derive(Debug, Clone)]
10pub struct SystemDiagnostics {
11 pub os: String,
13 pub arch: String,
15 pub memory_mb: Option<u64>,
17 pub cuda_available: bool,
19 pub coreml_available: bool,
21 pub tesseract_installed: bool,
23}
24
25impl SystemDiagnostics {
26 pub fn collect() -> Self {
28 Self {
29 os: std::env::consts::OS.to_string(),
30 arch: std::env::consts::ARCH.to_string(),
31 memory_mb: Self::get_available_memory(),
32 cuda_available: Self::check_cuda(),
33 coreml_available: Self::check_coreml(),
34 tesseract_installed: Self::check_tesseract(),
35 }
36 }
37
38 fn get_available_memory() -> Option<u64> {
40 #[cfg(target_os = "linux")]
42 {
43 std::fs::read_to_string("/proc/meminfo")
44 .ok()
45 .and_then(|content| {
46 content
47 .lines()
48 .find(|line| line.starts_with("MemAvailable:"))
49 .and_then(|line| {
50 line.split_whitespace()
51 .nth(1)
52 .and_then(|s| s.parse::<u64>().ok())
53 .map(|kb| kb / 1024)
54 })
55 })
56 }
57 #[cfg(not(target_os = "linux"))]
58 {
59 None
60 }
61 }
62
63 fn check_cuda() -> bool {
65 #[cfg(feature = "cuda")]
66 {
67 std::process::Command::new("nvidia-smi")
69 .output()
70 .map(|output| output.status.success())
71 .unwrap_or(false)
72 }
73 #[cfg(not(feature = "cuda"))]
74 {
75 false
76 }
77 }
78
79 fn check_coreml() -> bool {
81 #[cfg(all(target_os = "macos", feature = "coreml"))]
82 {
83 true
84 }
85 #[cfg(not(all(target_os = "macos", feature = "coreml")))]
86 {
87 false
88 }
89 }
90
91 fn check_tesseract() -> bool {
93 #[cfg(feature = "tesseract")]
94 {
95 std::process::Command::new("tesseract")
96 .arg("--version")
97 .output()
98 .map(|output| output.status.success())
99 .unwrap_or(false)
100 }
101 #[cfg(not(feature = "tesseract"))]
102 {
103 false
104 }
105 }
106}
107
108#[derive(Debug, Clone)]
110pub struct ErrorDiagnostic {
111 pub category: ErrorCategory,
113 pub suggestions: Vec<String>,
115 pub docs_links: Vec<String>,
117 pub system_info: Option<SystemDiagnostics>,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub enum ErrorCategory {
124 ModelLoad,
126 ModelNotLoaded,
128 ImageFormat,
130 OnnxRuntime,
132 Tesseract,
134 Configuration,
136 Resources,
138 Hardware,
140 Other,
142}
143
144impl ErrorDiagnostic {
145 pub fn model_load(path: &str) -> Self {
147 let mut suggestions = vec![
148 "Verify the model file exists at the specified path".to_string(),
149 "Check file permissions (read access required)".to_string(),
150 "Ensure the model file is in ONNX format".to_string(),
151 format!("Path provided: {}", path),
152 ];
153
154 if !Path::new(path).exists() {
156 suggestions.push("ERROR: Path does not exist".to_string());
157 suggestions.push("Download models from provider documentation".to_string());
158 } else if !Path::new(path).is_file() {
159 suggestions.push("ERROR: Path is a directory, not a file".to_string());
160 suggestions.push("Point to the .onnx model file directly".to_string());
161 }
162
163 Self {
164 category: ErrorCategory::ModelLoad,
165 suggestions,
166 docs_links: vec![
167 "https://github.com/cool-japan/oxify/blob/main/crates/oxify-connect-vision/README.md#provider-setup".to_string(),
168 ],
169 system_info: Some(SystemDiagnostics::collect()),
170 }
171 }
172
173 pub fn model_not_loaded() -> Self {
175 Self {
176 category: ErrorCategory::ModelNotLoaded,
177 suggestions: vec![
178 "Call provider.load_model().await before processing images".to_string(),
179 "Ensure load_model() completed successfully (check for errors)".to_string(),
180 "Example: let provider = create_provider(&config)?; provider.load_model().await?;".to_string(),
181 ],
182 docs_links: vec![
183 "https://github.com/cool-japan/oxify/blob/main/crates/oxify-connect-vision/README.md#quick-start".to_string(),
184 ],
185 system_info: None,
186 }
187 }
188
189 pub fn image_format(error_msg: &str) -> Self {
191 let mut suggestions = vec![
192 "Supported formats: PNG, JPEG, WebP, TIFF, BMP".to_string(),
193 "Check image file is not corrupted".to_string(),
194 "Verify image dimensions are reasonable (1-10000 pixels)".to_string(),
195 ];
196
197 if error_msg.contains("decode") {
198 suggestions
199 .push("Try opening the image in an image viewer to verify it's valid".to_string());
200 suggestions
201 .push("Consider converting to PNG format for better compatibility".to_string());
202 }
203
204 Self {
205 category: ErrorCategory::ImageFormat,
206 suggestions,
207 docs_links: vec![
208 "https://github.com/cool-japan/oxify/blob/main/crates/oxify-connect-vision/README.md#troubleshooting".to_string(),
209 ],
210 system_info: None,
211 }
212 }
213
214 pub fn onnx_runtime(error_msg: &str) -> Self {
216 let system_info = SystemDiagnostics::collect();
217 let mut suggestions = vec![];
218
219 if error_msg.contains("CUDA") || error_msg.contains("cuda") {
221 suggestions.push("CUDA error detected".to_string());
222 if system_info.cuda_available {
223 suggestions.push("CUDA runtime detected but model execution failed".to_string());
224 suggestions.push("Check NVIDIA driver version: nvidia-smi".to_string());
225 suggestions.push("Verify GPU has sufficient memory".to_string());
226 } else {
227 suggestions.push("CUDA not available on this system".to_string());
228 suggestions.push("Install NVIDIA drivers and CUDA toolkit".to_string());
229 suggestions.push("Or disable GPU: set use_gpu=false in config".to_string());
230 }
231 }
232
233 if error_msg.contains("CoreML") || error_msg.contains("coreml") {
235 suggestions.push("CoreML error detected".to_string());
236 if !system_info.coreml_available {
237 suggestions.push("CoreML is only available on macOS".to_string());
238 suggestions.push("Disable GPU or use CUDA on other platforms".to_string());
239 } else {
240 suggestions.push("CoreML detected but execution failed".to_string());
241 suggestions.push("Check macOS version (CoreML requires 10.13+)".to_string());
242 }
243 }
244
245 if error_msg.contains("memory") || error_msg.contains("Memory") {
247 suggestions.push("Memory allocation failed".to_string());
248 if let Some(mem_mb) = system_info.memory_mb {
249 suggestions.push(format!("Available memory: {} MB", mem_mb));
250 if mem_mb < 2048 {
251 suggestions.push("WARNING: Low memory detected (< 2GB available)".to_string());
252 suggestions.push("Close other applications to free memory".to_string());
253 suggestions.push("Consider using mock provider for testing".to_string());
254 }
255 }
256 suggestions.push("Try processing smaller images".to_string());
257 suggestions.push("Reduce cache size if enabled".to_string());
258 }
259
260 if error_msg.contains("opset") || error_msg.contains("Opset") {
262 suggestions.push("ONNX opset version mismatch".to_string());
263 suggestions.push("Update ONNX Runtime: cargo update ort".to_string());
264 suggestions.push("Or download compatible model version".to_string());
265 }
266
267 if suggestions.is_empty() {
269 suggestions.push("ONNX Runtime execution failed".to_string());
270 suggestions.push("Check model file integrity".to_string());
271 suggestions.push("Try re-downloading the model files".to_string());
272 suggestions.push(format!("System: {} {}", system_info.os, system_info.arch));
273 }
274
275 Self {
276 category: ErrorCategory::OnnxRuntime,
277 suggestions,
278 docs_links: vec![
279 "https://github.com/cool-japan/oxify/blob/main/crates/oxify-connect-vision/README.md#onnx-runtime-errors".to_string(),
280 ],
281 system_info: Some(system_info),
282 }
283 }
284
285 pub fn tesseract(error_msg: &str) -> Self {
287 let system_info = SystemDiagnostics::collect();
288 let mut suggestions = vec![];
289
290 if !system_info.tesseract_installed {
291 suggestions.push("Tesseract OCR not detected on system".to_string());
292 suggestions.push("Install Tesseract:".to_string());
293
294 match system_info.os.as_str() {
295 "linux" => {
296 suggestions.push(" Ubuntu/Debian: sudo apt install tesseract-ocr".to_string());
297 suggestions.push(" Fedora: sudo dnf install tesseract".to_string());
298 }
299 "macos" => {
300 suggestions.push(" macOS: brew install tesseract".to_string());
301 }
302 "windows" => {
303 suggestions.push(
304 " Windows: Download from https://github.com/UB-Mannheim/tesseract/wiki"
305 .to_string(),
306 );
307 }
308 _ => {
309 suggestions
310 .push(" See: https://github.com/tesseract-ocr/tesseract/wiki".to_string());
311 }
312 }
313 } else if error_msg.contains("language") || error_msg.contains("lang") {
314 suggestions.push("Language data file missing".to_string());
315 suggestions.push("Install language packs:".to_string());
316 suggestions.push(" Ubuntu/Debian: sudo apt install tesseract-ocr-<lang>".to_string());
317 suggestions.push(" Example: sudo apt install tesseract-ocr-jpn".to_string());
318 suggestions.push("Check installed languages: tesseract --list-langs".to_string());
319 } else {
320 suggestions.push("Tesseract execution failed".to_string());
321 suggestions.push("Verify Tesseract is working: tesseract --version".to_string());
322 suggestions.push("Check image quality (DPI, contrast, noise)".to_string());
323 }
324
325 Self {
326 category: ErrorCategory::Tesseract,
327 suggestions,
328 docs_links: vec![
329 "https://github.com/cool-japan/oxify/blob/main/crates/oxify-connect-vision/README.md#tesseract-installation".to_string(),
330 ],
331 system_info: Some(system_info),
332 }
333 }
334
335 pub fn configuration(config_issue: &str) -> Self {
337 let mut suggestions = vec![
338 format!("Configuration issue: {}", config_issue),
339 "Review provider configuration parameters".to_string(),
340 ];
341
342 if config_issue.contains("model_path") {
343 suggestions
344 .push("model_path is required for ONNX providers (Surya, PaddleOCR)".to_string());
345 suggestions.push(
346 "Example: VisionProviderConfig::surya(\"/path/to/models\", false)".to_string(),
347 );
348 }
349
350 if config_issue.contains("language") {
351 suggestions.push("Check language code format".to_string());
352 suggestions.push("Examples: 'en', 'ja', 'zh', 'ko', 'de', 'fr'".to_string());
353 }
354
355 Self {
356 category: ErrorCategory::Configuration,
357 suggestions,
358 docs_links: vec![
359 "https://github.com/cool-japan/oxify/blob/main/crates/oxify-connect-vision/README.md#quick-start".to_string(),
360 ],
361 system_info: None,
362 }
363 }
364
365 pub fn resource_exhaustion(resource: &str) -> Self {
367 let system_info = SystemDiagnostics::collect();
368 let mut suggestions = vec![format!("Resource exhausted: {}", resource)];
369
370 if resource.contains("memory") {
371 if let Some(mem_mb) = system_info.memory_mb {
372 suggestions.push(format!("Available memory: {} MB", mem_mb));
373 }
374 suggestions.push("Close unnecessary applications".to_string());
375 suggestions.push("Process images in smaller batches".to_string());
376 suggestions.push("Reduce cache size".to_string());
377 suggestions.push("Consider using CPU instead of GPU".to_string());
378 }
379
380 if resource.contains("cache") {
381 suggestions.push("Clear cache: cache.clear()".to_string());
382 suggestions.push("Reduce cache size: cache.set_max_entries(100)".to_string());
383 }
384
385 Self {
386 category: ErrorCategory::Resources,
387 suggestions,
388 docs_links: vec![
389 "https://github.com/cool-japan/oxify/blob/main/crates/oxify-connect-vision/README.md#memory-issues".to_string(),
390 ],
391 system_info: Some(system_info),
392 }
393 }
394
395 pub fn format(&self) -> String {
397 let mut output = String::new();
398
399 output.push_str("\n╔══════════════════════════════════════════════════════════════╗\n");
400 output.push_str("║ DIAGNOSTIC INFORMATION ║\n");
401 output.push_str("╚══════════════════════════════════════════════════════════════╝\n\n");
402
403 if !self.suggestions.is_empty() {
405 output.push_str("💡 Suggestions:\n");
406 for (i, suggestion) in self.suggestions.iter().enumerate() {
407 output.push_str(&format!(" {}. {}\n", i + 1, suggestion));
408 }
409 output.push('\n');
410 }
411
412 if !self.docs_links.is_empty() {
414 output.push_str("📚 Documentation:\n");
415 for link in &self.docs_links {
416 output.push_str(&format!(" {}\n", link));
417 }
418 output.push('\n');
419 }
420
421 if let Some(ref sys_info) = self.system_info {
423 output.push_str("🖥️ System Information:\n");
424 output.push_str(&format!(" OS: {} ({})\n", sys_info.os, sys_info.arch));
425 if let Some(mem_mb) = sys_info.memory_mb {
426 output.push_str(&format!(" Available Memory: {} MB\n", mem_mb));
427 }
428 output.push_str(&format!(" CUDA Available: {}\n", sys_info.cuda_available));
429 output.push_str(&format!(
430 " CoreML Available: {}\n",
431 sys_info.coreml_available
432 ));
433 output.push_str(&format!(
434 " Tesseract Installed: {}\n",
435 sys_info.tesseract_installed
436 ));
437 }
438
439 output
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446
447 #[test]
448 fn test_system_diagnostics_collect() {
449 let diag = SystemDiagnostics::collect();
450 assert!(!diag.os.is_empty());
451 assert!(!diag.arch.is_empty());
452 }
453
454 #[test]
455 fn test_error_diagnostic_model_load() {
456 let diag = ErrorDiagnostic::model_load("/nonexistent/path/model.onnx");
457 assert_eq!(diag.category, ErrorCategory::ModelLoad);
458 assert!(!diag.suggestions.is_empty());
459 assert!(!diag.docs_links.is_empty());
460 }
461
462 #[test]
463 fn test_error_diagnostic_model_not_loaded() {
464 let diag = ErrorDiagnostic::model_not_loaded();
465 assert_eq!(diag.category, ErrorCategory::ModelNotLoaded);
466 assert!(!diag.suggestions.is_empty());
467 }
468
469 #[test]
470 fn test_error_diagnostic_image_format() {
471 let diag = ErrorDiagnostic::image_format("decode error");
472 assert_eq!(diag.category, ErrorCategory::ImageFormat);
473 assert!(!diag.suggestions.is_empty());
474 }
475
476 #[test]
477 fn test_error_diagnostic_onnx_runtime() {
478 let diag = ErrorDiagnostic::onnx_runtime("CUDA memory allocation failed");
479 assert_eq!(diag.category, ErrorCategory::OnnxRuntime);
480 assert!(!diag.suggestions.is_empty());
481 assert!(diag.system_info.is_some());
482 }
483
484 #[test]
485 fn test_error_diagnostic_tesseract() {
486 let diag = ErrorDiagnostic::tesseract("language not found");
487 assert_eq!(diag.category, ErrorCategory::Tesseract);
488 assert!(!diag.suggestions.is_empty());
489 }
490
491 #[test]
492 fn test_error_diagnostic_format() {
493 let diag = ErrorDiagnostic::model_not_loaded();
494 let formatted = diag.format();
495 assert!(formatted.contains("Suggestions"));
496 assert!(formatted.contains("Documentation"));
497 }
498
499 #[test]
500 fn test_error_categories() {
501 let categories = vec![
502 ErrorCategory::ModelLoad,
503 ErrorCategory::ModelNotLoaded,
504 ErrorCategory::ImageFormat,
505 ErrorCategory::OnnxRuntime,
506 ErrorCategory::Tesseract,
507 ErrorCategory::Configuration,
508 ErrorCategory::Resources,
509 ErrorCategory::Hardware,
510 ErrorCategory::Other,
511 ];
512
513 for category in categories {
514 let _ = format!("{:?}", category);
516 }
517 }
518}