diff --git a/src/binding.cc b/src/binding.cc index 777c434b..ee38af35 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -115,6 +115,11 @@ void v8__Isolate__Enter(v8::Isolate* isolate) { isolate->Enter(); } void v8__Isolate__Exit(v8::Isolate* isolate) { isolate->Exit(); } +void v8__Isolate__GetHeapStatistics(v8::Isolate* isolate, + v8::HeapStatistics* s) { + isolate->GetHeapStatistics(s); +} + const v8::Context* v8__Isolate__GetCurrentContext(v8::Isolate* isolate) { return local_to_ptr(isolate->GetCurrentContext()); } @@ -1778,4 +1783,35 @@ int v8__internal__Object__GetHash(const v8::Data& data) { return hash; } +void v8__HeapStatistics__CONSTRUCT(uninit_t* buf) { + // Should be <= than its counterpart in src/isolate.rs + static_assert(sizeof(v8::HeapStatistics) <= sizeof(uintptr_t[16]), + "HeapStatistics mismatch"); + construct_in_place(buf); +} + +// The const_cast doesn't violate const correctness, the methods +// are simple getters that don't mutate the object or global state. +#define V(name) \ + size_t v8__HeapStatistics__##name(const v8::HeapStatistics* s) { \ + return const_cast(s)->name(); \ + } + +V(total_heap_size) +V(total_heap_size_executable) +V(total_physical_size) +V(total_available_size) +V(total_global_handles_size) +V(used_global_handles_size) +V(used_heap_size) +V(heap_size_limit) +V(malloced_memory) +V(external_memory) +V(peak_malloced_memory) +V(number_of_native_contexts) +V(number_of_detached_contexts) +V(does_zap_garbage) // Returns size_t, not bool like you'd expect. + +#undef V + } // extern "C" diff --git a/src/isolate.rs b/src/isolate.rs index 6365a606..7e896bde 100644 --- a/src/isolate.rs +++ b/src/isolate.rs @@ -20,6 +20,7 @@ use std::any::TypeId; use std::cell::{Ref, RefCell, RefMut}; use std::collections::HashMap; use std::ffi::c_void; +use std::mem::MaybeUninit; use std::ops::Deref; use std::ops::DerefMut; use std::ptr::null_mut; @@ -75,6 +76,14 @@ pub type NearHeapLimitCallback = extern "C" fn( initial_heap_limit: usize, ) -> usize; +/// Collection of V8 heap information. +/// +/// Instances of this class can be passed to v8::Isolate::GetHeapStatistics to +/// get heap statistics from V8. +// Must be >= sizeof(v8::HeapStatistics), see v8__HeapStatistics__CONSTRUCT(). +#[repr(C)] +pub struct HeapStatistics([usize; 16]); + extern "C" { fn v8__Isolate__New(params: *const raw::CreateParams) -> *mut Isolate; fn v8__Isolate__Dispose(this: *mut Isolate); @@ -83,6 +92,7 @@ extern "C" { fn v8__Isolate__GetNumberOfDataSlots(this: *const Isolate) -> u32; fn v8__Isolate__Enter(this: *mut Isolate); fn v8__Isolate__Exit(this: *mut Isolate); + fn v8__Isolate__GetHeapStatistics(this: *mut Isolate, s: *mut HeapStatistics); fn v8__Isolate__SetCaptureStackTraceForUncaughtExceptions( this: *mut Isolate, caputre: bool, @@ -133,6 +143,37 @@ extern "C" { callback: extern "C" fn(*mut c_void, *const u8, usize) -> bool, arg: *mut c_void, ); + + fn v8__HeapStatistics__CONSTRUCT(s: *mut MaybeUninit); + fn v8__HeapStatistics__total_heap_size(s: *const HeapStatistics) -> usize; + fn v8__HeapStatistics__total_heap_size_executable( + s: *const HeapStatistics, + ) -> usize; + fn v8__HeapStatistics__total_physical_size(s: *const HeapStatistics) + -> usize; + fn v8__HeapStatistics__total_available_size( + s: *const HeapStatistics, + ) -> usize; + fn v8__HeapStatistics__total_global_handles_size( + s: *const HeapStatistics, + ) -> usize; + fn v8__HeapStatistics__used_global_handles_size( + s: *const HeapStatistics, + ) -> usize; + fn v8__HeapStatistics__used_heap_size(s: *const HeapStatistics) -> usize; + fn v8__HeapStatistics__heap_size_limit(s: *const HeapStatistics) -> usize; + fn v8__HeapStatistics__malloced_memory(s: *const HeapStatistics) -> usize; + fn v8__HeapStatistics__external_memory(s: *const HeapStatistics) -> usize; + fn v8__HeapStatistics__peak_malloced_memory( + s: *const HeapStatistics, + ) -> usize; + fn v8__HeapStatistics__number_of_native_contexts( + s: *const HeapStatistics, + ) -> usize; + fn v8__HeapStatistics__number_of_detached_contexts( + s: *const HeapStatistics, + ) -> usize; + fn v8__HeapStatistics__does_zap_garbage(s: *const HeapStatistics) -> usize; } #[repr(C)] @@ -305,6 +346,11 @@ impl Isolate { unsafe { v8__Isolate__Exit(self) } } + /// Get statistics about the heap memory usage. + pub fn get_heap_statistics(&mut self, s: &mut HeapStatistics) { + unsafe { v8__Isolate__GetHeapStatistics(self, s) } + } + /// Tells V8 to capture current stack trace when uncaught exception occurs /// and report it to the message listeners. The option is off by default. pub fn set_capture_stack_trace_for_uncaught_exceptions( @@ -621,3 +667,73 @@ impl DerefMut for OwnedIsolate { unsafe { self.cxx_isolate.as_mut() } } } + +impl HeapStatistics { + pub fn total_heap_size(&self) -> usize { + unsafe { v8__HeapStatistics__total_heap_size(self) } + } + + pub fn total_heap_size_executable(&self) -> usize { + unsafe { v8__HeapStatistics__total_heap_size_executable(self) } + } + + pub fn total_physical_size(&self) -> usize { + unsafe { v8__HeapStatistics__total_physical_size(self) } + } + + pub fn total_available_size(&self) -> usize { + unsafe { v8__HeapStatistics__total_available_size(self) } + } + + pub fn total_global_handles_size(&self) -> usize { + unsafe { v8__HeapStatistics__total_global_handles_size(self) } + } + + pub fn used_global_handles_size(&self) -> usize { + unsafe { v8__HeapStatistics__used_global_handles_size(self) } + } + + pub fn used_heap_size(&self) -> usize { + unsafe { v8__HeapStatistics__used_heap_size(self) } + } + + pub fn heap_size_limit(&self) -> usize { + unsafe { v8__HeapStatistics__heap_size_limit(self) } + } + + pub fn malloced_memory(&self) -> usize { + unsafe { v8__HeapStatistics__malloced_memory(self) } + } + + pub fn external_memory(&self) -> usize { + unsafe { v8__HeapStatistics__external_memory(self) } + } + + pub fn peak_malloced_memory(&self) -> usize { + unsafe { v8__HeapStatistics__peak_malloced_memory(self) } + } + + pub fn number_of_native_contexts(&self) -> usize { + unsafe { v8__HeapStatistics__number_of_native_contexts(self) } + } + + pub fn number_of_detached_contexts(&self) -> usize { + unsafe { v8__HeapStatistics__number_of_detached_contexts(self) } + } + + /// Returns a 0/1 boolean, which signifies whether the V8 overwrite heap + /// garbage with a bit pattern. + pub fn does_zap_garbage(&self) -> usize { + unsafe { v8__HeapStatistics__does_zap_garbage(self) } + } +} + +impl Default for HeapStatistics { + fn default() -> Self { + let mut s = MaybeUninit::::uninit(); + unsafe { + v8__HeapStatistics__CONSTRUCT(&mut s); + s.assume_init() + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 075a34df..8c443c6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,6 +82,7 @@ pub use function::*; pub use handle::Global; pub use handle::Handle; pub use handle::Local; +pub use isolate::HeapStatistics; pub use isolate::HostImportModuleDynamicallyCallback; pub use isolate::HostInitializeImportMetaObjectCallback; pub use isolate::Isolate; diff --git a/tests/test_api.rs b/tests/test_api.rs index 7c89675c..3515cc10 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -3365,3 +3365,29 @@ fn heap_limits() { } assert_eq!(1, test_state.near_heap_limit_callback_calls); } + +#[test] +fn heap_statistics() { + let _setup_guard = setup(); + + let params = v8::CreateParams::default().heap_limits(0, 10 << 20); // 10 MB. + let isolate = &mut v8::Isolate::new(params); + + let mut s = v8::HeapStatistics::default(); + isolate.get_heap_statistics(&mut s); + assert!(s.heap_size_limit() > 0); + assert!(s.total_heap_size() > 0); + assert!(s.total_global_handles_size() >= s.used_global_handles_size()); + assert!(s.used_heap_size() > 0); + assert!(s.heap_size_limit() >= s.used_heap_size()); + assert!(s.peak_malloced_memory() >= s.malloced_memory()); + assert_eq!(s.number_of_native_contexts(), 0); + + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + let _ = eval(scope, "").unwrap(); + + scope.get_heap_statistics(&mut s); + assert_ne!(s.number_of_native_contexts(), 0); +}