use winapi::shared::minwindef::FILETIME;
use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE};
use winapi::um::processthreadsapi::{GetProcessTimes, OpenProcess};
use winapi::um::psapi::{K32GetProcessMemoryInfo, PROCESS_MEMORY_COUNTERS};
use winapi::um::winnt::{HANDLE, PROCESS_QUERY_LIMITED_INFORMATION};
pub const PEAK_RSS_READABLE_AFTER_EXIT: bool = true;
pub const MAX_TREE_RSS_PROCESSES: usize = 4096;
fn with_query_handle<T>(pid: u32, read: impl FnOnce(HANDLE) -> Option<T>) -> Option<T> {
let handle = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid) };
if handle.is_null() {
return None;
}
let value = read(handle);
unsafe { CloseHandle(handle) };
value
}
pub fn cpu_ticks_for_pid(pid: u32) -> Option<u64> {
with_query_handle(pid, |handle| {
let mut times: [FILETIME; 4] = unsafe { std::mem::zeroed() };
let [creation, exit, kernel, user] = &mut times;
if unsafe { GetProcessTimes(handle, creation, exit, kernel, user) } == 0 {
return None;
}
let value =
|time: &FILETIME| (u64::from(time.dwHighDateTime) << 32) | u64::from(time.dwLowDateTime);
Some(value(×[2]).wrapping_add(value(×[3])))
})
}
fn memory_counters(pid: u32) -> Option<PROCESS_MEMORY_COUNTERS> {
with_query_handle(pid, |handle| {
let size = std::mem::size_of::<PROCESS_MEMORY_COUNTERS>() as u32;
let mut counters: PROCESS_MEMORY_COUNTERS = unsafe { std::mem::zeroed() };
counters.cb = size;
(unsafe { K32GetProcessMemoryInfo(handle, &mut counters, size) } != 0).then_some(counters)
})
}
pub fn peak_rss_bytes_for_pid(pid: u32) -> Option<u64> {
memory_counters(pid).map(|counters| counters.PeakWorkingSetSize as u64)
}
fn children_by_parent() -> std::collections::HashMap<u32, Vec<u32>> {
use winapi::um::tlhelp32::{
CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W,
TH32CS_SNAPPROCESS,
};
let mut children: std::collections::HashMap<u32, Vec<u32>> = std::collections::HashMap::new();
unsafe {
let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if snapshot == INVALID_HANDLE_VALUE || snapshot.is_null() {
return children;
}
let mut entry: PROCESSENTRY32W = std::mem::zeroed();
entry.dwSize = std::mem::size_of::<PROCESSENTRY32W>() as u32;
let mut more = Process32FirstW(snapshot, &mut entry) != 0;
while more {
if entry.th32ProcessID != entry.th32ParentProcessID {
children
.entry(entry.th32ParentProcessID)
.or_default()
.push(entry.th32ProcessID);
}
more = Process32NextW(snapshot, &mut entry) != 0;
}
CloseHandle(snapshot);
}
children
}
pub fn tree_rss_bytes_for_pid(pid: u32) -> Option<u64> {
let mut total = memory_counters(pid)?.WorkingSetSize as u64;
let children = children_by_parent();
let mut seen = std::collections::HashSet::from([pid]);
let mut stack = vec![pid];
while let Some(parent) = stack.pop() {
for &child in children.get(&parent).map(Vec::as_slice).unwrap_or_default() {
if seen.len() >= MAX_TREE_RSS_PROCESSES || !seen.insert(child) {
continue;
}
if let Some(counters) = memory_counters(child) {
total = total.saturating_add(counters.WorkingSetSize as u64);
}
stack.push(child);
}
}
Some(total)
}