浏览代码

Disambiguate names for kcachegrind under the call_tree option

When using the call_tree option and generating a graph for kcachegrind,
it will merge back nodes that are distinct on the tree, producing some
confusing results. Add a suffix so that these entries are kept separate.

This addresses the problem described in
http://yosefk.com/blog/how-profilers-lie-the-cases-of-gprof-and-kcachegrind.html ,
particularly the summary "Choosing a profiler is hard" section.
Raul Silvera 8 年前
父节点
当前提交
81cfe92f9b
共有 3 个文件被更改,包括 101 次插入9 次删除
  1. 24
    6
      internal/graph/graph.go
  2. 46
    3
      internal/report/report.go
  3. 31
    0
      internal/report/report_test.go

+ 24
- 6
internal/graph/graph.go 查看文件

50
 // Node is an entry on a profiling report. It represents a unique
50
 // Node is an entry on a profiling report. It represents a unique
51
 // program location.
51
 // program location.
52
 type Node struct {
52
 type Node struct {
53
-	// Information associated to this entry.
53
+	// Info describes the source location associated to this node.
54
 	Info NodeInfo
54
 	Info NodeInfo
55
 
55
 
56
-	// values associated to this node.
57
-	// Flat is exclusive to this node, cum includes all descendents.
56
+	// Function represents the function that this node belongs to.  On
57
+	// graphs with sub-function resolution (eg line number or
58
+	// addresses), two nodes in a NodeMap that are part of the same
59
+	// function have the same value of Node.Function. If the Node
60
+	// represents the whole function, it points back to itself.
61
+	Function *Node
62
+
63
+	// Values associated to this node. Flat is exclusive to this node,
64
+	// Cum includes all descendents.
58
 	Flat, Cum int64
65
 	Flat, Cum int64
59
 
66
 
60
-	// in and out contains the nodes immediately reaching or reached by this nodes.
67
+	// In and out Contains the nodes immediately reaching or reached by
68
+	// this node.
61
 	In, Out EdgeMap
69
 	In, Out EdgeMap
62
 
70
 
63
-	// tags provide additional information about subsets of a sample.
71
+	// LabelTags provide additional information about subsets of a sample.
64
 	LabelTags TagMap
72
 	LabelTags TagMap
65
 
73
 
66
-	// Numeric tags provide additional values for subsets of a sample.
74
+	// NumericTags provide additional values for subsets of a sample.
67
 	// Numeric tags are optionally associated to a label tag. The key
75
 	// Numeric tags are optionally associated to a label tag. The key
68
 	// for NumericTags is the name of the LabelTag they are associated
76
 	// for NumericTags is the name of the LabelTag they are associated
69
 	// to, or "" for numeric tags not associated to a label tag.
77
 	// to, or "" for numeric tags not associated to a label tag.
176
 		NumericTags: make(map[string]TagMap),
184
 		NumericTags: make(map[string]TagMap),
177
 	}
185
 	}
178
 	nm[info] = n
186
 	nm[info] = n
187
+	if info.Address == 0 && info.Lineno == 0 {
188
+		// This node represents the whole function, so point Function
189
+		// back to itself.
190
+		n.Function = n
191
+		return n
192
+	}
193
+	// Find a node that represents the whole function.
194
+	info.Address = 0
195
+	info.Lineno = 0
196
+	n.Function = nm.FindOrInsertNode(info, nil)
179
 	return n
197
 	return n
180
 }
198
 }
181
 
199
 

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

203
 	gopt := &graph.Options{
203
 	gopt := &graph.Options{
204
 		SampleValue:  o.SampleValue,
204
 		SampleValue:  o.SampleValue,
205
 		FormatTag:    formatTag,
205
 		FormatTag:    formatTag,
206
-		CallTree:     o.CallTree && o.OutputFormat == Dot,
206
+		CallTree:     o.CallTree && (o.OutputFormat == Dot || o.OutputFormat == Callgrind),
207
 		DropNegative: o.DropNegative,
207
 		DropNegative: o.DropNegative,
208
 		KeptNodes:    nodes,
208
 		KeptNodes:    nodes,
209
 	}
209
 	}
627
 	g, _, _, _ := rpt.newTrimmedGraph()
627
 	g, _, _, _ := rpt.newTrimmedGraph()
628
 	rpt.selectOutputUnit(g)
628
 	rpt.selectOutputUnit(g)
629
 
629
 
630
+	nodeNames := getDisambiguatedNames(g)
630
 	fmt.Fprintln(w, "events:", o.SampleType+"("+o.OutputUnit+")")
631
 	fmt.Fprintln(w, "events:", o.SampleType+"("+o.OutputUnit+")")
631
 
632
 
632
 	files := make(map[string]int)
633
 	files := make(map[string]int)
633
 	names := make(map[string]int)
634
 	names := make(map[string]int)
634
 	for _, n := range g.Nodes {
635
 	for _, n := range g.Nodes {
635
 		fmt.Fprintln(w, "fl="+callgrindName(files, n.Info.File))
636
 		fmt.Fprintln(w, "fl="+callgrindName(files, n.Info.File))
636
-		fmt.Fprintln(w, "fn="+callgrindName(names, n.Info.Name))
637
+		fmt.Fprintln(w, "fn="+callgrindName(names, nodeNames[n]))
637
 		sv, _ := measurement.Scale(n.Flat, o.SampleUnit, o.OutputUnit)
638
 		sv, _ := measurement.Scale(n.Flat, o.SampleUnit, o.OutputUnit)
638
 		fmt.Fprintf(w, "%d %d\n", n.Info.Lineno, int64(sv))
639
 		fmt.Fprintf(w, "%d %d\n", n.Info.Lineno, int64(sv))
639
 
640
 
642
 			c, _ := measurement.Scale(out.Weight, o.SampleUnit, o.OutputUnit)
643
 			c, _ := measurement.Scale(out.Weight, o.SampleUnit, o.OutputUnit)
643
 			callee := out.Dest
644
 			callee := out.Dest
644
 			fmt.Fprintln(w, "cfl="+callgrindName(files, callee.Info.File))
645
 			fmt.Fprintln(w, "cfl="+callgrindName(files, callee.Info.File))
645
-			fmt.Fprintln(w, "cfn="+callgrindName(names, callee.Info.Name))
646
+			fmt.Fprintln(w, "cfn="+callgrindName(names, nodeNames[callee]))
646
 			// pprof doesn't have a flat weight for a call, leave as 0.
647
 			// pprof doesn't have a flat weight for a call, leave as 0.
647
 			fmt.Fprintln(w, "calls=0", callee.Info.Lineno)
648
 			fmt.Fprintln(w, "calls=0", callee.Info.Lineno)
648
 			fmt.Fprintln(w, n.Info.Lineno, int64(c))
649
 			fmt.Fprintln(w, n.Info.Lineno, int64(c))
653
 	return nil
654
 	return nil
654
 }
655
 }
655
 
656
 
657
+// getDisambiguatedNames returns a map from each node in the graph to
658
+// the name to use in the callgrind output. Callgrind merges all
659
+// functions with the same [file name, function name]. Add a [%d/n]
660
+// suffix to disambiguate nodes with different values of
661
+// node.Function, which we want to keep separate. In particular, this
662
+// affects graphs created with --call_tree, where nodes from different
663
+// contexts are associated to different Functions.
664
+func getDisambiguatedNames(g *graph.Graph) map[*graph.Node]string {
665
+	nodeName := make(map[*graph.Node]string, len(g.Nodes))
666
+
667
+	type names struct {
668
+		file, function string
669
+	}
670
+
671
+	// nameFunctionIndex maps the callgrind names (filename, function)
672
+	// to the node.Function values found for that name, and each
673
+	// node.Function value to a sequential index to be used on the
674
+	// disambiguated name.
675
+	nameFunctionIndex := make(map[names]map[*graph.Node]int)
676
+	for _, n := range g.Nodes {
677
+		nm := names{n.Info.File, n.Info.Name}
678
+		p, ok := nameFunctionIndex[nm]
679
+		if !ok {
680
+			p = make(map[*graph.Node]int)
681
+			nameFunctionIndex[nm] = p
682
+		}
683
+		if _, ok := p[n.Function]; !ok {
684
+			p[n.Function] = len(p)
685
+		}
686
+	}
687
+
688
+	for _, n := range g.Nodes {
689
+		nm := names{n.Info.File, n.Info.Name}
690
+		nodeName[n] = n.Info.Name
691
+		if p := nameFunctionIndex[nm]; len(p) > 1 {
692
+			// If there is more than one function, add suffix to disambiguate.
693
+			nodeName[n] += fmt.Sprintf(" [%d/%d]", p[n.Function]+1, len(p))
694
+		}
695
+	}
696
+	return nodeName
697
+}
698
+
656
 // callgrindName implements the callgrind naming compression scheme.
699
 // callgrindName implements the callgrind naming compression scheme.
657
 // For names not previously seen returns "(N) name", where N is a
700
 // For names not previously seen returns "(N) name", where N is a
658
 // unique index.  For names previously seen returns "(N)" where N is
701
 // unique index.  For names previously seen returns "(N)" where N is

+ 31
- 0
internal/report/report_test.go 查看文件

21
 	"testing"
21
 	"testing"
22
 
22
 
23
 	"github.com/google/pprof/internal/binutils"
23
 	"github.com/google/pprof/internal/binutils"
24
+	"github.com/google/pprof/internal/graph"
24
 	"github.com/google/pprof/internal/proftest"
25
 	"github.com/google/pprof/internal/proftest"
25
 	"github.com/google/pprof/profile"
26
 	"github.com/google/pprof/profile"
26
 )
27
 )
205
 	Function: testF,
206
 	Function: testF,
206
 	Mapping:  testM,
207
 	Mapping:  testM,
207
 }
208
 }
209
+
210
+func TestDisambiguation(t *testing.T) {
211
+	parent1 := &graph.Node{Info: graph.NodeInfo{Name: "parent1"}}
212
+	parent2 := &graph.Node{Info: graph.NodeInfo{Name: "parent2"}}
213
+	child1 := &graph.Node{Info: graph.NodeInfo{Name: "child"}, Function: parent1}
214
+	child2 := &graph.Node{Info: graph.NodeInfo{Name: "child"}, Function: parent2}
215
+	child3 := &graph.Node{Info: graph.NodeInfo{Name: "child"}, Function: parent1}
216
+	sibling := &graph.Node{Info: graph.NodeInfo{Name: "sibling"}, Function: parent1}
217
+
218
+	n := []*graph.Node{parent1, parent2, child1, child2, child3, sibling}
219
+
220
+	wanted := map[*graph.Node]string{
221
+		parent1: "parent1",
222
+		parent2: "parent2",
223
+		child1:  "child [1/2]",
224
+		child2:  "child [2/2]",
225
+		child3:  "child [1/2]",
226
+		sibling: "sibling",
227
+	}
228
+
229
+	g := &graph.Graph{n}
230
+
231
+	names := getDisambiguatedNames(g)
232
+
233
+	for node, want := range wanted {
234
+		if got := names[node]; got != want {
235
+			t.Errorf("name %s, got %s, want %s", node.Info.Name, got, want)
236
+		}
237
+	}
238
+}