Bläddra i källkod

Render icicle graph in "Flame Graph" view (#367)

Vladimir Varankin 7 år sedan
förälder
incheckning
be265fae9c

+ 21
- 1
internal/driver/flamegraph.go Visa fil

@@ -27,6 +27,7 @@ import (
27 27
 
28 28
 type treeNode struct {
29 29
 	Name      string      `json:"n"`
30
+	FullName  string      `json:"f"`
30 31
 	Cum       int64       `json:"v"`
31 32
 	CumFormat string      `json:"l"`
32 33
 	Percent   string      `json:"p"`
@@ -52,8 +53,10 @@ func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) {
52 53
 	// Make all nodes and the map, collect the roots.
53 54
 	for _, n := range g.Nodes {
54 55
 		v := n.CumValue()
56
+		fullName := n.Info.PrintableName()
55 57
 		node := &treeNode{
56
-			Name:      n.Info.PrintableName(),
58
+			Name:      getNodeShortName(fullName),
59
+			FullName:  fullName,
57 60
 			Cum:       v,
58 61
 			CumFormat: config.FormatValue(v),
59 62
 			Percent:   strings.TrimSpace(measurement.Percentage(v, config.Total)),
@@ -78,6 +81,7 @@ func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) {
78 81
 
79 82
 	rootNode := &treeNode{
80 83
 		Name:      "root",
84
+		FullName:  "root",
81 85
 		Cum:       rootValue,
82 86
 		CumFormat: config.FormatValue(rootValue),
83 87
 		Percent:   strings.TrimSpace(measurement.Percentage(rootValue, config.Total)),
@@ -97,3 +101,19 @@ func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) {
97 101
 		Nodes:      nodeArr,
98 102
 	})
99 103
 }
104
+
105
+// getNodeShortName builds a short node name from fullName.
106
+func getNodeShortName(name string) string {
107
+	chunks := strings.SplitN(name, "(", 2)
108
+	head := chunks[0]
109
+	pathSep := strings.LastIndexByte(head, '/')
110
+	if pathSep == -1 || pathSep+1 >= len(head) {
111
+		return name
112
+	}
113
+	// Check if name is a stdlib package, i.e. doesn't have "." before "/"
114
+	if dot := strings.IndexByte(head, '.'); dot == -1 || dot > pathSep {
115
+		return name
116
+	}
117
+	// Trim package path prefix from node name
118
+	return name[pathSep+1:]
119
+}

+ 46
- 0
internal/driver/flamegraph_test.go Visa fil

@@ -0,0 +1,46 @@
1
+package driver
2
+
3
+import "testing"
4
+
5
+func TestGetNodeShortName(t *testing.T) {
6
+	type testCase struct {
7
+		name string
8
+		want string
9
+	}
10
+	testcases := []testCase{
11
+		{
12
+			"root",
13
+			"root",
14
+		},
15
+		{
16
+			"syscall.Syscall",
17
+			"syscall.Syscall",
18
+		},
19
+		{
20
+			"net/http.(*conn).serve",
21
+			"net/http.(*conn).serve",
22
+		},
23
+		{
24
+			"github.com/blah/foo.Foo",
25
+			"foo.Foo",
26
+		},
27
+		{
28
+			"github.com/blah/foo_bar.(*FooBar).Foo",
29
+			"foo_bar.(*FooBar).Foo",
30
+		},
31
+		{
32
+			"encoding/json.(*structEncoder).(encoding/json.encode)-fm",
33
+			"encoding/json.(*structEncoder).(encoding/json.encode)-fm",
34
+		},
35
+		{
36
+			"github.com/blah/blah/vendor/gopkg.in/redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm",
37
+			"redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm",
38
+		},
39
+	}
40
+	for _, tc := range testcases {
41
+		name := getNodeShortName(tc.name)
42
+		if got, want := name, tc.want; got != want {
43
+			t.Errorf("for %s, got %q, want %q", tc.name, got, want)
44
+		}
45
+	}
46
+}

+ 19
- 19
internal/driver/webhtml.go Visa fil

@@ -17,13 +17,11 @@ package driver
17 17
 import "html/template"
18 18
 
19 19
 import "github.com/google/pprof/third_party/d3"
20
-import "github.com/google/pprof/third_party/d3tip"
21 20
 import "github.com/google/pprof/third_party/d3flamegraph"
22 21
 
23 22
 // addTemplates adds a set of template definitions to templates.
24 23
 func addTemplates(templates *template.Template) {
25 24
 	template.Must(templates.Parse(`{{define "d3script"}}` + d3.JSSource + `{{end}}`))
26
-	template.Must(templates.Parse(`{{define "d3tipscript"}}` + d3tip.JSSource + `{{end}}`))
27 25
 	template.Must(templates.Parse(`{{define "d3flamegraphscript"}}` + d3flamegraph.JSSource + `{{end}}`))
28 26
 	template.Must(templates.Parse(`{{define "d3flamegraphcss"}}` + d3flamegraph.CSSSource + `{{end}}`))
29 27
 	template.Must(templates.Parse(`
@@ -224,7 +222,7 @@ table tr td {
224 222
   cursor: ns-resize;
225 223
 }
226 224
 .hilite {
227
-  background-color: #ebf5fb; 
225
+  background-color: #ebf5fb;
228 226
   font-weight: bold;
229 227
 }
230 228
 </style>
@@ -1031,49 +1029,51 @@ function viewer(baseUrl, nodes) {
1031 1029
       width: 90%;
1032 1030
       min-width: 90%;
1033 1031
       margin-left: 5%;
1034
-      padding-bottom: 41px;
1032
+      padding: 15px 0 35px;
1035 1033
     }
1036 1034
   </style>
1037 1035
 </head>
1038 1036
 <body>
1039 1037
   {{template "header" .}}
1040 1038
   <div id="bodycontainer">
1039
+    <div id="flamegraphdetails" class="flamegraph-details"></div>
1041 1040
     <div class="flamegraph-content">
1042 1041
       <div id="chart"></div>
1043 1042
     </div>
1044
-    <div id="flamegraphdetails" class="flamegraph-details"></div>
1045 1043
   </div>
1046 1044
   {{template "script" .}}
1047 1045
   <script>viewer(new URL(window.location.href), {{.Nodes}});</script>
1048 1046
   <script>{{template "d3script" .}}</script>
1049
-  <script>{{template "d3tipscript" .}}</script>
1050 1047
   <script>{{template "d3flamegraphscript" .}}</script>
1051
-  <script type="text/javascript">
1048
+  <script>
1052 1049
     var data = {{.FlameGraph}};
1053
-    var label = function(d) {
1054
-      return d.data.n + ' (' + d.data.p + ', ' + d.data.l + ')';
1055
-    };
1056 1050
 
1057 1051
     var width = document.getElementById('chart').clientWidth;
1058 1052
 
1059
-    var flameGraph = d3.flameGraph()
1053
+    var flameGraph = d3.flamegraph()
1060 1054
       .width(width)
1061 1055
       .cellHeight(18)
1062 1056
       .minFrameSize(1)
1063 1057
       .transitionDuration(750)
1064 1058
       .transitionEase(d3.easeCubic)
1065
-      .sort(true)
1059
+      .inverted(true)
1066 1060
       .title('')
1067
-      .label(label)
1061
+      .tooltip(false)
1068 1062
       .details(document.getElementById('flamegraphdetails'));
1069 1063
 
1070
-    var tip = d3.tip()
1071
-      .direction('s')
1072
-      .offset([8, 0])
1073
-      .attr('class', 'd3-flame-graph-tip')
1074
-      .html(function(d) { return 'name: ' + d.data.n + ', value: ' + d.data.l; });
1064
+    // <full name> (percentage, value)
1065
+    flameGraph.label((d) => d.data.f + ' (' + d.data.p + ', ' + d.data.l + ')');
1066
+
1067
+    (function(flameGraph) {
1068
+      var oldColorMapper = flameGraph.color();
1069
+      function colorMapper(d) {
1070
+        // Hack to force default color mapper to use 'warm' color scheme by not passing libtype
1071
+        const { data, highlight } = d;
1072
+        return oldColorMapper({ data: { n: data.n }, highlight });
1073
+      }
1075 1074
 
1076
-    flameGraph.tooltip(tip);
1075
+      flameGraph.color(colorMapper);
1076
+    }(flameGraph));
1077 1077
 
1078 1078
     d3.select('#chart')
1079 1079
       .datum(data)

+ 1
- 1
internal/driver/webui_test.go Visa fil

@@ -81,7 +81,7 @@ func TestWebInterface(t *testing.T) {
81 81
 			[]string{"300ms.*F1", "200ms.*300ms.*F2"}, false},
82 82
 		{"/disasm?f=" + url.QueryEscape("F[12]"),
83 83
 			[]string{"f1:asm", "f2:asm"}, false},
84
-		{"/flamegraph", []string{"File: testbin", "\"n\":\"root\"", "\"n\":\"F1\"", "function tip", "function flameGraph", "function hierarchy"}, false},
84
+		{"/flamegraph", []string{"File: testbin", "\"n\":\"root\"", "\"n\":\"F1\"", "var flamegraph = function", "function hierarchy"}, false},
85 85
 	}
86 86
 	for _, c := range testcases {
87 87
 		if c.needDot && !haveDot {

+ 801
- 503
third_party/d3flamegraph/d3_flame_graph.go
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 0
- 8
third_party/d3tip/LICENSE Visa fil

@@ -1,8 +0,0 @@
1
-The MIT License (MIT)
2
-Copyright (c) 2013 Justin Palmer
3
-
4
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
-
6
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
-
8
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 0
- 328
third_party/d3tip/d3_tip.go Visa fil

@@ -1,328 +0,0 @@
1
-// Tooltips for d3.js visualizations
2
-// https://github.com/Caged/d3-tip
3
-// Version 0.7.1
4
-// See LICENSE file for license details
5
-
6
-package d3tip
7
-
8
-// JSSource returns the d3-tip.js file
9
-const JSSource = `
10
-(function (root, factory) {
11
-  if (typeof define === 'function' && define.amd) {
12
-    // AMD. Register as an anonymous module with d3 as a dependency.
13
-    define(['d3'], factory)
14
-  } else if (typeof module === 'object' && module.exports) {
15
-    // CommonJS
16
-    var d3 = require('d3')
17
-    module.exports = factory(d3)
18
-  } else {
19
-    // Browser global.
20
-    root.d3.tip = factory(root.d3)
21
-  }
22
-}(this, function (d3) {
23
-
24
-  // Public - contructs a new tooltip
25
-  //
26
-  // Returns a tip
27
-  return function() {
28
-    var direction = d3_tip_direction,
29
-        offset    = d3_tip_offset,
30
-        html      = d3_tip_html,
31
-        node      = initNode(),
32
-        svg       = null,
33
-        point     = null,
34
-        target    = null
35
-
36
-    function tip(vis) {
37
-      svg = getSVGNode(vis)
38
-      point = svg.createSVGPoint()
39
-      document.body.appendChild(node)
40
-    }
41
-
42
-    // Public - show the tooltip on the screen
43
-    //
44
-    // Returns a tip
45
-    tip.show = function() {
46
-      var args = Array.prototype.slice.call(arguments)
47
-      if(args[args.length - 1] instanceof SVGElement) target = args.pop()
48
-
49
-      var content = html.apply(this, args),
50
-          poffset = offset.apply(this, args),
51
-          dir     = direction.apply(this, args),
52
-          nodel   = getNodeEl(),
53
-          i       = directions.length,
54
-          coords,
55
-          scrollTop  = document.documentElement.scrollTop || document.body.scrollTop,
56
-          scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft
57
-
58
-      nodel.html(content)
59
-        .style('opacity', 1).style('pointer-events', 'all')
60
-
61
-      while(i--) nodel.classed(directions[i], false)
62
-      coords = direction_callbacks.get(dir).apply(this)
63
-      nodel.classed(dir, true)
64
-      	.style('top', (coords.top +  poffset[0]) + scrollTop + 'px')
65
-      	.style('left', (coords.left + poffset[1]) + scrollLeft + 'px')
66
-
67
-      return tip;
68
-    };
69
-
70
-    // Public - hide the tooltip
71
-    //
72
-    // Returns a tip
73
-    tip.hide = function() {
74
-      var nodel = getNodeEl()
75
-      nodel.style('opacity', 0).style('pointer-events', 'none')
76
-      return tip
77
-    }
78
-
79
-    // Public: Proxy attr calls to the d3 tip container.  Sets or gets attribute value.
80
-    //
81
-    // n - name of the attribute
82
-    // v - value of the attribute
83
-    //
84
-    // Returns tip or attribute value
85
-    tip.attr = function(n, v) {
86
-      if (arguments.length < 2 && typeof n === 'string') {
87
-        return getNodeEl().attr(n)
88
-      } else {
89
-        var args =  Array.prototype.slice.call(arguments)
90
-        d3.selection.prototype.attr.apply(getNodeEl(), args)
91
-      }
92
-
93
-      return tip
94
-    }
95
-
96
-    // Public: Proxy style calls to the d3 tip container.  Sets or gets a style value.
97
-    //
98
-    // n - name of the property
99
-    // v - value of the property
100
-    //
101
-    // Returns tip or style property value
102
-    tip.style = function(n, v) {
103
-      if (arguments.length < 2 && typeof n === 'string') {
104
-        return getNodeEl().style(n)
105
-      } else {
106
-        var args = Array.prototype.slice.call(arguments)
107
-        d3.selection.prototype.style.apply(getNodeEl(), args)
108
-      }
109
-
110
-      return tip
111
-    }
112
-
113
-    // Public: Set or get the direction of the tooltip
114
-    //
115
-    // v - One of n(north), s(south), e(east), or w(west), nw(northwest),
116
-    //     sw(southwest), ne(northeast) or se(southeast)
117
-    //
118
-    // Returns tip or direction
119
-    tip.direction = function(v) {
120
-      if (!arguments.length) return direction
121
-      direction = v == null ? v : functor(v)
122
-
123
-      return tip
124
-    }
125
-
126
-    // Public: Sets or gets the offset of the tip
127
-    //
128
-    // v - Array of [x, y] offset
129
-    //
130
-    // Returns offset or
131
-    tip.offset = function(v) {
132
-      if (!arguments.length) return offset
133
-      offset = v == null ? v : functor(v)
134
-
135
-      return tip
136
-    }
137
-
138
-    // Public: sets or gets the html value of the tooltip
139
-    //
140
-    // v - String value of the tip
141
-    //
142
-    // Returns html value or tip
143
-    tip.html = function(v) {
144
-      if (!arguments.length) return html
145
-      html = v == null ? v : functor(v)
146
-
147
-      return tip
148
-    }
149
-
150
-    // Public: destroys the tooltip and removes it from the DOM
151
-    //
152
-    // Returns a tip
153
-    tip.destroy = function() {
154
-      if(node) {
155
-        getNodeEl().remove();
156
-        node = null;
157
-      }
158
-      return tip;
159
-    }
160
-
161
-    function d3_tip_direction() { return 'n' }
162
-    function d3_tip_offset() { return [0, 0] }
163
-    function d3_tip_html() { return ' ' }
164
-
165
-    var direction_callbacks = d3.map({
166
-      n:  direction_n,
167
-      s:  direction_s,
168
-      e:  direction_e,
169
-      w:  direction_w,
170
-      nw: direction_nw,
171
-      ne: direction_ne,
172
-      sw: direction_sw,
173
-      se: direction_se
174
-    }),
175
-
176
-    directions = direction_callbacks.keys()
177
-
178
-    function direction_n() {
179
-      var bbox = getScreenBBox()
180
-      return {
181
-        top:  bbox.n.y - node.offsetHeight,
182
-        left: bbox.n.x - node.offsetWidth / 2
183
-      }
184
-    }
185
-
186
-    function direction_s() {
187
-      var bbox = getScreenBBox()
188
-      return {
189
-        top:  bbox.s.y,
190
-        left: bbox.s.x - node.offsetWidth / 2
191
-      }
192
-    }
193
-
194
-    function direction_e() {
195
-      var bbox = getScreenBBox()
196
-      return {
197
-        top:  bbox.e.y - node.offsetHeight / 2,
198
-        left: bbox.e.x
199
-      }
200
-    }
201
-
202
-    function direction_w() {
203
-      var bbox = getScreenBBox()
204
-      return {
205
-        top:  bbox.w.y - node.offsetHeight / 2,
206
-        left: bbox.w.x - node.offsetWidth
207
-      }
208
-    }
209
-
210
-    function direction_nw() {
211
-      var bbox = getScreenBBox()
212
-      return {
213
-        top:  bbox.nw.y - node.offsetHeight,
214
-        left: bbox.nw.x - node.offsetWidth
215
-      }
216
-    }
217
-
218
-    function direction_ne() {
219
-      var bbox = getScreenBBox()
220
-      return {
221
-        top:  bbox.ne.y - node.offsetHeight,
222
-        left: bbox.ne.x
223
-      }
224
-    }
225
-
226
-    function direction_sw() {
227
-      var bbox = getScreenBBox()
228
-      return {
229
-        top:  bbox.sw.y,
230
-        left: bbox.sw.x - node.offsetWidth
231
-      }
232
-    }
233
-
234
-    function direction_se() {
235
-      var bbox = getScreenBBox()
236
-      return {
237
-        top:  bbox.se.y,
238
-        left: bbox.e.x
239
-      }
240
-    }
241
-
242
-    function initNode() {
243
-      var node = d3.select(document.createElement('div'));
244
-      node.style('position', 'absolute').style('top', 0).style('opacity', 0)
245
-      	.style('pointer-events', 'none').style('box-sizing', 'border-box')
246
-
247
-      return node.node()
248
-    }
249
-
250
-    function getSVGNode(el) {
251
-      el = el.node()
252
-      if(el.tagName.toLowerCase() === 'svg')
253
-        return el
254
-
255
-      return el.ownerSVGElement
256
-    }
257
-
258
-    function getNodeEl() {
259
-      if(node === null) {
260
-        node = initNode();
261
-        // re-add node to DOM
262
-        document.body.appendChild(node);
263
-      };
264
-      return d3.select(node);
265
-    }
266
-
267
-    // Private - gets the screen coordinates of a shape
268
-    //
269
-    // Given a shape on the screen, will return an SVGPoint for the directions
270
-    // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest),
271
-    // sw(southwest).
272
-    //
273
-    //    +-+-+
274
-    //    |   |
275
-    //    +   +
276
-    //    |   |
277
-    //    +-+-+
278
-    //
279
-    // Returns an Object {n, s, e, w, nw, sw, ne, se}
280
-    function getScreenBBox() {
281
-      var targetel   = target || d3.event.target;
282
-
283
-      while ('undefined' === typeof targetel.getScreenCTM && 'undefined' === targetel.parentNode) {
284
-          targetel = targetel.parentNode;
285
-      }
286
-
287
-      var bbox       = {},
288
-          matrix     = targetel.getScreenCTM(),
289
-          tbbox      = targetel.getBBox(),
290
-          width      = tbbox.width,
291
-          height     = tbbox.height,
292
-          x          = tbbox.x,
293
-          y          = tbbox.y
294
-
295
-      point.x = x
296
-      point.y = y
297
-      bbox.nw = point.matrixTransform(matrix)
298
-      point.x += width
299
-      bbox.ne = point.matrixTransform(matrix)
300
-      point.y += height
301
-      bbox.se = point.matrixTransform(matrix)
302
-      point.x -= width
303
-      bbox.sw = point.matrixTransform(matrix)
304
-      point.y -= height / 2
305
-      bbox.w  = point.matrixTransform(matrix)
306
-      point.x += width
307
-      bbox.e = point.matrixTransform(matrix)
308
-      point.x -= width / 2
309
-      point.y -= height / 2
310
-      bbox.n = point.matrixTransform(matrix)
311
-      point.y += height
312
-      bbox.s = point.matrixTransform(matrix)
313
-
314
-      return bbox
315
-    }
316
-    
317
-    // Private - replace D3JS 3.X d3.functor() function
318
-    function functor(v) {
319
-    	return typeof v === "function" ? v : function() {
320
-        return v
321
-    	}
322
-    }
323
-
324
-    return tip
325
-  };
326
-
327
-}));
328
-`

+ 44
- 0
update_d3flamegraph.sh Visa fil

@@ -0,0 +1,44 @@
1
+#!/usr/bin/env bash
2
+
3
+set -eu
4
+set -o pipefail
5
+
6
+D3FLAMEGRAPH_REPO="https://raw.githubusercontent.com/spiermar/d3-flame-graph"
7
+D3FLAMEGRAPH_VERSION="2.0.0-alpha4"
8
+D3FLAMEGRAPH_JS="d3-flamegraph.js"
9
+D3FLAMEGRAPH_CSS="d3-flamegraph.css"
10
+
11
+D3FLAMEGRAPH_DIR=third_party/d3flamegraph
12
+
13
+generate_d3flamegraph_go() {
14
+    local d3_js=$(curl -s "${D3FLAMEGRAPH_REPO}/${D3FLAMEGRAPH_VERSION}/dist/${D3FLAMEGRAPH_JS}" | sed 's/`/`+"`"+`/g')
15
+    local d3_css=$(curl -s "${D3FLAMEGRAPH_REPO}/${D3FLAMEGRAPH_VERSION}/dist/${D3FLAMEGRAPH_CSS}")
16
+
17
+    cat <<-EOF > $D3FLAMEGRAPH_DIR/d3_flame_graph.go
18
+// A D3.js plugin that produces flame graphs from hierarchical data.
19
+// https://github.com/spiermar/d3-flame-graph
20
+// Version $D3FLAMEGRAPH_VERSION
21
+// See LICENSE file for license details
22
+
23
+package d3flamegraph
24
+
25
+// JSSource returns the $D3FLAMEGRAPH_JS file
26
+const JSSource = \`
27
+$d3_js
28
+\`
29
+
30
+// CSSSource returns the $D3FLAMEGRAPH_CSS file
31
+const CSSSource = \`
32
+$d3_css
33
+\`
34
+EOF
35
+    gofmt -w $D3FLAMEGRAPH_DIR/d3_flame_graph.go
36
+}
37
+
38
+get_license() {
39
+    curl -s -o $D3FLAMEGRAPH_DIR/LICENSE "${D3FLAMEGRAPH_REPO}/${D3FLAMEGRAPH_VERSION}/LICENSE"
40
+}
41
+
42
+mkdir -p $D3FLAMEGRAPH_DIR
43
+get_license
44
+generate_d3flamegraph_go