Преглед изворни кода

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 година
родитељ
комит
e5e7096d4f
3 измењених фајлова са 208 додато и 141 уклоњено
  1. 0
    2
      internal/driver/testdata/pprof.cpu.cum.lines.topproto.hide
  2. 194
    134
      internal/graph/graph.go
  3. 14
    5
      internal/report/report.go

+ 0
- 2
internal/driver/testdata/pprof.cpu.cum.lines.topproto.hide Прегледај датотеку

@@ -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 Прегледај датотеку

@@ -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 Прегледај датотеку

@@ -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)