diff --git a/cpuprofile.go b/cpuprofile.go index daf1bf36..be78a33f 100644 --- a/cpuprofile.go +++ b/cpuprofile.go @@ -7,6 +7,7 @@ package v8go // #include // #include "v8go.h" import "C" +import "time" // CPUProfile contains a CPU profile in a form of top-down call tree // (from main() down to functions that do all the work). @@ -16,6 +17,8 @@ type CPUProfile struct { title string topDownRoot *CPUProfileNode + startTime time.Time + endTime time.Time } // Returns the root node of the top down call tree. @@ -28,6 +31,19 @@ func (c *CPUProfile) GetTitle() string { return c.title } +// Returns time when the profile recording was started (in microseconds) +// since some unspecified starting point. +func (c *CPUProfile) GetStartTime() time.Time { + return c.startTime +} + +// Returns time when the profile recording was stopped (in microseconds) +// since some unspecified starting point. +// The point is equal to the starting point used by GetStartTime. +func (c *CPUProfile) GetEndTime() time.Time { + return c.endTime +} + func (c *CPUProfile) Delete() { if c.ptr == nil { return diff --git a/cpuprofile_test.go b/cpuprofile_test.go index d2be3bc3..5b176918 100644 --- a/cpuprofile_test.go +++ b/cpuprofile_test.go @@ -6,6 +6,7 @@ package v8go_test import ( "testing" + "time" v8 "rogchap.com/v8go" ) @@ -39,9 +40,12 @@ func TestCPUProfile(t *testing.T) { cpuProfiler.StartProfiling("cpuprofiletest") - _, err := ctx.RunScript(`function foo() {}; foo();`, "script.js") + _, err := ctx.RunScript(`function foo() { }; foo();`, "script.js") fatalIf(t, err) + // Ensure different start/end time + time.Sleep(10 * time.Microsecond) + cpuProfile := cpuProfiler.StopProfiling("cpuprofiletest") if cpuProfile == nil { t.Fatal("expected profiler not to be nil") @@ -55,4 +59,8 @@ func TestCPUProfile(t *testing.T) { if cpuProfile.GetTopDownRoot() == nil { t.Fatal("expected root not to be nil") } + + if cpuProfile.GetStartTime().Equal(cpuProfile.GetEndTime()) { + t.Fatal("expected different start and end times") + } } diff --git a/cpuprofilenode.go b/cpuprofilenode.go index 76f9ea83..d2583142 100644 --- a/cpuprofilenode.go +++ b/cpuprofilenode.go @@ -10,15 +10,13 @@ import "C" // CPUProfileNode represents a node in a call graph. type CPUProfileNode struct { - functionName string - lineNumber int - columnNumber int - children []*CPUProfileNode -} + scriptResourceName string + functionName string + lineNumber int + columnNumber int -// Returns function name (empty string for anonymous functions.) -func (c *CPUProfileNode) GetFunctionName() string { - return c.functionName + parent *CPUProfileNode + children []*CPUProfileNode } // Retrieves number of children. @@ -34,6 +32,19 @@ func (c *CPUProfileNode) GetChild(index int) *CPUProfileNode { return c.children[index] } +func (c *CPUProfileNode) GetParent() *CPUProfileNode { + return c.parent +} + +func (c *CPUProfileNode) GetScriptResourceName() string { + return c.scriptResourceName +} + +// Returns function name (empty string for anonymous functions.) +func (c *CPUProfileNode) GetFunctionName() string { + return c.functionName +} + // Returns the number, 1-based, of the line where the function originates. func (c *CPUProfileNode) GetLineNumber() int { return c.lineNumber diff --git a/cpuprofilenode_test.go b/cpuprofilenode_test.go index 4741043f..46414595 100644 --- a/cpuprofilenode_test.go +++ b/cpuprofilenode_test.go @@ -23,7 +23,7 @@ func TestCPUProfileNode(t *testing.T) { cpuProfiler.StartProfiling("cpuprofilenodetest") - _, _ = ctx.RunScript(profileScript, "") + _, _ = ctx.RunScript(profileScript, "script.js") val, _ := ctx.Global().Get("start") fn, _ := val.AsFunction() _, _ = fn.Call(ctx.Global()) @@ -32,17 +32,11 @@ func TestCPUProfileNode(t *testing.T) { defer cpuProfile.Delete() node := cpuProfile.GetTopDownRoot() + err := checkNode(node, "(root)", 0, 0) + fatalIf(t, err) - if node.GetFunctionName() != "(root)" { - t.Fatalf("expected start but got %s", node.GetFunctionName()) - } - - if node.GetLineNumber() != 0 { - t.Fatalf("expected 0 but got %d", node.GetLineNumber()) - } - - if node.GetColumnNumber() != 0 { - t.Fatalf("expected 0 but got %d", node.GetColumnNumber()) + if node.GetParent() != nil { + t.Fatal("expected root node to have nil parent") } if node.GetChildrenCount() < 2 { @@ -52,4 +46,12 @@ func TestCPUProfileNode(t *testing.T) { if node.GetChild(1).GetFunctionName() != "start" { t.Fatalf("expected child node with name `start` but got %s", node.GetChild(1).GetFunctionName()) } + + if node.GetChild(1).GetScriptResourceName() != "script.js" { + t.Fatalf("expected child to have script resource name `script.js` but had `%s`", node.GetScriptResourceName()) + } + + if node.GetChild(0).GetParent() != node { + t.Fatal("expected child's parent to be the same node") + } } diff --git a/cpuprofiler.go b/cpuprofiler.go index c668665a..f67c98dc 100644 --- a/cpuprofiler.go +++ b/cpuprofiler.go @@ -8,6 +8,7 @@ package v8go // #include "v8go.h" import "C" import ( + "time" "unsafe" ) @@ -62,41 +63,44 @@ func (c *CPUProfiler) StopProfiling(title string) *CPUProfile { rootPtr := C.CpuProfileGetTopDownRoot(profilePtr) rootNode := &CPUProfileNode{ - functionName: C.GoString(C.CpuProfileNodeGetFunctionName(rootPtr)), - lineNumber: int(C.CpuProfileNodeGetLineNumber(rootPtr)), - columnNumber: int(C.CpuProfileNodeGetColumnNumber(rootPtr)), + parent: nil, + scriptResourceName: C.GoString(C.CpuProfileNodeGetScriptResourceName(rootPtr)), + functionName: C.GoString(C.CpuProfileNodeGetFunctionName(rootPtr)), + lineNumber: int(C.CpuProfileNodeGetLineNumber(rootPtr)), + columnNumber: int(C.CpuProfileNodeGetColumnNumber(rootPtr)), } - rootNode.children = getChildren(rootPtr) + rootNode.children = getChildren(rootNode, rootPtr) return &CPUProfile{ ptr: profilePtr, iso: c.iso, title: title, topDownRoot: rootNode, + startTime: time.Unix(0, int64(C.CpuProfileGetStartTime(profilePtr))/1000), + endTime: time.Unix(0, int64(C.CpuProfileGetEndTime(profilePtr))/1000), } } -func getChildren(ptr C.CpuProfileNodePtr) []*CPUProfileNode { +func getChildren(parent *CPUProfileNode, ptr C.CpuProfileNodePtr) []*CPUProfileNode { count := C.CpuProfileNodeGetChildrenCount(ptr) if int(count) == 0 { return []*CPUProfileNode{} } - node := &CPUProfileNode{ - children: make([]*CPUProfileNode, count), - } + parent.children = make([]*CPUProfileNode, count) for i := 0; i < int(count); i++ { childNodePtr := C.CpuProfileNodeGetChild(ptr, C.int(i)) - children := getChildren(childNodePtr) - - node.children[i] = &CPUProfileNode{ - functionName: C.GoString(C.CpuProfileNodeGetFunctionName(childNodePtr)), - lineNumber: int(C.CpuProfileNodeGetLineNumber(childNodePtr)), - columnNumber: int(C.CpuProfileNodeGetColumnNumber(childNodePtr)), - children: children, + childNode := &CPUProfileNode{ + parent: parent, + scriptResourceName: C.GoString(C.CpuProfileNodeGetScriptResourceName(childNodePtr)), + functionName: C.GoString(C.CpuProfileNodeGetFunctionName(childNodePtr)), + lineNumber: int(C.CpuProfileNodeGetLineNumber(childNodePtr)), + columnNumber: int(C.CpuProfileNodeGetColumnNumber(childNodePtr)), } + childNode.children = getChildren(childNode, childNodePtr) + parent.children[i] = childNode } - return node.children + return parent.children } diff --git a/cpuprofiler_test.go b/cpuprofiler_test.go index 1c4f69fc..2fe6a0b3 100644 --- a/cpuprofiler_test.go +++ b/cpuprofiler_test.go @@ -5,6 +5,7 @@ package v8go_test import ( + "fmt" "testing" "rogchap.com/v8go" @@ -34,7 +35,7 @@ func TestCPUProfilerDispose(t *testing.T) { } func TestCPUProfiler(t *testing.T) { - t.Parallel() + // t.Parallel() ctx := v8go.NewContext(nil) iso := ctx.Isolate() @@ -61,63 +62,68 @@ func TestCPUProfiler(t *testing.T) { } defer cpuProfile.Delete() - if cpuProfile.GetTitle() != "cpuprofilertest" { - t.Fatalf("expected cpu profile to be %s, but got %s", "cpuprofilertest", cpuProfile.GetTitle()) - } - root := cpuProfile.GetTopDownRoot() if root == nil { t.Fatal("expected root not to be nil") } - if root.GetFunctionName() != "(root)" { - t.Fatalf("expected (root), but got %v", root.GetFunctionName()) - } - checkNode(t, root, "(root)", 0, 0) - checkChildren(t, root, []string{"(program)", "start", "(garbage collector)"}) + err = checkNode(root, "(root)", 0, 0) + fatalIf(t, err) + err = checkChildren(root, []string{"(program)", "start", "(garbage collector)"}) + fatalIf(t, err) start := root.GetChild(1) - checkNode(t, start, "start", 23, 15) - checkChildren(t, start, []string{"foo"}) + err = checkNode(start, "start", 23, 15) + fatalIf(t, err) + err = checkChildren(start, []string{"foo"}) + fatalIf(t, err) foo := start.GetChild(0) - checkNode(t, foo, "foo", 15, 13) - checkChildren(t, foo, []string{"delay", "bar", "baz"}) + err = checkNode(foo, "foo", 15, 13) + fatalIf(t, err) + err = checkChildren(foo, []string{"delay", "bar", "baz"}) + fatalIf(t, err) baz := foo.GetChild(2) - checkNode(t, baz, "baz", 14, 13) - checkChildren(t, baz, []string{"delay"}) + err = checkNode(baz, "baz", 14, 13) + fatalIf(t, err) + err = checkChildren(baz, []string{"delay"}) + fatalIf(t, err) delay := baz.GetChild(0) - checkNode(t, delay, "delay", 12, 15) - checkChildren(t, delay, []string{"loop"}) + err = checkNode(delay, "delay", 12, 15) + fatalIf(t, err) + err = checkChildren(delay, []string{"loop"}) + fatalIf(t, err) } -func checkChildren(t *testing.T, node *v8go.CPUProfileNode, names []string) { +func checkChildren(node *v8go.CPUProfileNode, names []string) error { nodeName := node.GetFunctionName() if node.GetChildrenCount() != len(names) { present := []string{} for i := 0; i < node.GetChildrenCount(); i++ { present = append(present, node.GetChild(i).GetFunctionName()) } - t.Fatalf("child count for node %s should be %d but was %d: %v", nodeName, len(names), node.GetChildrenCount(), present) + return fmt.Errorf("child count for node %s should be %d but was %d: %v", nodeName, len(names), node.GetChildrenCount(), present) } for i, n := range names { if node.GetChild(i).GetFunctionName() != n { - t.Fatalf("expected %s child %d to have name %s", nodeName, i, n) + return fmt.Errorf("expected %s child %d to have name %s", nodeName, i, n) } } + return nil } -func checkNode(t *testing.T, node *v8go.CPUProfileNode, name string, line, column int) { +func checkNode(node *v8go.CPUProfileNode, name string, line, column int) error { if node.GetFunctionName() != name { - t.Fatalf("expected node to have function name `%s` but had `%s`", name, node.GetFunctionName()) + return fmt.Errorf("expected node to have function name `%s` but had `%s`", name, node.GetFunctionName()) } if node.GetLineNumber() != line { - t.Fatalf("expected node %s at line %d, but got %d", name, line, node.GetLineNumber()) + return fmt.Errorf("expected node %s at line %d, but got %d", name, line, node.GetLineNumber()) } if node.GetColumnNumber() != column { - t.Fatalf("expected node %s at column %d, but got %d", name, column, node.GetColumnNumber()) + return fmt.Errorf("expected node %s at column %d, but got %d", name, column, node.GetColumnNumber()) } + return nil } // const profileTree = ` diff --git a/v8go.cc b/v8go.cc index 49704eb3..389f64be 100644 --- a/v8go.cc +++ b/v8go.cc @@ -58,6 +58,7 @@ struct m_cpuProfile { struct m_cpuProfileNode { const CpuProfileNode* ptr; const char* functionName; + const char* scriptResourceName; int lineNumber; int columnNumber; int childrenCount; @@ -272,6 +273,7 @@ CpuProfilePtr CpuProfilerStopProfiling(IsolatePtr iso_ptr, CpuProfilerPtr cpuPro m_cpuProfileNode* root = new m_cpuProfileNode; root->ptr = r; root->childrenCount = r->GetChildrenCount(); + root->scriptResourceName = r->GetScriptResourceNameStr(); root->functionName = r->GetFunctionNameStr(); root->lineNumber = r->GetLineNumber(); root->columnNumber = r->GetColumnNumber(); @@ -287,10 +289,22 @@ CpuProfileNodePtr CpuProfileGetTopDownRoot(CpuProfilePtr ptr) { return ptr->root; } +int CpuProfileGetStartTime(CpuProfilePtr cpuProfilePtr) { + return cpuProfilePtr->ptr->GetStartTime(); +} + +int CpuProfileGetEndTime(CpuProfilePtr cpuProfilePtr) { + return cpuProfilePtr->ptr->GetEndTime(); +} + int CpuProfileNodeGetChildrenCount(CpuProfileNodePtr ptr) { return ptr->childrenCount; } +const char* CpuProfileNodeGetScriptResourceName(CpuProfileNodePtr ptr) { + return ptr->scriptResourceName; +} + const char* CpuProfileNodeGetFunctionName(CpuProfileNodePtr ptr) { return ptr->functionName; } @@ -307,6 +321,7 @@ CpuProfileNodePtr CpuProfileNodeGetChild(CpuProfileNodePtr cpuProfileNode, int i const CpuProfileNode* child = cpuProfileNode->ptr->GetChild(index); m_cpuProfileNode* c = new m_cpuProfileNode; c->ptr = child; + c->scriptResourceName = child->GetScriptResourceNameStr(); c->functionName = child->GetFunctionNameStr(); c->lineNumber = child->GetLineNumber(); c->columnNumber = child->GetColumnNumber(); diff --git a/v8go.h b/v8go.h index 86ad9199..5d44e49f 100644 --- a/v8go.h +++ b/v8go.h @@ -91,17 +91,18 @@ extern void CpuProfilerStartProfiling(IsolatePtr iso_ptr, extern CpuProfilePtr CpuProfilerStopProfiling(IsolatePtr iso_ptr, CpuProfilerPtr ptr, const char* title); -/* extern const char* CpuProfileGetTitle(IsolatePtr iso_ptr, CpuProfilePtr ptr); */ +extern int CpuProfileGetStartTime(CpuProfilePtr ptr); +extern int CpuProfileGetEndTime(CpuProfilePtr ptr); extern int CpuProfileGetSamplesCount(CpuProfilePtr ptr); extern void CpuProfileDelete(CpuProfilePtr ptr); extern CpuProfileNodePtr CpuProfileGetTopDownRoot(CpuProfilePtr ptr); +extern const char* CpuProfileNodeGetScriptResourceName(CpuProfileNodePtr ptr); extern const char* CpuProfileNodeGetFunctionName(CpuProfileNodePtr ptr); extern int CpuProfileNodeGetChildrenCount(CpuProfileNodePtr ptr); extern CpuProfileNodePtr CpuProfileNodeGetChild(CpuProfileNodePtr ptr, int index); -/* extern CpuProfileNodePtr CpuProfileNodeGetParent(CpuProfileNodePtr ptr); */ extern int CpuProfileNodeGetLineNumber(CpuProfileNodePtr ptr); extern int CpuProfileNodeGetColumnNumber(CpuProfileNodePtr ptr);