浏览代码

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
 	kept := o.KeptNodes
288
 	kept := o.KeptNodes
289
 	keepBinary := o.ObjNames
289
 	keepBinary := o.ObjNames
290
 	parentNodeMap := make(map[*Node]NodeMap, len(prof.Sample))
290
 	parentNodeMap := make(map[*Node]NodeMap, len(prof.Sample))
291
+nextSample:
291
 	for _, sample := range prof.Sample {
292
 	for _, sample := range prof.Sample {
292
 		weight := o.SampleValue(sample.Value)
293
 		weight := o.SampleValue(sample.Value)
293
 		if weight == 0 {
294
 		if weight == 0 {
294
 			continue
295
 			continue
295
 		}
296
 		}
296
 		var parent *Node
297
 		var parent *Node
297
-		// A residual edge goes over one or more nodes that were not kept.
298
-		residual := false
299
 		labels := joinLabels(sample)
298
 		labels := joinLabels(sample)
300
 		// Group the sample frames, based on a per-node map.
299
 		// Group the sample frames, based on a per-node map.
301
 		for i := len(sample.Location) - 1; i >= 0; i-- {
300
 		for i := len(sample.Location) - 1; i >= 0; i-- {
302
 			l := sample.Location[i]
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
 				if n == nil {
313
 				if n == nil {
312
-					residual = true
313
-					continue
314
+					continue nextSample
314
 				}
315
 				}
315
 				n.addSample(weight, labels, sample.NumLabel, o.FormatTag, false)
316
 				n.addSample(weight, labels, sample.NumLabel, o.FormatTag, false)
316
 				if parent != nil {
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
 				parent = n
320
 				parent = n
320
-				residual = false
321
 			}
321
 			}
322
 		}
322
 		}
323
-		if parent != nil && !residual {
323
+		if parent != nil {
324
 			parent.addSample(weight, labels, sample.NumLabel, o.FormatTag, true)
324
 			parent.addSample(weight, labels, sample.NumLabel, o.FormatTag, true)
325
 		}
325
 		}
326
 	}
326
 	}
395
 
395
 
396
 	nm := make(NodeMap, len(prof.Location))
396
 	nm := make(NodeMap, len(prof.Location))
397
 	for _, l := range prof.Location {
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
 	return nm.nodes(), locations
408
 	return nm.nodes(), locations
403
 }
409
 }
410
 	return nodes
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
 	var objfile string
420
 	var objfile string
415
 	if m := l.Mapping; m != nil && m.File != "" {
421
 	if m := l.Mapping; m != nil && m.File != "" {
416
 		objfile = filepath.Base(m.File)
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
 	if line.Function == nil {
432
 	if line.Function == nil {
441
-		if l.Address == 0 {
442
-			return nil
443
-		}
444
 		return &NodeInfo{Address: l.Address, Objfile: objfile}
433
 		return &NodeInfo{Address: l.Address, Objfile: objfile}
445
 	}
434
 	}
446
-
447
 	ni := &NodeInfo{
435
 	ni := &NodeInfo{
448
 		Address:  l.Address,
436
 		Address:  l.Address,
449
 		Lineno:   int(line.Line),
437
 		Lineno:   int(line.Line),
450
 		Name:     line.Function.Name,
438
 		Name:     line.Function.Name,
451
 		OrigName: line.Function.SystemName,
439
 		OrigName: line.Function.SystemName,
452
-		Objfile:  objfile,
453
 	}
440
 	}
454
 	if fname := line.Function.Filename; fname != "" {
441
 	if fname := line.Function.Filename; fname != "" {
455
 		ni.File = filepath.Clean(fname)
442
 		ni.File = filepath.Clean(fname)
456
 	}
443
 	}
457
 	if keepBinary {
444
 	if keepBinary {
445
+		ni.Objfile = objfile
458
 		ni.StartLine = int(line.Function.StartLine)
446
 		ni.StartLine = int(line.Function.StartLine)
459
 	}
447
 	}
460
 	return ni
448
 	return ni
479
 	return t.t[i].Name < t.t[j].Name
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
 func (ns Nodes) Sum() (flat int64, cum int64) {
471
 func (ns Nodes) Sum() (flat int64, cum int64) {
484
 	for _, n := range ns {
472
 	for _, n := range ns {
485
 		flat += n.Flat
473
 		flat += n.Flat

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

77
 
77
 
78
 	// Build a graph and refine it. On each refinement step we must rebuild the graph from the samples,
78
 	// Build a graph and refine it. On each refinement step we must rebuild the graph from the samples,
79
 	// as the graph itself doesn't contain enough information to preserve full precision.
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
 	// First step: Build complete graph to identify low frequency nodes, based on their cum weight.
83
 	// First step: Build complete graph to identify low frequency nodes, based on their cum weight.
82
 	g = rpt.newGraph(nil)
84
 	g = rpt.newGraph(nil)
84
 	nodeCutoff := abs64(int64(float64(totalValue) * o.NodeFraction))
86
 	nodeCutoff := abs64(int64(float64(totalValue) * o.NodeFraction))
85
 	edgeCutoff := abs64(int64(float64(totalValue) * o.EdgeFraction))
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
 	// Filter out nodes with cum value below nodeCutoff.
99
 	// Filter out nodes with cum value below nodeCutoff.
88
 	if nodeCutoff > 0 {
100
 	if nodeCutoff > 0 {
89
 		if nodesKept := g.DiscardLowFrequencyNodes(nodeCutoff); len(g.Nodes) != len(nodesKept) {
101
 		if nodesKept := g.DiscardLowFrequencyNodes(nodeCutoff); len(g.Nodes) != len(nodesKept) {
95
 
107
 
96
 	// Second step: Limit the total number of nodes. Apply specialized heuristics to improve
108
 	// Second step: Limit the total number of nodes. Apply specialized heuristics to improve
97
 	// visualization when generating dot output.
109
 	// visualization when generating dot output.
98
-	visualMode := o.OutputFormat == Dot
99
-	g.SortNodes(o.CumSort, visualMode)
110
+	g.SortNodes(cumSort, visualMode)
100
 	if nodeCount := o.NodeCount; nodeCount > 0 {
111
 	if nodeCount := o.NodeCount; nodeCount > 0 {
101
 		// Remove low frequency tags and edges as they affect selection.
112
 		// Remove low frequency tags and edges as they affect selection.
102
 		g.TrimLowFrequencyTags(nodeCutoff)
113
 		g.TrimLowFrequencyTags(nodeCutoff)
103
 		g.TrimLowFrequencyEdges(edgeCutoff)
114
 		g.TrimLowFrequencyEdges(edgeCutoff)
104
 		if nodesKept := g.SelectTopNodes(nodeCount, visualMode); len(nodesKept) != len(g.Nodes) {
115
 		if nodesKept := g.SelectTopNodes(nodeCount, visualMode); len(nodesKept) != len(g.Nodes) {
105
 			g = rpt.newGraph(nodesKept)
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
 digraph "unnamed" {
1
 digraph "unnamed" {
2
 node [style=filled fillcolor="#f8f8f8"]
2
 node [style=filled fillcolor="#f8f8f8"]
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"] }
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
 N7 [label="foo\nsource1:4\n0 of 10 (0.09%)" fontsize=8 shape=box tooltip="foo testdata/source1:4 (10)" color="#b2b2b1" fillcolor="#ededed"]
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
 }