Skip to content

Commit

Permalink
Render icicle graph in "Flame Graph" view (google#367)
Browse files Browse the repository at this point in the history
  • Loading branch information
narqo authored and Gabriel Marin committed Dec 17, 2020
1 parent d91df23 commit a183b8a
Show file tree
Hide file tree
Showing 8 changed files with 932 additions and 860 deletions.
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>
<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

0 comments on commit a183b8a

Please sign in to comment.