Ver código fonte

Merge pull request #38 from rauls5382/mean

Fix mean computation during graph creation
Hyoun Kyu Cho 8 anos atrás
pai
commit
35a78323ac

+ 12
- 18
internal/driver/driver.go Ver arquivo

@@ -206,7 +206,7 @@ func aggregate(prof *profile.Profile, v variables) error {
206 206
 
207 207
 func reportOptions(p *profile.Profile, vars variables) (*report.Options, error) {
208 208
 	si, mean := vars["sample_index"].value, vars["mean"].boolValue()
209
-	value, sample, err := sampleFormat(p, si, mean)
209
+	value, meanDiv, sample, err := sampleFormat(p, si, mean)
210 210
 	if err != nil {
211 211
 		return nil, err
212 212
 	}
@@ -233,9 +233,10 @@ func reportOptions(p *profile.Profile, vars variables) (*report.Options, error)
233 233
 		NodeFraction: vars["nodefraction"].floatValue(),
234 234
 		EdgeFraction: vars["edgefraction"].floatValue(),
235 235
 
236
-		SampleValue: value,
237
-		SampleType:  stype,
238
-		SampleUnit:  sample.Unit,
236
+		SampleValue:       value,
237
+		SampleMeanDivisor: meanDiv,
238
+		SampleType:        stype,
239
+		SampleUnit:        sample.Unit,
239 240
 
240 241
 		OutputUnit: vars["unit"].value,
241 242
 
@@ -253,18 +254,20 @@ type sampleValueFunc func([]int64) int64
253 254
 
254 255
 // sampleFormat returns a function to extract values out of a profile.Sample,
255 256
 // and the type/units of those values.
256
-func sampleFormat(p *profile.Profile, sampleIndex string, mean bool) (sampleValueFunc, *profile.ValueType, error) {
257
+func sampleFormat(p *profile.Profile, sampleIndex string, mean bool) (value, meanDiv sampleValueFunc, v *profile.ValueType, err error) {
257 258
 	if len(p.SampleType) == 0 {
258
-		return nil, nil, fmt.Errorf("profile has no samples")
259
+		return nil, nil, nil, fmt.Errorf("profile has no samples")
259 260
 	}
260 261
 	index, err := locateSampleIndex(p, sampleIndex)
261 262
 	if err != nil {
262
-		return nil, nil, err
263
+		return nil, nil, nil, err
263 264
 	}
265
+	value = valueExtractor(index)
264 266
 	if mean {
265
-		return meanExtractor(index), p.SampleType[index], nil
267
+		meanDiv = valueExtractor(0)
266 268
 	}
267
-	return valueExtractor(index), p.SampleType[index], nil
269
+	v = p.SampleType[index]
270
+	return
268 271
 }
269 272
 
270 273
 func valueExtractor(ix int) sampleValueFunc {
@@ -272,12 +275,3 @@ func valueExtractor(ix int) sampleValueFunc {
272 275
 		return v[ix]
273 276
 	}
274 277
 }
275
-
276
-func meanExtractor(ix int) sampleValueFunc {
277
-	return func(v []int64) int64 {
278
-		if v[0] == 0 {
279
-			return 0
280
-		}
281
-		return v[ix] / v[0]
282
-	}
283
-}

+ 26
- 18
internal/graph/dotgraph.go Ver arquivo

@@ -67,11 +67,11 @@ func ComposeDot(w io.Writer, g *Graph, a *DotAttributes, c *DotConfig) {
67 67
 	nodeIDMap := make(map[*Node]int)
68 68
 	hasNodelets := make(map[*Node]bool)
69 69
 
70
-	maxFlat := float64(abs64(g.Nodes[0].Flat))
70
+	maxFlat := float64(abs64(g.Nodes[0].FlatValue()))
71 71
 	for i, n := range g.Nodes {
72 72
 		nodeIDMap[n] = i + 1
73
-		if float64(abs64(n.Flat)) > maxFlat {
74
-			maxFlat = float64(abs64(n.Flat))
73
+		if float64(abs64(n.FlatValue())) > maxFlat {
74
+			maxFlat = float64(abs64(n.FlatValue()))
75 75
 		}
76 76
 	}
77 77
 
@@ -128,7 +128,7 @@ func (b *builder) addLegend() {
128 128
 
129 129
 // addNode generates a graph node in DOT format.
130 130
 func (b *builder) addNode(node *Node, nodeID int, maxFlat float64) {
131
-	flat, cum := node.Flat, node.Cum
131
+	flat, cum := node.FlatValue(), node.CumValue()
132 132
 	attrs := b.attributes.Nodes[node]
133 133
 
134 134
 	// Populate label for node.
@@ -177,8 +177,8 @@ func (b *builder) addNode(node *Node, nodeID int, maxFlat float64) {
177 177
 	// Create DOT attribute for node.
178 178
 	attr := fmt.Sprintf(`label="%s" fontsize=%d shape=%s tooltip="%s (%s)" color="%s" fillcolor="%s"`,
179 179
 		label, fontSize, shape, node.Info.PrintableName(), cumValue,
180
-		dotColor(float64(node.Cum)/float64(abs64(b.config.Total)), false),
181
-		dotColor(float64(node.Cum)/float64(abs64(b.config.Total)), true))
180
+		dotColor(float64(node.CumValue())/float64(abs64(b.config.Total)), false),
181
+		dotColor(float64(node.CumValue())/float64(abs64(b.config.Total)), true))
182 182
 
183 183
 	// Add on extra attributes if provided.
184 184
 	if attrs != nil {
@@ -230,9 +230,9 @@ func (b *builder) addNodelets(node *Node, nodeID int) bool {
230 230
 		ts = ts[:maxNodelets]
231 231
 	}
232 232
 	for i, t := range ts {
233
-		w := t.Cum
233
+		w := t.CumValue()
234 234
 		if flatTags {
235
-			w = t.Flat
235
+			w = t.FlatValue()
236 236
 		}
237 237
 		if w == 0 {
238 238
 			continue
@@ -259,9 +259,9 @@ func (b *builder) numericNodelets(nts []*Tag, maxNumNodelets int, flatTags bool,
259 259
 	// Collapse numeric labels into maxNumNodelets buckets, of the form:
260 260
 	// 1MB..2MB, 3MB..5MB, ...
261 261
 	for j, t := range collapsedTags(nts, maxNumNodelets, flatTags) {
262
-		w, attr := t.Cum, ` style="dotted"`
263
-		if flatTags || t.Flat == t.Cum {
264
-			w, attr = t.Flat, ""
262
+		w, attr := t.CumValue(), ` style="dotted"`
263
+		if flatTags || t.FlatValue() == t.CumValue() {
264
+			w, attr = t.FlatValue(), ""
265 265
 		}
266 266
 		if w != 0 {
267 267
 			weight := b.config.FormatValue(w)
@@ -278,18 +278,18 @@ func (b *builder) addEdge(edge *Edge, from, to int, hasNodelets bool) {
278 278
 	if edge.Inline {
279 279
 		inline = `\n (inline)`
280 280
 	}
281
-	w := b.config.FormatValue(edge.Weight)
281
+	w := b.config.FormatValue(edge.WeightValue())
282 282
 	attr := fmt.Sprintf(`label=" %s%s"`, w, inline)
283 283
 	if b.config.Total != 0 {
284 284
 		// Note: edge.weight > b.config.Total is possible for profile diffs.
285
-		if weight := 1 + int(min64(abs64(edge.Weight*100/b.config.Total), 100)); weight > 1 {
285
+		if weight := 1 + int(min64(abs64(edge.WeightValue()*100/b.config.Total), 100)); weight > 1 {
286 286
 			attr = fmt.Sprintf(`%s weight=%d`, attr, weight)
287 287
 		}
288
-		if width := 1 + int(min64(abs64(edge.Weight*5/b.config.Total), 5)); width > 1 {
288
+		if width := 1 + int(min64(abs64(edge.WeightValue()*5/b.config.Total), 5)); width > 1 {
289 289
 			attr = fmt.Sprintf(`%s penwidth=%d`, attr, width)
290 290
 		}
291 291
 		attr = fmt.Sprintf(`%s color="%s"`, attr,
292
-			dotColor(float64(edge.Weight)/float64(abs64(b.config.Total)), false))
292
+			dotColor(float64(edge.WeightValue())/float64(abs64(b.config.Total)), false))
293 293
 	}
294 294
 	arrow := "->"
295 295
 	if edge.Residual {
@@ -442,12 +442,12 @@ func tagDistance(t, u *Tag) float64 {
442 442
 func tagGroupLabel(g []*Tag) (label string, flat, cum int64) {
443 443
 	if len(g) == 1 {
444 444
 		t := g[0]
445
-		return measurement.Label(t.Value, t.Unit), t.Flat, t.Cum
445
+		return measurement.Label(t.Value, t.Unit), t.FlatValue(), t.CumValue()
446 446
 	}
447 447
 	min := g[0]
448 448
 	max := g[0]
449
-	f := min.Flat
450
-	c := min.Cum
449
+	df, f := min.FlatDiv, min.Flat
450
+	dc, c := min.CumDiv, min.Cum
451 451
 	for _, t := range g[1:] {
452 452
 		if v, _ := measurement.Scale(t.Value, t.Unit, min.Unit); int64(v) < min.Value {
453 453
 			min = t
@@ -456,7 +456,15 @@ func tagGroupLabel(g []*Tag) (label string, flat, cum int64) {
456 456
 			max = t
457 457
 		}
458 458
 		f += t.Flat
459
+		df += t.FlatDiv
459 460
 		c += t.Cum
461
+		dc += t.CumDiv
462
+	}
463
+	if df != 0 {
464
+		f = f / df
465
+	}
466
+	if dc != 0 {
467
+		c = c / dc
460 468
 	}
461 469
 	return measurement.Label(min.Value, min.Unit) + ".." + measurement.Label(max.Value, max.Unit), f, c
462 470
 }

+ 23
- 18
internal/graph/dotgraph_test.go Ver arquivo

@@ -224,35 +224,40 @@ func TestMultilinePrintableName(t *testing.T) {
224 224
 }
225 225
 
226 226
 func TestTagCollapse(t *testing.T) {
227
+
228
+	makeTag := func(name, unit string, value, flat, cum int64) *Tag {
229
+		return &Tag{name, unit, value, flat, 0, cum, 0}
230
+	}
231
+
227 232
 	tagSource := []*Tag{
228
-		{"12mb", "mb", 12, 100, 100},
229
-		{"1kb", "kb", 1, 1, 1},
230
-		{"1mb", "mb", 1, 1000, 1000},
231
-		{"2048mb", "mb", 2048, 1000, 1000},
232
-		{"1b", "b", 1, 100, 100},
233
-		{"2b", "b", 2, 100, 100},
234
-		{"7b", "b", 7, 100, 100},
233
+		makeTag("12mb", "mb", 12, 100, 100),
234
+		makeTag("1kb", "kb", 1, 1, 1),
235
+		makeTag("1mb", "mb", 1, 1000, 1000),
236
+		makeTag("2048mb", "mb", 2048, 1000, 1000),
237
+		makeTag("1b", "b", 1, 100, 100),
238
+		makeTag("2b", "b", 2, 100, 100),
239
+		makeTag("7b", "b", 7, 100, 100),
235 240
 	}
236 241
 
237 242
 	tagWant := [][]*Tag{
238 243
 		[]*Tag{
239
-			{"1B..2GB", "", 0, 2401, 2401},
244
+			makeTag("1B..2GB", "", 0, 2401, 2401),
240 245
 		},
241 246
 		[]*Tag{
242
-			{"2GB", "", 0, 1000, 1000},
243
-			{"1B..12MB", "", 0, 1401, 1401},
247
+			makeTag("2GB", "", 0, 1000, 1000),
248
+			makeTag("1B..12MB", "", 0, 1401, 1401),
244 249
 		},
245 250
 		[]*Tag{
246
-			{"2GB", "", 0, 1000, 1000},
247
-			{"12MB", "", 0, 100, 100},
248
-			{"1B..1MB", "", 0, 1301, 1301},
251
+			makeTag("2GB", "", 0, 1000, 1000),
252
+			makeTag("12MB", "", 0, 100, 100),
253
+			makeTag("1B..1MB", "", 0, 1301, 1301),
249 254
 		},
250 255
 		[]*Tag{
251
-			{"2GB", "", 0, 1000, 1000},
252
-			{"1MB", "", 0, 1000, 1000},
253
-			{"2B..1kB", "", 0, 201, 201},
254
-			{"1B", "", 0, 100, 100},
255
-			{"12MB", "", 0, 100, 100},
256
+			makeTag("2GB", "", 0, 1000, 1000),
257
+			makeTag("1MB", "", 0, 1000, 1000),
258
+			makeTag("2B..1kB", "", 0, 201, 201),
259
+			makeTag("1B", "", 0, 100, 100),
260
+			makeTag("12MB", "", 0, 100, 100),
256 261
 		},
257 262
 	}
258 263
 

+ 97
- 31
internal/graph/graph.go Ver arquivo

@@ -34,10 +34,11 @@ type Graph struct {
34 34
 
35 35
 // Options encodes the options for constructing a graph
36 36
 type Options struct {
37
-	SampleValue func(s []int64) int64      // Function to compute the value of a sample
38
-	FormatTag   func(int64, string) string // Function to format a sample tag value into a string
39
-	ObjNames    bool                       // Always preserve obj filename
40
-	OrigFnNames bool                       // Preserve original (eg mangled) function names
37
+	SampleValue       func(s []int64) int64      // Function to compute the value of a sample
38
+	SampleMeanDivisor func(s []int64) int64      // Function to compute the divisor for mean graphs, or nil
39
+	FormatTag         func(int64, string) string // Function to format a sample tag value into a string
40
+	ObjNames          bool                       // Always preserve obj filename
41
+	OrigFnNames       bool                       // Preserve original (eg mangled) function names
41 42
 
42 43
 	CallTree     bool // Build a tree instead of a graph
43 44
 	DropNegative bool // Drop nodes with overall negative values
@@ -63,7 +64,7 @@ type Node struct {
63 64
 
64 65
 	// Values associated to this node. Flat is exclusive to this node,
65 66
 	// Cum includes all descendents.
66
-	Flat, Cum int64
67
+	Flat, FlatDiv, Cum, CumDiv int64
67 68
 
68 69
 	// In and out Contains the nodes immediately reaching or reached by
69 70
 	// this node.
@@ -79,15 +80,40 @@ type Node struct {
79 80
 	NumericTags map[string]TagMap
80 81
 }
81 82
 
83
+// FlatValue returns the exclusive value for this node, computing the
84
+// mean if a divisor is available.
85
+func (n *Node) FlatValue() int64 {
86
+	if n.FlatDiv == 0 {
87
+		return n.Flat
88
+	}
89
+	return n.Flat / n.FlatDiv
90
+}
91
+
92
+// CumValue returns the inclusive value for this node, computing the
93
+// mean if a divisor is available.
94
+func (n *Node) CumValue() int64 {
95
+	if n.CumDiv == 0 {
96
+		return n.Cum
97
+	}
98
+	return n.Cum / n.CumDiv
99
+}
100
+
82 101
 // AddToEdge increases the weight of an edge between two nodes. If
83 102
 // there isn't such an edge one is created.
84
-func (n *Node) AddToEdge(to *Node, w int64, residual, inline bool) {
103
+func (n *Node) AddToEdge(to *Node, v int64, residual, inline bool) {
104
+	n.AddToEdgeDiv(to, 0, v, residual, inline)
105
+}
106
+
107
+// AddToEdgeDiv increases the weight of an edge between two nodes. If
108
+// there isn't such an edge one is created.
109
+func (n *Node) AddToEdgeDiv(to *Node, dv, v int64, residual, inline bool) {
85 110
 	if n.Out[to] != to.In[n] {
86 111
 		panic(fmt.Errorf("asymmetric edges %v %v", *n, *to))
87 112
 	}
88 113
 
89 114
 	if e := n.Out[to]; e != nil {
90
-		e.Weight += w
115
+		e.WeightDiv += dv
116
+		e.Weight += v
91 117
 		if residual {
92 118
 			e.Residual = true
93 119
 		}
@@ -97,7 +123,7 @@ func (n *Node) AddToEdge(to *Node, w int64, residual, inline bool) {
97 123
 		return
98 124
 	}
99 125
 
100
-	info := &Edge{Src: n, Dest: to, Weight: w, Residual: residual, Inline: inline}
126
+	info := &Edge{Src: n, Dest: to, WeightDiv: dv, Weight: v, Residual: residual, Inline: inline}
101 127
 	n.Out[to] = info
102 128
 	to.In[n] = info
103 129
 }
@@ -205,7 +231,8 @@ type EdgeMap map[*Node]*Edge
205 231
 type Edge struct {
206 232
 	Src, Dest *Node
207 233
 	// The summary weight of the edge
208
-	Weight int64
234
+	Weight, WeightDiv int64
235
+
209 236
 	// residual edges connect nodes that were connected through a
210 237
 	// separate node, which has been removed from the report.
211 238
 	Residual bool
@@ -213,13 +240,38 @@ type Edge struct {
213 240
 	Inline bool
214 241
 }
215 242
 
243
+func (e *Edge) WeightValue() int64 {
244
+	if e.WeightDiv == 0 {
245
+		return e.Weight
246
+	}
247
+	return e.Weight / e.WeightDiv
248
+}
249
+
216 250
 // Tag represent sample annotations
217 251
 type Tag struct {
218
-	Name  string
219
-	Unit  string // Describe the value, "" for non-numeric tags
220
-	Value int64
221
-	Flat  int64
222
-	Cum   int64
252
+	Name          string
253
+	Unit          string // Describe the value, "" for non-numeric tags
254
+	Value         int64
255
+	Flat, FlatDiv int64
256
+	Cum, CumDiv   int64
257
+}
258
+
259
+// FlatValue returns the exclusive value for this tag, computing the
260
+// mean if a divisor is available.
261
+func (t *Tag) FlatValue() int64 {
262
+	if t.FlatDiv == 0 {
263
+		return t.Flat
264
+	}
265
+	return t.Flat / t.FlatDiv
266
+}
267
+
268
+// CumValue returns the inclusive value for this tag, computing the
269
+// mean if a divisor is available.
270
+func (t *Tag) CumValue() int64 {
271
+	if t.CumDiv == 0 {
272
+		return t.Cum
273
+	}
274
+	return t.Cum / t.CumDiv
223 275
 }
224 276
 
225 277
 // TagMap is a collection of tags, classified by their name.
@@ -247,8 +299,12 @@ func New(prof *profile.Profile, o *Options) *Graph {
247 299
 func newGraph(prof *profile.Profile, o *Options) (*Graph, map[uint64]Nodes) {
248 300
 	nodes, locationMap := CreateNodes(prof, o)
249 301
 	for _, sample := range prof.Sample {
250
-		weight := o.SampleValue(sample.Value)
251
-		if weight == 0 {
302
+		var w, dw int64
303
+		w = o.SampleValue(sample.Value)
304
+		if o.SampleMeanDivisor != nil {
305
+			dw = o.SampleMeanDivisor(sample.Value)
306
+		}
307
+		if dw == 0 && w == 0 {
252 308
 			continue
253 309
 		}
254 310
 		seenNode := make(map[*Node]bool, len(sample.Location))
@@ -271,12 +327,12 @@ func newGraph(prof *profile.Profile, o *Options) (*Graph, map[uint64]Nodes) {
271 327
 				// Add cum weight to all nodes in stack, avoiding double counting.
272 328
 				if _, ok := seenNode[n]; !ok {
273 329
 					seenNode[n] = true
274
-					n.addSample(weight, labels, sample.NumLabel, o.FormatTag, false)
330
+					n.addSample(dw, w, labels, sample.NumLabel, o.FormatTag, false)
275 331
 				}
276 332
 				// Update edge weights for all edges in stack, avoiding double counting.
277 333
 				if _, ok := seenEdge[nodePair{n, parent}]; !ok && parent != nil && n != parent {
278 334
 					seenEdge[nodePair{n, parent}] = true
279
-					parent.AddToEdge(n, weight, residual, ni != len(locNodes)-1)
335
+					parent.AddToEdgeDiv(n, dw, w, residual, ni != len(locNodes)-1)
280 336
 				}
281 337
 				parent = n
282 338
 				residual = false
@@ -284,7 +340,7 @@ func newGraph(prof *profile.Profile, o *Options) (*Graph, map[uint64]Nodes) {
284 340
 		}
285 341
 		if parent != nil && !residual {
286 342
 			// Add flat weight to leaf node.
287
-			parent.addSample(weight, labels, sample.NumLabel, o.FormatTag, true)
343
+			parent.addSample(dw, w, labels, sample.NumLabel, o.FormatTag, true)
288 344
 		}
289 345
 	}
290 346
 
@@ -316,8 +372,12 @@ type nodePair struct {
316 372
 func newTree(prof *profile.Profile, o *Options) (g *Graph) {
317 373
 	parentNodeMap := make(map[*Node]NodeMap, len(prof.Sample))
318 374
 	for _, sample := range prof.Sample {
319
-		weight := o.SampleValue(sample.Value)
320
-		if weight == 0 {
375
+		var w, dw int64
376
+		w = o.SampleValue(sample.Value)
377
+		if o.SampleMeanDivisor != nil {
378
+			dw = o.SampleMeanDivisor(sample.Value)
379
+		}
380
+		if dw == 0 && w == 0 {
321 381
 			continue
322 382
 		}
323 383
 		var parent *Node
@@ -339,15 +399,15 @@ func newTree(prof *profile.Profile, o *Options) (g *Graph) {
339 399
 				if n == nil {
340 400
 					continue
341 401
 				}
342
-				n.addSample(weight, labels, sample.NumLabel, o.FormatTag, false)
402
+				n.addSample(dw, w, labels, sample.NumLabel, o.FormatTag, false)
343 403
 				if parent != nil {
344
-					parent.AddToEdge(n, weight, false, lidx != len(lines)-1)
404
+					parent.AddToEdgeDiv(n, dw, w, false, lidx != len(lines)-1)
345 405
 				}
346 406
 				parent = n
347 407
 			}
348 408
 		}
349 409
 		if parent != nil {
350
-			parent.addSample(weight, labels, sample.NumLabel, o.FormatTag, true)
410
+			parent.addSample(dw, w, labels, sample.NumLabel, o.FormatTag, true)
351 411
 		}
352 412
 	}
353 413
 
@@ -540,21 +600,25 @@ func (ns Nodes) Sum() (flat int64, cum int64) {
540 600
 	return
541 601
 }
542 602
 
543
-func (n *Node) addSample(value int64, labels string, numLabel map[string][]int64, format func(int64, string) string, flat bool) {
603
+func (n *Node) addSample(dw, w int64, labels string, numLabel map[string][]int64, format func(int64, string) string, flat bool) {
544 604
 	// Update sample value
545 605
 	if flat {
546
-		n.Flat += value
606
+		n.FlatDiv += dw
607
+		n.Flat += w
547 608
 	} else {
548
-		n.Cum += value
609
+		n.CumDiv += dw
610
+		n.Cum += w
549 611
 	}
550 612
 
551 613
 	// Add string tags
552 614
 	if labels != "" {
553 615
 		t := n.LabelTags.findOrAddTag(labels, "", 0)
554 616
 		if flat {
555
-			t.Flat += value
617
+			t.FlatDiv += dw
618
+			t.Flat += w
556 619
 		} else {
557
-			t.Cum += value
620
+			t.CumDiv += dw
621
+			t.Cum += w
558 622
 		}
559 623
 	}
560 624
 
@@ -571,9 +635,11 @@ func (n *Node) addSample(value int64, labels string, numLabel map[string][]int64
571 635
 		for _, v := range nvals {
572 636
 			t := numericTags.findOrAddTag(format(v, key), key, v)
573 637
 			if flat {
574
-				t.Flat += value
638
+				t.FlatDiv += dw
639
+				t.Flat += w
575 640
 			} else {
576
-				t.Cum += value
641
+				t.CumDiv += dw
642
+				t.Cum += w
577 643
 			}
578 644
 		}
579 645
 	}

+ 46
- 26
internal/report/report.go Ver arquivo

@@ -141,9 +141,9 @@ func (rpt *Report) selectOutputUnit(g *graph.Graph) {
141 141
 	var minValue int64
142 142
 
143 143
 	for _, n := range g.Nodes {
144
-		nodeMin := abs64(n.Flat)
144
+		nodeMin := abs64(n.FlatValue())
145 145
 		if nodeMin == 0 {
146
-			nodeMin = abs64(n.Cum)
146
+			nodeMin = abs64(n.CumValue())
147 147
 		}
148 148
 		if nodeMin > 0 && (minValue == 0 || nodeMin < minValue) {
149 149
 			minValue = nodeMin
@@ -201,11 +201,12 @@ func (rpt *Report) newGraph(nodes graph.NodeSet) *graph.Graph {
201 201
 	}
202 202
 
203 203
 	gopt := &graph.Options{
204
-		SampleValue:  o.SampleValue,
205
-		FormatTag:    formatTag,
206
-		CallTree:     o.CallTree && (o.OutputFormat == Dot || o.OutputFormat == Callgrind),
207
-		DropNegative: o.DropNegative,
208
-		KeptNodes:    nodes,
204
+		SampleValue:       o.SampleValue,
205
+		SampleMeanDivisor: o.SampleMeanDivisor,
206
+		FormatTag:         formatTag,
207
+		CallTree:          o.CallTree && (o.OutputFormat == Dot || o.OutputFormat == Callgrind),
208
+		DropNegative:      o.DropNegative,
209
+		KeptNodes:         nodes,
209 210
 	}
210 211
 
211 212
 	// Only keep binary names for disassembly-based reports, otherwise
@@ -240,7 +241,7 @@ func printTopProto(w io.Writer, rpt *Report) error {
240 241
 	}
241 242
 	var flatSum int64
242 243
 	for i, n := range g.Nodes {
243
-		name, flat, cum := n.Info.PrintableName(), n.Flat, n.Cum
244
+		name, flat, cum := n.Info.PrintableName(), n.FlatValue(), n.CumValue()
244 245
 
245 246
 		flatSum += flat
246 247
 		f := &profile.Function{
@@ -319,7 +320,7 @@ func printAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
319 320
 			percentage(cumSum, rpt.total))
320 321
 
321 322
 		for _, n := range ns {
322
-			fmt.Fprintf(w, "%10s %10s %10x: %s\n", valueOrDot(n.Flat, rpt), valueOrDot(n.Cum, rpt), n.Info.Address, n.Info.Name)
323
+			fmt.Fprintf(w, "%10s %10s %10x: %s\n", valueOrDot(n.FlatValue(), rpt), valueOrDot(n.CumValue(), rpt), n.Info.Address, n.Info.Name)
323 324
 		}
324 325
 	}
325 326
 	return nil
@@ -442,7 +443,9 @@ func annotateAssembly(insns []plugin.Inst, samples graph.Nodes, base uint64) gra
442 443
 		// Sum all the samples until the next instruction (to account
443 444
 		// for samples attributed to the middle of an instruction).
444 445
 		for next := insns[ix+1].Addr; s < len(samples) && samples[s].Info.Address-base < next; s++ {
446
+			n.FlatDiv += samples[s].FlatDiv
445 447
 			n.Flat += samples[s].Flat
448
+			n.CumDiv += samples[s].CumDiv
446 449
 			n.Cum += samples[s].Cum
447 450
 			if samples[s].Info.File != "" {
448 451
 				n.Info.File = trimPath(samples[s].Info.File)
@@ -521,10 +524,10 @@ func printTags(w io.Writer, rpt *Report) error {
521 524
 		fmt.Fprintf(w, "%s: Total %d\n", key, total)
522 525
 		for _, t := range graph.SortTags(tags, true) {
523 526
 			if total > 0 {
524
-				fmt.Fprintf(w, "  %8d (%s): %s\n", t.Flat,
525
-					percentage(t.Flat, total), t.Name)
527
+				fmt.Fprintf(w, "  %8d (%s): %s\n", t.FlatValue(),
528
+					percentage(t.FlatValue(), total), t.Name)
526 529
 			} else {
527
-				fmt.Fprintf(w, "  %8d: %s\n", t.Flat, t.Name)
530
+				fmt.Fprintf(w, "  %8d: %s\n", t.FlatValue(), t.Name)
528 531
 			}
529 532
 		}
530 533
 		fmt.Fprintln(w)
@@ -544,7 +547,7 @@ func printText(w io.Writer, rpt *Report) error {
544 547
 
545 548
 	var flatSum int64
546 549
 	for _, n := range g.Nodes {
547
-		name, flat, cum := n.Info.PrintableName(), n.Flat, n.Cum
550
+		name, flat, cum := n.Info.PrintableName(), n.FlatValue(), n.CumValue()
548 551
 
549 552
 		var inline, noinline bool
550 553
 		for _, e := range n.In {
@@ -604,11 +607,17 @@ func printTraces(w io.Writer, rpt *Report) error {
604 607
 		}
605 608
 		sort.Strings(labels)
606 609
 		fmt.Fprint(w, strings.Join(labels, ""))
610
+		var d, v int64
611
+		v = o.SampleValue(sample.Value)
612
+		if o.SampleMeanDivisor != nil {
613
+			d = o.SampleMeanDivisor(sample.Value)
614
+		}
607 615
 		// Print call stack.
616
+		if d != 0 {
617
+			v = v / d
618
+		}
608 619
 		fmt.Fprintf(w, "%10s   %s\n",
609
-			rpt.formatValue(o.SampleValue(sample.Value)),
610
-			stack[0].Info.PrintableName())
611
-
620
+			rpt.formatValue(v), stack[0].Info.PrintableName())
612 621
 		for _, s := range stack[1:] {
613 622
 			fmt.Fprintf(w, "%10s   %s\n", "", s.Info.PrintableName())
614 623
 		}
@@ -648,7 +657,7 @@ func printCallgrind(w io.Writer, rpt *Report) error {
648 657
 		}
649 658
 
650 659
 		addr := callgrindAddress(prevInfo, n.Info.Address)
651
-		sv, _ := measurement.Scale(n.Flat, o.SampleUnit, o.OutputUnit)
660
+		sv, _ := measurement.Scale(n.FlatValue(), o.SampleUnit, o.OutputUnit)
652 661
 		fmt.Fprintf(w, "%s %d %d\n", addr, n.Info.Lineno, int64(sv))
653 662
 
654 663
 		// Print outgoing edges.
@@ -772,7 +781,7 @@ func printTree(w io.Writer, rpt *Report) error {
772 781
 
773 782
 	rx := rpt.options.Symbol
774 783
 	for _, n := range g.Nodes {
775
-		name, flat, cum := n.Info.PrintableName(), n.Flat, n.Cum
784
+		name, flat, cum := n.Info.PrintableName(), n.FlatValue(), n.CumValue()
776 785
 
777 786
 		// Skip any entries that do not match the regexp (for the "peek" command).
778 787
 		if rx != nil && !rx.MatchString(name) {
@@ -902,7 +911,7 @@ func reportLabels(rpt *Report, g *graph.Graph, origCount, droppedNodes, droppedE
902 911
 
903 912
 	var flatSum int64
904 913
 	for _, n := range g.Nodes {
905
-		flatSum = flatSum + n.Flat
914
+		flatSum = flatSum + n.FlatValue()
906 915
 	}
907 916
 
908 917
 	label = append(label, fmt.Sprintf("Showing nodes accounting for %s, %s of %s total", rpt.formatValue(flatSum), strings.TrimSpace(percentage(flatSum, rpt.total)), rpt.formatValue(rpt.total)))
@@ -965,9 +974,10 @@ type Options struct {
965 974
 	NodeFraction float64
966 975
 	EdgeFraction float64
967 976
 
968
-	SampleValue func(s []int64) int64
969
-	SampleType  string
970
-	SampleUnit  string // Unit for the sample data from the profile.
977
+	SampleValue       func(s []int64) int64
978
+	SampleMeanDivisor func(s []int64) int64
979
+	SampleType        string
980
+	SampleUnit        string // Unit for the sample data from the profile.
971 981
 
972 982
 	OutputUnit string // Units for data formatting in report.
973 983
 
@@ -985,7 +995,7 @@ func New(prof *profile.Profile, o *Options) *Report {
985 995
 		}
986 996
 		return measurement.ScaledLabel(v, o.SampleUnit, o.OutputUnit)
987 997
 	}
988
-	return &Report{prof, computeTotal(prof, o.SampleValue, !o.PositivePercentages),
998
+	return &Report{prof, computeTotal(prof, o.SampleValue, o.SampleMeanDivisor, !o.PositivePercentages),
989 999
 		o, format}
990 1000
 }
991 1001
 
@@ -1010,15 +1020,25 @@ func NewDefault(prof *profile.Profile, options Options) *Report {
1010 1020
 // absolute values to provide a meaningful percentage for both
1011 1021
 // negative and positive values. Otherwise only use positive values,
1012 1022
 // which is useful when comparing profiles from different jobs.
1013
-func computeTotal(prof *profile.Profile, value func(v []int64) int64, includeNegative bool) int64 {
1014
-	var ret int64
1023
+func computeTotal(prof *profile.Profile, value, meanDiv func(v []int64) int64, includeNegative bool) int64 {
1024
+	var div, ret int64
1015 1025
 	for _, sample := range prof.Sample {
1016
-		if v := value(sample.Value); v > 0 {
1026
+		var d, v int64
1027
+		v = value(sample.Value)
1028
+		if meanDiv != nil {
1029
+			d = meanDiv(sample.Value)
1030
+		}
1031
+		if v >= 0 {
1017 1032
 			ret += v
1033
+			div += d
1018 1034
 		} else if includeNegative {
1019 1035
 			ret -= v
1036
+			div += d
1020 1037
 		}
1021 1038
 	}
1039
+	if div != 0 {
1040
+		return ret / div
1041
+	}
1022 1042
 	return ret
1023 1043
 }
1024 1044