diff --git a/cli/tests/unit/os_test.ts b/cli/tests/unit/os_test.ts index 72e0b57bae..5e88f02c19 100644 --- a/cli/tests/unit/os_test.ts +++ b/cli/tests/unit/os_test.ts @@ -272,3 +272,12 @@ Deno.test({ permissions: { sys: ["gid"] } }, function getGid() { assert(gid > 0); } }); + +Deno.test(function memoryUsage() { + const mem = Deno.memoryUsage(); + assert(typeof mem.rss === "number"); + assert(typeof mem.heapTotal === "number"); + assert(typeof mem.heapUsed === "number"); + assert(typeof mem.external === "number"); + assert(mem.rss >= mem.heapTotal); +}); diff --git a/core/ops_builtin_v8.rs b/core/ops_builtin_v8.rs index 5f4f875ee6..880a87c8d7 100644 --- a/core/ops_builtin_v8.rs +++ b/core/ops_builtin_v8.rs @@ -654,7 +654,7 @@ fn op_get_proxy_details<'a>( #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct MemoryUsage { - rss: usize, + physical_total: usize, heap_total: usize, heap_used: usize, external: usize, @@ -668,7 +668,7 @@ fn op_memory_usage(scope: &mut v8::HandleScope) -> MemoryUsage { let mut s = v8::HeapStatistics::default(); scope.get_heap_statistics(&mut s); MemoryUsage { - rss: s.total_physical_size(), + physical_total: s.total_physical_size(), heap_total: s.total_heap_size(), heap_used: s.used_heap_size(), external: s.external_memory(), diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index cc7710dfe7..8d198e7861 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -94,7 +94,7 @@ uuid.workspace = true [target.'cfg(windows)'.dependencies] fwdansi.workspace = true -winapi = { workspace = true, features = ["commapi", "knownfolders", "mswsock", "objbase", "shlobj", "tlhelp32", "winbase", "winerror", "winsock2"] } +winapi = { workspace = true, features = ["commapi", "knownfolders", "mswsock", "objbase", "psapi", "shlobj", "tlhelp32", "winbase", "winerror", "winsock2"] } ntapi = "0.4.0" [target.'cfg(unix)'.dependencies] diff --git a/runtime/js/90_deno_ns.js b/runtime/js/90_deno_ns.js index e3ccf1b6fd..94a6114564 100644 --- a/runtime/js/90_deno_ns.js +++ b/runtime/js/90_deno_ns.js @@ -29,7 +29,7 @@ makeTempDir: __bootstrap.fs.makeTempDir, makeTempFileSync: __bootstrap.fs.makeTempFileSync, makeTempFile: __bootstrap.fs.makeTempFile, - memoryUsage: core.memoryUsage, + memoryUsage: () => core.ops.op_runtime_memory_usage(), mkdirSync: __bootstrap.fs.mkdirSync, mkdir: __bootstrap.fs.mkdir, chdir: __bootstrap.fs.chdir, diff --git a/runtime/ops/os/mod.rs b/runtime/ops/os/mod.rs index b93935955b..613b4507db 100644 --- a/runtime/ops/os/mod.rs +++ b/runtime/ops/os/mod.rs @@ -4,11 +4,14 @@ use super::utils::into_string; use crate::permissions::Permissions; use crate::worker::ExitCode; use deno_core::error::{type_error, AnyError}; +use deno_core::op; use deno_core::url::Url; +use deno_core::v8; use deno_core::Extension; +use deno_core::ExtensionBuilder; use deno_core::OpState; -use deno_core::{op, ExtensionBuilder}; use deno_node::NODE_ENV_VAR_ALLOWLIST; +use serde::Serialize; use std::collections::HashMap; use std::env; @@ -30,6 +33,7 @@ fn init_ops(builder: &mut ExtensionBuilder) -> &mut ExtensionBuilder { op_set_exit_code::decl(), op_system_memory_info::decl(), op_uid::decl(), + op_runtime_memory_usage::decl(), ]) } @@ -297,3 +301,125 @@ fn op_uid(state: &mut OpState) -> Result, AnyError> { .check("uid", Some("Deno.uid()"))?; Ok(None) } + +// HeapStats stores values from a isolate.get_heap_statistics() call +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct MemoryUsage { + rss: usize, + heap_total: usize, + heap_used: usize, + external: usize, +} + +#[op(v8)] +fn op_runtime_memory_usage(scope: &mut v8::HandleScope) -> MemoryUsage { + let mut s = v8::HeapStatistics::default(); + scope.get_heap_statistics(&mut s); + MemoryUsage { + rss: rss(), + heap_total: s.total_heap_size(), + heap_used: s.used_heap_size(), + external: s.external_memory(), + } +} + +#[cfg(target_os = "linux")] +fn rss() -> usize { + // Inspired by https://github.com/Arc-blroth/memory-stats/blob/5364d0d09143de2a470d33161b2330914228fde9/src/linux.rs + + // Extracts a positive integer from a string that + // may contain leading spaces and trailing chars. + // Returns the extracted number and the index of + // the next character in the string. + fn scan_int(string: &str) -> (usize, usize) { + let mut out = 0; + let mut idx = 0; + let mut chars = string.chars().peekable(); + while let Some(' ') = chars.next_if_eq(&' ') { + idx += 1; + } + for n in chars { + idx += 1; + if ('0'..='9').contains(&n) { + out *= 10; + out += n as usize - '0' as usize; + } else { + break; + } + } + (out, idx) + } + + let statm_content = if let Ok(c) = std::fs::read_to_string("/proc/self/statm") + { + c + } else { + return 0; + }; + + // statm returns the virtual size and rss, in + // multiples of the page size, as the first + // two columns of output. + // SAFETY: libc call + let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) }; + + if page_size < 0 { + return 0; + } + + let (_total_size_pages, idx) = scan_int(&statm_content); + let (total_rss_pages, _) = scan_int(&statm_content[idx..]); + + total_rss_pages * page_size as usize +} + +#[cfg(target_os = "macos")] +fn rss() -> usize { + // Inspired by https://github.com/Arc-blroth/memory-stats/blob/5364d0d09143de2a470d33161b2330914228fde9/src/darwin.rs + + let mut task_info = + std::mem::MaybeUninit::::uninit(); + let mut count = libc::MACH_TASK_BASIC_INFO_COUNT; + // SAFETY: libc calls + let r = unsafe { + libc::task_info( + libc::mach_task_self(), + libc::MACH_TASK_BASIC_INFO, + task_info.as_mut_ptr() as libc::task_info_t, + &mut count as *mut libc::mach_msg_type_number_t, + ) + }; + // According to libuv this should never fail + assert_eq!(r, libc::KERN_SUCCESS); + // SAFETY: we just asserted that it was success + let task_info = unsafe { task_info.assume_init() }; + task_info.resident_size as usize +} + +#[cfg(windows)] +fn rss() -> usize { + use winapi::shared::minwindef::DWORD; + use winapi::shared::minwindef::FALSE; + use winapi::um::processthreadsapi::GetCurrentProcess; + use winapi::um::psapi::GetProcessMemoryInfo; + use winapi::um::psapi::PROCESS_MEMORY_COUNTERS; + + // SAFETY: winapi calls + unsafe { + // this handle is a constant—no need to close it + let current_process = GetCurrentProcess(); + let mut pmc: PROCESS_MEMORY_COUNTERS = std::mem::zeroed(); + + if GetProcessMemoryInfo( + current_process, + &mut pmc, + std::mem::size_of::() as DWORD, + ) != FALSE + { + pmc.WorkingSetSize + } else { + 0 + } + } +}