torsh_utils/collect_env.rs
1//! # Environment Information Collection
2//!
3//! This module provides comprehensive system and environment information collection
4//! for debugging, issue reproduction, and system requirements verification.
5//!
6//! ## Features
7//!
8//! - **System Information**: OS, architecture, CPU details
9//! - **Hardware Detection**: CPU features (AVX, SSE, etc.), GPU information
10//! - **Memory Information**: Total and available RAM
11//! - **Software Environment**: Rust version, ToRSh version, dependencies
12//! - **GPU Detection**: NVIDIA CUDA, Apple Metal support
13//! - **Python Integration**: Python version and installed packages (if available)
14//! - **Environment Variables**: Relevant environment variables for debugging
15//!
16//! ## Quick Start
17//!
18//! ### Basic Usage
19//!
20//! ```rust,no_run
21//! use torsh_utils::collect_env::{collect_env, print_env_info};
22//!
23//! # fn example() -> Result<(), torsh_core::TorshError> {
24//! // Collect environment information
25//! let env_info = collect_env()?;
26//!
27//! // Print formatted report
28//! print_env_info(&env_info);
29//!
30//! // Access specific information
31//! println!("ToRSh version: {}", env_info.torsh_version);
32//! println!("Rust version: {}", env_info.rust_version);
33//! println!("OS: {}", env_info.os);
34//! # Ok(())
35//! # }
36//! ```
37//!
38//! ### Hardware Information
39//!
40//! ```rust,no_run
41//! use torsh_utils::collect_env::collect_env;
42//!
43//! # fn example() -> Result<(), torsh_core::TorshError> {
44//! let env_info = collect_env()?;
45//!
46//! // CPU information
47//! println!("CPU: {}", env_info.cpu_info.brand);
48//! println!("CPU cores: {} physical, {} logical",
49//! env_info.cpu_info.cores,
50//! env_info.cpu_info.threads
51//! );
52//! println!("CPU features: {}", env_info.cpu_info.features.join(", "));
53//!
54//! // Memory information
55//! println!("Total RAM: {} MB", env_info.memory_info.total_mb);
56//! println!("Available RAM: {} MB", env_info.memory_info.available_mb);
57//!
58//! // GPU information
59//! if !env_info.gpu_info.is_empty() {
60//! for (i, gpu) in env_info.gpu_info.iter().enumerate() {
61//! println!("GPU {}: {} ({} MB)",
62//! i,
63//! gpu.name,
64//! gpu.memory_mb
65//! );
66//! if let Some(cuda) = &gpu.cuda_version {
67//! println!(" CUDA version: {}", cuda);
68//! }
69//! }
70//! } else {
71//! println!("No GPUs detected");
72//! }
73//! # Ok(())
74//! # }
75//! ```
76//!
77//! ### Environment Variables
78//!
79//! ```rust,no_run
80//! use torsh_utils::collect_env::collect_env;
81//!
82//! # fn example() -> Result<(), torsh_core::TorshError> {
83//! let env_info = collect_env()?;
84//!
85//! // Check CUDA configuration
86//! if let Some(cuda_home) = env_info.env_vars.get("CUDA_HOME") {
87//! println!("CUDA_HOME: {}", cuda_home);
88//! }
89//!
90//! if let Some(cuda_path) = env_info.env_vars.get("CUDA_PATH") {
91//! println!("CUDA_PATH: {}", cuda_path);
92//! }
93//!
94//! // Check library paths
95//! if let Some(ld_path) = env_info.env_vars.get("LD_LIBRARY_PATH") {
96//! println!("LD_LIBRARY_PATH: {}", ld_path);
97//! }
98//! # Ok(())
99//! # }
100//! ```
101//!
102//! ### Export to JSON
103//!
104//! ```rust,no_run
105//! use torsh_utils::collect_env::collect_env;
106//! use std::fs;
107//!
108//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
109//! let env_info = collect_env()?;
110//!
111//! // Serialize to JSON
112//! let json = serde_json::to_string_pretty(&env_info)?;
113//!
114//! // Save to file
115//! fs::write("environment_info.json", json)?;
116//!
117//! println!("Environment info saved to environment_info.json");
118//! # Ok(())
119//! # }
120//! ```
121//!
122//! ### Python Environment Detection
123//!
124//! ```rust,no_run
125//! use torsh_utils::collect_env::collect_env;
126//!
127//! # fn example() -> Result<(), torsh_core::TorshError> {
128//! let env_info = collect_env()?;
129//!
130//! if let Some(python) = &env_info.python_info {
131//! println!("Python version: {}", python.version);
132//! println!("Python executable: {}", python.executable);
133//!
134//! // Check for specific packages
135//! if let Some(torch_version) = python.packages.get("torch") {
136//! println!("PyTorch installed: {}", torch_version);
137//! }
138//!
139//! if let Some(numpy_version) = python.packages.get("numpy") {
140//! println!("NumPy installed: {}", numpy_version);
141//! }
142//! } else {
143//! println!("Python not detected");
144//! }
145//! # Ok(())
146//! # }
147//! ```
148//!
149//! ## Use Cases
150//!
151//! ### 1. Bug Reports
152//!
153//! Include environment information in bug reports for easier reproduction:
154//!
155//! ```rust,no_run
156//! use torsh_utils::collect_env::{collect_env, print_env_info};
157//!
158//! # fn example() -> Result<(), torsh_core::TorshError> {
159//! println!("=== Bug Report ===");
160//! println!("Issue: Model crashes during training");
161//! println!();
162//! println!("=== Environment ===");
163//!
164//! let env_info = collect_env()?;
165//! print_env_info(&env_info);
166//! # Ok(())
167//! # }
168//! ```
169//!
170//! ### 2. System Requirements Check
171//!
172//! Verify system meets minimum requirements:
173//!
174//! ```rust,no_run
175//! use torsh_utils::collect_env::collect_env;
176//!
177//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
178//! let env_info = collect_env()?;
179//!
180//! // Check minimum requirements
181//! let min_ram_mb = 4096; // 4GB
182//! let min_cpu_cores = 2;
183//!
184//! if env_info.memory_info.total_mb < min_ram_mb {
185//! eprintln!("⚠️ WARNING: Insufficient RAM. Required: {} MB, Available: {} MB",
186//! min_ram_mb, env_info.memory_info.total_mb);
187//! }
188//!
189//! if env_info.cpu_info.cores < min_cpu_cores {
190//! eprintln!("⚠️ WARNING: Insufficient CPU cores. Required: {}, Available: {}",
191//! min_cpu_cores, env_info.cpu_info.cores);
192//! }
193//!
194//! // Check for required CPU features
195//! let required_features = vec!["SSE2", "AVX"];
196//! for feature in &required_features {
197//! if !env_info.cpu_info.features.contains(&feature.to_string()) {
198//! eprintln!("⚠️ WARNING: Missing CPU feature: {}", feature);
199//! }
200//! }
201//!
202//! // Check for CUDA if GPU training is needed
203//! let needs_cuda = true;
204//! if needs_cuda && env_info.gpu_info.is_empty() {
205//! eprintln!("⚠️ WARNING: No CUDA GPUs detected");
206//! }
207//! # Ok(())
208//! # }
209//! ```
210//!
211//! ### 3. CI/CD Integration
212//!
213//! Log environment information in CI pipelines:
214//!
215//! ```rust,no_run
216//! use torsh_utils::collect_env::{collect_env, print_env_info};
217//!
218//! # fn example() -> Result<(), torsh_core::TorshError> {
219//! // In CI script
220//! println!("=== CI Environment Information ===");
221//! let env_info = collect_env()?;
222//! print_env_info(&env_info);
223//!
224//! // Save for artifacts
225//! let json = serde_json::to_string_pretty(&env_info).unwrap();
226//! std::fs::write("ci_environment.json", json).unwrap();
227//! # Ok(())
228//! # }
229//! ```
230//!
231//! ## Collected Information
232//!
233//! ### System Information
234//! - Operating system name and version
235//! - CPU architecture (x86_64, ARM, etc.)
236//! - OS family (Unix, Windows, etc.)
237//!
238//! ### CPU Information
239//! - Brand/model name
240//! - Number of physical cores
241//! - Number of logical threads (with hyperthreading)
242//! - Supported instruction sets (SSE, AVX, AVX2, AVX512)
243//!
244//! ### GPU Information
245//! - NVIDIA GPUs via nvidia-smi:
246//! - GPU name/model
247//! - Total memory
248//! - Driver version
249//! - CUDA version (if available)
250//! - Apple Metal GPUs (on macOS)
251//!
252//! ### Memory Information
253//! - Total system RAM
254//! - Available RAM
255//!
256//! ### Software Versions
257//! - ToRSh version
258//! - Rust compiler version
259//! - Installed Rust packages
260//!
261//! ### Python Environment (if available)
262//! - Python version
263//! - Python executable path
264//! - Installed Python packages (via pip)
265//!
266//! ### Environment Variables
267//! Collects relevant variables including:
268//! - `CUDA_HOME`, `CUDA_PATH`
269//! - `CUDNN_PATH`
270//! - `LD_LIBRARY_PATH`, `DYLD_LIBRARY_PATH`
271//! - `PATH`
272//! - `RUST_BACKTRACE`, `RUST_LOG`
273//! - `OMP_NUM_THREADS`, `MKL_NUM_THREADS`
274//!
275//! ## Privacy Considerations
276//!
277//! The collected information includes:
278//! - ✅ Hardware specifications (safe to share)
279//! - ✅ Software versions (safe to share)
280//! - ⚠️ Environment variables (may contain paths)
281//! - ⚠️ Installed packages (usually safe)
282//!
283//! **Recommendation**: Review the output before sharing publicly, especially
284//! environment variables that might contain sensitive paths or credentials.
285//!
286//! ## Best Practices
287//!
288//! 1. **Always Include in Bug Reports**: Helps maintainers reproduce issues
289//! 2. **Version Compatibility**: Check before deploying to new environments
290//! 3. **Automated Checks**: Integrate into CI/CD for consistent environments
291//! 4. **Documentation**: Include in deployment documentation
292//! 5. **Review Before Sharing**: Remove sensitive information if present
293//!
294//! ## Platform Support
295//!
296//! | Platform | System Info | CPU Info | GPU Detection | Python Detection |
297//! |----------|-------------|----------|---------------|------------------|
298//! | Linux | ✅ Full | ✅ Full | ✅ NVIDIA | ✅ Full |
299//! | macOS | ✅ Full | ✅ Full | ✅ Metal | ✅ Full |
300//! | Windows | ✅ Full | ✅ Full | ✅ NVIDIA | ✅ Full |
301//!
302//! ## Comparison with PyTorch
303//!
304//! Similar to `torch.utils.collect_env.get_pretty_env_info()`, but provides:
305//! - More detailed CPU feature detection
306//! - Rust ecosystem information
307//! - Better structured output (JSON serializable)
308//! - Lower overhead (no Python runtime required)
309//!
310//! ## See Also
311//!
312//! - [`benchmark`](crate::benchmark): For performance benchmarking
313//! - [`bottleneck`](crate::bottleneck): For performance profiling
314//! - [Tutorial Guide](https://docs.torsh.rs/tutorial#environment)
315//! - [Troubleshooting Guide](https://docs.torsh.rs/troubleshooting)
316
317// Framework infrastructure - components designed for future use
318#![allow(dead_code)]
319use serde::{Deserialize, Serialize};
320use std::collections::HashMap;
321use std::env;
322use torsh_core::error::Result;
323
324/// Environment information
325#[derive(Debug, Clone, Serialize, Deserialize)]
326pub struct EnvironmentInfo {
327 pub torsh_version: String,
328 pub rust_version: String,
329 pub os: String,
330 pub cpu_info: CpuInfo,
331 pub gpu_info: Vec<GpuInfo>,
332 pub memory_info: MemoryInfo,
333 pub python_info: Option<PythonInfo>,
334 pub env_vars: HashMap<String, String>,
335 pub installed_packages: HashMap<String, String>,
336}
337
338/// CPU information
339#[derive(Debug, Clone, Serialize, Deserialize)]
340pub struct CpuInfo {
341 pub brand: String,
342 pub cores: usize,
343 pub threads: usize,
344 pub features: Vec<String>,
345}
346
347/// GPU information
348#[derive(Debug, Clone, Serialize, Deserialize)]
349pub struct GpuInfo {
350 pub name: String,
351 pub memory_mb: usize,
352 pub driver_version: String,
353 pub cuda_version: Option<String>,
354}
355
356/// Memory information
357#[derive(Debug, Clone, Serialize, Deserialize)]
358pub struct MemoryInfo {
359 pub total_mb: usize,
360 pub available_mb: usize,
361}
362
363/// Python environment information
364#[derive(Debug, Clone, Serialize, Deserialize)]
365pub struct PythonInfo {
366 pub version: String,
367 pub executable: String,
368 pub packages: HashMap<String, String>,
369}
370
371/// Collect environment information
372pub fn collect_env() -> Result<EnvironmentInfo> {
373 let torsh_version = env!("CARGO_PKG_VERSION").to_string();
374 let rust_version = get_rust_version();
375 let os = get_os_info();
376 let cpu_info = get_cpu_info();
377 let gpu_info = get_gpu_info();
378 let memory_info = get_memory_info();
379 let python_info = get_python_info();
380 let env_vars = get_relevant_env_vars();
381 let installed_packages = get_installed_packages();
382
383 Ok(EnvironmentInfo {
384 torsh_version,
385 rust_version,
386 os,
387 cpu_info,
388 gpu_info,
389 memory_info,
390 python_info,
391 env_vars,
392 installed_packages,
393 })
394}
395
396/// Get Rust version
397fn get_rust_version() -> String {
398 env::var("RUSTC_VERSION")
399 .or_else(|_| {
400 std::process::Command::new("rustc")
401 .arg("--version")
402 .output()
403 .map(|output| String::from_utf8_lossy(&output.stdout).to_string())
404 .map_err(|_| std::env::VarError::NotPresent)
405 })
406 .unwrap_or_else(|_| "unknown".to_string())
407}
408
409/// Get OS information
410fn get_os_info() -> String {
411 format!(
412 "{} {} ({})",
413 env::consts::OS,
414 env::consts::ARCH,
415 env::consts::FAMILY
416 )
417}
418
419/// Get CPU information
420fn get_cpu_info() -> CpuInfo {
421 #[cfg(feature = "collect_env")]
422 {
423 use sysinfo::System;
424
425 let mut sys = System::new();
426 sys.refresh_cpu_all();
427
428 let cpus = sys.cpus();
429 let brand = cpus
430 .first()
431 .map(|cpu| cpu.brand().to_string())
432 .unwrap_or_else(|| "Unknown".to_string());
433
434 let cores = num_cpus::get_physical();
435 let threads = num_cpus::get();
436
437 let features = get_cpu_features();
438
439 CpuInfo {
440 brand,
441 cores,
442 threads,
443 features,
444 }
445 }
446
447 #[cfg(not(feature = "collect_env"))]
448 {
449 CpuInfo {
450 brand: "Unknown".to_string(),
451 cores: num_cpus::get_physical(),
452 threads: num_cpus::get(),
453 features: vec![],
454 }
455 }
456}
457
458/// Get CPU features
459fn get_cpu_features() -> Vec<String> {
460 #[cfg_attr(not(target_arch = "x86_64"), allow(unused_mut))]
461 let mut features = vec![];
462
463 #[cfg(target_arch = "x86_64")]
464 {
465 if is_x86_feature_detected!("avx") {
466 features.push("AVX".to_string());
467 }
468 if is_x86_feature_detected!("avx2") {
469 features.push("AVX2".to_string());
470 }
471 if is_x86_feature_detected!("avx512f") {
472 features.push("AVX512F".to_string());
473 }
474 if is_x86_feature_detected!("sse") {
475 features.push("SSE".to_string());
476 }
477 if is_x86_feature_detected!("sse2") {
478 features.push("SSE2".to_string());
479 }
480 if is_x86_feature_detected!("sse3") {
481 features.push("SSE3".to_string());
482 }
483 if is_x86_feature_detected!("sse4.1") {
484 features.push("SSE4.1".to_string());
485 }
486 if is_x86_feature_detected!("sse4.2") {
487 features.push("SSE4.2".to_string());
488 }
489 }
490
491 features
492}
493
494/// Get GPU information
495fn get_gpu_info() -> Vec<GpuInfo> {
496 let mut gpus = vec![];
497
498 // Check for NVIDIA GPUs
499 if let Ok(output) = std::process::Command::new("nvidia-smi")
500 .args([
501 "--query-gpu=name,memory.total,driver_version",
502 "--format=csv,noheader,nounits",
503 ])
504 .output()
505 {
506 if output.status.success() {
507 let output_str = String::from_utf8_lossy(&output.stdout);
508 for line in output_str.lines() {
509 let parts: Vec<&str> = line.split(',').map(|s| s.trim()).collect();
510 if parts.len() >= 3 {
511 gpus.push(GpuInfo {
512 name: parts[0].to_string(),
513 memory_mb: parts[1].parse().unwrap_or(0),
514 driver_version: parts[2].to_string(),
515 cuda_version: get_cuda_version(),
516 });
517 }
518 }
519 }
520 }
521
522 // Check for Metal (macOS)
523 #[cfg(target_os = "macos")]
524 {
525 if let Ok(_output) = std::process::Command::new("system_profiler")
526 .args(&["SPDisplaysDataType", "-json"])
527 .output()
528 {
529 // Parse JSON output for GPU info
530 // This is simplified - real implementation would parse properly
531 gpus.push(GpuInfo {
532 name: "Apple Metal GPU".to_string(),
533 memory_mb: 0,
534 driver_version: "Metal".to_string(),
535 cuda_version: None,
536 });
537 }
538 }
539
540 gpus
541}
542
543/// Get CUDA version
544fn get_cuda_version() -> Option<String> {
545 if let Ok(output) = std::process::Command::new("nvcc")
546 .args(["--version"])
547 .output()
548 {
549 if output.status.success() {
550 let output_str = String::from_utf8_lossy(&output.stdout);
551 for line in output_str.lines() {
552 if line.contains("release") {
553 return line
554 .split_whitespace()
555 .find(|s| s.starts_with("V"))
556 .map(|s| s.trim_start_matches('V').to_string());
557 }
558 }
559 }
560 }
561 None
562}
563
564/// Get memory information
565fn get_memory_info() -> MemoryInfo {
566 #[cfg(feature = "collect_env")]
567 {
568 use sysinfo::System;
569
570 let mut sys = System::new();
571 sys.refresh_memory();
572
573 MemoryInfo {
574 total_mb: (sys.total_memory() / 1024 / 1024) as usize,
575 available_mb: (sys.available_memory() / 1024 / 1024) as usize,
576 }
577 }
578
579 #[cfg(not(feature = "collect_env"))]
580 {
581 MemoryInfo {
582 total_mb: 0,
583 available_mb: 0,
584 }
585 }
586}
587
588/// Get Python information
589fn get_python_info() -> Option<PythonInfo> {
590 if let Ok(output) = std::process::Command::new("python3")
591 .args(["--version"])
592 .output()
593 {
594 if output.status.success() {
595 let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
596
597 let executable = which::which("python3").ok()?.to_string_lossy().to_string();
598
599 // Get installed packages
600 let mut packages = HashMap::new();
601 if let Ok(pip_output) = std::process::Command::new("python3")
602 .args(["-m", "pip", "list", "--format=json"])
603 .output()
604 {
605 if pip_output.status.success() {
606 if let Ok(json) = serde_json::from_slice::<Vec<PipPackage>>(&pip_output.stdout)
607 {
608 for pkg in json {
609 packages.insert(pkg.name, pkg.version);
610 }
611 }
612 }
613 }
614
615 return Some(PythonInfo {
616 version,
617 executable,
618 packages,
619 });
620 }
621 }
622 None
623}
624
625#[derive(Deserialize)]
626struct PipPackage {
627 name: String,
628 version: String,
629}
630
631/// Get relevant environment variables
632fn get_relevant_env_vars() -> HashMap<String, String> {
633 let mut vars = HashMap::new();
634
635 let relevant_vars = vec![
636 "CUDA_HOME",
637 "CUDA_PATH",
638 "CUDNN_PATH",
639 "LD_LIBRARY_PATH",
640 "DYLD_LIBRARY_PATH",
641 "PATH",
642 "RUST_BACKTRACE",
643 "RUST_LOG",
644 "OMP_NUM_THREADS",
645 "MKL_NUM_THREADS",
646 "TORCH_NUM_THREADS",
647 ];
648
649 for var in relevant_vars {
650 if let Ok(value) = env::var(var) {
651 vars.insert(var.to_string(), value);
652 }
653 }
654
655 vars
656}
657
658/// Get installed Rust packages
659fn get_installed_packages() -> HashMap<String, String> {
660 let mut packages = HashMap::new();
661
662 // Add core ToRSh packages
663 packages.insert("torsh".to_string(), env!("CARGO_PKG_VERSION").to_string());
664
665 // Get cargo dependencies
666 if let Ok(output) = std::process::Command::new("cargo")
667 .args(["tree", "--depth", "1", "--format", "{p}"])
668 .current_dir(env!("CARGO_MANIFEST_DIR"))
669 .output()
670 {
671 if output.status.success() {
672 let output_str = String::from_utf8_lossy(&output.stdout);
673 for line in output_str.lines() {
674 if let Some((name, version)) = parse_cargo_tree_line(line) {
675 packages.insert(name, version);
676 }
677 }
678 }
679 }
680
681 packages
682}
683
684/// Parse cargo tree output line
685fn parse_cargo_tree_line(line: &str) -> Option<(String, String)> {
686 let parts: Vec<&str> = line.split_whitespace().collect();
687 if parts.len() >= 2 {
688 let name = parts[0].to_string();
689 let version = parts[1].trim_start_matches('v').to_string();
690 Some((name, version))
691 } else {
692 None
693 }
694}
695
696/// Pretty print environment information
697pub fn print_env_info(info: &EnvironmentInfo) {
698 println!("=== ToRSh Environment Information ===");
699 println!();
700 println!("ToRSh Version: {}", info.torsh_version);
701 println!("Rust Version: {}", info.rust_version);
702 println!("OS: {}", info.os);
703 println!();
704
705 println!("CPU Information:");
706 println!(" Brand: {}", info.cpu_info.brand);
707 println!(
708 " Cores: {} physical, {} logical",
709 info.cpu_info.cores, info.cpu_info.threads
710 );
711 println!(" Features: {}", info.cpu_info.features.join(", "));
712 println!();
713
714 if !info.gpu_info.is_empty() {
715 println!("GPU Information:");
716 for (i, gpu) in info.gpu_info.iter().enumerate() {
717 println!(" GPU {}: {}", i, gpu.name);
718 println!(" Memory: {} MB", gpu.memory_mb);
719 println!(" Driver: {}", gpu.driver_version);
720 if let Some(cuda) = &gpu.cuda_version {
721 println!(" CUDA: {}", cuda);
722 }
723 }
724 println!();
725 }
726
727 println!("Memory:");
728 println!(" Total: {} MB", info.memory_info.total_mb);
729 println!(" Available: {} MB", info.memory_info.available_mb);
730 println!();
731
732 if let Some(python) = &info.python_info {
733 println!("Python:");
734 println!(" Version: {}", python.version);
735 println!(" Executable: {}", python.executable);
736 if !python.packages.is_empty() {
737 println!(" Key packages:");
738 for (name, version) in &python.packages {
739 if name.contains("torch") || name.contains("numpy") || name.contains("scipy") {
740 println!(" {}: {}", name, version);
741 }
742 }
743 }
744 println!();
745 }
746
747 if !info.env_vars.is_empty() {
748 println!("Environment Variables:");
749 for (key, value) in &info.env_vars {
750 if key.contains("CUDA") || key.contains("PATH") {
751 println!(" {}: {}", key, value);
752 }
753 }
754 }
755}
756
757#[cfg(test)]
758mod tests {
759 use super::*;
760
761 #[test]
762 fn test_collect_env() {
763 let info = collect_env().unwrap();
764
765 assert!(!info.torsh_version.is_empty());
766 assert!(!info.rust_version.is_empty());
767 assert!(!info.os.is_empty());
768 assert!(info.cpu_info.cores > 0);
769 assert!(info.cpu_info.threads > 0);
770 // Note: threads is typically >= cores with hyperthreading,
771 // but detection may vary across systems
772 }
773
774 #[test]
775 fn test_cpu_features() {
776 let features = get_cpu_features();
777 // At least SSE2 should be available on modern x86_64
778 #[cfg(target_arch = "x86_64")]
779 assert!(!features.is_empty());
780 // On other architectures, just verify the function works
781 #[cfg(not(target_arch = "x86_64"))]
782 let _ = features;
783 }
784}