diff options
Diffstat (limited to 'ext/node/ops/os')
-rw-r--r-- | ext/node/ops/os/cpus.rs | 306 | ||||
-rw-r--r-- | ext/node/ops/os/mod.rs | 90 | ||||
-rw-r--r-- | ext/node/ops/os/priority.rs | 136 |
3 files changed, 532 insertions, 0 deletions
diff --git a/ext/node/ops/os/cpus.rs b/ext/node/ops/os/cpus.rs new file mode 100644 index 000000000..5af55372f --- /dev/null +++ b/ext/node/ops/os/cpus.rs @@ -0,0 +1,306 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use deno_core::serde::Serialize; + +#[derive(Debug, Default, Serialize, Clone)] +pub struct CpuTimes { + pub user: u64, + pub nice: u64, + pub sys: u64, + pub idle: u64, + pub irq: u64, +} + +#[derive(Debug, Default, Serialize, Clone)] +pub struct CpuInfo { + pub model: String, + /* in MHz */ + pub speed: u64, + pub times: CpuTimes, +} + +impl CpuInfo { + pub fn new() -> Self { + Self::default() + } +} + +#[cfg(target_os = "macos")] +pub fn cpu_info() -> Option<Vec<CpuInfo>> { + let mut model: [u8; 512] = [0; 512]; + let mut size = std::mem::size_of_val(&model); + + // Safety: Assumes correct behavior of platform-specific syscalls and data structures. + // Relies on specific sysctl names and sysconf parameter existence. + unsafe { + let ticks = libc::sysconf(libc::_SC_CLK_TCK); + let multiplier = 1000u64 / ticks as u64; + if libc::sysctlbyname( + "machdep.cpu.brand_string\0".as_ptr() as *const libc::c_char, + model.as_mut_ptr() as _, + &mut size, + std::ptr::null_mut(), + 0, + ) != 0 + && libc::sysctlbyname( + "hw.model\0".as_ptr() as *const libc::c_char, + model.as_mut_ptr() as _, + &mut size, + std::ptr::null_mut(), + 0, + ) != 0 + { + return None; + } + + let mut cpu_speed: u64 = 0; + let mut cpu_speed_size = std::mem::size_of_val(&cpu_speed); + + libc::sysctlbyname( + "hw.cpufrequency\0".as_ptr() as *const libc::c_char, + &mut cpu_speed as *mut _ as *mut libc::c_void, + &mut cpu_speed_size, + std::ptr::null_mut(), + 0, + ); + + if cpu_speed == 0 { + // https://github.com/libuv/libuv/pull/3679 + // + // hw.cpufrequency sysctl seems to be missing on darwin/arm64 + // so we instead hardcode a plausible value. This value matches + // what the mach kernel will report when running Rosetta apps. + cpu_speed = 2_400_000_000; + } + + let mut num_cpus: libc::natural_t = 0; + let mut info: *mut libc::processor_cpu_load_info_data_t = + std::ptr::null_mut(); + let mut msg_type: libc::mach_msg_type_number_t = 0; + if libc::host_processor_info( + libc::mach_host_self(), + libc::PROCESSOR_CPU_LOAD_INFO, + &mut num_cpus, + &mut info as *mut _ as *mut libc::processor_info_array_t, + &mut msg_type, + ) != 0 + { + return None; + } + + let mut cpus = vec![CpuInfo::new(); num_cpus as usize]; + + let info = std::slice::from_raw_parts(info, num_cpus as usize); + let model = std::ffi::CStr::from_ptr(model.as_ptr() as _) + .to_string_lossy() + .into_owned(); + for (i, cpu) in cpus.iter_mut().enumerate() { + cpu.times.user = + info[i].cpu_ticks[libc::CPU_STATE_USER as usize] as u64 * multiplier; + cpu.times.nice = + info[i].cpu_ticks[libc::CPU_STATE_NICE as usize] as u64 * multiplier; + cpu.times.sys = + info[i].cpu_ticks[libc::CPU_STATE_SYSTEM as usize] as u64 * multiplier; + cpu.times.idle = + info[i].cpu_ticks[libc::CPU_STATE_IDLE as usize] as u64 * multiplier; + + cpu.times.irq = 0; + + cpu.model = model.clone(); + cpu.speed = cpu_speed / 1000000; + } + + libc::vm_deallocate( + libc::mach_task_self(), + info.as_ptr() as libc::vm_address_t, + msg_type as _, + ); + + Some(cpus) + } +} + +#[cfg(target_os = "windows")] +pub fn cpu_info() -> Option<Vec<CpuInfo>> { + use windows_sys::Win32::System::WindowsProgramming::NtQuerySystemInformation; + use windows_sys::Win32::System::WindowsProgramming::SystemProcessorPerformanceInformation; + use windows_sys::Win32::System::WindowsProgramming::SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION; + + use std::os::windows::ffi::OsStrExt; + use std::os::windows::ffi::OsStringExt; + + fn encode_wide(s: &str) -> Vec<u16> { + std::ffi::OsString::from(s) + .encode_wide() + .chain(Some(0)) + .collect() + } + + // Safety: Assumes correct behavior of platform-specific syscalls and data structures. + unsafe { + let mut system_info: winapi::um::sysinfoapi::SYSTEM_INFO = + std::mem::zeroed(); + winapi::um::sysinfoapi::GetSystemInfo(&mut system_info); + + let cpu_count = system_info.dwNumberOfProcessors as usize; + + let mut cpus = vec![CpuInfo::new(); cpu_count]; + + let mut sppi: Vec<SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION> = + vec![std::mem::zeroed(); cpu_count]; + + let sppi_size = std::mem::size_of_val(&sppi[0]) * cpu_count; + let mut result_size = 0; + + let status = NtQuerySystemInformation( + SystemProcessorPerformanceInformation, + sppi.as_mut_ptr() as *mut _, + sppi_size as u32, + &mut result_size, + ); + if status != 0 { + return None; + } + + assert_eq!(result_size, sppi_size as u32); + + for i in 0..cpu_count { + let key_name = + format!("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\{}", i); + let key_name = encode_wide(&key_name); + + let mut processor_key: windows_sys::Win32::System::Registry::HKEY = + std::mem::zeroed(); + let err = windows_sys::Win32::System::Registry::RegOpenKeyExW( + windows_sys::Win32::System::Registry::HKEY_LOCAL_MACHINE, + key_name.as_ptr(), + 0, + windows_sys::Win32::System::Registry::KEY_QUERY_VALUE, + &mut processor_key, + ); + + if err != 0 { + return None; + } + + let mut cpu_speed = 0; + let mut cpu_speed_size = std::mem::size_of_val(&cpu_speed) as u32; + + let err = windows_sys::Win32::System::Registry::RegQueryValueExW( + processor_key, + encode_wide("~MHz").as_ptr() as *mut _, + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut cpu_speed as *mut _ as *mut _, + &mut cpu_speed_size, + ); + + if err != 0 { + return None; + } + + let cpu_brand: [u16; 512] = [0; 512]; + let mut cpu_brand_size = std::mem::size_of_val(&cpu_brand) as u32; + + let err = windows_sys::Win32::System::Registry::RegQueryValueExW( + processor_key, + encode_wide("ProcessorNameString").as_ptr() as *mut _, + std::ptr::null_mut(), + std::ptr::null_mut(), + cpu_brand.as_ptr() as *mut _, + &mut cpu_brand_size, + ); + windows_sys::Win32::System::Registry::RegCloseKey(processor_key); + + if err != 0 { + return None; + } + + let cpu_brand = + std::ffi::OsString::from_wide(&cpu_brand[..cpu_brand_size as usize]) + .into_string() + .unwrap(); + + cpus[i].model = cpu_brand; + cpus[i].speed = cpu_speed as u64; + + cpus[i].times.user = sppi[i].UserTime as u64 / 10000; + cpus[i].times.sys = + (sppi[i].KernelTime - sppi[i].IdleTime) as u64 / 10000; + cpus[i].times.idle = sppi[i].IdleTime as u64 / 10000; + /* InterruptTime is Reserved1[1] */ + cpus[i].times.irq = sppi[i].Reserved1[1] as u64 / 10000; + cpus[i].times.nice = 0; + } + Some(cpus) + } +} + +#[cfg(target_os = "linux")] +pub fn cpu_info() -> Option<Vec<CpuInfo>> { + use std::io::BufRead; + + let mut cpus = vec![CpuInfo::new(); 8192]; /* Kernel maxmimum */ + + let fp = std::fs::File::open("/proc/stat").ok()?; + let reader = std::io::BufReader::new(fp); + + for (i, line) in reader.lines().enumerate() { + let line = line.ok()?; + if !line.starts_with("cpu") { + break; + } + let mut fields = line.split_whitespace(); + fields.next()?; + let user = fields.next()?.parse::<u64>().ok()?; + let nice = fields.next()?.parse::<u64>().ok()?; + let sys = fields.next()?.parse::<u64>().ok()?; + let idle = fields.next()?.parse::<u64>().ok()?; + let irq = fields.next()?.parse::<u64>().ok()?; + + cpus[i].times.user = user; + cpus[i].times.nice = nice; + cpus[i].times.sys = sys; + cpus[i].times.idle = idle; + cpus[i].times.irq = irq; + } + + let fp = std::fs::File::open("/proc/cpuinfo").ok()?; + let reader = std::io::BufReader::new(fp); + + let mut i = 0; + for line in reader.lines() { + let line = line.ok()?; + if !line.starts_with("model name") { + continue; + } + let mut fields = line.splitn(2, ':'); + fields.next()?; + let model = fields.next()?.trim(); + + cpus[i].model = model.to_string(); + i += 1; + } + + cpus.truncate(i); + Some(cpus) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cpu_info() { + let info = cpu_info(); + assert!(info.is_some()); + let info = info.unwrap(); + assert!(!info.is_empty()); + for cpu in info { + assert!(!cpu.model.is_empty()); + assert!(cpu.times.user > 0); + assert!(cpu.times.sys > 0); + assert!(cpu.times.idle > 0); + } + } +} diff --git a/ext/node/ops/os/mod.rs b/ext/node/ops/os/mod.rs new file mode 100644 index 000000000..4fadc1ff8 --- /dev/null +++ b/ext/node/ops/os/mod.rs @@ -0,0 +1,90 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use crate::NodePermissions; +use deno_core::error::type_error; +use deno_core::error::AnyError; +use deno_core::op2; +use deno_core::OpState; + +mod cpus; +mod priority; + +#[op2(fast)] +pub fn op_node_os_get_priority<P>( + state: &mut OpState, + pid: u32, +) -> Result<i32, AnyError> +where + P: NodePermissions + 'static, +{ + { + let permissions = state.borrow_mut::<P>(); + permissions.check_sys("getPriority", "node:os.getPriority()")?; + } + + priority::get_priority(pid) +} + +#[op2(fast)] +pub fn op_node_os_set_priority<P>( + state: &mut OpState, + pid: u32, + priority: i32, +) -> Result<(), AnyError> +where + P: NodePermissions + 'static, +{ + { + let permissions = state.borrow_mut::<P>(); + permissions.check_sys("setPriority", "node:os.setPriority()")?; + } + + priority::set_priority(pid, priority) +} + +#[op2] +#[string] +pub fn op_node_os_username<P>(state: &mut OpState) -> Result<String, AnyError> +where + P: NodePermissions + 'static, +{ + { + let permissions = state.borrow_mut::<P>(); + permissions.check_sys("userInfo", "node:os.userInfo()")?; + } + + Ok(deno_whoami::username()) +} + +#[op2(fast)] +pub fn op_geteuid<P>(state: &mut OpState) -> Result<u32, AnyError> +where + P: NodePermissions + 'static, +{ + { + let permissions = state.borrow_mut::<P>(); + permissions.check_sys("geteuid", "node:os.geteuid()")?; + } + + #[cfg(windows)] + let euid = 0; + #[cfg(unix)] + // SAFETY: Call to libc geteuid. + let euid = unsafe { libc::geteuid() }; + + Ok(euid) +} + +#[op2] +#[serde] +pub fn op_cpus<P>(state: &mut OpState) -> Result<Vec<cpus::CpuInfo>, AnyError> +where + P: NodePermissions + 'static, +{ + { + let permissions = state.borrow_mut::<P>(); + permissions.check_sys("cpus", "node:os.cpus()")?; + } + + cpus::cpu_info().ok_or_else(|| type_error("Failed to get cpu info")) +} diff --git a/ext/node/ops/os/priority.rs b/ext/node/ops/os/priority.rs new file mode 100644 index 000000000..2d5994705 --- /dev/null +++ b/ext/node/ops/os/priority.rs @@ -0,0 +1,136 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::AnyError; + +pub use impl_::*; + +#[cfg(unix)] +mod impl_ { + use super::*; + use errno::errno; + use errno::set_errno; + use errno::Errno; + use libc::id_t; + use libc::PRIO_PROCESS; + + const PRIORITY_HIGH: i32 = -14; + + // Ref: https://github.com/libuv/libuv/blob/55376b044b74db40772e8a6e24d67a8673998e02/src/unix/core.c#L1533-L1547 + pub fn get_priority(pid: u32) -> Result<i32, AnyError> { + set_errno(Errno(0)); + match ( + // SAFETY: libc::getpriority is unsafe + unsafe { libc::getpriority(PRIO_PROCESS, pid as id_t) }, + errno(), + ) { + (-1, Errno(0)) => Ok(PRIORITY_HIGH), + (-1, _) => Err(std::io::Error::last_os_error().into()), + (priority, _) => Ok(priority), + } + } + + pub fn set_priority(pid: u32, priority: i32) -> Result<(), AnyError> { + // SAFETY: libc::setpriority is unsafe + match unsafe { libc::setpriority(PRIO_PROCESS, pid as id_t, priority) } { + -1 => Err(std::io::Error::last_os_error().into()), + _ => Ok(()), + } + } +} + +#[cfg(windows)] +mod impl_ { + use super::*; + use deno_core::error::type_error; + use winapi::shared::minwindef::DWORD; + use winapi::shared::minwindef::FALSE; + use winapi::shared::ntdef::NULL; + use winapi::um::handleapi::CloseHandle; + use winapi::um::processthreadsapi::GetCurrentProcess; + use winapi::um::processthreadsapi::GetPriorityClass; + use winapi::um::processthreadsapi::OpenProcess; + use winapi::um::processthreadsapi::SetPriorityClass; + use winapi::um::winbase::ABOVE_NORMAL_PRIORITY_CLASS; + use winapi::um::winbase::BELOW_NORMAL_PRIORITY_CLASS; + use winapi::um::winbase::HIGH_PRIORITY_CLASS; + use winapi::um::winbase::IDLE_PRIORITY_CLASS; + use winapi::um::winbase::NORMAL_PRIORITY_CLASS; + use winapi::um::winbase::REALTIME_PRIORITY_CLASS; + use winapi::um::winnt::PROCESS_QUERY_LIMITED_INFORMATION; + + // Taken from: https://github.com/libuv/libuv/blob/a877ca2435134ef86315326ef4ef0c16bdbabf17/include/uv.h#L1318-L1323 + const PRIORITY_LOW: i32 = 19; + const PRIORITY_BELOW_NORMAL: i32 = 10; + const PRIORITY_NORMAL: i32 = 0; + const PRIORITY_ABOVE_NORMAL: i32 = -7; + const PRIORITY_HIGH: i32 = -14; + const PRIORITY_HIGHEST: i32 = -20; + + // Ported from: https://github.com/libuv/libuv/blob/a877ca2435134ef86315326ef4ef0c16bdbabf17/src/win/util.c#L1649-L1685 + pub fn get_priority(pid: u32) -> Result<i32, AnyError> { + // SAFETY: Windows API calls + unsafe { + let handle = if pid == 0 { + GetCurrentProcess() + } else { + OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid as DWORD) + }; + if handle == NULL { + Err(std::io::Error::last_os_error().into()) + } else { + let result = match GetPriorityClass(handle) { + 0 => Err(std::io::Error::last_os_error().into()), + REALTIME_PRIORITY_CLASS => Ok(PRIORITY_HIGHEST), + HIGH_PRIORITY_CLASS => Ok(PRIORITY_HIGH), + ABOVE_NORMAL_PRIORITY_CLASS => Ok(PRIORITY_ABOVE_NORMAL), + NORMAL_PRIORITY_CLASS => Ok(PRIORITY_NORMAL), + BELOW_NORMAL_PRIORITY_CLASS => Ok(PRIORITY_BELOW_NORMAL), + IDLE_PRIORITY_CLASS => Ok(PRIORITY_LOW), + _ => Ok(PRIORITY_LOW), + }; + CloseHandle(handle); + result + } + } + } + + // Ported from: https://github.com/libuv/libuv/blob/a877ca2435134ef86315326ef4ef0c16bdbabf17/src/win/util.c#L1688-L1719 + pub fn set_priority(pid: u32, priority: i32) -> Result<(), AnyError> { + // SAFETY: Windows API calls + unsafe { + let handle = if pid == 0 { + GetCurrentProcess() + } else { + OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid as DWORD) + }; + if handle == NULL { + Err(std::io::Error::last_os_error().into()) + } else { + #[allow(clippy::manual_range_contains)] + let priority_class = + if priority < PRIORITY_HIGHEST || priority > PRIORITY_LOW { + return Err(type_error("Invalid priority")); + } else if priority < PRIORITY_HIGH { + REALTIME_PRIORITY_CLASS + } else if priority < PRIORITY_ABOVE_NORMAL { + HIGH_PRIORITY_CLASS + } else if priority < PRIORITY_NORMAL { + ABOVE_NORMAL_PRIORITY_CLASS + } else if priority < PRIORITY_BELOW_NORMAL { + NORMAL_PRIORITY_CLASS + } else if priority < PRIORITY_LOW { + BELOW_NORMAL_PRIORITY_CLASS + } else { + IDLE_PRIORITY_CLASS + }; + + let result = match SetPriorityClass(handle, priority_class) { + FALSE => Err(std::io::Error::last_os_error().into()), + _ => Ok(()), + }; + CloseHandle(handle); + result + } + } + } +} |