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

Introduce CpuProfiler, CpuProfile, and CpuProfileNode #167

Merged
merged 25 commits into from
Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8a0068f
Introduce CpuProfiler, CpuProfile, CpuProfileNode
Sep 4, 2021
65c45df
Split out cpu profile components, add tests
Sep 6, 2021
3252318
Merge branch 'master' into gl-cpuprofiler
Sep 8, 2021
8b2f3d5
Separate profile names, test invalid child index
Sep 8, 2021
00ee59b
Add method comments
Sep 8, 2021
ed9a5f0
Test no-op for subsequent disposes of cpu profiler
Sep 8, 2021
0d14d14
Verify is profiler or iso is nil within start/stop functions
Sep 30, 2021
06e7540
Update copyright year on new files.
Sep 30, 2021
b90c5e1
Merge branch 'master' into gl-cpuprofiler
Oct 1, 2021
1a5a4d9
Introduce cpu profiler, cpu profile, cpu profile node
Oct 2, 2021
7e1cd9b
First-pass implementation of CpuProfiler
ryanmoran Oct 8, 2021
bf396fb
Expand example in readme
Oct 12, 2021
7c70b11
Adjust script run time to ensure sampling
Oct 13, 2021
ad3270d
Remove unneeded unsafe pointer casts, fix time conversion, panic on nil
Oct 19, 2021
0026131
Merge remote-tracking branch 'rogchap/master' into gl-cpuprofiler
Oct 19, 2021
08fe180
Panic on nil ptr/iso in start/stop profiling
Oct 19, 2021
bc240f1
Add backports for timeUnixMicro, add comments to new objects fields
Oct 19, 2021
76505ff
Getter methods for cpu profile + cpu profile node
Oct 19, 2021
2794acf
Update Readme, add GetChildrenCount to node
Oct 19, 2021
3bd713f
Find start node on root, ignore program/garbage collector children
Oct 19, 2021
acd0219
Specify timeout for script + loosen search on children
Oct 19, 2021
6646a0d
Update readme instructions for profiler
Oct 20, 2021
439ebb1
Update changelog
Oct 20, 2021
755a126
Fix reversing of order of profile start and end time
dylanahsmith Oct 20, 2021
946873e
Expose profile duration instead of start and end time.
dylanahsmith Oct 20, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions cpuprofile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2019 Roger Chapman and the v8go contributors. All rights reserved.
genevieve marked this conversation as resolved.
Show resolved Hide resolved
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package v8go

// #include <stdlib.h>
// #include "v8go.h"
import "C"

// 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
}

// 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}
}

func (c *CPUProfile) GetTitle() string {
str := C.CpuProfileGetTitle(c.iso.ptr, c.ptr)
return C.GoString(str)
}
51 changes: 51 additions & 0 deletions cpuprofile_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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 TestCPUProfile(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("cpuprofiletest")

_, 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("cpuprofiletest", "")
if cpuProfile == nil {
t.Fatal("expected profiler not to be nil")
}

if cpuProfile.GetTitle() != "cpuprofiletest" {
t.Errorf("expected cpuprofiletest, but got %v", cpuProfile.GetTitle())
}

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())
}
}
57 changes: 57 additions & 0 deletions cpuprofilenode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// 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 <stdlib.h>
// #include "v8go.h"
import "C"

// CPUProfileNode represents a node in a call graph.
type CPUProfileNode struct {
ptr C.CpuProfileNodePtr
iso *Isolate
genevieve marked this conversation as resolved.
Show resolved Hide resolved
}

// Returns function name (empty string for anonymous functions.)
func (c *CPUProfileNode) GetFunctionName() string {
dylanahsmith marked this conversation as resolved.
Show resolved Hide resolved
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 {
i := C.CpuProfileNodeGetLineNumber(c.ptr)
genevieve marked this conversation as resolved.
Show resolved Hide resolved
return int(i)
}

// Returns 1-based number of the column where the function originates.
// kNoColumnNumberInfo if no column number information is available.
func (c *CPUProfileNode) GetColumnNumber() int {
i := C.CpuProfileNodeGetColumnNumber(c.ptr)
return int(i)
}
100 changes: 100 additions & 0 deletions cpuprofilenode_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// 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 TestCPUProfileNode(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("cpuprofilenodetest")

_, 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("cpuprofilenodetest", "")
if cpuProfile == nil {
t.Fatal("expected profile not to be nil")
}

root := cpuProfile.GetTopDownRoot()
if root == nil {
t.Fatal("expected top down 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)"})

invalidChild := root.GetChild(4)
if invalidChild != nil {
t.Errorf("expected nil child, but got %v", invalidChild.GetFunctionName())
}

startNode := root.GetChild(1)
if startNode.GetFunctionName() != "start" {
t.Errorf("expected start, but got %v", startNode.GetFunctionName())
}
checkPosition(t, startNode, 23, 15)

parentName := startNode.GetParent().GetFunctionName()
if parentName != "(root)" {
t.Errorf("expected (root), but got %v", parentName)
}

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())
}
}
46 changes: 46 additions & 0 deletions cpuprofiler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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 <stdlib.h>
// #include "v8go.h"
import "C"
import (
"unsafe"
)

type CPUProfiler struct {
ptr C.CpuProfilerPtr
iso *Isolate
}

func NewCPUProfiler(iso *Isolate) *CPUProfiler {
return &CPUProfiler{
ptr: C.NewCpuProfiler(iso.ptr),
iso: 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
}

func (c *CPUProfiler) StartProfiling(title string) {
genevieve marked this conversation as resolved.
Show resolved Hide resolved
tstr := C.CString(title)
defer C.free(unsafe.Pointer(tstr))
C.CpuProfilerStartProfiling(c.iso.ptr, c.ptr, tstr)
genevieve marked this conversation as resolved.
Show resolved Hide resolved
}

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}
}
77 changes: 77 additions & 0 deletions cpuprofiler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// 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("cpuprofilertest")

_, 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("cpuprofilertest", "")
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())
}
}

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;
};`
Loading