Skip to main content

spn_native/
platform.rs

1//! Platform-specific utilities for native inference.
2//!
3//! This module provides:
4//! - RAM detection for auto-quantization selection
5//! - Default model storage directory
6
7use std::path::PathBuf;
8
9// ============================================================================
10// Storage Location
11// ============================================================================
12
13/// Default model storage directory.
14///
15/// Models are stored in `~/.spn/models/` by default.
16///
17/// # Example
18///
19/// ```
20/// use spn_native::default_model_dir;
21///
22/// let dir = default_model_dir();
23/// assert!(dir.to_string_lossy().contains("models"));
24/// ```
25#[must_use]
26pub fn default_model_dir() -> PathBuf {
27    dirs::home_dir()
28        .map(|h| h.join(".spn").join("models"))
29        .unwrap_or_else(|| PathBuf::from(".spn/models"))
30}
31
32// ============================================================================
33// RAM Detection
34// ============================================================================
35
36/// Detect available system RAM in gigabytes.
37///
38/// Returns a conservative estimate if detection fails.
39///
40/// # Platform Support
41///
42/// - **macOS**: Uses `sysctl hw.memsize`
43/// - **Linux**: Reads `/proc/meminfo`
44/// - **Windows**: Returns 16GB default (TODO: implement via winapi)
45/// - **Other**: Returns 8GB default
46///
47/// # Example
48///
49/// ```
50/// use spn_native::detect_available_ram_gb;
51///
52/// let ram = detect_available_ram_gb();
53/// println!("System has {}GB RAM", ram);
54/// assert!(ram >= 1); // At least 1GB
55/// ```
56#[cfg(target_os = "macos")]
57#[must_use]
58pub fn detect_available_ram_gb() -> u32 {
59    use std::process::Command;
60
61    let output = Command::new("sysctl")
62        .args(["-n", "hw.memsize"])
63        .output()
64        .ok();
65
66    output
67        .and_then(|o| String::from_utf8(o.stdout).ok())
68        .and_then(|s| s.trim().parse::<u64>().ok())
69        .map(|bytes| (bytes / 1_073_741_824) as u32) // bytes to GB
70        .unwrap_or(8) // Conservative default
71}
72
73/// Detect available system RAM in gigabytes.
74#[cfg(target_os = "linux")]
75#[must_use]
76pub fn detect_available_ram_gb() -> u32 {
77    use std::fs;
78
79    fs::read_to_string("/proc/meminfo")
80        .ok()
81        .and_then(|content| {
82            content
83                .lines()
84                .find(|line| line.starts_with("MemTotal:"))
85                .and_then(|line| {
86                    line.split_whitespace()
87                        .nth(1)
88                        .and_then(|kb| kb.parse::<u64>().ok())
89                })
90        })
91        .map(|kb| (kb / 1_048_576) as u32) // KB to GB
92        .unwrap_or(8)
93}
94
95/// Detect available system RAM in gigabytes.
96#[cfg(target_os = "windows")]
97#[must_use]
98pub fn detect_available_ram_gb() -> u32 {
99    // TODO: Use winapi to get actual RAM
100    // For now, assume 16GB on Windows
101    16
102}
103
104/// Detect available system RAM in gigabytes.
105#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
106#[must_use]
107pub fn detect_available_ram_gb() -> u32 {
108    8 // Conservative default
109}
110
111// ============================================================================
112// Tests
113// ============================================================================
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_default_model_dir() {
121        let dir = default_model_dir();
122        let dir_str = dir.to_string_lossy();
123        assert!(dir_str.contains(".spn") || dir_str.contains("spn"));
124        assert!(dir_str.contains("models"));
125    }
126
127    #[test]
128    fn test_detect_ram() {
129        let ram = detect_available_ram_gb();
130        // Should return a reasonable value (at least 1GB, no more than 1TB)
131        assert!(ram >= 1);
132        assert!(ram <= 1024);
133    }
134
135    #[test]
136    fn test_detect_ram_consistency() {
137        // Multiple calls should return the same value
138        let ram1 = detect_available_ram_gb();
139        let ram2 = detect_available_ram_gb();
140        assert_eq!(ram1, ram2);
141    }
142}