From 8a0068fa82093f89daea43e69cb918c0ff713e3b Mon Sep 17 00:00:00 2001 From: Genevieve L'Esperance Date: Fri, 3 Sep 2021 17:32:35 -0700 Subject: [PATCH 01/22] Introduce CpuProfiler, CpuProfile, CpuProfileNode --- cpuprofiler.go | 114 ++++++++++++++++++++++++++++++++++++++++++ cpuprofiler_test.go | 118 ++++++++++++++++++++++++++++++++++++++++++++ v8go.cc | 101 +++++++++++++++++++++++++++++++++++-- v8go.h | 22 +++++++++ 4 files changed, 351 insertions(+), 4 deletions(-) create mode 100644 cpuprofiler.go create mode 100644 cpuprofiler_test.go diff --git a/cpuprofiler.go b/cpuprofiler.go new file mode 100644 index 00000000..2290e2bc --- /dev/null +++ b/cpuprofiler.go @@ -0,0 +1,114 @@ +// Copyright 2019 Roger Chapman and the v8go contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package v8go + +// #include +// #include "v8go.h" +import "C" +import ( + "unsafe" +) + +type CpuProfiler struct { + ptr C.CpuProfilerPtr + iso *Isolate +} + +// CpuProfile contains a CPU profile in a form of top-down call tree +// (from main() down to functions that do all the work). +type CpuProfile struct { + ptr C.CpuProfilePtr + iso *Isolate +} + +// CpuProfileNode represents a node in a call graph. +type CpuProfileNode struct { + ptr C.CpuProfileNodePtr + iso *Isolate +} + +// Returns the root node of the top down call tree. +func (c *CpuProfile) GetTopDownRoot() *CpuProfileNode { + ptr := C.CpuProfileGetTopDownRoot(c.ptr) + return &CpuProfileNode{ptr: ptr, iso: c.iso} +} + +// Returns function name (empty string for anonymous functions.) +func (c *CpuProfileNode) GetFunctionName() string { + str := C.CpuProfileNodeGetFunctionName(c.ptr) + return C.GoString(str) +} + +// Retrieves number of children. +func (c *CpuProfileNode) GetChildrenCount() int { + i := C.CpuProfileNodeGetChildrenCount(c.ptr) + return int(i) +} + +// Retrieves a child node by index. +func (c *CpuProfileNode) GetChild(index int) *CpuProfileNode { + count := c.GetChildrenCount() + if index < 0 || index > count { + return nil + } + ptr := C.CpuProfileNodeGetChild(c.ptr, C.int(index)) + return &CpuProfileNode{ptr: ptr, iso: c.iso} +} + +// Retrieves the ancestor node, or null if the root. +func (c *CpuProfileNode) GetParent() *CpuProfileNode { + ptr := C.CpuProfileNodeGetParent(c.ptr) + return &CpuProfileNode{ptr: ptr, iso: c.iso} +} + +// Returns the number, 1-based, of the line where the function originates. +// kNoLineNumberInfo if no line number information is available. +func (c *CpuProfileNode) GetLineNumber() int { + no := C.CpuProfileNodeGetLineNumber(c.ptr) + return int(no) +} + +// Returns 1-based number of the column where the function originates. +// kNoColumnNumberInfo if no column number information is available. +func (c *CpuProfileNode) GetColumnNumber() int { + no := C.CpuProfileNodeGetColumnNumber(c.ptr) + return int(no) +} + +func NewCpuProfiler(iso *Isolate) *CpuProfiler { + return &CpuProfiler{ + ptr: C.NewCpuProfiler(iso.ptr), + iso: iso, + } +} + +func (c *CpuProfiler) StartProfiling(title string) { + tstr := C.CString(title) + defer C.free(unsafe.Pointer(tstr)) + C.CpuProfilerStartProfiling(c.iso.ptr, c.ptr, tstr) +} + +func (c *CpuProfiler) StopProfiling(title string, securityToken string) *CpuProfile { + tstr := C.CString(title) + defer C.free(unsafe.Pointer(tstr)) + ptr := C.CpuProfilerStopProfiling(c.iso.ptr, c.ptr, tstr) + return &CpuProfile{ptr: ptr, iso: c.iso} +} + +// Dispose will dispose the profiler; subsequent calls will panic. +func (c *CpuProfiler) Dispose() { + if c.ptr == nil { + return + } + C.CpuProfilerDispose(c.ptr) + c.ptr = nil +} + +// TODO +// The profiler must be dispoed after use by calling Dispose() +// static int GetProfilesCount () +// static const CpuProfile * GetProfile (int index, Handle< Value > security_token=Handle< Value >()) +// static const CpuProfile * FindProfile (unsigned uid, Handle< Value > security_token=Handle< Value >()) +// static void DeleteAllProfiles () diff --git a/cpuprofiler_test.go b/cpuprofiler_test.go new file mode 100644 index 00000000..bf83879d --- /dev/null +++ b/cpuprofiler_test.go @@ -0,0 +1,118 @@ +// Copyright 2019 Roger Chapman and the v8go contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package v8go_test + +import ( + "testing" + + "rogchap.com/v8go" +) + +func TestCpuProfiler(t *testing.T) { + t.Parallel() + + ctx, _ := v8go.NewContext(nil) + iso := ctx.Isolate() + defer iso.Dispose() + defer ctx.Close() + + cpuProfiler := v8go.NewCpuProfiler(iso) + defer cpuProfiler.Dispose() + + cpuProfiler.StartProfiling("test") + + _, err := ctx.RunScript(profileScript, "script.js") + failIf(t, err) + val, err := ctx.Global().Get("start") + failIf(t, err) + fn, err := val.AsFunction() + failIf(t, err) + _, err = fn.Call() + failIf(t, err) + + cpuProfile := cpuProfiler.StopProfiling("test", "") + if cpuProfile == nil { + t.Fatal("expected profiler not to be nil") + } + root := cpuProfile.GetTopDownRoot() + if root == nil { + t.Fatal("expected root not to be nil") + } + if root.GetFunctionName() != "(root)" { + t.Errorf("expected (root), but got %v", root.GetFunctionName()) + } + + checkChildren(t, root, []string{"(program)", "start", "(garbage collector)"}) + + startNode := root.GetChild(1) + checkChildren(t, startNode, []string{"foo"}) + + fooNode := startNode.GetChild(0) + checkChildren(t, fooNode, []string{"delay", "bar", "baz"}) + checkPosition(t, fooNode, 15, 13) + + delayNode := fooNode.GetChild(0) + checkChildren(t, delayNode, []string{"loop"}) + checkPosition(t, delayNode, 12, 15) + + barNode := fooNode.GetChild(1) + checkChildren(t, barNode, []string{"delay"}) + + bazNode := fooNode.GetChild(2) + checkChildren(t, bazNode, []string{"delay"}) +} + +func checkChildren(t *testing.T, node *v8go.CpuProfileNode, names []string) { + nodeName := node.GetFunctionName() + if node.GetChildrenCount() != len(names) { + t.Fatalf("expected child count for node %s to equal length of child names", nodeName) + } + for i, n := range names { + if node.GetChild(i).GetFunctionName() != n { + t.Errorf("expected %s child %d to have name %s", nodeName, i, n) + } + } +} + +func checkPosition(t *testing.T, node *v8go.CpuProfileNode, line, column int) { + nodeName := node.GetFunctionName() + if node.GetLineNumber() != line { + t.Errorf("expected node %s at line %d, but got %d", nodeName, line, node.GetLineNumber()) + } + if node.GetColumnNumber() != column { + t.Errorf("expected node %s at column %d, but got %d", nodeName, column, node.GetColumnNumber()) + } +} + +const profileScript = `function loop(timeout) { + this.mmm = 0; + var start = Date.now(); + while (Date.now() - start < timeout) { + var n = 100; + while(n > 1) { + n--; + this.mmm += n * n * n; + } + } +} +function delay() { try { loop(10); } catch(e) { } } +function bar() { delay(); } +function baz() { delay(); } +function foo() { + try { + delay(); + bar(); + delay(); + baz(); + } catch (e) { } +} +function start(timeout) { + var start = Date.now(); + do { + foo(); + var duration = Date.now() - start; + } while (duration < timeout); + return duration; +};` diff --git a/v8go.cc b/v8go.cc index 49cbc255..d06a0080 100644 --- a/v8go.cc +++ b/v8go.cc @@ -20,6 +20,7 @@ struct _EXCEPTION_POINTERS; #include "libplatform/libplatform.h" #include "v8.h" +#include "v8-profiler.h" using namespace v8; @@ -43,6 +44,11 @@ typedef struct { Persistent