Skip to content

Commit

Permalink
Add Isolate::take_heap_snapshot()
Browse files Browse the repository at this point in the history
This doesn't really follow the current V8 API (it's pretty close to how
V8 used to be back in 2012 though.) However:

1. The C++ API is very C++-y and doesn't carry over well to Rust, and
2. It addresses the immediate need of being able to take heap snapshots.

I can expand it into something that mimics the V8 API more closely later
but right now it's Sunday afternoon and I want to go bouldering. :-)

Refs denoland#298
  • Loading branch information
bnoordhuis committed Mar 8, 2020
1 parent e1b59ec commit ee1e88a
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 0 deletions.
35 changes: 35 additions & 0 deletions src/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<void>(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_t>(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<v8::HeapSnapshot*>(snapshot)->Delete();
}

} // extern "C"
33 changes: 33 additions & 0 deletions src/isolate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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<F>(&mut self, mut callback: F)
where
F: FnMut(&[u8]) -> bool,
{
extern "C" fn trampoline<F>(
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::<F>, arg) }
}
}

pub(crate) struct IsolateAnnex {
Expand Down
30 changes: 30 additions & 0 deletions tests/test_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<u8>::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());
}
}

0 comments on commit ee1e88a

Please sign in to comment.