windows_timer.rs (hyperfine-1.14.0) | : | windows_timer.rs (hyperfine-1.15.0) | ||
---|---|---|---|---|
#![cfg(windows)] | #![cfg(windows)] | |||
#![warn(unsafe_op_in_unsafe_fn)] | ||||
use std::mem; | use std::{mem, os::windows::io::AsRawHandle, process, ptr}; | |||
use std::os::windows::io::{AsRawHandle, RawHandle}; | ||||
use std::process::Child; | ||||
use winapi::um::processthreadsapi::GetProcessTimes; | use winapi::{ | |||
use winapi::um::winnt::HANDLE; | shared::{ntdef::NTSTATUS, ntstatus::STATUS_SUCCESS}, | |||
um::{ | ||||
handleapi::CloseHandle, | ||||
jobapi2::{AssignProcessToJobObject, CreateJobObjectW, QueryInformationJo | ||||
bObject}, | ||||
libloaderapi::{GetModuleHandleA, GetProcAddress}, | ||||
winnt::{ | ||||
JobObjectBasicAccountingInformation, HANDLE, JOBOBJECT_BASIC_ACCOUNT | ||||
ING_INFORMATION, | ||||
}, | ||||
}, | ||||
}; | ||||
#[cfg(windows_process_extensions_main_thread_handle)] | ||||
use winapi::shared::minwindef::DWORD; | ||||
#[cfg(not(windows_process_extensions_main_thread_handle))] | ||||
use once_cell::sync::Lazy; | ||||
use crate::timer::CPUTimes; | ||||
use crate::util::units::Second; | use crate::util::units::Second; | |||
const HUNDRED_NS_PER_MS: i64 = 10; | const HUNDRED_NS_PER_MS: i64 = 10; | |||
#[cfg(not(windows_process_extensions_main_thread_handle))] | ||||
#[allow(non_upper_case_globals)] | ||||
static NtResumeProcess: Lazy<unsafe extern "system" fn(ProcessHandle: HANDLE) -> | ||||
NTSTATUS> = | ||||
Lazy::new(|| { | ||||
// SAFETY: Getting the module handle for ntdll.dll is safe | ||||
let ntdll = unsafe { GetModuleHandleA(b"ntdll.dll\0".as_ptr().cast()) }; | ||||
assert!(!ntdll.is_null(), "GetModuleHandleA failed"); | ||||
// SAFETY: The ntdll handle is valid | ||||
let nt_resume_process = | ||||
unsafe { GetProcAddress(ntdll, b"NtResumeProcess\0".as_ptr().cast()) | ||||
}; | ||||
assert!(!nt_resume_process.is_null(), "GetProcAddress failed"); | ||||
// SAFETY: We transmute to the correct function signature | ||||
unsafe { mem::transmute(nt_resume_process) } | ||||
}); | ||||
pub struct CPUTimer { | pub struct CPUTimer { | |||
handle: RawHandle, | job_object: HANDLE, | |||
} | } | |||
impl CPUTimer { | impl CPUTimer { | |||
pub fn start_for_process(process: &Child) -> Self { | pub unsafe fn start_suspended_process(child: &process::Child) -> Self { | |||
CPUTimer { | // SAFETY: Creating a new job object is safe | |||
handle: process.as_raw_handle(), | let job_object = unsafe { CreateJobObjectW(ptr::null_mut(), ptr::null_mu | |||
t()) }; | ||||
assert!(!job_object.is_null(), "CreateJobObjectW failed"); | ||||
// SAFETY: The job object handle is valid | ||||
let ret = unsafe { AssignProcessToJobObject(job_object, child.as_raw_han | ||||
dle()) }; | ||||
assert!(ret != 0, "AssignProcessToJobObject failed"); | ||||
#[cfg(windows_process_extensions_main_thread_handle)] | ||||
{ | ||||
// SAFETY: The main thread handle is valid | ||||
let ret = unsafe { ResumeThread(child.main_thread_handle().as_raw_ha | ||||
ndle()) }; | ||||
assert!(ret != -1 as DWORD, "ResumeThread failed"); | ||||
} | } | |||
#[cfg(not(windows_process_extensions_main_thread_handle))] | ||||
{ | ||||
// Since we can't get the main thread handle on stable rust, we use | ||||
// the undocumented but widely known `NtResumeProcess` function to | ||||
// resume a process by it's handle. | ||||
// SAFETY: The process handle is valid | ||||
let ret = unsafe { NtResumeProcess(child.as_raw_handle()) }; | ||||
assert!(ret == STATUS_SUCCESS, "NtResumeProcess failed"); | ||||
} | ||||
Self { job_object } | ||||
} | } | |||
pub fn stop(&self) -> (Second, Second) { | pub fn stop(&self) -> (Second, Second) { | |||
let times = get_cpu_times(self.handle); | let mut job_object_info = | |||
( | mem::MaybeUninit::<JOBOBJECT_BASIC_ACCOUNTING_INFORMATION>::uninit() | |||
times.user_usec as f64 * 1e-6, | ; | |||
times.system_usec as f64 * 1e-6, | ||||
) | ||||
} | ||||
} | ||||
/// Read CPU execution times | // SAFETY: A valid job object got created in `start_suspended_process` | |||
fn get_cpu_times(handle: RawHandle) -> CPUTimes { | let res = unsafe { | |||
let (user_usec, system_usec) = unsafe { | QueryInformationJobObject( | |||
let mut _ctime = mem::zeroed(); | self.job_object, | |||
let mut _etime = mem::zeroed(); | JobObjectBasicAccountingInformation, | |||
let mut kernel_time = mem::zeroed(); | job_object_info.as_mut_ptr().cast(), | |||
let mut user_time = mem::zeroed(); | mem::size_of::<JOBOBJECT_BASIC_ACCOUNTING_INFORMATION>() as u32, | |||
let res = GetProcessTimes( | ptr::null_mut(), | |||
handle as HANDLE, | ) | |||
&mut _ctime, | }; | |||
&mut _etime, | ||||
&mut kernel_time, | ||||
&mut user_time, | ||||
); | ||||
// GetProcessTimes will exit with non-zero if success as per: https://ms dn.microsoft.com/en-us/library/windows/desktop/ms683223(v=vs.85).aspx | ||||
if res != 0 { | if res != 0 { | |||
// Extract times as laid out here: https://support.microsoft.com/en- | // SAFETY: The job object info got correctly initialized | |||
us/help/188768/info-working-with-the-filetime-structure | let job_object_info = unsafe { job_object_info.assume_init() }; | |||
// Both user_time and kernel_time are spans that the process spent i | ||||
n either. | // SAFETY: The `TotalUserTime` is "The total amount of user-mode exe | |||
let user: i64 = (((user_time.dwHighDateTime as i64) << 32) | cution time for | |||
+ user_time.dwLowDateTime as i64) | // all active processes associated with the job, as well as all term | |||
/ HUNDRED_NS_PER_MS; | inated processes no | |||
let kernel: i64 = (((kernel_time.dwHighDateTime as i64) << 32) | // longer associated with the job, in 100-nanosecond ticks." and is | |||
+ kernel_time.dwLowDateTime as i64) | safe to extract | |||
/ HUNDRED_NS_PER_MS; | let user: i64 = unsafe { job_object_info.TotalUserTime.QuadPart() } | |||
(user, kernel) | / HUNDRED_NS_PER_MS; | |||
// SAFETY: The `TotalKernelTime` is "The total amount of kernel-mode | ||||
execution time | ||||
// for all active processes associated with the job, as well as all | ||||
terminated | ||||
// processes no longer associated with the job, in 100-nanosecond ti | ||||
cks." and is safe | ||||
// to extract | ||||
let kernel: i64 = | ||||
unsafe { job_object_info.TotalKernelTime.QuadPart() } / HUNDRED_ | ||||
NS_PER_MS; | ||||
(user as f64 * 1e-6, kernel as f64 * 1e-6) | ||||
} else { | } else { | |||
(0, 0) | (0.0, 0.0) | |||
} | } | |||
}; | } | |||
} | ||||
CPUTimes { | impl Drop for CPUTimer { | |||
user_usec, | fn drop(self: &mut Self) { | |||
system_usec, | // SAFETY: A valid job object got created in `start_suspended_process` | |||
unsafe { CloseHandle(self.job_object) }; | ||||
} | } | |||
} | } | |||
End of changes. 15 change blocks. | ||||
48 lines changed or deleted | 114 lines changed or added |