瀏覽代碼

Ensure pprof call_tree output is a tree

When generating a call tree, pprof was using a map to keep track of all the
inline nodes for a location. That is incorrect as it may cause inline functions
at different nesting levels to reuse the same node, causing the resulting graph
to not be a tree.

When creating a tree nodes with the same info may appear on multiple places
in the tree. Keeping one of them preserves them all, which may cause disconnected
nodes to remain. To ensure the resulting graph is a connected tree, do not include
children on any removed node, which is suitable for the normal tree refinement
(nodecount and nodefraction) but does not allow visual refinement, which may eliminate
intermediate nodes. Disable visual mode refinement for call_tree to avoid this issue.
Raul Silvera 9 年之前
父節點
當前提交
198d319c7d
共有 3 個檔案被更改,包括 55 行新增56 行删除
  1. 29
    41
      internal/graph/graph.go
  2. 14
    3
      internal/report/report.go
  3. 12
    12
      internal/report/testdata/source.dot

+ 29
- 41
internal/graph/graph.go 查看文件

@@ -288,39 +288,39 @@ func newTree(prof *profile.Profile, o *Options) (g *Graph) {
288 288
 	kept := o.KeptNodes
289 289
 	keepBinary := o.ObjNames
290 290
 	parentNodeMap := make(map[*Node]NodeMap, len(prof.Sample))
291
+nextSample:
291 292
 	for _, sample := range prof.Sample {
292 293
 		weight := o.SampleValue(sample.Value)
293 294
 		if weight == 0 {
294 295
 			continue
295 296
 		}
296 297
 		var parent *Node
297
-		// A residual edge goes over one or more nodes that were not kept.
298
-		residual := false
299 298
 		labels := joinLabels(sample)
300 299
 		// Group the sample frames, based on a per-node map.
301 300
 		for i := len(sample.Location) - 1; i >= 0; i-- {
302 301
 			l := sample.Location[i]
303
-			nodeMap := parentNodeMap[parent]
304
-			if nodeMap == nil {
305
-				nodeMap = make(NodeMap)
306
-				parentNodeMap[parent] = nodeMap
302
+			lines := l.Line
303
+			if len(lines) == 0 {
304
+				lines = []profile.Line{{}} // Create empty line to include location info.
307 305
 			}
308
-			locNodes := nodeMap.findOrInsertLocation(l, keepBinary, kept)
309
-			for ni := len(locNodes) - 1; ni >= 0; ni-- {
310
-				n := locNodes[ni]
306
+			for lidx := len(lines) - 1; lidx >= 0; lidx-- {
307
+				nodeMap := parentNodeMap[parent]
308
+				if nodeMap == nil {
309
+					nodeMap = make(NodeMap)
310
+					parentNodeMap[parent] = nodeMap
311
+				}
312
+				n := nodeMap.findOrInsertLine(l, lines[lidx], keepBinary, kept)
311 313
 				if n == nil {
312
-					residual = true
313
-					continue
314
+					continue nextSample
314 315
 				}
315 316
 				n.addSample(weight, labels, sample.NumLabel, o.FormatTag, false)
316 317
 				if parent != nil {
317
-					parent.AddToEdge(n, weight, residual, ni != len(locNodes)-1)
318
+					parent.AddToEdge(n, weight, false, lidx != len(lines)-1)
318 319
 				}
319 320
 				parent = n
320
-				residual = false
321 321
 			}
322 322
 		}
323
-		if parent != nil && !residual {
323
+		if parent != nil {
324 324
 			parent.addSample(weight, labels, sample.NumLabel, o.FormatTag, true)
325 325
 		}
326 326
 	}
@@ -395,9 +395,15 @@ func CreateNodes(prof *profile.Profile, keepBinary bool, kept NodeSet) (Nodes, m
395 395
 
396 396
 	nm := make(NodeMap, len(prof.Location))
397 397
 	for _, l := range prof.Location {
398
-		if nodes := nm.findOrInsertLocation(l, keepBinary, kept); nodes != nil {
399
-			locations[l.ID] = nodes
398
+		lines := l.Line
399
+		if len(lines) == 0 {
400
+			lines = []profile.Line{{}} // Create empty line to include location info.
400 401
 		}
402
+		nodes := make(Nodes, len(lines))
403
+		for ln := range lines {
404
+			nodes[ln] = nm.findOrInsertLine(l, lines[ln], keepBinary, kept)
405
+		}
406
+		locations[l.ID] = nodes
401 407
 	}
402 408
 	return nm.nodes(), locations
403 409
 }
@@ -410,51 +416,33 @@ func (nm NodeMap) nodes() Nodes {
410 416
 	return nodes
411 417
 }
412 418
 
413
-func (nm NodeMap) findOrInsertLocation(l *profile.Location, keepBinary bool, kept NodeSet) Nodes {
419
+func (nm NodeMap) findOrInsertLine(l *profile.Location, li profile.Line, keepBinary bool, kept NodeSet) *Node {
414 420
 	var objfile string
415 421
 	if m := l.Mapping; m != nil && m.File != "" {
416 422
 		objfile = filepath.Base(m.File)
417 423
 	}
418 424
 
419
-	if len(l.Line) == 0 {
420
-		ni := NodeInfo{
421
-			Address: l.Address,
422
-			Objfile: objfile,
423
-		}
424
-		return Nodes{nm.FindOrInsertNode(ni, kept)}
425
+	if ni := nodeInfo(l, li, objfile, keepBinary); ni != nil {
426
+		return nm.FindOrInsertNode(*ni, kept)
425 427
 	}
426
-	locNodes := make(Nodes, len(l.Line))
427
-	for li := range l.Line {
428
-		if ni := nodeInfo(l, li, objfile, keepBinary); ni != nil {
429
-			locNodes[li] = nm.FindOrInsertNode(*ni, kept)
430
-		}
431
-	}
432
-	return locNodes
428
+	return nil
433 429
 }
434 430
 
435
-func nodeInfo(l *profile.Location, li int, objfile string, keepBinary bool) *NodeInfo {
436
-	if !keepBinary {
437
-		objfile = ""
438
-	}
439
-	line := l.Line[li]
431
+func nodeInfo(l *profile.Location, line profile.Line, objfile string, keepBinary bool) *NodeInfo {
440 432
 	if line.Function == nil {
441
-		if l.Address == 0 {
442
-			return nil
443
-		}
444 433
 		return &NodeInfo{Address: l.Address, Objfile: objfile}
445 434
 	}
446
-
447 435
 	ni := &NodeInfo{
448 436
 		Address:  l.Address,
449 437
 		Lineno:   int(line.Line),
450 438
 		Name:     line.Function.Name,
451 439
 		OrigName: line.Function.SystemName,
452
-		Objfile:  objfile,
453 440
 	}
454 441
 	if fname := line.Function.Filename; fname != "" {
455 442
 		ni.File = filepath.Clean(fname)
456 443
 	}
457 444
 	if keepBinary {
445
+		ni.Objfile = objfile
458 446
 		ni.StartLine = int(line.Function.StartLine)
459 447
 	}
460 448
 	return ni
@@ -479,7 +467,7 @@ func (t tags) Less(i, j int) bool {
479 467
 	return t.t[i].Name < t.t[j].Name
480 468
 }
481 469
 
482
-// Sum adds the Flat and sum values on a report.
470
+// Sum adds the flat and cum values of a set of nodes.
483 471
 func (ns Nodes) Sum() (flat int64, cum int64) {
484 472
 	for _, n := range ns {
485 473
 		flat += n.Flat

+ 14
- 3
internal/report/report.go 查看文件

@@ -77,6 +77,8 @@ func (rpt *Report) newTrimmedGraph() (g *graph.Graph, origCount, droppedNodes, d
77 77
 
78 78
 	// Build a graph and refine it. On each refinement step we must rebuild the graph from the samples,
79 79
 	// as the graph itself doesn't contain enough information to preserve full precision.
80
+	visualMode := o.OutputFormat == Dot
81
+	cumSort := o.CumSort
80 82
 
81 83
 	// First step: Build complete graph to identify low frequency nodes, based on their cum weight.
82 84
 	g = rpt.newGraph(nil)
@@ -84,6 +86,16 @@ func (rpt *Report) newTrimmedGraph() (g *graph.Graph, origCount, droppedNodes, d
84 86
 	nodeCutoff := abs64(int64(float64(totalValue) * o.NodeFraction))
85 87
 	edgeCutoff := abs64(int64(float64(totalValue) * o.EdgeFraction))
86 88
 
89
+	// Visual mode optimization only supports graph output, not tree.
90
+	// Do not apply edge cutoff to preserve tree structure.
91
+	if o.CallTree {
92
+		visualMode = false
93
+		if o.OutputFormat == Dot {
94
+			cumSort = true
95
+		}
96
+		edgeCutoff = 0
97
+	}
98
+
87 99
 	// Filter out nodes with cum value below nodeCutoff.
88 100
 	if nodeCutoff > 0 {
89 101
 		if nodesKept := g.DiscardLowFrequencyNodes(nodeCutoff); len(g.Nodes) != len(nodesKept) {
@@ -95,15 +107,14 @@ func (rpt *Report) newTrimmedGraph() (g *graph.Graph, origCount, droppedNodes, d
95 107
 
96 108
 	// Second step: Limit the total number of nodes. Apply specialized heuristics to improve
97 109
 	// visualization when generating dot output.
98
-	visualMode := o.OutputFormat == Dot
99
-	g.SortNodes(o.CumSort, visualMode)
110
+	g.SortNodes(cumSort, visualMode)
100 111
 	if nodeCount := o.NodeCount; nodeCount > 0 {
101 112
 		// Remove low frequency tags and edges as they affect selection.
102 113
 		g.TrimLowFrequencyTags(nodeCutoff)
103 114
 		g.TrimLowFrequencyEdges(edgeCutoff)
104 115
 		if nodesKept := g.SelectTopNodes(nodeCount, visualMode); len(nodesKept) != len(g.Nodes) {
105 116
 			g = rpt.newGraph(nodesKept)
106
-			g.SortNodes(o.CumSort, visualMode)
117
+			g.SortNodes(cumSort, visualMode)
107 118
 		}
108 119
 	}
109 120
 

+ 12
- 12
internal/report/testdata/source.dot 查看文件

@@ -1,17 +1,17 @@
1 1
 digraph "unnamed" {
2 2
 node [style=filled fillcolor="#f8f8f8"]
3 3
 subgraph cluster_L { "Duration: 10s, Total samples = 11111 " [shape=box fontsize=16 label="Duration: 10s, Total samples = 11111 \lShowing nodes accounting for 11111, 100% of 11111 total\l"] }
4
-N1 [label="tee\nsource2:8\n10000 (90.00%)" fontsize=24 shape=box tooltip="tee testdata/source2:8 (10000)" color="#b20500" fillcolor="#edd6d5"]
5
-N2 [label="main\nsource1:2\n1 (0.009%)\nof 11111 (100%)" fontsize=9 shape=box tooltip="main testdata/source1:2 (11111)" color="#b20000" fillcolor="#edd5d5"]
6
-N3 [label="tee\nsource2:2\n1000 (9.00%)\nof 11000 (99.00%)" fontsize=14 shape=box tooltip="tee testdata/source2:2 (11000)" color="#b20000" fillcolor="#edd5d5"]
7
-N4 [label="tee\nsource2:8\n100 (0.9%)" fontsize=10 shape=box tooltip="tee testdata/source2:8 (100)" color="#b2b0aa" fillcolor="#edecec"]
8
-N5 [label="bar\nsource1:10\n10 (0.09%)" fontsize=9 shape=box tooltip="bar testdata/source1:10 (10)" color="#b2b2b1" fillcolor="#ededed"]
9
-N6 [label="bar\nsource1:10\n0 of 100 (0.9%)" fontsize=8 shape=box tooltip="bar testdata/source1:10 (100)" color="#b2b0aa" fillcolor="#edecec"]
4
+N1 [label="main\nsource1:2\n1 (0.009%)\nof 11111 (100%)" fontsize=9 shape=box tooltip="main testdata/source1:2 (11111)" color="#b20000" fillcolor="#edd5d5"]
5
+N2 [label="tee\nsource2:2\n1000 (9.00%)\nof 11000 (99.00%)" fontsize=14 shape=box tooltip="tee testdata/source2:2 (11000)" color="#b20000" fillcolor="#edd5d5"]
6
+N3 [label="tee\nsource2:8\n10000 (90.00%)" fontsize=24 shape=box tooltip="tee testdata/source2:8 (10000)" color="#b20500" fillcolor="#edd6d5"]
7
+N4 [label="bar\nsource1:10\n0 of 100 (0.9%)" fontsize=8 shape=box tooltip="bar testdata/source1:10 (100)" color="#b2b0aa" fillcolor="#edecec"]
8
+N5 [label="tee\nsource2:8\n100 (0.9%)" fontsize=10 shape=box tooltip="tee testdata/source2:8 (100)" color="#b2b0aa" fillcolor="#edecec"]
9
+N6 [label="bar\nsource1:10\n10 (0.09%)" fontsize=9 shape=box tooltip="bar testdata/source1:10 (10)" color="#b2b2b1" fillcolor="#ededed"]
10 10
 N7 [label="foo\nsource1:4\n0 of 10 (0.09%)" fontsize=8 shape=box tooltip="foo testdata/source1:4 (10)" color="#b2b2b1" fillcolor="#ededed"]
11
-N2 -> N3 [label=" 11000" weight=100 penwidth=5 color="#b20000" tooltip="main testdata/source1:2 -> tee testdata/source2:2 (11000)" labeltooltip="main testdata/source1:2 -> tee testdata/source2:2 (11000)"]
12
-N3 -> N1 [label=" 10000" weight=91 penwidth=5 color="#b20500" tooltip="tee testdata/source2:2 -> tee testdata/source2:8 (10000)" labeltooltip="tee testdata/source2:2 -> tee testdata/source2:8 (10000)"]
13
-N6 -> N4 [label=" 100" color="#b2b0aa" tooltip="bar testdata/source1:10 -> tee testdata/source2:8 (100)" labeltooltip="bar testdata/source1:10 -> tee testdata/source2:8 (100)"]
14
-N2 -> N6 [label=" 100" color="#b2b0aa" tooltip="main testdata/source1:2 -> bar testdata/source1:10 (100)" labeltooltip="main testdata/source1:2 -> bar testdata/source1:10 (100)"]
15
-N7 -> N5 [label=" 10" color="#b2b2b1" tooltip="foo testdata/source1:4 -> bar testdata/source1:10 (10)" labeltooltip="foo testdata/source1:4 -> bar testdata/source1:10 (10)"]
16
-N2 -> N7 [label=" 10" color="#b2b2b1" tooltip="main testdata/source1:2 -> foo testdata/source1:4 (10)" labeltooltip="main testdata/source1:2 -> foo testdata/source1:4 (10)"]
11
+N1 -> N2 [label=" 11000" weight=100 penwidth=5 color="#b20000" tooltip="main testdata/source1:2 -> tee testdata/source2:2 (11000)" labeltooltip="main testdata/source1:2 -> tee testdata/source2:2 (11000)"]
12
+N2 -> N3 [label=" 10000" weight=91 penwidth=5 color="#b20500" tooltip="tee testdata/source2:2 -> tee testdata/source2:8 (10000)" labeltooltip="tee testdata/source2:2 -> tee testdata/source2:8 (10000)"]
13
+N4 -> N5 [label=" 100" color="#b2b0aa" tooltip="bar testdata/source1:10 -> tee testdata/source2:8 (100)" labeltooltip="bar testdata/source1:10 -> tee testdata/source2:8 (100)"]
14
+N1 -> N4 [label=" 100" color="#b2b0aa" tooltip="main testdata/source1:2 -> bar testdata/source1:10 (100)" labeltooltip="main testdata/source1:2 -> bar testdata/source1:10 (100)"]
15
+N7 -> N6 [label=" 10" color="#b2b2b1" tooltip="foo testdata/source1:4 -> bar testdata/source1:10 (10)" labeltooltip="foo testdata/source1:4 -> bar testdata/source1:10 (10)"]
16
+N1 -> N7 [label=" 10" color="#b2b2b1" tooltip="main testdata/source1:2 -> foo testdata/source1:4 (10)" labeltooltip="main testdata/source1:2 -> foo testdata/source1:4 (10)"]
17 17
 }