Pārlūkot izejas kodu

Speed up graph creation

Separate implementation of graph and tree creation to speed it up.
Graph implementation maps upfront all locations to sequences of nodes,
tree implementation uses a per-parent map to keep track of a different
node per location per parent.
Raul Silvera 9 gadus atpakaļ
vecāks
revīzija
e5e7096d4f

+ 0
- 2
internal/driver/testdata/pprof.cpu.cum.lines.topproto.hide Parādīt failu

1
 Showing nodes accounting for 1s, 100% of 1s total
1
 Showing nodes accounting for 1s, 100% of 1s total
2
       flat  flat%   sum%        cum   cum%
2
       flat  flat%   sum%        cum   cum%
3
         1s   100%   100%         1s   100%  mangled1000 testdata/file1000.src:1
3
         1s   100%   100%         1s   100%  mangled1000 testdata/file1000.src:1
4
-         0     0%   100%          0     0%  mangled2000 testdata/file2000.src:4
5
-         0     0%   100%          0     0%  mangled2001 testdata/file2000.src:9

+ 194
- 134
internal/graph/graph.go Parādīt failu

20
 	"math"
20
 	"math"
21
 	"path/filepath"
21
 	"path/filepath"
22
 	"sort"
22
 	"sort"
23
+	"strconv"
23
 	"strings"
24
 	"strings"
24
 
25
 
25
 	"github.com/google/pprof/profile"
26
 	"github.com/google/pprof/profile"
69
 	NumericTags map[string]TagMap
70
 	NumericTags map[string]TagMap
70
 }
71
 }
71
 
72
 
72
-// BumpWeight increases the weight of an edge between two nodes. If
73
+// AddToEdge increases the weight of an edge between two nodes. If
73
 // there isn't such an edge one is created.
74
 // there isn't such an edge one is created.
74
-func (n *Node) BumpWeight(to *Node, w int64, residual, inline bool) {
75
+func (n *Node) AddToEdge(to *Node, w int64, residual, inline bool) {
75
 	if n.Out[to] != to.In[n] {
76
 	if n.Out[to] != to.In[n] {
76
 		panic(fmt.Errorf("asymmetric edges %v %v", *n, *to))
77
 		panic(fmt.Errorf("asymmetric edges %v %v", *n, *to))
77
 	}
78
 	}
136
 	return name
137
 	return name
137
 }
138
 }
138
 
139
 
139
-// ExtendedNodeInfo extends the NodeInfo with a pointer to a parent node, to
140
-// identify nodes with identical information and different callers. This is
141
-// used when creating call trees.
142
-type ExtendedNodeInfo struct {
143
-	NodeInfo
144
-	parent *Node
145
-}
146
-
147
 // NodeMap maps from a node info struct to a node. It is used to merge
140
 // NodeMap maps from a node info struct to a node. It is used to merge
148
 // report entries with the same info.
141
 // report entries with the same info.
149
-type NodeMap map[ExtendedNodeInfo]*Node
142
+type NodeMap map[NodeInfo]*Node
150
 
143
 
151
 // NodeSet maps is a collection of node info structs.
144
 // NodeSet maps is a collection of node info structs.
152
 type NodeSet map[NodeInfo]bool
145
 type NodeSet map[NodeInfo]bool
153
 
146
 
154
 // FindOrInsertNode takes the info for a node and either returns a matching node
147
 // FindOrInsertNode takes the info for a node and either returns a matching node
155
 // from the node map if one exists, or adds one to the map if one does not.
148
 // from the node map if one exists, or adds one to the map if one does not.
156
-// If parent is non-nil, return a match with the same parent.
157
 // If kept is non-nil, nodes are only added if they can be located on it.
149
 // If kept is non-nil, nodes are only added if they can be located on it.
158
-func (m NodeMap) FindOrInsertNode(info NodeInfo, parent *Node, kept NodeSet) *Node {
159
-	if kept != nil && !kept[info] {
160
-		return nil
161
-	}
162
-
163
-	extendedInfo := ExtendedNodeInfo{
164
-		info,
165
-		parent,
150
+func (nm NodeMap) FindOrInsertNode(info NodeInfo, kept NodeSet) *Node {
151
+	if kept != nil {
152
+		if _, ok := kept[info]; !ok {
153
+			return nil
154
+		}
166
 	}
155
 	}
167
 
156
 
168
-	if n := m[extendedInfo]; n != nil {
157
+	if n, ok := nm[info]; ok {
169
 		return n
158
 		return n
170
 	}
159
 	}
171
 
160
 
176
 		LabelTags:   make(TagMap),
165
 		LabelTags:   make(TagMap),
177
 		NumericTags: make(map[string]TagMap),
166
 		NumericTags: make(map[string]TagMap),
178
 	}
167
 	}
179
-	m[extendedInfo] = n
168
+	nm[info] = n
180
 	return n
169
 	return n
181
 }
170
 }
182
 
171
 
216
 
205
 
217
 // New summarizes performance data from a profile into a graph.
206
 // New summarizes performance data from a profile into a graph.
218
 func New(prof *profile.Profile, o *Options) (g *Graph) {
207
 func New(prof *profile.Profile, o *Options) (g *Graph) {
219
-	locations := NewLocInfo(prof, o.ObjNames)
220
-	nm := make(NodeMap)
208
+	if o.CallTree {
209
+		return newTree(prof, o)
210
+	}
211
+
212
+	nodes, locationMap := CreateNodes(prof, o.ObjNames, o.KeptNodes)
221
 	for _, sample := range prof.Sample {
213
 	for _, sample := range prof.Sample {
222
-		if sample.Location == nil {
214
+		weight := o.SampleValue(sample.Value)
215
+		if weight == 0 {
223
 			continue
216
 			continue
224
 		}
217
 		}
218
+		seenNode := make(map[*Node]bool, len(sample.Location))
219
+		seenEdge := make(map[nodePair]bool, len(sample.Location))
220
+		var parent *Node
221
+		// A residual edge goes over one or more nodes that were not kept.
222
+		residual := false
225
 
223
 
226
-		// Construct list of node names for sample.
227
-		// Keep track of the index on the Sample for each frame,
228
-		// to determine inlining status.
229
-
230
-		var stack []NodeInfo
231
-		var locIndex []int
232
-		for i, loc := range sample.Location {
233
-			id := loc.ID
234
-			stack = append(stack, locations[id]...)
235
-			for _ = range locations[id] {
236
-				locIndex = append(locIndex, i)
224
+		labels := joinLabels(sample)
225
+		// Group the sample frames, based on a global map.
226
+		for i := len(sample.Location) - 1; i >= 0; i-- {
227
+			l := sample.Location[i]
228
+			locNodes := locationMap[l.ID]
229
+			for ni := len(locNodes) - 1; ni >= 0; ni-- {
230
+				n := locNodes[ni]
231
+				if n == nil {
232
+					residual = true
233
+					continue
234
+				}
235
+				// Add cum weight to all nodes in stack, avoiding double counting.
236
+				if _, ok := seenNode[n]; !ok {
237
+					seenNode[n] = true
238
+					n.addSample(weight, labels, sample.NumLabel, o.FormatTag, false)
239
+				}
240
+				// Update edge weights for all edges in stack, avoiding double counting.
241
+				if _, ok := seenEdge[nodePair{n, parent}]; !ok && parent != nil && n != parent {
242
+					seenEdge[nodePair{n, parent}] = true
243
+					parent.AddToEdge(n, weight, residual, ni != len(locNodes)-1)
244
+				}
245
+				parent = n
246
+				residual = false
237
 			}
247
 			}
238
 		}
248
 		}
249
+		if parent != nil && !residual {
250
+			// Add flat weight to leaf node.
251
+			parent.addSample(weight, labels, sample.NumLabel, o.FormatTag, true)
252
+		}
253
+	}
254
+
255
+	return selectNodesForGraph(nodes, o.DropNegative)
256
+}
257
+
258
+func selectNodesForGraph(nodes Nodes, dropNegative bool) *Graph {
259
+	// Collect nodes into a graph.
260
+	gNodes := make(Nodes, 0, len(nodes))
261
+	for _, n := range nodes {
262
+		if n == nil {
263
+			continue
264
+		}
265
+		if n.Cum == 0 && n.Flat == 0 {
266
+			continue
267
+		}
268
+		if dropNegative && isNegative(n) {
269
+			continue
270
+		}
271
+		gNodes = append(gNodes, n)
272
+	}
273
+	return &Graph{gNodes}
274
+}
239
 
275
 
276
+type nodePair struct {
277
+	src, dest *Node
278
+}
279
+
280
+func newTree(prof *profile.Profile, o *Options) (g *Graph) {
281
+	kept := o.KeptNodes
282
+	keepBinary := o.ObjNames
283
+	parentNodeMap := make(map[*Node]NodeMap, len(prof.Sample))
284
+	for _, sample := range prof.Sample {
240
 		weight := o.SampleValue(sample.Value)
285
 		weight := o.SampleValue(sample.Value)
241
-		seenEdge := make(map[*Node]map[*Node]bool)
242
-		var nn *Node
243
-		nlocIndex := -1
286
+		if weight == 0 {
287
+			continue
288
+		}
289
+		var parent *Node
290
+		// A residual edge goes over one or more nodes that were not kept.
244
 		residual := false
291
 		residual := false
245
-		// Walk top-down over the frames in a sample, keeping track
246
-		// of the current parent if we're building a tree.
247
-		for i := len(stack); i > 0; i-- {
248
-			var parent *Node
249
-			if o.CallTree {
250
-				parent = nn
251
-			}
252
-			n := nm.FindOrInsertNode(stack[i-1], parent, o.KeptNodes)
253
-			if n == nil {
254
-				residual = true
255
-				continue
256
-			}
257
-			// Add flat weight to leaf node.
258
-			if i == 1 {
259
-				n.addSample(sample, weight, o.FormatTag, true)
260
-			}
261
-			// Add cum weight to all nodes in stack, avoiding double counting.
262
-			if seenEdge[n] == nil {
263
-				seenEdge[n] = make(map[*Node]bool)
264
-				n.addSample(sample, weight, o.FormatTag, false)
292
+		labels := joinLabels(sample)
293
+		// Group the sample frames, based on a per-node map.
294
+		for i := len(sample.Location) - 1; i >= 0; i-- {
295
+			l := sample.Location[i]
296
+			nodeMap := parentNodeMap[parent]
297
+			if nodeMap == nil {
298
+				nodeMap = make(NodeMap)
299
+				parentNodeMap[parent] = nodeMap
265
 			}
300
 			}
266
-			// Update edge weights for all edges in stack, avoiding double counting.
267
-			if nn != nil && n != nn && !seenEdge[n][nn] {
268
-				seenEdge[n][nn] = true
269
-				// This is an inlined edge if the caller and the callee
270
-				// correspond to the same entry in the sample.
271
-				nn.BumpWeight(n, weight, residual, locIndex[i-1] == nlocIndex)
301
+			locNodes := nodeMap.findOrInsertLocation(l, keepBinary, kept)
302
+			for ni := len(locNodes) - 1; ni >= 0; ni-- {
303
+				n := locNodes[ni]
304
+				if n == nil {
305
+					residual = true
306
+					continue
307
+				}
308
+				n.addSample(weight, labels, sample.NumLabel, o.FormatTag, false)
309
+				if parent != nil {
310
+					parent.AddToEdge(n, weight, residual, ni != len(locNodes)-1)
311
+				}
312
+				parent = n
313
+				residual = false
272
 			}
314
 			}
273
-			nn = n
274
-			nlocIndex = locIndex[i-1]
275
-			residual = false
315
+		}
316
+		if parent != nil && !residual {
317
+			parent.addSample(weight, labels, sample.NumLabel, o.FormatTag, true)
276
 		}
318
 		}
277
 	}
319
 	}
278
 
320
 
279
-	// Collect nodes into a graph.
280
-	ns := make(Nodes, 0, len(nm))
281
-	for _, n := range nm {
282
-		if o.DropNegative && isNegative(n) {
283
-			continue
284
-		}
285
-		ns = append(ns, n)
321
+	nodes := make(Nodes, len(prof.Location))
322
+	for _, nm := range parentNodeMap {
323
+		nodes = append(nodes, nm.nodes()...)
286
 	}
324
 	}
325
+	return selectNodesForGraph(nodes, o.DropNegative)
326
+}
287
 
327
 
288
-	return &Graph{ns}
328
+func joinLabels(s *profile.Sample) string {
329
+	if len(s.Label) == 0 {
330
+		return ""
331
+	}
332
+
333
+	var labels []string
334
+	for key, vals := range s.Label {
335
+		for _, v := range vals {
336
+			labels = append(labels, key+":"+v)
337
+		}
338
+	}
339
+	sort.Strings(labels)
340
+	return strings.Join(labels, `\n`)
289
 }
341
 }
290
 
342
 
291
 // isNegative returns true if the node is considered as "negative" for the
343
 // isNegative returns true if the node is considered as "negative" for the
301
 	}
353
 	}
302
 }
354
 }
303
 
355
 
304
-// NewLocInfo creates a slice of formatted names for a location.
305
-func NewLocInfo(prof *profile.Profile, keepBinary bool) map[uint64][]NodeInfo {
306
-	locations := make(map[uint64][]NodeInfo)
356
+// CreateNodes creates graph nodes for all locations in a profile.  It
357
+// returns set of all nodes, plus a mapping of each location to the
358
+// set of corresponding nodes (one per location.Line). If kept is
359
+// non-nil, only nodes in that set are included; nodes that do not
360
+// match are represented as a nil.
361
+func CreateNodes(prof *profile.Profile, keepBinary bool, kept NodeSet) (Nodes, map[uint64]Nodes) {
362
+	locations := make(map[uint64]Nodes, len(prof.Location))
307
 
363
 
364
+	nm := make(NodeMap, len(prof.Location))
308
 	for _, l := range prof.Location {
365
 	for _, l := range prof.Location {
309
-		var objfile string
310
-
311
-		if m := l.Mapping; m != nil {
312
-			objfile = filepath.Base(m.File)
366
+		if nodes := nm.findOrInsertLocation(l, keepBinary, kept); nodes != nil {
367
+			locations[l.ID] = nodes
313
 		}
368
 		}
369
+	}
370
+	return nm.nodes(), locations
371
+}
314
 
372
 
315
-		if len(l.Line) == 0 {
316
-			locations[l.ID] = []NodeInfo{
317
-				{
318
-					Address: l.Address,
319
-					Objfile: objfile,
320
-				},
321
-			}
322
-			continue
373
+func (nm NodeMap) nodes() Nodes {
374
+	nodes := make(Nodes, 0, len(nm))
375
+	for _, n := range nm {
376
+		nodes = append(nodes, n)
377
+	}
378
+	return nodes
379
+}
380
+
381
+func (nm NodeMap) findOrInsertLocation(l *profile.Location, keepBinary bool, kept NodeSet) Nodes {
382
+	var objfile string
383
+	if m := l.Mapping; m != nil {
384
+		objfile = filepath.Base(m.File)
385
+	}
386
+
387
+	if len(l.Line) == 0 {
388
+		ni := NodeInfo{
389
+			Address: l.Address,
390
+			Objfile: objfile,
391
+		}
392
+		return Nodes{nm.FindOrInsertNode(ni, kept)}
393
+	}
394
+	var locNodes Nodes
395
+	for _, line := range l.Line {
396
+		ni := NodeInfo{
397
+			Address: l.Address,
398
+			Lineno:  int(line.Line),
323
 		}
399
 		}
324
-		var info []NodeInfo
325
-		for _, line := range l.Line {
326
-			ni := NodeInfo{
327
-				Address: l.Address,
328
-				Lineno:  int(line.Line),
329
-			}
330
 
400
 
331
-			if line.Function != nil {
332
-				ni.Name = line.Function.Name
333
-				ni.OrigName = line.Function.SystemName
334
-				if fname := line.Function.Filename; fname != "" {
335
-					ni.File = filepath.Clean(fname)
336
-				}
337
-				if keepBinary {
338
-					ni.StartLine = int(line.Function.StartLine)
339
-				}
401
+		if line.Function != nil {
402
+			ni.Name = line.Function.Name
403
+			ni.OrigName = line.Function.SystemName
404
+			if fname := line.Function.Filename; fname != "" {
405
+				ni.File = filepath.Clean(fname)
340
 			}
406
 			}
341
-			if keepBinary || line.Function == nil {
342
-				ni.Objfile = objfile
407
+			if keepBinary {
408
+				ni.StartLine = int(line.Function.StartLine)
343
 			}
409
 			}
344
-			info = append(info, ni)
345
 		}
410
 		}
346
-		locations[l.ID] = info
411
+		if keepBinary || line.Function == nil {
412
+			ni.Objfile = objfile
413
+		}
414
+		locNodes = append(locNodes, nm.FindOrInsertNode(ni, kept))
347
 	}
415
 	}
348
-	return locations
416
+	return locNodes
349
 }
417
 }
350
 
418
 
351
 type tags struct {
419
 type tags struct {
376
 	return
444
 	return
377
 }
445
 }
378
 
446
 
379
-func (n *Node) addSample(s *profile.Sample, value int64, format func(int64, string) string, flat bool) {
447
+func (n *Node) addSample(value int64, labels string, numLabel map[string][]int64, format func(int64, string) string, flat bool) {
380
 	// Update sample value
448
 	// Update sample value
381
 	if flat {
449
 	if flat {
382
 		n.Flat += value
450
 		n.Flat += value
385
 	}
453
 	}
386
 
454
 
387
 	// Add string tags
455
 	// Add string tags
388
-	var labels []string
389
-	for key, vals := range s.Label {
390
-		for _, v := range vals {
391
-			labels = append(labels, key+":"+v)
392
-		}
393
-	}
394
-	var joinedLabels string
395
-	if len(labels) > 0 {
396
-		sort.Strings(labels)
397
-		joinedLabels = strings.Join(labels, `\n`)
398
-		t := n.LabelTags.findOrAddTag(joinedLabels, "", 0)
456
+	if labels != "" {
457
+		t := n.LabelTags.findOrAddTag(labels, "", 0)
399
 		if flat {
458
 		if flat {
400
 			t.Flat += value
459
 			t.Flat += value
401
 		} else {
460
 		} else {
403
 		}
462
 		}
404
 	}
463
 	}
405
 
464
 
406
-	numericTags := n.NumericTags[joinedLabels]
465
+	numericTags := n.NumericTags[labels]
407
 	if numericTags == nil {
466
 	if numericTags == nil {
408
 		numericTags = TagMap{}
467
 		numericTags = TagMap{}
409
-		n.NumericTags[joinedLabels] = numericTags
468
+		n.NumericTags[labels] = numericTags
410
 	}
469
 	}
411
 	// Add numeric tags
470
 	// Add numeric tags
412
-	for key, nvals := range s.NumLabel {
471
+	if format == nil {
472
+		format = defaultLabelFormat
473
+	}
474
+	for key, nvals := range numLabel {
413
 		for _, v := range nvals {
475
 		for _, v := range nvals {
414
-			var label string
415
-			if format != nil {
416
-				label = format(v, key)
417
-			} else {
418
-				label = fmt.Sprintf("%d", v)
419
-			}
420
-			t := numericTags.findOrAddTag(label, key, v)
476
+			t := numericTags.findOrAddTag(format(v, key), key, v)
421
 			if flat {
477
 			if flat {
422
 				t.Flat += value
478
 				t.Flat += value
423
 			} else {
479
 			} else {
427
 	}
483
 	}
428
 }
484
 }
429
 
485
 
486
+func defaultLabelFormat(v int64, key string) string {
487
+	return strconv.FormatInt(v, 10)
488
+}
489
+
430
 func (m TagMap) findOrAddTag(label, unit string, value int64) *Tag {
490
 func (m TagMap) findOrAddTag(label, unit string, value int64) *Tag {
431
 	l := m[label]
491
 	l := m[label]
432
 	if l == nil {
492
 	if l == nil {
630
 // predecessors collects all the predecessors to node n, excluding edge e.
690
 // predecessors collects all the predecessors to node n, excluding edge e.
631
 func predecessors(e *Edge, n *Node) map[*Node]bool {
691
 func predecessors(e *Edge, n *Node) map[*Node]bool {
632
 	seen := map[*Node]bool{n: true}
692
 	seen := map[*Node]bool{n: true}
633
-	queue := []*Node{n}
693
+	queue := Nodes{n}
634
 	for len(queue) > 0 {
694
 	for len(queue) > 0 {
635
 		n := queue[0]
695
 		n := queue[0]
636
 		queue = queue[1:]
696
 		queue = queue[1:]

+ 14
- 5
internal/report/report.go Parādīt failu

174
 	for _, f := range prof.Function {
174
 	for _, f := range prof.Function {
175
 		f.Filename = trimPath(f.Filename)
175
 		f.Filename = trimPath(f.Filename)
176
 	}
176
 	}
177
+	// Remove numeric tags not recognized by pprof.
178
+	for _, s := range prof.Sample {
179
+		numLabels := make(map[string][]int64, len(s.NumLabel))
180
+		for k, v := range s.NumLabel {
181
+			if k == "bytes" {
182
+				numLabels[k] = append(numLabels[k], v...)
183
+			}
184
+		}
185
+		s.NumLabel = numLabels
186
+	}
177
 
187
 
178
 	gopt := &graph.Options{
188
 	gopt := &graph.Options{
179
 		SampleValue:  o.SampleValue,
189
 		SampleValue:  o.SampleValue,
559
 
569
 
560
 	const separator = "-----------+-------------------------------------------------------"
570
 	const separator = "-----------+-------------------------------------------------------"
561
 
571
 
562
-	locations := graph.NewLocInfo(prof, false)
563
-
572
+	_, locations := graph.CreateNodes(prof, false, nil)
564
 	for _, sample := range prof.Sample {
573
 	for _, sample := range prof.Sample {
565
-		var stack []graph.NodeInfo
574
+		var stack graph.Nodes
566
 		for _, loc := range sample.Location {
575
 		for _, loc := range sample.Location {
567
 			id := loc.ID
576
 			id := loc.ID
568
 			stack = append(stack, locations[id]...)
577
 			stack = append(stack, locations[id]...)
583
 		// Print call stack.
592
 		// Print call stack.
584
 		fmt.Fprintf(w, "%10s   %s\n",
593
 		fmt.Fprintf(w, "%10s   %s\n",
585
 			rpt.formatValue(o.SampleValue(sample.Value)),
594
 			rpt.formatValue(o.SampleValue(sample.Value)),
586
-			stack[0].PrintableName())
595
+			stack[0].Info.PrintableName())
587
 
596
 
588
 		for _, s := range stack[1:] {
597
 		for _, s := range stack[1:] {
589
-			fmt.Fprintf(w, "%10s   %s\n", "", s.PrintableName())
598
+			fmt.Fprintf(w, "%10s   %s\n", "", s.Info.PrintableName())
590
 		}
599
 		}
591
 	}
600
 	}
592
 	fmt.Fprintln(w, separator)
601
 	fmt.Fprintln(w, separator)