aster/diagnostics/
system.rs1use super::checker::DiagnosticCheck;
6
7pub struct SystemChecker;
9
10impl SystemChecker {
11 #[cfg(unix)]
13 pub fn check_cpu_load() -> DiagnosticCheck {
14 let cores = std::thread::available_parallelism()
16 .map(|n| n.get())
17 .unwrap_or(1);
18
19 #[cfg(target_os = "macos")]
21 let load_result = std::process::Command::new("sysctl")
22 .args(["-n", "vm.loadavg"])
23 .output();
24
25 #[cfg(target_os = "linux")]
26 let load_result = std::fs::read_to_string("/proc/loadavg")
27 .map(|s| std::process::Output {
28 status: std::process::ExitStatus::default(),
29 stdout: s.into_bytes(),
30 stderr: Vec::new(),
31 })
32 .map_err(std::io::Error::other);
33
34 match load_result {
35 Ok(output) => {
36 let load_str = String::from_utf8_lossy(&output.stdout);
37 let load_1min: f64 = load_str
38 .split_whitespace()
39 .next()
40 .and_then(|s| s.trim_matches(|c| c == '{' || c == '}').parse().ok())
41 .unwrap_or(0.0);
42
43 let load_per_core = load_1min / cores as f64;
44
45 if load_per_core >= 2.0 {
46 DiagnosticCheck::warn(
47 "CPU 负载",
48 format!("负载较高: {:.2} ({} 核心)", load_1min, cores),
49 )
50 .with_details(format!("每核负载: {:.2}", load_per_core))
51 .with_fix("系统负载较高,性能可能受影响")
52 } else {
53 DiagnosticCheck::pass(
54 "CPU 负载",
55 format!("负载: {:.2} ({} 核心)", load_1min, cores),
56 )
57 }
58 }
59 Err(_) => DiagnosticCheck::warn("CPU 负载", "无法获取 CPU 负载"),
60 }
61 }
62
63 #[cfg(not(unix))]
64 pub fn check_cpu_load() -> DiagnosticCheck {
65 let cores = std::thread::available_parallelism()
66 .map(|n| n.get())
67 .unwrap_or(1);
68 DiagnosticCheck::pass("CPU 负载", format!("{} 核心可用", cores))
69 }
70
71 pub fn check_session_directory() -> DiagnosticCheck {
73 let session_dir = dirs::data_dir()
74 .map(|p| p.join("aster").join("sessions"))
75 .unwrap_or_else(|| std::path::PathBuf::from("~/.aster/sessions"));
76
77 if !session_dir.exists() {
78 if std::fs::create_dir_all(&session_dir).is_ok() {
79 return DiagnosticCheck::pass("会话目录", "目录已创建")
80 .with_details(format!("路径: {}", session_dir.display()));
81 }
82 return DiagnosticCheck::fail("会话目录", "无法创建会话目录")
83 .with_fix(format!("请手动创建: {}", session_dir.display()));
84 }
85
86 let session_count = std::fs::read_dir(&session_dir)
88 .map(|entries| {
89 entries
90 .filter_map(|e| e.ok())
91 .filter(|e| {
92 e.path()
93 .extension()
94 .map(|ext| ext == "json" || ext == "jsonl")
95 .unwrap_or(false)
96 })
97 .count()
98 })
99 .unwrap_or(0);
100
101 let total_size = Self::calculate_dir_size(&session_dir);
103 let size_mb = total_size as f64 / (1024.0 * 1024.0);
104
105 DiagnosticCheck::pass(
106 "会话目录",
107 format!("{} 个会话, {:.2} MB", session_count, size_mb),
108 )
109 .with_details(format!("路径: {}", session_dir.display()))
110 }
111
112 pub fn check_cache_directory() -> DiagnosticCheck {
114 let cache_dir = dirs::cache_dir()
115 .map(|p| p.join("aster"))
116 .unwrap_or_else(|| std::path::PathBuf::from("~/.cache/aster"));
117
118 if !cache_dir.exists() {
119 return DiagnosticCheck::pass("缓存目录", "无缓存目录(将按需创建)");
120 }
121
122 let total_size = Self::calculate_dir_size(&cache_dir);
123 let size_mb = total_size as f64 / (1024.0 * 1024.0);
124
125 if size_mb > 500.0 {
126 DiagnosticCheck::warn("缓存目录", format!("缓存较大: {:.2} MB", size_mb))
127 .with_details(format!("路径: {}", cache_dir.display()))
128 .with_fix(format!("考虑清理缓存: rm -rf {}", cache_dir.display()))
129 } else {
130 DiagnosticCheck::pass("缓存目录", format!("缓存: {:.2} MB", size_mb))
131 .with_details(format!("路径: {}", cache_dir.display()))
132 }
133 }
134
135 fn calculate_dir_size(path: &std::path::Path) -> u64 {
137 let mut size = 0u64;
138
139 if let Ok(entries) = std::fs::read_dir(path) {
140 for entry in entries.filter_map(|e| e.ok()) {
141 let path = entry.path();
142 if path.is_dir() {
143 size += Self::calculate_dir_size(&path);
144 } else if let Ok(metadata) = path.metadata() {
145 size += metadata.len();
146 }
147 }
148 }
149
150 size
151 }
152
153 pub fn check_mcp_servers() -> DiagnosticCheck {
155 let mcp_config_paths = [
156 dirs::config_dir()
157 .map(|p| p.join("aster").join("mcp.json"))
158 .unwrap_or_default(),
159 std::env::current_dir()
160 .map(|p| p.join(".aster").join("mcp.json"))
161 .unwrap_or_default(),
162 ];
163
164 for config_path in &mcp_config_paths {
165 if config_path.exists() {
166 match std::fs::read_to_string(config_path) {
167 Ok(content) => match serde_json::from_str::<serde_json::Value>(&content) {
168 Ok(config) => {
169 let servers = config
170 .get("mcpServers")
171 .and_then(|s| s.as_object())
172 .map(|o| o.keys().cloned().collect::<Vec<_>>())
173 .unwrap_or_default();
174
175 if servers.is_empty() {
176 return DiagnosticCheck::pass("MCP 服务器", "未配置 MCP 服务器");
177 }
178
179 return DiagnosticCheck::pass(
180 "MCP 服务器",
181 format!("{} 个服务器: {}", servers.len(), servers.join(", ")),
182 );
183 }
184 Err(e) => {
185 return DiagnosticCheck::warn("MCP 服务器", "MCP 配置格式错误")
186 .with_details(e.to_string());
187 }
188 },
189 Err(e) => {
190 return DiagnosticCheck::warn("MCP 服务器", "无法读取 MCP 配置")
191 .with_details(e.to_string());
192 }
193 }
194 }
195 }
196
197 DiagnosticCheck::pass("MCP 服务器", "未配置 MCP 服务器")
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use crate::diagnostics::checker::CheckStatus;
205
206 #[test]
207 fn test_check_cpu_load() {
208 let result = SystemChecker::check_cpu_load();
209 assert!(result.status == CheckStatus::Pass || result.status == CheckStatus::Warn);
211 assert!(result.name == "CPU 负载");
212 }
213
214 #[test]
215 fn test_check_session_directory() {
216 let result = SystemChecker::check_session_directory();
217 assert!(result.status == CheckStatus::Pass || result.status == CheckStatus::Fail);
219 }
220
221 #[test]
222 fn test_check_cache_directory() {
223 let result = SystemChecker::check_cache_directory();
224 assert!(result.status == CheckStatus::Pass || result.status == CheckStatus::Warn);
226 }
227
228 #[test]
229 fn test_check_mcp_servers() {
230 let result = SystemChecker::check_mcp_servers();
231 assert!(result.status == CheckStatus::Pass || result.status == CheckStatus::Warn);
233 }
234
235 #[test]
236 fn test_calculate_dir_size() {
237 let temp_dir = std::env::temp_dir();
238 let size = SystemChecker::calculate_dir_size(&temp_dir);
239 let _ = size;
242 }
243
244 #[test]
245 fn test_calculate_dir_size_nonexistent() {
246 let path = std::path::Path::new("/nonexistent/path/12345");
247 let size = SystemChecker::calculate_dir_size(path);
248 assert_eq!(size, 0);
249 }
250}