From 3a17381c78fbe298ce57f39697873a789a150bca Mon Sep 17 00:00:00 2001 From: Sai Pinapati Date: Mon, 6 Feb 2023 16:06:27 -0800 Subject: [PATCH 1/5] Heap Snapshots in v8go --- CHANGELOG.md | 1 + README.md | 21 ++++++++++++++ heap_profiler.go | 31 +++++++++++++++++++++ heap_profiler_test.go | 35 +++++++++++++++++++++++ v8go.cc | 64 +++++++++++++++++++++++++++++++++++++++++++ v8go.h | 12 ++++++++ 6 files changed, 164 insertions(+) create mode 100644 heap_profiler.go create mode 100644 heap_profiler_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index d7ba16682..ec43db8b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added support for Value.release() and FunctionCallbackInfo.release(). This is useful when using v8go in a long-running context. +- Support for Heap Snapshots ### Fixed - Use string length to ensure null character-containing strings in Go/JS are not terminated early. diff --git a/README.md b/README.md index 2b10506fb..9118c0bfb 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,27 @@ func printTree(nest string, node *v8.CPUProfileNode) { // (garbage collector) :0:0 ``` +### Heap Snapshots + +```go +func createHeapSnapshot() { + iso := v8.NewIsolate() + heapProfiler := v8.NewHeapProfiler(iso) + defer iso.Dispose() + printfn := v8.NewFunctionTemplate(iso, func(info *v8.FunctionCallbackInfo) *v8.Value { + fmt.Printf("%v", info.Args()) + return nil + }) + global := v8.NewObjectTemplate(iso) + global.Set("print", printfn) + ctx := v8.NewContext(iso, global) + ctx.RunScript("print('foo')", "print.js") + + // This snapshot can be loaded in Chrome dev tools in memory tab + str, err := heapProfiler.TakeHeapSnapshot() + ioutil.WriteFile("isolate.heapsnapshot", []byte(str), 0755) +} +``` ## Documentation Go Reference & more examples: https://pkg.go.dev/rogchap.com/v8go diff --git a/heap_profiler.go b/heap_profiler.go new file mode 100644 index 000000000..b4a46cc15 --- /dev/null +++ b/heap_profiler.go @@ -0,0 +1,31 @@ +package v8go + +/* +#include +#include "v8go.h" +*/ +import "C" +import "unsafe" + +type HeapProfiler struct { + p *C.V8HeapProfiler + iso *Isolate +} + +func NewHeapProfiler(iso *Isolate) *HeapProfiler { + profiler := C.NewHeapProfiler(iso.ptr) + return &HeapProfiler{ + p: profiler, + iso: iso, + } +} + +func (c *HeapProfiler) TakeHeapSnapshot() (string, error) { + if c.p == nil || c.iso.ptr == nil { + panic("heap profiler or isolate is nil") + } + + str := C.TakeHeapSnapshot(c.p) + defer C.free(unsafe.Pointer(str)) + return C.GoString(str), nil +} diff --git a/heap_profiler_test.go b/heap_profiler_test.go new file mode 100644 index 000000000..a3b18c53e --- /dev/null +++ b/heap_profiler_test.go @@ -0,0 +1,35 @@ +package v8go_test + +import ( + "encoding/json" + "fmt" + "testing" + + v8 "rogchap.com/v8go" +) + +func TestHeapSnapshot(t *testing.T) { + t.Parallel() + iso := v8.NewIsolate() + heapProfiler := v8.NewHeapProfiler(iso) + defer iso.Dispose() + printfn := v8.NewFunctionTemplate(iso, func(info *v8.FunctionCallbackInfo) *v8.Value { + fmt.Printf("%v", info.Args()) + return nil + }) + global := v8.NewObjectTemplate(iso) + global.Set("print", printfn) + ctx := v8.NewContext(iso, global) + ctx.RunScript("print('foo')", "print.js") + + str, err := heapProfiler.TakeHeapSnapshot() + if err != nil { + t.Errorf("expected nil but got error: %v", err) + } + + var snapshot map[string]interface{} + err = json.Unmarshal([]byte(str), &snapshot) + if err != nil { + t.Fatal(err) + } +} diff --git a/v8go.cc b/v8go.cc index 86cdc8742..745843ad7 100644 --- a/v8go.cc +++ b/v8go.cc @@ -278,6 +278,70 @@ ValuePtr IsolateThrowException(IsolatePtr iso, ValuePtr value) { return tracked_value(ctx, new_val); } +/********** HeapProfiler **********/ + +V8HeapProfiler* NewHeapProfiler(IsolatePtr iso_ptr) { + Isolate* iso = static_cast(iso_ptr); + Locker locker(iso); + Isolate::Scope isolate_scope(iso); + HandleScope handle_scope(iso); + + V8HeapProfiler* c = new V8HeapProfiler; + c->iso = iso; + c->ptr = iso->GetHeapProfiler(); + return c; +} + +// v8::OutputStream is required for snapshot serialization +class BufferOutputStream : public v8::OutputStream { + public: + BufferOutputStream() : buffer(new ExternalStringResource()) {} + + void EndOfStream() override {} + int GetChunkSize() override { return 1024 * 1024; } + WriteResult WriteAsciiChunk(char* data, int size) override { + buffer->Append(data, size); + return kContinue; + } + + Local ToString(Isolate* isolate) { + return String::NewExternalOneByte(isolate, + buffer.release()).ToLocalChecked(); + } + + private: + class ExternalStringResource : public String::ExternalOneByteStringResource { + public: + void Append(char* data, size_t count) { + store.append(data, count); + } + + const char* data() const override { return store.data(); } + size_t length() const override { return store.size(); } + + private: + std::string store; + }; + + std::unique_ptr buffer; +}; + +const char* TakeHeapSnapshot(V8HeapProfiler* profiler) { + if (profiler->iso == nullptr) { + return nullptr; + } + Locker locker(profiler->iso); + Isolate::Scope isolate_scope(profiler->iso); + HandleScope handle_scope(profiler->iso); + const HeapSnapshot* snapshot = profiler->ptr->TakeHeapSnapshot(); + BufferOutputStream stream; + snapshot->Serialize(&stream); + const_cast(snapshot)->Delete(); + String::Utf8Value json(profiler->iso, stream.ToString(profiler->iso)); + return CopyString(json); +} + + /********** CpuProfiler **********/ CPUProfiler* NewCPUProfiler(IsolatePtr iso_ptr) { diff --git a/v8go.h b/v8go.h index b20daca4d..c9afaa57c 100644 --- a/v8go.h +++ b/v8go.h @@ -15,6 +15,7 @@ typedef v8::CpuProfiler* CpuProfilerPtr; typedef v8::CpuProfile* CpuProfilePtr; typedef const v8::CpuProfileNode* CpuProfileNodePtr; typedef v8::ScriptCompiler::CachedData* ScriptCompilerCachedDataPtr; +typedef v8::HeapProfiler* HeapProfilerPtr; extern "C" { #else @@ -22,6 +23,9 @@ extern "C" { typedef struct v8Isolate v8Isolate; typedef v8Isolate* IsolatePtr; +typedef struct v8HeapProfiler v8HeapProfiler; +typedef v8HeapProfiler* HeapProfilerPtr; + typedef struct v8CpuProfiler v8CpuProfiler; typedef v8CpuProfiler* CpuProfilerPtr; @@ -77,6 +81,11 @@ typedef struct { int compileOption; } CompileOptions; +typedef struct { + HeapProfilerPtr ptr; + IsolatePtr iso; +} V8HeapProfiler; + typedef struct { CpuProfilerPtr ptr; IsolatePtr iso; @@ -156,6 +165,9 @@ extern void ScriptCompilerCachedDataDelete( ScriptCompilerCachedData* cached_data); extern RtnValue UnboundScriptRun(ContextPtr ctx_ptr, UnboundScriptPtr us_ptr); +extern V8HeapProfiler* NewHeapProfiler(IsolatePtr iso_ptr); +extern const char* TakeHeapSnapshot(V8HeapProfiler* ptr); + extern CPUProfiler* NewCPUProfiler(IsolatePtr iso_ptr); extern void CPUProfilerDispose(CPUProfiler* ptr); extern void CPUProfilerStartProfiling(CPUProfiler* ptr, const char* title); From 3113df7ba04d79bfe37131b3c8a8050549ead2f8 Mon Sep 17 00:00:00 2001 From: Sai Pinapati Date: Mon, 6 Feb 2023 16:09:10 -0800 Subject: [PATCH 2/5] format readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9118c0bfb..2f879d2fa 100644 --- a/README.md +++ b/README.md @@ -197,9 +197,9 @@ func createHeapSnapshot() { ctx := v8.NewContext(iso, global) ctx.RunScript("print('foo')", "print.js") - // This snapshot can be loaded in Chrome dev tools in memory tab + // This snapshot can be loaded in Chrome dev tools in memory tab str, err := heapProfiler.TakeHeapSnapshot() - ioutil.WriteFile("isolate.heapsnapshot", []byte(str), 0755) + ioutil.WriteFile("isolate.heapsnapshot", []byte(str), 0755) } ``` ## Documentation From a5e528afe2ca59d768bb39da7f74c2ec2084ce5d Mon Sep 17 00:00:00 2001 From: Sai Pinapati Date: Mon, 6 Feb 2023 16:21:33 -0800 Subject: [PATCH 3/5] modify comments --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2f879d2fa..b8940be69 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,7 @@ func createHeapSnapshot() { ctx := v8.NewContext(iso, global) ctx.RunScript("print('foo')", "print.js") - // This snapshot can be loaded in Chrome dev tools in memory tab + // This snapshot can be loaded in Chrome dev tools for debugging and inspection str, err := heapProfiler.TakeHeapSnapshot() ioutil.WriteFile("isolate.heapsnapshot", []byte(str), 0755) } From 66c87f68b8599e3e45b5811dd1e464f2a475206f Mon Sep 17 00:00:00 2001 From: Sai Pinapati Date: Mon, 6 Feb 2023 16:48:38 -0800 Subject: [PATCH 4/5] formatting fixes --- v8go.cc | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/v8go.cc b/v8go.cc index 745843ad7..488998f41 100644 --- a/v8go.cc +++ b/v8go.cc @@ -305,16 +305,14 @@ class BufferOutputStream : public v8::OutputStream { } Local ToString(Isolate* isolate) { - return String::NewExternalOneByte(isolate, - buffer.release()).ToLocalChecked(); + return String::NewExternalOneByte(isolate, buffer.release()) + .ToLocalChecked(); } private: class ExternalStringResource : public String::ExternalOneByteStringResource { public: - void Append(char* data, size_t count) { - store.append(data, count); - } + void Append(char* data, size_t count) { store.append(data, count); } const char* data() const override { return store.data(); } size_t length() const override { return store.size(); } @@ -327,21 +325,20 @@ class BufferOutputStream : public v8::OutputStream { }; const char* TakeHeapSnapshot(V8HeapProfiler* profiler) { - if (profiler->iso == nullptr) { - return nullptr; - } - Locker locker(profiler->iso); - Isolate::Scope isolate_scope(profiler->iso); - HandleScope handle_scope(profiler->iso); - const HeapSnapshot* snapshot = profiler->ptr->TakeHeapSnapshot(); - BufferOutputStream stream; - snapshot->Serialize(&stream); - const_cast(snapshot)->Delete(); - String::Utf8Value json(profiler->iso, stream.ToString(profiler->iso)); - return CopyString(json); + if (profiler->iso == nullptr) { + return nullptr; + } + Locker locker(profiler->iso); + Isolate::Scope isolate_scope(profiler->iso); + HandleScope handle_scope(profiler->iso); + const HeapSnapshot* snapshot = profiler->ptr->TakeHeapSnapshot(); + BufferOutputStream stream; + snapshot->Serialize(&stream); + const_cast(snapshot)->Delete(); + String::Utf8Value json(profiler->iso, stream.ToString(profiler->iso)); + return CopyString(json); } - /********** CpuProfiler **********/ CPUProfiler* NewCPUProfiler(IsolatePtr iso_ptr) { From 87b023a6a71ddd386ede319021fac84e5d05960b Mon Sep 17 00:00:00 2001 From: Sai Pinapati Date: Mon, 10 Apr 2023 09:01:58 -0700 Subject: [PATCH 5/5] modify changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec43db8b4..d7ba16682 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added support for Value.release() and FunctionCallbackInfo.release(). This is useful when using v8go in a long-running context. -- Support for Heap Snapshots ### Fixed - Use string length to ensure null character-containing strings in Go/JS are not terminated early.