oneshim-monitor
시스템 모니터링을 담당하는 크레이트. CPU/메모리/디스크, 활성 창, 유휴 감지를 수행합니다.
역할
- 시스템 메트릭: CPU, 메모리, 디스크, 네트워크 사용량
- 활성 창 추적: 현재 포커스된 창/앱 정보
- 프로세스 목록: 실행 중인 프로세스 정보
- 유휴 감지: 사용자 활동 없음 감지
디렉토리 구조
oneshim-monitor/src/
├── lib.rs # 크레이트 루트
├── system.rs # SysInfoMonitor - 시스템 메트릭
├── process.rs # ProcessTracker - 프로세스/창 추적
├── activity.rs # ActivityTracker - 유휴 감지
├── macos.rs # macOS 전용 구현
└── windows.rs # Windows 전용 구현
주요 컴포넌트
SysInfoMonitor (system.rs)
시스템 리소스 모니터링 (SystemMonitor 포트 구현):
pub struct SysInfoMonitor {
sys: RwLock<System>,
}
#[async_trait]
impl SystemMonitor for SysInfoMonitor {
async fn get_metrics(&self) -> Result<SystemMetrics, CoreError> {
let mut sys = self.sys.write().await;
sys.refresh_all();
Ok(SystemMetrics {
cpu_usage: sys.global_cpu_usage() as f64,
memory_used_mb: sys.used_memory() / 1024 / 1024,
memory_total_mb: sys.total_memory() / 1024 / 1024,
disk_used_gb: self.calculate_disk_used(&sys),
disk_total_gb: self.calculate_disk_total(&sys),
network_rx_bytes: self.get_network_rx(&sys),
network_tx_bytes: self.get_network_tx(&sys),
collected_at: Utc::now(),
})
}
}
수집 메트릭:
| 메트릭 | 설명 | 단위 |
|---|---|---|
cpu_usage | 전체 CPU 사용률 | % |
memory_used_mb | 사용 중인 메모리 | MB |
memory_total_mb | 전체 메모리 | MB |
disk_used_gb | 사용 중인 디스크 | GB |
disk_total_gb | 전체 디스크 | GB |
network_rx_bytes | 네트워크 수신량 | bytes |
network_tx_bytes | 네트워크 송신량 | bytes |
ProcessTracker (process.rs)
프로세스 및 활성 창 추적 (ProcessMonitor 포트 구현):
pub struct ProcessTracker {
sys: RwLock<System>,
}
#[async_trait]
impl ProcessMonitor for ProcessTracker {
async fn get_active_window(&self) -> Result<ActiveWindow, CoreError> {
#[cfg(target_os = "macos")]
return self.get_active_window_macos().await;
#[cfg(target_os = "windows")]
return self.get_active_window_windows().await;
#[cfg(target_os = "linux")]
return self.get_active_window_linux().await;
}
async fn get_running_processes(&self) -> Result<Vec<ProcessInfo>, CoreError> {
let sys = self.sys.read().await;
Ok(sys.processes()
.iter()
.map(|(pid, process)| ProcessInfo {
pid: pid.as_u32(),
name: process.name().to_string(),
cpu_usage: process.cpu_usage(),
memory_mb: process.memory() / 1024 / 1024,
})
.collect())
}
}
macOS 구현 (macos.rs)
AppleScript를 통한 활성 창 정보 획득:
#[cfg(target_os = "macos")]
impl ProcessTracker {
async fn get_active_window_macos(&self) -> Result<ActiveWindow, CoreError> {
// osascript로 활성 앱/창 정보 획득
let script = r#"
tell application "System Events"
set frontApp to first application process whose frontmost is true
set appName to name of frontApp
tell frontApp
set windowTitle to name of window 1
end tell
end tell
return appName & "|" & windowTitle
"#;
let output = Command::new("osascript")
.arg("-e")
.arg(script)
.output()
.await?;
let result = String::from_utf8_lossy(&output.stdout);
let parts: Vec<&str> = result.trim().split('|').collect();
Ok(ActiveWindow {
application: parts.get(0).unwrap_or(&"Unknown").to_string(),
title: parts.get(1).unwrap_or(&"").to_string(),
pid: self.get_frontmost_pid().await?,
})
}
}
Windows 구현 (windows.rs)
Win32 API를 통한 활성 창 정보 획득:
#[cfg(target_os = "windows")]
impl ProcessTracker {
async fn get_active_window_windows(&self) -> Result<ActiveWindow, CoreError> {
use windows_sys::Win32::UI::WindowsAndMessaging::*;
use windows_sys::Win32::Foundation::*;
unsafe {
// 포그라운드 윈도우 핸들 획득
let hwnd = GetForegroundWindow();
if hwnd == 0 {
return Err(CoreError::Internal("No foreground window".into()));
}
// 윈도우 타이틀 획득
let mut title_buf = [0u16; 512];
let len = GetWindowTextW(hwnd, title_buf.as_mut_ptr(), 512);
let title = String::from_utf16_lossy(&title_buf[..len as usize]);
// 프로세스 ID 획득
let mut pid: u32 = 0;
GetWindowThreadProcessId(hwnd, &mut pid);
// 프로세스 이름 획득 (sysinfo 사용)
let sys = self.sys.read().await;
let app_name = sys.process(Pid::from_u32(pid))
.map(|p| p.name().to_string())
.unwrap_or_else(|| "Unknown".to_string());
Ok(ActiveWindow {
application: app_name,
title,
pid,
})
}
}
}
ActivityTracker (activity.rs)
사용자 유휴 상태 감지 (ActivityMonitor 포트 구현):
pub struct ActivityTracker {
idle_threshold_secs: u64,
last_activity: RwLock<Instant>,
}
#[async_trait]
impl ActivityMonitor for ActivityTracker {
async fn is_idle(&self) -> Result<bool, CoreError> {
let duration = self.get_idle_duration().await?;
Ok(duration.as_secs() >= self.idle_threshold_secs)
}
async fn get_idle_duration(&self) -> Result<Duration, CoreError> {
let last = *self.last_activity.read().await;
Ok(Instant::now().duration_since(last))
}
}
impl ActivityTracker {
pub async fn record_activity(&self) {
*self.last_activity.write().await = Instant::now();
}
}
유휴 감지 로직:
- 기본 임계값: 5분 (300초)
- 키보드/마우스 이벤트로
record_activity()호출 - 임계값 초과 시
is_idle() = true
데이터 흐름
플랫폼 지원
| 기능 | macOS | Windows | Linux |
|---|---|---|---|
| CPU/메모리 | ✅ | ✅ | ✅ |
| 디스크/네트워크 | ✅ | ✅ | ✅ |
| 활성 창 | ✅ (osascript) | ✅ (Win32) | ⚠️ (X11) |
| 프로세스 목록 | ✅ | ✅ | ✅ |
| 유휴 감지 | ✅ | ✅ | ✅ |
의존성
| 크레이트 | 용도 |
|---|---|
sysinfo | 시스템 메트릭 수집 |
windows-sys | Windows API (조건부) |
tokio | 비동기 Command 실행 |
관련 문서:
- 클라이언트 개요
- oneshim-core - 모니터 포트 인터페이스
- oneshim-vision - 캡처 트리거 연동