Quellcode durchsuchen

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 vor 9 Jahren
Ursprung
Commit
e5e7096d4f

+ 0
- 2
internal/driver/testdata/pprof.cpu.cum.lines.topproto.hide Datei anzeigen

@@ -1,5 +1,3 @@
1 1
 Showing nodes accounting for 1s, 100% of 1s total
2 2
       flat  flat%   sum%        cum   cum%
3 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 Datei anzeigen

@@ -20,6 +20,7 @@ import (
20 20
 	"math"
21 21
 	"path/filepath"
22 22
 	"sort"
23
+	"strconv"
23 24
 	"strings"
24 25
 
25 26
 	"github.com/google/pprof/profile"
@@ -69,9 +70,9 @@ type Node struct {
69 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 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 76
 	if n.Out[to] != to.In[n] {
76 77
 		panic(fmt.Errorf("asymmetric edges %v %v", *n, *to))
77 78
 	}
@@ -136,36 +137,24 @@ func (i *NodeInfo) NameComponents() []string {
136 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 140
 // NodeMap maps from a node info struct to a node. It is used to merge
148 141
 // report entries with the same info.
149
-type NodeMap map[ExtendedNodeInfo]*Node
142
+type NodeMap map[NodeInfo]*Node
150 143
 
151 144
 // NodeSet maps is a collection of node info structs.
152 145
 type NodeSet map[NodeInfo]bool
153 146
 
154 147
 // FindOrInsertNode takes the info for a node and either returns a matching node
155 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 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 158
 		return n
170 159
 	}
171 160
 
@@ -176,7 +165,7 @@ func (m NodeMap) FindOrInsertNode(info NodeInfo, parent *Node, kept NodeSet) *No
176 165
 		LabelTags:   make(TagMap),
177 166
 		NumericTags: make(map[string]TagMap),
178 167
 	}
179
-	m[extendedInfo] = n
168
+	nm[info] = n
180 169
 	return n
181 170
 }
182 171
 
@@ -216,76 +205,139 @@ func SortTags(t []*Tag, flat bool) []*Tag {
216 205
 
217 206
 // New summarizes performance data from a profile into a graph.
218 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 213
 	for _, sample := range prof.Sample {
222
-		if sample.Location == nil {
214
+		weight := o.SampleValue(sample.Value)
215
+		if weight == 0 {
223 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 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 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 343
 // isNegative returns true if the node is considered as "negative" for the
@@ -301,51 +353,67 @@ func isNegative(n *Node) bool {
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 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 419
 type tags struct {
@@ -376,7 +444,7 @@ func (ns Nodes) Sum() (flat int64, cum int64) {
376 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 448
 	// Update sample value
381 449
 	if flat {
382 450
 		n.Flat += value
@@ -385,17 +453,8 @@ func (n *Node) addSample(s *profile.Sample, value int64, format func(int64, stri
385 453
 	}
386 454
 
387 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 458
 		if flat {
400 459
 			t.Flat += value
401 460
 		} else {
@@ -403,21 +462,18 @@ func (n *Node) addSample(s *profile.Sample, value int64, format func(int64, stri
403 462
 		}
404 463
 	}
405 464
 
406
-	numericTags := n.NumericTags[joinedLabels]
465
+	numericTags := n.NumericTags[labels]
407 466
 	if numericTags == nil {
408 467
 		numericTags = TagMap{}
409
-		n.NumericTags[joinedLabels] = numericTags
468
+		n.NumericTags[labels] = numericTags
410 469
 	}
411 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 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 477
 			if flat {
422 478
 				t.Flat += value
423 479
 			} else {
@@ -427,6 +483,10 @@ func (n *Node) addSample(s *profile.Sample, value int64, format func(int64, stri
427 483
 	}
428 484
 }
429 485
 
486
+func defaultLabelFormat(v int64, key string) string {
487
+	return strconv.FormatInt(v, 10)
488
+}
489
+
430 490
 func (m TagMap) findOrAddTag(label, unit string, value int64) *Tag {
431 491
 	l := m[label]
432 492
 	if l == nil {
@@ -630,7 +690,7 @@ func isRedundant(e *Edge) bool {
630 690
 // predecessors collects all the predecessors to node n, excluding edge e.
631 691
 func predecessors(e *Edge, n *Node) map[*Node]bool {
632 692
 	seen := map[*Node]bool{n: true}
633
-	queue := []*Node{n}
693
+	queue := Nodes{n}
634 694
 	for len(queue) > 0 {
635 695
 		n := queue[0]
636 696
 		queue = queue[1:]

+ 14
- 5
internal/report/report.go Datei anzeigen

@@ -174,6 +174,16 @@ func (rpt *Report) newGraph(nodes graph.NodeSet) *graph.Graph {
174 174
 	for _, f := range prof.Function {
175 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 188
 	gopt := &graph.Options{
179 189
 		SampleValue:  o.SampleValue,
@@ -559,10 +569,9 @@ func printTraces(w io.Writer, rpt *Report) error {
559 569
 
560 570
 	const separator = "-----------+-------------------------------------------------------"
561 571
 
562
-	locations := graph.NewLocInfo(prof, false)
563
-
572
+	_, locations := graph.CreateNodes(prof, false, nil)
564 573
 	for _, sample := range prof.Sample {
565
-		var stack []graph.NodeInfo
574
+		var stack graph.Nodes
566 575
 		for _, loc := range sample.Location {
567 576
 			id := loc.ID
568 577
 			stack = append(stack, locations[id]...)
@@ -583,10 +592,10 @@ func printTraces(w io.Writer, rpt *Report) error {
583 592
 		// Print call stack.
584 593
 		fmt.Fprintf(w, "%10s   %s\n",
585 594
 			rpt.formatValue(o.SampleValue(sample.Value)),
586
-			stack[0].PrintableName())
595
+			stack[0].Info.PrintableName())
587 596
 
588 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 601
 	fmt.Fprintln(w, separator)