diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2022-12-17 17:25:51 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-12-17 23:25:51 +0100 |
commit | e9ecfdd20ac3f8a9d920bbcc4e5dd25bd262b4b7 (patch) | |
tree | 7604e5e42823656d5378d60791a4f68b66f7ca69 /runtime/ops/os/mod.rs | |
parent | f46df3e35940fc78163945eed33e58fafed0b06b (diff) |
fix(runtime): `Deno.memoryUsage().rss` should return correct value (#17088)
This commit changes implementation of "Deno.memoryUsage()" to return
correct value for "rss" field. To do that we implement a specialized function
per os to retrieve this information.
Diffstat (limited to 'runtime/ops/os/mod.rs')
-rw-r--r-- | runtime/ops/os/mod.rs | 128 |
1 files changed, 127 insertions, 1 deletions
diff --git a/runtime/ops/os/mod.rs b/runtime/ops/os/mod.rs index b93935955..613b4507d 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<Option<u32>, 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::<libc::mach_task_basic_info_data_t>::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::<PROCESS_MEMORY_COUNTERS>() as DWORD, + ) != FALSE + { + pmc.WorkingSetSize + } else { + 0 + } + } +} |