Browse Source

Add mechanism to reduce profile size

Add new trimproto option that generates a new profile while removing
symbol information for functions below nodefraction. This reduces the
profile sizes significantly.
Raul Silvera 9 years ago
parent
commit
9c191ebbce
4 changed files with 95 additions and 23 deletions
  1. 1
    0
      internal/driver/commands.go
  2. 65
    21
      internal/graph/graph.go
  3. 18
    1
      internal/report/report.go
  4. 11
    1
      profile/merge.go

+ 1
- 0
internal/driver/commands.go View File

93
 	// Save binary formats to a file
93
 	// Save binary formats to a file
94
 	"callgrind": {report.Callgrind, awayFromTTY("callgraph.out"), false, "Outputs a graph in callgrind format", reportHelp("callgrind", false, true)},
94
 	"callgrind": {report.Callgrind, awayFromTTY("callgraph.out"), false, "Outputs a graph in callgrind format", reportHelp("callgrind", false, true)},
95
 	"proto":     {report.Proto, awayFromTTY("pb.gz"), false, "Outputs the profile in compressed protobuf format", ""},
95
 	"proto":     {report.Proto, awayFromTTY("pb.gz"), false, "Outputs the profile in compressed protobuf format", ""},
96
+	"trimproto": {report.TrimProto, awayFromTTY("pb.gz"), false, "Outputs the profile in compressed protobuf format, removing info about infrequent functions to reduce profile size", ""},
96
 
97
 
97
 	// Generate report in DOT format and postprocess with dot
98
 	// Generate report in DOT format and postprocess with dot
98
 	"gif": {report.Dot, invokeDot("gif"), false, "Outputs a graph image in GIF format", reportHelp("gif", false, true)},
99
 	"gif": {report.Dot, invokeDot("gif"), false, "Outputs a graph image in GIF format", reportHelp("gif", false, true)},

+ 65
- 21
internal/graph/graph.go View File

204
 }
204
 }
205
 
205
 
206
 // New summarizes performance data from a profile into a graph.
206
 // New summarizes performance data from a profile into a graph.
207
-func New(prof *profile.Profile, o *Options) (g *Graph) {
207
+func New(prof *profile.Profile, o *Options) *Graph {
208
 	if o.CallTree {
208
 	if o.CallTree {
209
 		return newTree(prof, o)
209
 		return newTree(prof, o)
210
 	}
210
 	}
211
+	g, _ := newGraph(prof, o)
212
+	return g
213
+}
211
 
214
 
215
+// newGraph computes a graph from a profile. It returns the graph, and
216
+// a map from the profile location indices to the corresponding graph
217
+// nodes.
218
+func newGraph(prof *profile.Profile, o *Options) (*Graph, map[uint64]Nodes) {
212
 	nodes, locationMap := CreateNodes(prof, o.ObjNames, o.KeptNodes)
219
 	nodes, locationMap := CreateNodes(prof, o.ObjNames, o.KeptNodes)
213
 	for _, sample := range prof.Sample {
220
 	for _, sample := range prof.Sample {
214
 		weight := o.SampleValue(sample.Value)
221
 		weight := o.SampleValue(sample.Value)
252
 		}
259
 		}
253
 	}
260
 	}
254
 
261
 
255
-	return selectNodesForGraph(nodes, o.DropNegative)
262
+	return selectNodesForGraph(nodes, o.DropNegative), locationMap
256
 }
263
 }
257
 
264
 
258
 func selectNodesForGraph(nodes Nodes, dropNegative bool) *Graph {
265
 func selectNodesForGraph(nodes Nodes, dropNegative bool) *Graph {
340
 	return strings.Join(labels, `\n`)
347
 	return strings.Join(labels, `\n`)
341
 }
348
 }
342
 
349
 
350
+// TrimProfile reduces the size of a profile by removing information
351
+// about locations that contribute to infrequent graph nodes,
352
+// determined by the value of nodefraction. The locations are
353
+// preserved, but their line information is removed.
354
+func TrimProfile(p *profile.Profile, o *Options, nodeFraction float64) *profile.Profile {
355
+	g, locationMap := newGraph(p, o)
356
+
357
+	totalValue, _ := g.Nodes.Sum()
358
+	cutoff := abs64(int64(float64(totalValue) * nodeFraction))
359
+
360
+	for _, l := range p.Location {
361
+		nodes := locationMap[l.ID]
362
+		if len(nodes) == 0 || len(l.Line) != len(nodes) {
363
+			continue
364
+		}
365
+		for i, n := range nodes {
366
+			if n.Cum < cutoff {
367
+				l.Line[i] = profile.Line{}
368
+			}
369
+		}
370
+	}
371
+
372
+	return p.Compact()
373
+}
374
+
343
 // isNegative returns true if the node is considered as "negative" for the
375
 // isNegative returns true if the node is considered as "negative" for the
344
 // purposes of drop_negative.
376
 // purposes of drop_negative.
345
 func isNegative(n *Node) bool {
377
 func isNegative(n *Node) bool {
391
 		}
423
 		}
392
 		return Nodes{nm.FindOrInsertNode(ni, kept)}
424
 		return Nodes{nm.FindOrInsertNode(ni, kept)}
393
 	}
425
 	}
394
-	var locNodes Nodes
395
-	for _, line := range l.Line {
396
-		ni := NodeInfo{
397
-			Address: l.Address,
398
-			Lineno:  int(line.Line),
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)
399
 		}
430
 		}
431
+	}
432
+	return locNodes
433
+}
400
 
434
 
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)
406
-			}
407
-			if keepBinary {
408
-				ni.StartLine = int(line.Function.StartLine)
409
-			}
410
-		}
411
-		if keepBinary || line.Function == nil {
412
-			ni.Objfile = objfile
435
+func nodeInfo(l *profile.Location, li int, objfile string, keepBinary bool) *NodeInfo {
436
+	if !keepBinary {
437
+		objfile = ""
438
+	}
439
+	line := l.Line[li]
440
+	if line.Function == nil {
441
+		if l.Address == 0 {
442
+			return nil
413
 		}
443
 		}
414
-		locNodes = append(locNodes, nm.FindOrInsertNode(ni, kept))
444
+		return &NodeInfo{Address: l.Address, Objfile: objfile}
415
 	}
445
 	}
416
-	return locNodes
446
+
447
+	ni := &NodeInfo{
448
+		Address:  l.Address,
449
+		Lineno:   int(line.Line),
450
+		Name:     line.Function.Name,
451
+		OrigName: line.Function.SystemName,
452
+		Objfile:  objfile,
453
+	}
454
+	if fname := line.Function.Filename; fname != "" {
455
+		ni.File = filepath.Clean(fname)
456
+	}
457
+	if keepBinary {
458
+		ni.StartLine = int(line.Function.StartLine)
459
+	}
460
+	return ni
417
 }
461
 }
418
 
462
 
419
 type tags struct {
463
 type tags struct {

+ 18
- 1
internal/report/report.go View File

54
 		return printTags(w, rpt)
54
 		return printTags(w, rpt)
55
 	case Proto:
55
 	case Proto:
56
 		return rpt.prof.Write(w)
56
 		return rpt.prof.Write(w)
57
+	case TrimProto:
58
+		return printTrimmedProto(w, rpt)
57
 	case TopProto:
59
 	case TopProto:
58
 		return printTopProto(w, rpt)
60
 		return printTopProto(w, rpt)
59
 	case Dis:
61
 	case Dis:
184
 		}
186
 		}
185
 		s.NumLabel = numLabels
187
 		s.NumLabel = numLabels
186
 	}
188
 	}
189
+	return graph.New(rpt.prof, graphOptions(o, nodes))
190
+}
187
 
191
 
192
+func graphOptions(o *Options, nodes graph.NodeSet) *graph.Options {
188
 	gopt := &graph.Options{
193
 	gopt := &graph.Options{
189
 		SampleValue:  o.SampleValue,
194
 		SampleValue:  o.SampleValue,
190
 		FormatTag:    formatTag,
195
 		FormatTag:    formatTag,
200
 		gopt.ObjNames = true
205
 		gopt.ObjNames = true
201
 	}
206
 	}
202
 
207
 
203
-	return graph.New(rpt.prof, gopt)
208
+	return gopt
204
 }
209
 }
205
 
210
 
206
 func formatTag(v int64, key string) string {
211
 func formatTag(v int64, key string) string {
256
 	return out.Write(w)
261
 	return out.Write(w)
257
 }
262
 }
258
 
263
 
264
+// printTrimmedProto writes a profile in a serialize profile.proto,
265
+// removing symbol information from infrequent nodes to reduce the
266
+// size of the profile.
267
+func printTrimmedProto(w io.Writer, rpt *Report) error {
268
+	o := rpt.options
269
+	prof := rpt.prof
270
+	prof = graph.TrimProfile(prof, graphOptions(o, nil), o.NodeFraction)
271
+
272
+	return prof.Write(w)
273
+}
274
+
259
 // printAssembly prints an annotated assembly listing.
275
 // printAssembly prints an annotated assembly listing.
260
 func printAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
276
 func printAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
261
 	o := rpt.options
277
 	o := rpt.options
844
 	WebList
860
 	WebList
845
 	Callgrind
861
 	Callgrind
846
 	TopProto
862
 	TopProto
863
+	TrimProto
847
 )
864
 )
848
 
865
 
849
 // Options are the formatting and filtering options used to generate a
866
 // Options are the formatting and filtering options used to generate a

+ 11
- 1
profile/merge.go View File

22
 	"strings"
22
 	"strings"
23
 )
23
 )
24
 
24
 
25
+// Compact performs garbage collection on a profile to remove any
26
+// unreferenced fields. This is useful to reduce the size of a profile
27
+// after samples or locations have been removed.
28
+func (p *Profile) Compact() *Profile {
29
+	p, _ = Merge([]*Profile{p})
30
+	return p
31
+}
32
+
25
 // Merge merges all the profiles in profs into a single Profile.
33
 // Merge merges all the profiles in profs into a single Profile.
26
 // Returns a new profile independent of the input profiles. The merged
34
 // Returns a new profile independent of the input profiles. The merged
27
 // profile is compacted to eliminate unused samples, locations,
35
 // profile is compacted to eliminate unused samples, locations,
220
 	}
228
 	}
221
 	lines := make([]string, len(l.Line)*2)
229
 	lines := make([]string, len(l.Line)*2)
222
 	for i, line := range l.Line {
230
 	for i, line := range l.Line {
223
-		lines[i*2] = strconv.FormatUint(line.Function.ID, 16)
231
+		if line.Function != nil {
232
+			lines[i*2] = strconv.FormatUint(line.Function.ID, 16)
233
+		}
224
 		lines[i*2+1] = strconv.FormatInt(line.Line, 16)
234
 		lines[i*2+1] = strconv.FormatInt(line.Line, 16)
225
 	}
235
 	}
226
 	key.lines = strings.Join(lines, "|")
236
 	key.lines = strings.Join(lines, "|")