1use std::thread;
4use std::time::Duration;
5
6use windows::Win32::Foundation::FILETIME;
7use windows::Win32::System::ProcessStatus::{
8 GetProcessMemoryInfo, PROCESS_MEMORY_COUNTERS, PROCESS_MEMORY_COUNTERS_EX,
9};
10use windows::Win32::System::SystemInformation::{GlobalMemoryStatusEx, MEMORYSTATUSEX};
11use windows::Win32::System::Threading::{
12 ALL_PROCESSOR_GROUPS, GetActiveProcessorCount, GetProcessTimes, GetSystemTimes,
13};
14
15use super::processes::Process;
16use super::types::{
17 HostMemoryMetrics, HostMetrics, ProcessCpuTimes, ProcessMemoryMetrics, ProcessMetrics,
18};
19use crate::error::{Error, InvalidParameterError, ProcessError, ProcessOpenError, Result};
20
21impl Process {
22 pub fn cpu_times(&self) -> Result<ProcessCpuTimes> {
26 let mut creation_time = FILETIME::default();
27 let mut exit_time = FILETIME::default();
28 let mut kernel_time = FILETIME::default();
29 let mut user_time = FILETIME::default();
30
31 unsafe {
32 GetProcessTimes(
33 self.as_raw_handle(),
34 &mut creation_time,
35 &mut exit_time,
36 &mut kernel_time,
37 &mut user_time,
38 )
39 }
40 .map_err(|e| {
41 Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
42 self.id().as_u32(),
43 "Failed to get process CPU times",
44 e.code().0,
45 )))
46 })?;
47
48 let kernel = filetime_to_u64_100ns(kernel_time);
49 let user = filetime_to_u64_100ns(user_time);
50
51 Ok(ProcessCpuTimes {
52 user_time_100ns: user,
53 kernel_time_100ns: kernel,
54 total_time_100ns: kernel.saturating_add(user),
55 })
56 }
57
58 pub fn memory_metrics(&self) -> Result<ProcessMemoryMetrics> {
60 let mut counters = PROCESS_MEMORY_COUNTERS_EX {
61 cb: std::mem::size_of::<PROCESS_MEMORY_COUNTERS_EX>() as u32,
62 ..Default::default()
63 };
64
65 unsafe {
66 GetProcessMemoryInfo(
67 self.as_raw_handle(),
68 &mut counters as *mut PROCESS_MEMORY_COUNTERS_EX as *mut PROCESS_MEMORY_COUNTERS,
69 counters.cb,
70 )
71 }
72 .map_err(|e| {
73 Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
74 self.id().as_u32(),
75 "Failed to get process memory metrics",
76 e.code().0,
77 )))
78 })?;
79
80 Ok(ProcessMemoryMetrics {
81 working_set_bytes: counters.WorkingSetSize,
82 peak_working_set_bytes: counters.PeakWorkingSetSize,
83 page_fault_count: counters.PageFaultCount,
84 private_usage_bytes: counters.PrivateUsage,
85 commit_usage_bytes: counters.PagefileUsage,
86 peak_commit_usage_bytes: counters.PeakPagefileUsage,
87 })
88 }
89
90 pub fn metrics(&self) -> Result<ProcessMetrics> {
92 Ok(ProcessMetrics {
93 memory: self.memory_metrics()?,
94 cpu: self.cpu_times()?,
95 })
96 }
97
98 pub fn cpu_usage(&self, interval: Duration) -> Result<f64> {
102 if interval.is_zero() {
103 return Err(Error::InvalidParameter(InvalidParameterError::new(
104 "interval",
105 "interval must be greater than zero",
106 )));
107 }
108
109 let start_proc = self.cpu_times()?;
110 let (_, start_kernel, start_user) = read_system_times_100ns()?;
111
112 thread::sleep(interval);
113
114 let end_proc = self.cpu_times()?;
115 let (_, end_kernel, end_user) = read_system_times_100ns()?;
116
117 let start_total = start_kernel.saturating_add(start_user);
118 let end_total = end_kernel.saturating_add(end_user);
119
120 Ok(calculate_cpu_percentage(
121 start_proc.total_time_100ns,
122 end_proc.total_time_100ns,
123 start_total,
124 end_total,
125 ))
126 }
127}
128
129pub fn host_metrics() -> Result<HostMetrics> {
131 let mut memory_status = MEMORYSTATUSEX {
132 dwLength: std::mem::size_of::<MEMORYSTATUSEX>() as u32,
133 ..Default::default()
134 };
135
136 unsafe { GlobalMemoryStatusEx(&mut memory_status) }.map_err(|e| {
137 Error::WindowsApi(crate::error::WindowsApiError::with_context(
138 e,
139 "GlobalMemoryStatusEx",
140 ))
141 })?;
142
143 let logical_cpu_count = unsafe { GetActiveProcessorCount(ALL_PROCESSOR_GROUPS) };
144
145 Ok(HostMetrics {
146 logical_cpu_count,
147 memory: HostMemoryMetrics {
148 total_physical_bytes: memory_status.ullTotalPhys,
149 available_physical_bytes: memory_status.ullAvailPhys,
150 total_virtual_bytes: memory_status.ullTotalVirtual,
151 available_virtual_bytes: memory_status.ullAvailVirtual,
152 memory_load_percent: memory_status.dwMemoryLoad,
153 },
154 })
155}
156
157pub fn host_cpu_usage(interval: Duration) -> Result<f64> {
161 if interval.is_zero() {
162 return Err(Error::InvalidParameter(InvalidParameterError::new(
163 "interval",
164 "interval must be greater than zero",
165 )));
166 }
167
168 let (idle_start, kernel_start, user_start) = read_system_times_100ns()?;
169 thread::sleep(interval);
170 let (idle_end, kernel_end, user_end) = read_system_times_100ns()?;
171
172 let total_start = kernel_start.saturating_add(user_start);
173 let total_end = kernel_end.saturating_add(user_end);
174 let total_delta = total_end.saturating_sub(total_start);
175 if total_delta == 0 {
176 return Ok(0.0);
177 }
178
179 let idle_delta = idle_end.saturating_sub(idle_start);
180 let busy_delta = total_delta.saturating_sub(idle_delta);
181 let usage = (busy_delta as f64 / total_delta as f64) * 100.0;
182 Ok(usage.clamp(0.0, 100.0))
183}
184
185fn read_system_times_100ns() -> Result<(u64, u64, u64)> {
186 let mut idle = FILETIME::default();
187 let mut kernel = FILETIME::default();
188 let mut user = FILETIME::default();
189
190 unsafe {
191 GetSystemTimes(
192 Some(&mut idle as *mut FILETIME),
193 Some(&mut kernel as *mut FILETIME),
194 Some(&mut user as *mut FILETIME),
195 )
196 }
197 .map_err(|e| {
198 Error::WindowsApi(crate::error::WindowsApiError::with_context(
199 e,
200 "GetSystemTimes",
201 ))
202 })?;
203
204 Ok((
205 filetime_to_u64_100ns(idle),
206 filetime_to_u64_100ns(kernel),
207 filetime_to_u64_100ns(user),
208 ))
209}
210
211fn filetime_to_u64_100ns(file_time: FILETIME) -> u64 {
212 ((file_time.dwHighDateTime as u64) << 32) | (file_time.dwLowDateTime as u64)
213}
214
215fn calculate_cpu_percentage(start_proc: u64, end_proc: u64, start_sys: u64, end_sys: u64) -> f64 {
216 let proc_delta = end_proc.saturating_sub(start_proc);
217 let sys_delta = end_sys.saturating_sub(start_sys);
218
219 if sys_delta == 0 {
220 return 0.0;
221 }
222
223 ((proc_delta as f64 / sys_delta as f64) * 100.0).clamp(0.0, 100.0)
224}
225
226#[cfg(test)]
227mod tests {
228 use super::calculate_cpu_percentage;
229
230 #[test]
231 fn cpu_percentage_zero_when_no_delta() {
232 let usage = calculate_cpu_percentage(100, 100, 1000, 1000);
233 assert_eq!(usage, 0.0);
234 }
235
236 #[test]
237 fn cpu_percentage_computes_expected_ratio() {
238 let usage = calculate_cpu_percentage(100, 300, 1000, 2000);
239 assert!((usage - 20.0).abs() < 0.000_1);
240 }
241}