diff --git a/src/binding.cc b/src/binding.cc index 9f9e4954..4946912f 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -6,6 +6,7 @@ #include "v8/include/libplatform/libplatform.h" #include "v8/include/v8-inspector.h" #include "v8/include/v8-platform.h" +#include "v8/include/v8-profiler.h" #include "v8/include/v8.h" using namespace support; @@ -1572,4 +1573,38 @@ v8::Value* v8__Module__Evaluate(v8::Module& self, return maybe_local_to_ptr(self.Evaluate(context)); } +using HeapSnapshotCallback = bool (*)(void*, const char*, size_t); + +void v8__HeapProfiler__TakeHeapSnapshot(v8::Isolate* isolate, + HeapSnapshotCallback callback, + void* arg) { + struct OutputStream : public v8::OutputStream { + OutputStream(HeapSnapshotCallback callback, void* arg) + : callback_(callback), arg_(arg) {} + void EndOfStream() override { + static_cast(callback_(arg_, nullptr, 0)); + } + v8::OutputStream::WriteResult WriteAsciiChunk(char* data, + int size) override { + assert(size >= 0); // Can never be < 0 barring bugs in V8. + if (callback_(arg_, data, static_cast(size))) + return v8::OutputStream::kContinue; + return v8::OutputStream::kAbort; + } + HeapSnapshotCallback const callback_; + void* const arg_; + }; + + const v8::HeapSnapshot* snapshot = + isolate->GetHeapProfiler()->TakeHeapSnapshot(); + if (snapshot == nullptr) return; // Snapshotting failed, probably OOM. + OutputStream stream(callback, arg); + snapshot->Serialize(&stream); + // We don't want to call HeapProfiler::DeleteAllHeapSnapshots() because that + // invalidates snapshots we don't own. The const_cast hack has been in use + // in node-heapdump for the last 8 years and I think there is a pretty + // good chance it'll keep working for 8 more. + const_cast(snapshot)->Delete(); +} + } // extern "C" diff --git a/src/isolate.rs b/src/isolate.rs index 16179de8..07e66a92 100644 --- a/src/isolate.rs +++ b/src/isolate.rs @@ -132,6 +132,11 @@ extern "C" { this: &mut CreateParams, snapshot_blob: *mut StartupData, ); + fn v8__HeapProfiler__TakeHeapSnapshot( + isolate: *mut Isolate, + callback: extern "C" fn(*mut c_void, *const u8, usize) -> bool, + arg: *mut c_void, + ); } #[repr(C)] @@ -299,6 +304,34 @@ impl Isolate { // deno where dropping Annex before the states causes a segfault. v8__Isolate__Dispose(self) } + + /// Take a heap snapshot. The callback is invoked one or more times + /// with byte slices containing the snapshot serialized as JSON. + /// It's the callback's responsibility to reassemble them into + /// a single document, e.g., by writing them to a file. + /// Note that Chrome DevTools refuses to load snapshots without + /// a .heapsnapshot suffix. + pub fn take_heap_snapshot(&mut self, mut callback: F) + where + F: FnMut(&[u8]) -> bool, + { + extern "C" fn trampoline( + arg: *mut c_void, + data: *const u8, + size: usize, + ) -> bool + where + F: FnMut(&[u8]) -> bool, + { + let p = arg as *mut F; + let callback = unsafe { &mut *p }; + let slice = unsafe { std::slice::from_raw_parts(data, size) }; + callback(slice) + } + + let arg = &mut callback as *mut F as *mut c_void; + unsafe { v8__HeapProfiler__TakeHeapSnapshot(self, trampoline::, arg) } + } } pub(crate) struct IsolateAnnex { diff --git a/tests/test_api.rs b/tests/test_api.rs index df443135..da816999 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -2752,3 +2752,33 @@ fn get_and_set_data() { assert_eq!(*b, 123); } } + +#[test] +fn take_heap_snapshot() { + let _setup_guard = setup(); + let mut params = v8::Isolate::create_params(); + params.set_array_buffer_allocator(v8::new_default_allocator()); + let mut isolate = v8::Isolate::new(params); + { + let mut hs = v8::HandleScope::new(&mut isolate); + let scope = hs.enter(); + let context = v8::Context::new(scope); + let mut cs = v8::ContextScope::new(scope, context); + let scope = cs.enter(); + let source = r#" + { + class Eyecatcher {} + const eyecatchers = globalThis.eyecatchers = []; + for (let i = 0; i < 1e4; i++) eyecatchers.push(new Eyecatcher); + } + "#; + let _ = eval(scope, context, source).unwrap(); + let mut vec = Vec::::new(); + isolate.take_heap_snapshot(|chunk| { + vec.extend_from_slice(chunk); + true + }); + let s = std::str::from_utf8(&vec).unwrap(); + assert!(s.find(r#""Eyecatcher""#).is_some()); + } +}