diff --git a/src/windows/process.rs b/src/windows/process.rs index 95eba115a..79ff91101 100644 --- a/src/windows/process.rs +++ b/src/windows/process.rs @@ -19,11 +19,10 @@ use std::str; use std::sync::{Arc, OnceLock}; use libc::c_void; -use ntapi::ntexapi::{SystemProcessIdInformation, SYSTEM_PROCESS_ID_INFORMATION}; +use ntapi::ntexapi::SYSTEM_PROCESS_INFORMATION; use ntapi::ntrtl::RTL_USER_PROCESS_PARAMETERS; use ntapi::ntwow64::{PEB32, RTL_USER_PROCESS_PARAMETERS32}; use windows::core::PCWSTR; -use windows::Wdk::System::SystemInformation::{NtQuerySystemInformation, SYSTEM_INFORMATION_CLASS}; use windows::Wdk::System::SystemServices::RtlGetVersion; use windows::Wdk::System::Threading::{ NtQueryInformationProcess, ProcessBasicInformation, ProcessCommandLineInformation, @@ -36,12 +35,9 @@ use windows::Win32::Foundation::{ use windows::Win32::Security::{GetTokenInformation, TokenUser, TOKEN_QUERY, TOKEN_USER}; use windows::Win32::System::Diagnostics::Debug::ReadProcessMemory; use windows::Win32::System::Memory::{ - GetProcessHeap, HeapAlloc, HeapFree, LocalAlloc, VirtualQueryEx, HEAP_ZERO_MEMORY, LMEM_FIXED, - LMEM_ZEROINIT, MEMORY_BASIC_INFORMATION, -}; -use windows::Win32::System::ProcessStatus::{ - GetModuleFileNameExW, GetProcessMemoryInfo, PROCESS_MEMORY_COUNTERS_EX, + GetProcessHeap, HeapAlloc, HeapFree, VirtualQueryEx, HEAP_ZERO_MEMORY, MEMORY_BASIC_INFORMATION, }; +use windows::Win32::System::ProcessStatus::GetModuleFileNameExW; use windows::Win32::System::RemoteDesktop::ProcessIdToSessionId; use windows::Win32::System::SystemInformation::OSVERSIONINFOEXW; use windows::Win32::System::Threading::{ @@ -240,81 +236,6 @@ unsafe fn display_ntstatus_error(ntstatus: windows::core::HRESULT) { ); } -// Take a look at https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm -// for explanations. -unsafe fn get_process_name(pid: Pid) -> Option { - let mut info = SYSTEM_PROCESS_ID_INFORMATION { - ProcessId: pid.0 as _, - ImageName: MaybeUninit::zeroed().assume_init(), - }; - // `MaximumLength` MUST BE a power of 2: here 32768 because the the returned name may be a full - // UNC path (up to 32767). - info.ImageName.MaximumLength = 1 << 15; - - for i in 0.. { - let local_alloc = LocalAlloc( - LMEM_FIXED | LMEM_ZEROINIT, - info.ImageName.MaximumLength as _, - ); - match local_alloc { - Ok(buf) if !buf.0.is_null() => info.ImageName.Buffer = buf.0.cast(), - _ => { - sysinfo_debug!("Couldn't get process infos: LocalAlloc failed"); - return None; - } - } - match NtQuerySystemInformation( - SYSTEM_INFORMATION_CLASS(SystemProcessIdInformation as _), - &mut info as *mut _ as *mut _, - size_of::() as _, - null_mut(), - ) - .ok() - { - Ok(()) => break, - Err(err) if err.code() == STATUS_INFO_LENGTH_MISMATCH.to_hresult() => { - if !info.ImageName.Buffer.is_null() { - let _err = LocalFree(HLOCAL(info.ImageName.Buffer.cast())); - } - if i > 2 { - // Too many iterations, we should have the correct length at this point - // normally, aborting name retrieval. - sysinfo_debug!( - "NtQuerySystemInformation returned `STATUS_INFO_LENGTH_MISMATCH` too many times" - ); - return None; - } - // New length has been set into `MaximumLength` so we just continue the loop. - } - Err(_err) => { - if !info.ImageName.Buffer.is_null() { - let _err = LocalFree(HLOCAL(info.ImageName.Buffer.cast())); - } - - #[cfg(feature = "debug")] - { - display_ntstatus_error(_err.code()); - } - return None; - } - } - } - - if info.ImageName.Buffer.is_null() { - return None; - } - - let s = std::slice::from_raw_parts( - info.ImageName.Buffer, - // The length is in bytes, not the length of string - info.ImageName.Length as usize / std::mem::size_of::(), - ); - let os_str = OsString::from_wide(s); - let name = Path::new(&os_str).file_name().map(|s| s.to_os_string()); - let _err = LocalFree(HLOCAL(info.ImageName.Buffer.cast())); - name -} - unsafe fn get_exe(process_handler: &HandleWrapper) -> Option { let mut exe_buf = [0u16; MAX_PATH as usize + 1]; GetModuleFileNameExW( @@ -327,46 +248,7 @@ unsafe fn get_exe(process_handler: &HandleWrapper) -> Option { } impl ProcessInner { - pub(crate) fn new_from_pid(pid: Pid, now: u64) -> Option { - unsafe { - let process_handler = get_process_handler(pid)?; - let name = get_process_name(pid).unwrap_or_default(); - let (start_time, run_time) = get_start_and_run_time(*process_handler, now); - Some(Self { - handle: Some(Arc::new(process_handler)), - name, - pid, - parent: None, - user_id: None, - cmd: Vec::new(), - environ: Vec::new(), - exe: None, - cwd: None, - root: None, - status: ProcessStatus::Run, - memory: 0, - virtual_memory: 0, - cpu_usage: 0., - cpu_calc_values: CPUsageCalculationValues::new(), - start_time, - run_time, - updated: true, - old_read_bytes: 0, - old_written_bytes: 0, - read_bytes: 0, - written_bytes: 0, - }) - } - } - - pub(crate) fn new_full( - pid: Pid, - parent: Option, - memory: u64, - virtual_memory: u64, - name: OsString, - now: u64, - ) -> Self { + pub(crate) fn new(pid: Pid, parent: Option, now: u64, name: OsString) -> Self { let (handle, start_time, run_time) = if let Some(handle) = get_process_handler(pid) { let (start_time, run_time) = get_start_and_run_time(*handle, now); (Some(Arc::new(handle)), start_time, run_time) @@ -377,16 +259,16 @@ impl ProcessInner { handle, name, pid, - user_id: None, parent, + user_id: None, cmd: Vec::new(), environ: Vec::new(), exe: None, cwd: None, root: None, status: ProcessStatus::Run, - memory, - virtual_memory, + memory: 0, + virtual_memory: 0, cpu_usage: 0., cpu_calc_values: CPUsageCalculationValues::new(), start_time, @@ -405,6 +287,7 @@ impl ProcessInner { nb_cpus: u64, now: u64, refresh_parent: bool, + pi: &SYSTEM_PROCESS_INFORMATION, ) { if refresh_kind.cpu() { compute_cpu_usage(self, nb_cpus); @@ -413,7 +296,8 @@ impl ProcessInner { update_disk_usage(self); } if refresh_kind.memory() { - update_memory(self); + self.memory = pi.WorkingSetSize as _; + self.virtual_memory = pi.VirtualSize as _; } unsafe { get_process_user_id(self, refresh_kind); @@ -1129,24 +1013,6 @@ pub(crate) fn update_disk_usage(p: &mut ProcessInner) { } } -pub(crate) fn update_memory(p: &mut ProcessInner) { - if let Some(handle) = p.get_handle() { - unsafe { - let mut pmc: PROCESS_MEMORY_COUNTERS_EX = zeroed(); - if GetProcessMemoryInfo( - handle, - (&mut pmc as *mut PROCESS_MEMORY_COUNTERS_EX).cast(), - size_of::() as _, - ) - .is_ok() - { - p.memory = pmc.WorkingSetSize as _; - p.virtual_memory = pmc.PrivateUsage as _; - } - } - } -} - #[inline(always)] const fn filetime_to_u64(ft: FILETIME) -> u64 { (ft.dwHighDateTime as u64) << 32 | (ft.dwLowDateTime as u64) diff --git a/src/windows/system.rs b/src/windows/system.rs index 0ebb6e71e..a6c4a0621 100644 --- a/src/windows/system.rs +++ b/src/windows/system.rs @@ -3,7 +3,6 @@ use crate::{Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, ProcessRefreshKind}; use crate::sys::cpu::*; -use crate::sys::process::get_start_time; use crate::{Process, ProcessInner}; use crate::utils::into_iter; @@ -11,9 +10,10 @@ use crate::utils::into_iter; use std::cell::UnsafeCell; use std::collections::HashMap; use std::ffi::{OsStr, OsString}; -use std::mem::{size_of, zeroed}; +use std::mem::{replace, size_of, zeroed}; use std::os::windows::ffi::{OsStrExt, OsStringExt}; use std::ptr; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::{Duration, SystemTime}; use ntapi::ntexapi::SYSTEM_PROCESS_INFORMATION; @@ -182,37 +182,34 @@ impl SystemInner { None } - #[allow(clippy::map_entry)] pub(crate) fn refresh_process_specifics( &mut self, pid: Pid, refresh_kind: ProcessRefreshKind, ) -> bool { - let now = get_now(); - let nb_cpus = self.cpus.len() as u64; - - if let Some(proc_) = self.process_list.get_mut(&pid) { - if let Some(ret) = refresh_existing_process(proc_, nb_cpus, now, refresh_kind) { - return ret; - } - // We need to re-make the process because the PID owner changed. - } - if let Some(mut p) = ProcessInner::new_from_pid(pid, now) { - p.update(refresh_kind, nb_cpus, now, true); - p.updated = false; - self.process_list.insert(pid, Process { inner: p }); - true - } else { - false - } + self.refresh_processes_specifics_inner(Some(&[pid]), refresh_kind) != 0 } - #[allow(clippy::cast_ptr_alignment)] pub(crate) fn refresh_processes_specifics( &mut self, filter: Option<&[Pid]>, refresh_kind: ProcessRefreshKind, ) { + self.refresh_processes_specifics_inner(filter, refresh_kind); + } + + #[allow(clippy::cast_ptr_alignment)] + pub(crate) fn refresh_processes_specifics_inner( + &mut self, + filter: Option<&[Pid]>, + refresh_kind: ProcessRefreshKind, + ) -> usize { + if let Some(filter) = filter { + if filter.is_empty() { + return 0; + } + } + // Windows 10 notebook requires at least 512KiB of memory to make it in one go let mut buffer_size = 512 * 1024; let mut process_information: Vec = Vec::with_capacity(buffer_size); @@ -250,7 +247,7 @@ impl SystemInner { "Couldn't get process infos: NtQuerySystemInformation returned {}", _err, ); - return; + return 0; } } } @@ -266,7 +263,7 @@ impl SystemInner { } #[allow(clippy::type_complexity)] - let (filter, filter_callback): ( + let (filter_array, filter_callback): ( &[Pid], &(dyn Fn(Pid, &[Pid]) -> bool + Sync + Send), ) = if let Some(filter) = filter { @@ -279,6 +276,8 @@ impl SystemInner { // and the buffer contents are initialized process_information.set_len(buffer_size); + let nb_updated = AtomicUsize::new(0); + // Parse the data block to get process information let mut process_ids = Vec::with_capacity(500); let mut process_information_offset = 0; @@ -293,7 +292,7 @@ impl SystemInner { // under x86_64 wine (and possibly other systems) let pi = ptr::read_unaligned(p); - if filter_callback(Pid(pi.UniqueProcessId as _), filter) { + if filter_callback(Pid(pi.UniqueProcessId as _), filter_array) { process_ids.push(Wrap(p)); } @@ -319,6 +318,7 @@ impl SystemInner { // able to run it over `process_information` directly! let processes = into_iter(process_ids) .filter_map(|pi| { + nb_updated.fetch_add(1, Ordering::Relaxed); // as above, read_unaligned is necessary let pi = ptr::read_unaligned(pi.0); let pid = Pid(pi.UniqueProcessId as _); @@ -337,38 +337,29 @@ impl SystemInner { .map(|start| start == proc_.start_time()) .unwrap_or(true) { - if refresh_kind.memory() { - proc_.memory = pi.WorkingSetSize as _; - proc_.virtual_memory = pi.VirtualSize as _; - } - proc_.update(refresh_kind, nb_cpus, now, false); + proc_.update(refresh_kind, nb_cpus, now, false, &pi); // Update the parent in case it changed. proc_.parent = parent; return None; } // If the PID owner changed, we need to recompute the whole process. - sysinfo_debug!("owner changed for PID {}", proc_.pid()); + sysinfo_debug!("owner changed for PID {}", pid); } let name = get_process_name(&pi, pid); - let (memory, virtual_memory) = if refresh_kind.memory() { - (pi.WorkingSetSize as _, pi.VirtualSize as _) - } else { - (0, 0) - }; - let mut p = - ProcessInner::new_full(pid, parent, memory, virtual_memory, name, now); - p.update(refresh_kind.without_memory(), nb_cpus, now, false); + let mut p = ProcessInner::new(pid, parent, now, name); + p.update(refresh_kind, nb_cpus, now, false, &pi); Some(Process { inner: p }) }) .collect::>(); for p in processes.into_iter() { self.process_list.insert(p.pid(), p); } - self.process_list.retain(|_, v| { - let x = v.inner.updated; - v.inner.updated = false; - x - }); + if filter.is_none() { + // If it comes from `refresh_process` or `refresh_pids`, we don't remove + // dead processes. + self.process_list.retain(|_, v| replace(&mut v.inner.updated, false)); + } + nb_updated.into_inner() } } @@ -522,32 +513,6 @@ pub(crate) fn is_proc_running(handle: HANDLE) -> bool { && exit_code == STILL_ACTIVE.0 as u32 } -/// If it returns `None`, it means that the PID owner changed and that the `Process` must be -/// completely recomputed. -fn refresh_existing_process( - proc_: &mut Process, - nb_cpus: u64, - now: u64, - refresh_kind: ProcessRefreshKind, -) -> Option { - let proc_ = &mut proc_.inner; - if let Some(handle) = proc_.get_handle() { - if get_start_time(handle) != proc_.start_time() { - sysinfo_debug!("owner changed for PID {}", proc_.pid()); - // PID owner changed! - return None; - } - if !is_proc_running(handle) { - return Some(false); - } - } else { - return Some(false); - } - proc_.update(refresh_kind, nb_cpus, now, false); - proc_.updated = false; - Some(true) -} - #[allow(clippy::size_of_in_element_count)] //^ needed for "name.Length as usize / std::mem::size_of::()" pub(crate) fn get_process_name(process: &SYSTEM_PROCESS_INFORMATION, process_id: Pid) -> OsString {