Skip to content

Commit 94aca69

Browse files
bartlomiejuclaude
andauthored
feat: add HeapCodeStatistics API (#1921)
Expose `Isolate::get_heap_code_and_metadata_statistics()` which wraps V8's `GetHeapCodeAndMetadataStatistics`. Returns a `HeapCodeStatistics` struct with code_and_metadata_size, bytecode_and_metadata_size, external_script_source_size, and cpu_profiler_metadata_size. This is needed to implement Node.js `v8.getHeapCodeStatistics()` in Deno. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 154d2f2 commit 94aca69

File tree

5 files changed

+88
-0
lines changed

5 files changed

+88
-0
lines changed

src/binding.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,11 @@ bool v8__Isolate__GetHeapSpaceStatistics(
349349
return isolate->GetHeapSpaceStatistics(space_statistics, index);
350350
}
351351

352+
bool v8__Isolate__GetHeapCodeAndMetadataStatistics(v8::Isolate* isolate,
353+
v8::HeapCodeStatistics* s) {
354+
return isolate->GetHeapCodeAndMetadataStatistics(s);
355+
}
356+
352357
void v8__Isolate__RemoveNearHeapLimitCallback(
353358
v8::Isolate* isolate, v8::NearHeapLimitCallback callback,
354359
size_t heap_limit) {

src/binding.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ using v8__String__WriteFlags = v8::String::WriteFlags;
4242
using v8__ModuleImportPhase = v8::ModuleImportPhase;
4343
using v8__HeapStatistics = v8::HeapStatistics;
4444
using v8__HeapSpaceStatistics = v8::HeapSpaceStatistics;
45+
using v8__HeapCodeStatistics = v8::HeapCodeStatistics;
4546
using v8__GCType = v8::GCType;
4647
using v8__GCCallbackFlags = v8::GCCallbackFlags;
4748
using v8__Intercepted = v8::Intercepted;

src/isolate.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use crate::StartupData;
1818
use crate::String;
1919
use crate::V8::get_current_platform;
2020
use crate::Value;
21+
use crate::binding::v8__HeapCodeStatistics;
2122
use crate::binding::v8__HeapSpaceStatistics;
2223
use crate::binding::v8__HeapStatistics;
2324
use crate::binding::v8__Isolate__UseCounterFeature;
@@ -666,6 +667,10 @@ unsafe extern "C" {
666667
space_statistics: *mut v8__HeapSpaceStatistics,
667668
index: size_t,
668669
) -> bool;
670+
fn v8__Isolate__GetHeapCodeAndMetadataStatistics(
671+
isolate: *mut RealIsolate,
672+
code_statistics: *mut v8__HeapCodeStatistics,
673+
) -> bool;
669674
fn v8__Isolate__AddNearHeapLimitCallback(
670675
isolate: *mut RealIsolate,
671676
callback: NearHeapLimitCallback,
@@ -1269,6 +1274,26 @@ impl Isolate {
12691274
Some(HeapSpaceStatistics(inner))
12701275
}
12711276

1277+
/// Get code and metadata statistics for the heap.
1278+
///
1279+
/// \returns true on success.
1280+
#[inline(always)]
1281+
pub fn get_heap_code_and_metadata_statistics(
1282+
&mut self,
1283+
) -> Option<HeapCodeStatistics> {
1284+
let inner = unsafe {
1285+
let mut s = MaybeUninit::zeroed();
1286+
if !v8__Isolate__GetHeapCodeAndMetadataStatistics(
1287+
self.as_real_ptr(),
1288+
s.as_mut_ptr(),
1289+
) {
1290+
return None;
1291+
}
1292+
s.assume_init()
1293+
};
1294+
Some(HeapCodeStatistics(inner))
1295+
}
1296+
12721297
/// Tells V8 to capture current stack trace when uncaught exception occurs
12731298
/// and report it to the message listeners. The option is off by default.
12741299
#[inline(always)]
@@ -2234,6 +2259,26 @@ impl HeapSpaceStatistics {
22342259
}
22352260
}
22362261

2262+
pub struct HeapCodeStatistics(v8__HeapCodeStatistics);
2263+
2264+
impl HeapCodeStatistics {
2265+
pub fn code_and_metadata_size(&self) -> usize {
2266+
self.0.code_and_metadata_size_
2267+
}
2268+
2269+
pub fn bytecode_and_metadata_size(&self) -> usize {
2270+
self.0.bytecode_and_metadata_size_
2271+
}
2272+
2273+
pub fn external_script_source_size(&self) -> usize {
2274+
self.0.external_script_source_size_
2275+
}
2276+
2277+
pub fn cpu_profiler_metadata_size(&self) -> usize {
2278+
self.0.cpu_profiler_metadata_size_
2279+
}
2280+
}
2281+
22372282
impl<'s, F> MapFnFrom<F> for PrepareStackTraceCallback<'s>
22382283
where
22392284
F: UnitType

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ pub use handle::SealedLocal;
106106
pub use handle::TracedReference;
107107
pub use handle::Weak;
108108
pub use isolate::GarbageCollectionType;
109+
pub use isolate::HeapCodeStatistics;
109110
pub use isolate::HeapSpaceStatistics;
110111
pub use isolate::HeapStatistics;
111112
pub use isolate::HostCreateShadowRealmContextCallback;

tests/test_api.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7792,6 +7792,42 @@ fn heap_statistics() {
77927792
assert_ne!(s.number_of_native_contexts(), 0);
77937793
}
77947794

7795+
#[test]
7796+
fn heap_code_statistics() {
7797+
let _setup_guard = setup::parallel_test();
7798+
7799+
let isolate = &mut v8::Isolate::new(Default::default());
7800+
7801+
// Before running any code, statistics should still be available.
7802+
let s = isolate
7803+
.get_heap_code_and_metadata_statistics()
7804+
.expect("get_heap_code_and_metadata_statistics should succeed");
7805+
7806+
// Before running any code, code_and_metadata_size may or may not be > 0
7807+
// depending on the platform and V8 version.
7808+
// external_script_source_size and cpu_profiler_metadata_size start at 0.
7809+
assert_eq!(s.external_script_source_size(), 0);
7810+
assert_eq!(s.cpu_profiler_metadata_size(), 0);
7811+
7812+
// Run some JS to generate bytecode.
7813+
{
7814+
v8::scope!(let scope, isolate);
7815+
let context = v8::Context::new(scope, Default::default());
7816+
let scope = &mut v8::ContextScope::new(scope, context);
7817+
eval(scope, "function foo() { return 1 + 2; } foo();").unwrap();
7818+
}
7819+
7820+
let s2 = isolate
7821+
.get_heap_code_and_metadata_statistics()
7822+
.expect("get_heap_code_and_metadata_statistics should succeed");
7823+
7824+
// After compiling code, bytecode_and_metadata_size should increase.
7825+
assert!(s2.bytecode_and_metadata_size() > 0);
7826+
// code_and_metadata_size tracks JIT-compiled code, which may be 0 if V8
7827+
// only uses the interpreter for simple scripts.
7828+
assert!(s2.code_and_metadata_size() >= s.code_and_metadata_size());
7829+
}
7830+
77957831
#[test]
77967832
fn low_memory_notification() {
77977833
let _setup_guard = setup::parallel_test();

0 commit comments

Comments
 (0)