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}