Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Heap Profiler] Heap snapshots in v8go #369

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Expand Up @@ -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 for debugging and inspection
str, err := heapProfiler.TakeHeapSnapshot()
ioutil.WriteFile("isolate.heapsnapshot", []byte(str), 0755)
}
```
## Documentation

Go Reference & more examples: https://pkg.go.dev/rogchap.com/v8go
Expand Down
31 changes: 31 additions & 0 deletions heap_profiler.go
@@ -0,0 +1,31 @@
package v8go

/*
#include <stdlib.h>
#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
}
35 changes: 35 additions & 0 deletions 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)
}
}
61 changes: 61 additions & 0 deletions v8go.cc
Expand Up @@ -278,6 +278,67 @@ ValuePtr IsolateThrowException(IsolatePtr iso, ValuePtr value) {
return tracked_value(ctx, new_val);
}

/********** HeapProfiler **********/

V8HeapProfiler* NewHeapProfiler(IsolatePtr iso_ptr) {
Isolate* iso = static_cast<Isolate*>(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<String> 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<ExternalStringResource> 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<HeapSnapshot*>(snapshot)->Delete();
String::Utf8Value json(profiler->iso, stream.ToString(profiler->iso));
return CopyString(json);
}

/********** CpuProfiler **********/

CPUProfiler* NewCPUProfiler(IsolatePtr iso_ptr) {
Expand Down
12 changes: 12 additions & 0 deletions v8go.h
Expand Up @@ -15,13 +15,17 @@ 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
// Opaque to cgo, but useful to treat it as a pointer to a distinct type
typedef struct v8Isolate v8Isolate;
typedef v8Isolate* IsolatePtr;

typedef struct v8HeapProfiler v8HeapProfiler;
typedef v8HeapProfiler* HeapProfilerPtr;

typedef struct v8CpuProfiler v8CpuProfiler;
typedef v8CpuProfiler* CpuProfilerPtr;

Expand Down Expand Up @@ -77,6 +81,11 @@ typedef struct {
int compileOption;
} CompileOptions;

typedef struct {
HeapProfilerPtr ptr;
IsolatePtr iso;
} V8HeapProfiler;

typedef struct {
CpuProfilerPtr ptr;
IsolatePtr iso;
Expand Down Expand Up @@ -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);
Expand Down