From c9baf3849fdbe161a9251a712a71e2b91eeabf3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 15 Nov 2024 09:33:03 +0000 Subject: perf: use available system memory for v8 isolate memory limit (#26868) Instead of using the default 1.4Gb limit (which was meant for browser tabs) configure V8 to set the heap limit to the amount of memory available in the system. Closes https://github.com/denoland/deno/issues/23424 Closes https://github.com/denoland/deno/issues/26435 Closes https://github.com/denoland/deno/issues/21226 --- cli/lsp/tsc.rs | 2 + cli/tsc/mod.rs | 2 + cli/worker.rs | 15 +- runtime/lib.rs | 1 + runtime/ops/os/mod.rs | 3 +- runtime/ops/os/sys_info.rs | 425 --------------------------------------------- runtime/sys_info.rs | 425 +++++++++++++++++++++++++++++++++++++++++++++ runtime/web_worker.rs | 3 + 8 files changed, 448 insertions(+), 428 deletions(-) delete mode 100644 runtime/ops/os/sys_info.rs create mode 100644 runtime/sys_info.rs diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index c9b24176a..48dcb7964 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -34,6 +34,7 @@ use crate::util::path::relative_specifier; use crate::util::path::to_percent_decoded_str; use crate::util::result::InfallibleResultExt; use crate::util::v8::convert; +use crate::worker::create_isolate_create_params; use deno_core::convert::Smi; use deno_core::convert::ToV8; use deno_core::error::StdAnyError; @@ -4760,6 +4761,7 @@ fn run_tsc_thread( specifier_map, request_rx, )], + create_params: create_isolate_create_params(), startup_snapshot: Some(tsc::compiler_snapshot()), inspector: has_inspector_server, ..Default::default() diff --git a/cli/tsc/mod.rs b/cli/tsc/mod.rs index 452d5c165..8f8bed20a 100644 --- a/cli/tsc/mod.rs +++ b/cli/tsc/mod.rs @@ -9,6 +9,7 @@ use crate::npm::CliNpmResolver; use crate::resolver::CjsTracker; use crate::util::checksum; use crate::util::path::mapped_specifier_for_tsc; +use crate::worker::create_isolate_create_params; use deno_ast::MediaType; use deno_core::anyhow::anyhow; @@ -1104,6 +1105,7 @@ pub fn exec(request: Request) -> Result { root_map, remapped_specifiers, )], + create_params: create_isolate_create_params(), ..Default::default() }); diff --git a/cli/worker.rs b/cli/worker.rs index 1afe37e34..c6cbf77f1 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -555,6 +555,7 @@ impl CliMainWorkerFactory { permissions, v8_code_cache: shared.code_cache.clone(), }; + let options = WorkerOptions { bootstrap: BootstrapOptions { deno_version: crate::version::DENO_VERSION_INFO.deno.to_string(), @@ -585,7 +586,7 @@ impl CliMainWorkerFactory { }, extensions: custom_extensions, startup_snapshot: crate::js::deno_isolate_init(), - create_params: None, + create_params: create_isolate_create_params(), unsafely_ignore_certificate_errors: shared .options .unsafely_ignore_certificate_errors @@ -786,6 +787,7 @@ fn create_web_worker_callback( }, extensions: vec![], startup_snapshot: crate::js::deno_isolate_init(), + create_params: create_isolate_create_params(), unsafely_ignore_certificate_errors: shared .options .unsafely_ignore_certificate_errors @@ -806,6 +808,17 @@ fn create_web_worker_callback( }) } +/// By default V8 uses 1.4Gb heap limit which is meant for browser tabs. +/// Instead probe for the total memory on the system and use it instead +/// as a default. +pub fn create_isolate_create_params() -> Option { + let maybe_mem_info = deno_runtime::sys_info::mem_info(); + maybe_mem_info.map(|mem_info| { + v8::CreateParams::default() + .heap_limits_from_system_memory(mem_info.total, 0) + }) +} + #[allow(clippy::print_stdout)] #[allow(clippy::print_stderr)] #[cfg(test)] diff --git a/runtime/lib.rs b/runtime/lib.rs index 8a228c5b5..a6e60ced1 100644 --- a/runtime/lib.rs +++ b/runtime/lib.rs @@ -35,6 +35,7 @@ pub mod js; pub mod ops; pub mod permissions; pub mod snapshot; +pub mod sys_info; pub mod tokio_util; pub mod web_worker; pub mod worker; diff --git a/runtime/ops/os/mod.rs b/runtime/ops/os/mod.rs index b10a2939e..74c708c53 100644 --- a/runtime/ops/os/mod.rs +++ b/runtime/ops/os/mod.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use crate::sys_info; use crate::worker::ExitCode; use deno_core::op2; use deno_core::v8; @@ -11,8 +12,6 @@ use serde::Serialize; use std::collections::HashMap; use std::env; -mod sys_info; - deno_core::extension!( deno_os, ops = [ diff --git a/runtime/ops/os/sys_info.rs b/runtime/ops/os/sys_info.rs deleted file mode 100644 index cffc90e9d..000000000 --- a/runtime/ops/os/sys_info.rs +++ /dev/null @@ -1,425 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -#[cfg(target_family = "windows")] -use std::sync::Once; - -type LoadAvg = (f64, f64, f64); -const DEFAULT_LOADAVG: LoadAvg = (0.0, 0.0, 0.0); - -pub fn loadavg() -> LoadAvg { - #[cfg(any(target_os = "android", target_os = "linux"))] - { - use libc::SI_LOAD_SHIFT; - - let mut info = std::mem::MaybeUninit::uninit(); - // SAFETY: `info` is a valid pointer to a `libc::sysinfo` struct. - let res = unsafe { libc::sysinfo(info.as_mut_ptr()) }; - if res == 0 { - // SAFETY: `sysinfo` returns 0 on success, and `info` is initialized. - let info = unsafe { info.assume_init() }; - ( - info.loads[0] as f64 / (1 << SI_LOAD_SHIFT) as f64, - info.loads[1] as f64 / (1 << SI_LOAD_SHIFT) as f64, - info.loads[2] as f64 / (1 << SI_LOAD_SHIFT) as f64, - ) - } else { - DEFAULT_LOADAVG - } - } - #[cfg(any( - target_vendor = "apple", - target_os = "freebsd", - target_os = "openbsd" - ))] - { - let mut l: [f64; 3] = [0.; 3]; - // SAFETY: `&mut l` is a valid pointer to an array of 3 doubles - if unsafe { libc::getloadavg(&mut l as *mut f64, l.len() as _) } < 3 { - DEFAULT_LOADAVG - } else { - (l[0], l[1], l[2]) - } - } - #[cfg(target_os = "windows")] - { - DEFAULT_LOADAVG - } -} - -pub fn os_release() -> String { - #[cfg(target_os = "linux")] - { - #[allow(clippy::disallowed_methods)] - match std::fs::read_to_string("/proc/sys/kernel/osrelease") { - Ok(mut s) => { - s.pop(); // pop '\n' - s - } - _ => String::from(""), - } - } - #[cfg(target_os = "android")] - { - let mut info = std::mem::MaybeUninit::uninit(); - // SAFETY: `info` is a valid pointer to a `libc::utsname` struct. - let res = unsafe { libc::uname(info.as_mut_ptr()) }; - if res != 0 { - return String::from(""); - } - // SAFETY: `uname` returns 0 on success, and `info` is initialized. - let mut info = unsafe { info.assume_init() }; - let len = info.release.len(); - info.release[len - 1] = 0; - // SAFETY: `info.release` is a valid pointer and NUL-terminated. - let c_str = unsafe { std::ffi::CStr::from_ptr(info.release.as_ptr()) }; - c_str.to_string_lossy().into_owned() - } - #[cfg(any( - target_vendor = "apple", - target_os = "freebsd", - target_os = "openbsd" - ))] - { - let mut s = [0u8; 256]; - let mut mib = [libc::CTL_KERN, libc::KERN_OSRELEASE]; - // 256 is enough. - let mut len = s.len(); - // SAFETY: `sysctl` is thread-safe. - // `s` is only accessed if sysctl() succeeds and agrees with the `len` set - // by sysctl(). - if unsafe { - libc::sysctl( - mib.as_mut_ptr(), - mib.len() as _, - s.as_mut_ptr() as _, - &mut len, - std::ptr::null_mut(), - 0, - ) - } == -1 - { - return String::from("Unknown"); - } - - // without the NUL terminator - return String::from_utf8_lossy(&s[..len - 1]).to_string(); - } - #[cfg(target_family = "windows")] - { - use ntapi::ntrtl::RtlGetVersion; - use winapi::shared::ntdef::NT_SUCCESS; - use winapi::um::winnt::RTL_OSVERSIONINFOEXW; - - let mut version_info = - std::mem::MaybeUninit::::uninit(); - // SAFETY: we need to initialize dwOSVersionInfoSize. - unsafe { - (*version_info.as_mut_ptr()).dwOSVersionInfoSize = - std::mem::size_of::() as u32; - } - // SAFETY: `version_info` is pointer to a valid `RTL_OSVERSIONINFOEXW` struct and - // dwOSVersionInfoSize is set to the size of RTL_OSVERSIONINFOEXW. - if !NT_SUCCESS(unsafe { - RtlGetVersion(version_info.as_mut_ptr() as *mut _) - }) { - String::from("") - } else { - // SAFETY: we assume that RtlGetVersion() initializes the fields. - let version_info = unsafe { version_info.assume_init() }; - format!( - "{}.{}.{}", - version_info.dwMajorVersion, - version_info.dwMinorVersion, - version_info.dwBuildNumber - ) - } - } -} - -#[cfg(target_family = "windows")] -static WINSOCKET_INIT: Once = Once::new(); - -pub fn hostname() -> String { - #[cfg(target_family = "unix")] - // SAFETY: `sysconf` returns a system constant. - unsafe { - let buf_size = libc::sysconf(libc::_SC_HOST_NAME_MAX) as usize; - let mut buf = vec![0u8; buf_size + 1]; - let len = buf.len(); - if libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, len) < 0 { - return String::from(""); - } - // ensure NUL termination - buf[len - 1] = 0; - std::ffi::CStr::from_ptr(buf.as_ptr() as *const libc::c_char) - .to_string_lossy() - .to_string() - } - #[cfg(target_family = "windows")] - { - use std::ffi::OsString; - use std::mem; - use std::os::windows::ffi::OsStringExt; - use winapi::shared::minwindef::MAKEWORD; - use winapi::um::winsock2::GetHostNameW; - use winapi::um::winsock2::WSAStartup; - - let namelen = 256; - let mut name: Vec = vec![0u16; namelen]; - // Start winsock to make `GetHostNameW` work correctly - // https://github.com/retep998/winapi-rs/issues/296 - // SAFETY: winapi call - WINSOCKET_INIT.call_once(|| unsafe { - let mut data = mem::zeroed(); - let wsa_startup_result = WSAStartup(MAKEWORD(2, 2), &mut data); - if wsa_startup_result != 0 { - panic!("Failed to start winsocket"); - } - }); - let err = - // SAFETY: length of wide string is 256 chars or less. - // https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-gethostnamew - unsafe { GetHostNameW(name.as_mut_ptr(), namelen as libc::c_int) }; - - if err == 0 { - // TODO(@littledivy): Probably not the most efficient way. - let len = name.iter().take_while(|&&c| c != 0).count(); - OsString::from_wide(&name[..len]) - .to_string_lossy() - .into_owned() - } else { - String::from("") - } - } -} - -#[derive(serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct MemInfo { - pub total: u64, - pub free: u64, - pub available: u64, - pub buffers: u64, - pub cached: u64, - pub swap_total: u64, - pub swap_free: u64, -} - -pub fn mem_info() -> Option { - let mut mem_info = MemInfo { - total: 0, - free: 0, - available: 0, - buffers: 0, - cached: 0, - swap_total: 0, - swap_free: 0, - }; - #[cfg(any(target_os = "android", target_os = "linux"))] - { - let mut info = std::mem::MaybeUninit::uninit(); - // SAFETY: `info` is a valid pointer to a `libc::sysinfo` struct. - let res = unsafe { libc::sysinfo(info.as_mut_ptr()) }; - if res == 0 { - // SAFETY: `sysinfo` initializes the struct. - let info = unsafe { info.assume_init() }; - let mem_unit = info.mem_unit as u64; - mem_info.swap_total = info.totalswap * mem_unit; - mem_info.swap_free = info.freeswap * mem_unit; - mem_info.total = info.totalram * mem_unit; - mem_info.free = info.freeram * mem_unit; - mem_info.available = mem_info.free; - mem_info.buffers = info.bufferram * mem_unit; - } - - // Gets the available memory from /proc/meminfo in linux for compatibility - #[allow(clippy::disallowed_methods)] - if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") { - let line = meminfo.lines().find(|l| l.starts_with("MemAvailable:")); - if let Some(line) = line { - let mem = line.split_whitespace().nth(1); - let mem = mem.and_then(|v| v.parse::().ok()); - mem_info.available = mem.unwrap_or(0) * 1024; - } - } - } - #[cfg(target_vendor = "apple")] - { - let mut mib: [i32; 2] = [0, 0]; - mib[0] = libc::CTL_HW; - mib[1] = libc::HW_MEMSIZE; - // SAFETY: - // - We assume that `mach_host_self` always returns a valid value. - // - sysconf returns a system constant. - unsafe { - let mut size = std::mem::size_of::(); - libc::sysctl( - mib.as_mut_ptr(), - mib.len() as _, - &mut mem_info.total as *mut _ as *mut libc::c_void, - &mut size, - std::ptr::null_mut(), - 0, - ); - - let mut xs: libc::xsw_usage = std::mem::zeroed::(); - mib[0] = libc::CTL_VM; - mib[1] = libc::VM_SWAPUSAGE; - - let mut size = std::mem::size_of::(); - libc::sysctl( - mib.as_mut_ptr(), - mib.len() as _, - &mut xs as *mut _ as *mut libc::c_void, - &mut size, - std::ptr::null_mut(), - 0, - ); - - mem_info.swap_total = xs.xsu_total; - mem_info.swap_free = xs.xsu_avail; - - let mut count: u32 = libc::HOST_VM_INFO64_COUNT as _; - let mut stat = std::mem::zeroed::(); - if libc::host_statistics64( - // TODO(@littledivy): Put this in a once_cell. - libc::mach_host_self(), - libc::HOST_VM_INFO64, - &mut stat as *mut libc::vm_statistics64 as *mut _, - &mut count, - ) == libc::KERN_SUCCESS - { - // TODO(@littledivy): Put this in a once_cell - let page_size = libc::sysconf(libc::_SC_PAGESIZE) as u64; - mem_info.available = - (stat.free_count as u64 + stat.inactive_count as u64) * page_size - / 1024; - mem_info.free = - (stat.free_count as u64 - stat.speculative_count as u64) * page_size - / 1024; - } - } - } - #[cfg(target_family = "windows")] - // SAFETY: - // - `mem_status` is a valid pointer to a `libc::MEMORYSTATUSEX` struct. - // - `dwLength` is set to the size of the struct. - unsafe { - use std::mem; - use winapi::shared::minwindef; - use winapi::um::psapi::GetPerformanceInfo; - use winapi::um::psapi::PERFORMANCE_INFORMATION; - use winapi::um::sysinfoapi; - - let mut mem_status = - mem::MaybeUninit::::uninit(); - let length = - mem::size_of::() as minwindef::DWORD; - (*mem_status.as_mut_ptr()).dwLength = length; - - let result = sysinfoapi::GlobalMemoryStatusEx(mem_status.as_mut_ptr()); - if result != 0 { - let stat = mem_status.assume_init(); - mem_info.total = stat.ullTotalPhys; - mem_info.available = 0; - mem_info.free = stat.ullAvailPhys; - mem_info.cached = 0; - mem_info.buffers = 0; - - // `stat.ullTotalPageFile` is reliable only from GetPerformanceInfo() - // - // See https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex - // and https://github.com/GuillaumeGomez/sysinfo/issues/534 - - let mut perf_info = mem::MaybeUninit::::uninit(); - let result = GetPerformanceInfo( - perf_info.as_mut_ptr(), - mem::size_of::() as minwindef::DWORD, - ); - if result == minwindef::TRUE { - let perf_info = perf_info.assume_init(); - let swap_total = perf_info.PageSize - * perf_info - .CommitLimit - .saturating_sub(perf_info.PhysicalTotal); - let swap_free = perf_info.PageSize - * perf_info - .CommitLimit - .saturating_sub(perf_info.PhysicalTotal) - .saturating_sub(perf_info.PhysicalAvailable); - mem_info.swap_total = (swap_total / 1000) as u64; - mem_info.swap_free = (swap_free / 1000) as u64; - } - } - } - - Some(mem_info) -} - -pub fn os_uptime() -> u64 { - let uptime: u64; - - #[cfg(any(target_os = "android", target_os = "linux"))] - { - let mut info = std::mem::MaybeUninit::uninit(); - // SAFETY: `info` is a valid pointer to a `libc::sysinfo` struct. - let res = unsafe { libc::sysinfo(info.as_mut_ptr()) }; - uptime = if res == 0 { - // SAFETY: `sysinfo` initializes the struct. - let info = unsafe { info.assume_init() }; - info.uptime as u64 - } else { - 0 - } - } - - #[cfg(any( - target_vendor = "apple", - target_os = "freebsd", - target_os = "openbsd" - ))] - { - use std::mem; - use std::time::Duration; - use std::time::SystemTime; - let mut request = [libc::CTL_KERN, libc::KERN_BOOTTIME]; - // SAFETY: `boottime` is only accessed if sysctl() succeeds - // and agrees with the `size` set by sysctl(). - let mut boottime: libc::timeval = unsafe { mem::zeroed() }; - let mut size: libc::size_t = mem::size_of_val(&boottime) as libc::size_t; - // SAFETY: `sysctl` is thread-safe. - let res = unsafe { - libc::sysctl( - &mut request[0], - 2, - &mut boottime as *mut libc::timeval as *mut libc::c_void, - &mut size, - std::ptr::null_mut(), - 0, - ) - }; - uptime = if res == 0 { - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .map(|d| { - (d - Duration::new( - boottime.tv_sec as u64, - boottime.tv_usec as u32 * 1000, - )) - .as_secs() - }) - .unwrap_or_default() - } else { - 0 - } - } - - #[cfg(target_family = "windows")] - // SAFETY: windows API usage - unsafe { - // Windows is the only one that returns `uptime` in millisecond precision, - // so we need to get the seconds out of it to be in sync with other envs. - uptime = winapi::um::sysinfoapi::GetTickCount64() / 1000; - } - - uptime -} diff --git a/runtime/sys_info.rs b/runtime/sys_info.rs new file mode 100644 index 000000000..cffc90e9d --- /dev/null +++ b/runtime/sys_info.rs @@ -0,0 +1,425 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +#[cfg(target_family = "windows")] +use std::sync::Once; + +type LoadAvg = (f64, f64, f64); +const DEFAULT_LOADAVG: LoadAvg = (0.0, 0.0, 0.0); + +pub fn loadavg() -> LoadAvg { + #[cfg(any(target_os = "android", target_os = "linux"))] + { + use libc::SI_LOAD_SHIFT; + + let mut info = std::mem::MaybeUninit::uninit(); + // SAFETY: `info` is a valid pointer to a `libc::sysinfo` struct. + let res = unsafe { libc::sysinfo(info.as_mut_ptr()) }; + if res == 0 { + // SAFETY: `sysinfo` returns 0 on success, and `info` is initialized. + let info = unsafe { info.assume_init() }; + ( + info.loads[0] as f64 / (1 << SI_LOAD_SHIFT) as f64, + info.loads[1] as f64 / (1 << SI_LOAD_SHIFT) as f64, + info.loads[2] as f64 / (1 << SI_LOAD_SHIFT) as f64, + ) + } else { + DEFAULT_LOADAVG + } + } + #[cfg(any( + target_vendor = "apple", + target_os = "freebsd", + target_os = "openbsd" + ))] + { + let mut l: [f64; 3] = [0.; 3]; + // SAFETY: `&mut l` is a valid pointer to an array of 3 doubles + if unsafe { libc::getloadavg(&mut l as *mut f64, l.len() as _) } < 3 { + DEFAULT_LOADAVG + } else { + (l[0], l[1], l[2]) + } + } + #[cfg(target_os = "windows")] + { + DEFAULT_LOADAVG + } +} + +pub fn os_release() -> String { + #[cfg(target_os = "linux")] + { + #[allow(clippy::disallowed_methods)] + match std::fs::read_to_string("/proc/sys/kernel/osrelease") { + Ok(mut s) => { + s.pop(); // pop '\n' + s + } + _ => String::from(""), + } + } + #[cfg(target_os = "android")] + { + let mut info = std::mem::MaybeUninit::uninit(); + // SAFETY: `info` is a valid pointer to a `libc::utsname` struct. + let res = unsafe { libc::uname(info.as_mut_ptr()) }; + if res != 0 { + return String::from(""); + } + // SAFETY: `uname` returns 0 on success, and `info` is initialized. + let mut info = unsafe { info.assume_init() }; + let len = info.release.len(); + info.release[len - 1] = 0; + // SAFETY: `info.release` is a valid pointer and NUL-terminated. + let c_str = unsafe { std::ffi::CStr::from_ptr(info.release.as_ptr()) }; + c_str.to_string_lossy().into_owned() + } + #[cfg(any( + target_vendor = "apple", + target_os = "freebsd", + target_os = "openbsd" + ))] + { + let mut s = [0u8; 256]; + let mut mib = [libc::CTL_KERN, libc::KERN_OSRELEASE]; + // 256 is enough. + let mut len = s.len(); + // SAFETY: `sysctl` is thread-safe. + // `s` is only accessed if sysctl() succeeds and agrees with the `len` set + // by sysctl(). + if unsafe { + libc::sysctl( + mib.as_mut_ptr(), + mib.len() as _, + s.as_mut_ptr() as _, + &mut len, + std::ptr::null_mut(), + 0, + ) + } == -1 + { + return String::from("Unknown"); + } + + // without the NUL terminator + return String::from_utf8_lossy(&s[..len - 1]).to_string(); + } + #[cfg(target_family = "windows")] + { + use ntapi::ntrtl::RtlGetVersion; + use winapi::shared::ntdef::NT_SUCCESS; + use winapi::um::winnt::RTL_OSVERSIONINFOEXW; + + let mut version_info = + std::mem::MaybeUninit::::uninit(); + // SAFETY: we need to initialize dwOSVersionInfoSize. + unsafe { + (*version_info.as_mut_ptr()).dwOSVersionInfoSize = + std::mem::size_of::() as u32; + } + // SAFETY: `version_info` is pointer to a valid `RTL_OSVERSIONINFOEXW` struct and + // dwOSVersionInfoSize is set to the size of RTL_OSVERSIONINFOEXW. + if !NT_SUCCESS(unsafe { + RtlGetVersion(version_info.as_mut_ptr() as *mut _) + }) { + String::from("") + } else { + // SAFETY: we assume that RtlGetVersion() initializes the fields. + let version_info = unsafe { version_info.assume_init() }; + format!( + "{}.{}.{}", + version_info.dwMajorVersion, + version_info.dwMinorVersion, + version_info.dwBuildNumber + ) + } + } +} + +#[cfg(target_family = "windows")] +static WINSOCKET_INIT: Once = Once::new(); + +pub fn hostname() -> String { + #[cfg(target_family = "unix")] + // SAFETY: `sysconf` returns a system constant. + unsafe { + let buf_size = libc::sysconf(libc::_SC_HOST_NAME_MAX) as usize; + let mut buf = vec![0u8; buf_size + 1]; + let len = buf.len(); + if libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, len) < 0 { + return String::from(""); + } + // ensure NUL termination + buf[len - 1] = 0; + std::ffi::CStr::from_ptr(buf.as_ptr() as *const libc::c_char) + .to_string_lossy() + .to_string() + } + #[cfg(target_family = "windows")] + { + use std::ffi::OsString; + use std::mem; + use std::os::windows::ffi::OsStringExt; + use winapi::shared::minwindef::MAKEWORD; + use winapi::um::winsock2::GetHostNameW; + use winapi::um::winsock2::WSAStartup; + + let namelen = 256; + let mut name: Vec = vec![0u16; namelen]; + // Start winsock to make `GetHostNameW` work correctly + // https://github.com/retep998/winapi-rs/issues/296 + // SAFETY: winapi call + WINSOCKET_INIT.call_once(|| unsafe { + let mut data = mem::zeroed(); + let wsa_startup_result = WSAStartup(MAKEWORD(2, 2), &mut data); + if wsa_startup_result != 0 { + panic!("Failed to start winsocket"); + } + }); + let err = + // SAFETY: length of wide string is 256 chars or less. + // https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-gethostnamew + unsafe { GetHostNameW(name.as_mut_ptr(), namelen as libc::c_int) }; + + if err == 0 { + // TODO(@littledivy): Probably not the most efficient way. + let len = name.iter().take_while(|&&c| c != 0).count(); + OsString::from_wide(&name[..len]) + .to_string_lossy() + .into_owned() + } else { + String::from("") + } + } +} + +#[derive(serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MemInfo { + pub total: u64, + pub free: u64, + pub available: u64, + pub buffers: u64, + pub cached: u64, + pub swap_total: u64, + pub swap_free: u64, +} + +pub fn mem_info() -> Option { + let mut mem_info = MemInfo { + total: 0, + free: 0, + available: 0, + buffers: 0, + cached: 0, + swap_total: 0, + swap_free: 0, + }; + #[cfg(any(target_os = "android", target_os = "linux"))] + { + let mut info = std::mem::MaybeUninit::uninit(); + // SAFETY: `info` is a valid pointer to a `libc::sysinfo` struct. + let res = unsafe { libc::sysinfo(info.as_mut_ptr()) }; + if res == 0 { + // SAFETY: `sysinfo` initializes the struct. + let info = unsafe { info.assume_init() }; + let mem_unit = info.mem_unit as u64; + mem_info.swap_total = info.totalswap * mem_unit; + mem_info.swap_free = info.freeswap * mem_unit; + mem_info.total = info.totalram * mem_unit; + mem_info.free = info.freeram * mem_unit; + mem_info.available = mem_info.free; + mem_info.buffers = info.bufferram * mem_unit; + } + + // Gets the available memory from /proc/meminfo in linux for compatibility + #[allow(clippy::disallowed_methods)] + if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") { + let line = meminfo.lines().find(|l| l.starts_with("MemAvailable:")); + if let Some(line) = line { + let mem = line.split_whitespace().nth(1); + let mem = mem.and_then(|v| v.parse::().ok()); + mem_info.available = mem.unwrap_or(0) * 1024; + } + } + } + #[cfg(target_vendor = "apple")] + { + let mut mib: [i32; 2] = [0, 0]; + mib[0] = libc::CTL_HW; + mib[1] = libc::HW_MEMSIZE; + // SAFETY: + // - We assume that `mach_host_self` always returns a valid value. + // - sysconf returns a system constant. + unsafe { + let mut size = std::mem::size_of::(); + libc::sysctl( + mib.as_mut_ptr(), + mib.len() as _, + &mut mem_info.total as *mut _ as *mut libc::c_void, + &mut size, + std::ptr::null_mut(), + 0, + ); + + let mut xs: libc::xsw_usage = std::mem::zeroed::(); + mib[0] = libc::CTL_VM; + mib[1] = libc::VM_SWAPUSAGE; + + let mut size = std::mem::size_of::(); + libc::sysctl( + mib.as_mut_ptr(), + mib.len() as _, + &mut xs as *mut _ as *mut libc::c_void, + &mut size, + std::ptr::null_mut(), + 0, + ); + + mem_info.swap_total = xs.xsu_total; + mem_info.swap_free = xs.xsu_avail; + + let mut count: u32 = libc::HOST_VM_INFO64_COUNT as _; + let mut stat = std::mem::zeroed::(); + if libc::host_statistics64( + // TODO(@littledivy): Put this in a once_cell. + libc::mach_host_self(), + libc::HOST_VM_INFO64, + &mut stat as *mut libc::vm_statistics64 as *mut _, + &mut count, + ) == libc::KERN_SUCCESS + { + // TODO(@littledivy): Put this in a once_cell + let page_size = libc::sysconf(libc::_SC_PAGESIZE) as u64; + mem_info.available = + (stat.free_count as u64 + stat.inactive_count as u64) * page_size + / 1024; + mem_info.free = + (stat.free_count as u64 - stat.speculative_count as u64) * page_size + / 1024; + } + } + } + #[cfg(target_family = "windows")] + // SAFETY: + // - `mem_status` is a valid pointer to a `libc::MEMORYSTATUSEX` struct. + // - `dwLength` is set to the size of the struct. + unsafe { + use std::mem; + use winapi::shared::minwindef; + use winapi::um::psapi::GetPerformanceInfo; + use winapi::um::psapi::PERFORMANCE_INFORMATION; + use winapi::um::sysinfoapi; + + let mut mem_status = + mem::MaybeUninit::::uninit(); + let length = + mem::size_of::() as minwindef::DWORD; + (*mem_status.as_mut_ptr()).dwLength = length; + + let result = sysinfoapi::GlobalMemoryStatusEx(mem_status.as_mut_ptr()); + if result != 0 { + let stat = mem_status.assume_init(); + mem_info.total = stat.ullTotalPhys; + mem_info.available = 0; + mem_info.free = stat.ullAvailPhys; + mem_info.cached = 0; + mem_info.buffers = 0; + + // `stat.ullTotalPageFile` is reliable only from GetPerformanceInfo() + // + // See https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex + // and https://github.com/GuillaumeGomez/sysinfo/issues/534 + + let mut perf_info = mem::MaybeUninit::::uninit(); + let result = GetPerformanceInfo( + perf_info.as_mut_ptr(), + mem::size_of::() as minwindef::DWORD, + ); + if result == minwindef::TRUE { + let perf_info = perf_info.assume_init(); + let swap_total = perf_info.PageSize + * perf_info + .CommitLimit + .saturating_sub(perf_info.PhysicalTotal); + let swap_free = perf_info.PageSize + * perf_info + .CommitLimit + .saturating_sub(perf_info.PhysicalTotal) + .saturating_sub(perf_info.PhysicalAvailable); + mem_info.swap_total = (swap_total / 1000) as u64; + mem_info.swap_free = (swap_free / 1000) as u64; + } + } + } + + Some(mem_info) +} + +pub fn os_uptime() -> u64 { + let uptime: u64; + + #[cfg(any(target_os = "android", target_os = "linux"))] + { + let mut info = std::mem::MaybeUninit::uninit(); + // SAFETY: `info` is a valid pointer to a `libc::sysinfo` struct. + let res = unsafe { libc::sysinfo(info.as_mut_ptr()) }; + uptime = if res == 0 { + // SAFETY: `sysinfo` initializes the struct. + let info = unsafe { info.assume_init() }; + info.uptime as u64 + } else { + 0 + } + } + + #[cfg(any( + target_vendor = "apple", + target_os = "freebsd", + target_os = "openbsd" + ))] + { + use std::mem; + use std::time::Duration; + use std::time::SystemTime; + let mut request = [libc::CTL_KERN, libc::KERN_BOOTTIME]; + // SAFETY: `boottime` is only accessed if sysctl() succeeds + // and agrees with the `size` set by sysctl(). + let mut boottime: libc::timeval = unsafe { mem::zeroed() }; + let mut size: libc::size_t = mem::size_of_val(&boottime) as libc::size_t; + // SAFETY: `sysctl` is thread-safe. + let res = unsafe { + libc::sysctl( + &mut request[0], + 2, + &mut boottime as *mut libc::timeval as *mut libc::c_void, + &mut size, + std::ptr::null_mut(), + 0, + ) + }; + uptime = if res == 0 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .map(|d| { + (d - Duration::new( + boottime.tv_sec as u64, + boottime.tv_usec as u32 * 1000, + )) + .as_secs() + }) + .unwrap_or_default() + } else { + 0 + } + } + + #[cfg(target_family = "windows")] + // SAFETY: windows API usage + unsafe { + // Windows is the only one that returns `uptime` in millisecond precision, + // so we need to get the seconds out of it to be in sync with other envs. + uptime = winapi::um::sysinfoapi::GetTickCount64() / 1000; + } + + uptime +} diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs index b056e01fc..8e0c870d1 100644 --- a/runtime/web_worker.rs +++ b/runtime/web_worker.rs @@ -361,6 +361,8 @@ pub struct WebWorkerOptions { pub extensions: Vec, pub startup_snapshot: Option<&'static [u8]>, pub unsafely_ignore_certificate_errors: Option>, + /// Optional isolate creation parameters, such as heap limits. + pub create_params: Option, pub seed: Option, pub create_web_worker_cb: Arc, pub format_js_error_fn: Option>, @@ -563,6 +565,7 @@ impl WebWorker { let mut js_runtime = JsRuntime::new(RuntimeOptions { module_loader: Some(services.module_loader), startup_snapshot: options.startup_snapshot, + create_params: options.create_params, get_error_class_fn: options.get_error_class_fn, shared_array_buffer_store: services.shared_array_buffer_store, compiled_wasm_module_store: services.compiled_wasm_module_store, -- cgit v1.2.3