Kaynağa Gözat

Fix mean computation during graph creation

When -mean is selected, currently pprof divides the sample value
by value[0], which is expected to be the number of samples. This
is intended to produce mean value per sample. These means cannot
be added. Instead, we should add the value and the number of samples
independently and perform the division at the end.

To do this we will create a separate function to get the number of samples,
and accumulate it independently from the sample value (weigth) and apply
the division after the accumulation is completed.
Raul Silvera 8 yıl önce
ebeveyn
işleme
e10c124884

+ 12
- 18
internal/driver/driver.go Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

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