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

Render icicle graph in "Flame Graph" view #367

Merged
merged 3 commits into from
Apr 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 21 additions & 1 deletion internal/driver/flamegraph.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

type treeNode struct {
Name string `json:"n"`
FullName string `json:"f"`
Cum int64 `json:"v"`
CumFormat string `json:"l"`
Percent string `json:"p"`
Expand All @@ -52,8 +53,10 @@ func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) {
// Make all nodes and the map, collect the roots.
for _, n := range g.Nodes {
v := n.CumValue()
fullName := n.Info.PrintableName()
node := &treeNode{
Name: n.Info.PrintableName(),
Name: getNodeShortName(fullName),
FullName: fullName,
Cum: v,
CumFormat: config.FormatValue(v),
Percent: strings.TrimSpace(measurement.Percentage(v, config.Total)),
Expand All @@ -78,6 +81,7 @@ func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) {

rootNode := &treeNode{
Name: "root",
FullName: "root",
Cum: rootValue,
CumFormat: config.FormatValue(rootValue),
Percent: strings.TrimSpace(measurement.Percentage(rootValue, config.Total)),
Expand All @@ -97,3 +101,19 @@ func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) {
Nodes: nodeArr,
})
}

// getNodeShortName builds a short node name from fullName.
func getNodeShortName(name string) string {
chunks := strings.SplitN(name, "(", 2)
head := chunks[0]
pathSep := strings.LastIndexByte(head, '/')
if pathSep == -1 || pathSep+1 >= len(head) {
return name
}
// Check if name is a stdlib package, i.e. doesn't have "." before "/"
if dot := strings.IndexByte(head, '.'); dot == -1 || dot > pathSep {
return name
}
// Trim package path prefix from node name
return name[pathSep+1:]
}
46 changes: 46 additions & 0 deletions internal/driver/flamegraph_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package driver

import "testing"

func TestGetNodeShortName(t *testing.T) {
type testCase struct {
name string
want string
}
testcases := []testCase{
{
"root",
"root",
},
{
"syscall.Syscall",
"syscall.Syscall",
},
{
"net/http.(*conn).serve",
"net/http.(*conn).serve",
},
{
"github.com/blah/foo.Foo",
"foo.Foo",
},
{
"github.com/blah/foo_bar.(*FooBar).Foo",
"foo_bar.(*FooBar).Foo",
},
{
"encoding/json.(*structEncoder).(encoding/json.encode)-fm",
"encoding/json.(*structEncoder).(encoding/json.encode)-fm",
},
{
"github.com/blah/blah/vendor/gopkg.in/redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm",
"redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm",
},
}
for _, tc := range testcases {
name := getNodeShortName(tc.name)
if got, want := name, tc.want; got != want {
t.Errorf("for %s, got %q, want %q", tc.name, got, want)
}
}
}
38 changes: 19 additions & 19 deletions internal/driver/webhtml.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ package driver
import "html/template"

import "github.com/google/pprof/third_party/d3"
import "github.com/google/pprof/third_party/d3tip"
import "github.com/google/pprof/third_party/d3flamegraph"

// addTemplates adds a set of template definitions to templates.
func addTemplates(templates *template.Template) {
template.Must(templates.Parse(`{{define "d3script"}}` + d3.JSSource + `{{end}}`))
template.Must(templates.Parse(`{{define "d3tipscript"}}` + d3tip.JSSource + `{{end}}`))
template.Must(templates.Parse(`{{define "d3flamegraphscript"}}` + d3flamegraph.JSSource + `{{end}}`))
template.Must(templates.Parse(`{{define "d3flamegraphcss"}}` + d3flamegraph.CSSSource + `{{end}}`))
template.Must(templates.Parse(`
Expand Down Expand Up @@ -224,7 +222,7 @@ table tr td {
cursor: ns-resize;
}
.hilite {
background-color: #ebf5fb;
background-color: #ebf5fb;
font-weight: bold;
}
</style>
Expand Down Expand Up @@ -1031,49 +1029,51 @@ function viewer(baseUrl, nodes) {
width: 90%;
min-width: 90%;
margin-left: 5%;
padding-bottom: 41px;
padding: 15px 0 35px;
}
</style>
</head>
<body>
{{template "header" .}}
<div id="bodycontainer">
<div id="flamegraphdetails" class="flamegraph-details"></div>
<div class="flamegraph-content">
<div id="chart"></div>
</div>
<div id="flamegraphdetails" class="flamegraph-details"></div>
</div>
{{template "script" .}}
<script>viewer(new URL(window.location.href), {{.Nodes}});</script>
<script>{{template "d3script" .}}</script>
<script>{{template "d3tipscript" .}}</script>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this isn't used now, remove d3tip from third_party?

<script>{{template "d3flamegraphscript" .}}</script>
<script type="text/javascript">
<script>
var data = {{.FlameGraph}};
var label = function(d) {
return d.data.n + ' (' + d.data.p + ', ' + d.data.l + ')';
};

var width = document.getElementById('chart').clientWidth;

var flameGraph = d3.flameGraph()
var flameGraph = d3.flamegraph()
.width(width)
.cellHeight(18)
.minFrameSize(1)
.transitionDuration(750)
.transitionEase(d3.easeCubic)
.sort(true)
.inverted(true)
.title('')
.label(label)
.tooltip(false)
.details(document.getElementById('flamegraphdetails'));

var tip = d3.tip()
.direction('s')
.offset([8, 0])
.attr('class', 'd3-flame-graph-tip')
.html(function(d) { return 'name: ' + d.data.n + ', value: ' + d.data.l; });
// <full name> (percentage, value)
flameGraph.label((d) => d.data.f + ' (' + d.data.p + ', ' + d.data.l + ')');

(function(flameGraph) {
var oldColorMapper = flameGraph.color();
function colorMapper(d) {
// Hack to force default color mapper to use 'warm' color scheme by not passing libtype
const { data, highlight } = d;
return oldColorMapper({ data: { n: data.n }, highlight });
}

flameGraph.tooltip(tip);
flameGraph.color(colorMapper);
}(flameGraph));

d3.select('#chart')
.datum(data)
Expand Down
2 changes: 1 addition & 1 deletion internal/driver/webui_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func TestWebInterface(t *testing.T) {
[]string{"300ms.*F1", "200ms.*300ms.*F2"}, false},
{"/disasm?f=" + url.QueryEscape("F[12]"),
[]string{"f1:asm", "f2:asm"}, false},
{"/flamegraph", []string{"File: testbin", "\"n\":\"root\"", "\"n\":\"F1\"", "function tip", "function flameGraph", "function hierarchy"}, false},
{"/flamegraph", []string{"File: testbin", "\"n\":\"root\"", "\"n\":\"F1\"", "var flamegraph = function", "function hierarchy"}, false},
}
for _, c := range testcases {
if c.needDot && !haveDot {
Expand Down
Loading