| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 
 | // run
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Test heap sampling logic.
package main
import (
	"fmt"
	"math"
	"runtime"
)
var a16 *[16]byte
var a512 *[512]byte
var a256 *[256]byte
var a1k *[1024]byte
var a16k *[16 * 1024]byte
var a17k *[17 * 1024]byte
var a18k *[18 * 1024]byte
// This test checks that heap sampling produces reasonable results.
// Note that heap sampling uses randomization, so the results vary for
// run to run. To avoid flakes, this test performs multiple
// experiments and only complains if all of them consistently fail.
func main() {
	// Sample at 16K instead of default 512K to exercise sampling more heavily.
	runtime.MemProfileRate = 16 * 1024
	if err := testInterleavedAllocations(); err != nil {
		panic(err.Error())
	}
	if err := testSmallAllocations(); err != nil {
		panic(err.Error())
	}
}
// Repeatedly exercise a set of allocations and check that the heap
// profile collected by the runtime unsamples to a reasonable
// value. Because sampling is based on randomization, there can be
// significant variability on the unsampled data. To account for that,
// the testcase allows for a 10% margin of error, but only fails if it
// consistently fails across three experiments, avoiding flakes.
func testInterleavedAllocations() error {
	const iters = 50000
	// Sizes of the allocations performed by each experiment.
	frames := []string{"main.allocInterleaved1", "main.allocInterleaved2", "main.allocInterleaved3"}
	// Pass if at least one of three experiments has no errors. Use a separate
	// function for each experiment to identify each experiment in the profile.
	allocInterleaved1(iters)
	if checkAllocations(getMemProfileRecords(), frames[0:1], iters, allocInterleavedSizes) == nil {
		// Passed on first try, report no error.
		return nil
	}
	allocInterleaved2(iters)
	if checkAllocations(getMemProfileRecords(), frames[0:2], iters, allocInterleavedSizes) == nil {
		// Passed on second try, report no error.
		return nil
	}
	allocInterleaved3(iters)
	// If it fails a third time, we may be onto something.
	return checkAllocations(getMemProfileRecords(), frames[0:3], iters, allocInterleavedSizes)
}
var allocInterleavedSizes = []int64{17 * 1024, 1024, 18 * 1024, 512, 16 * 1024, 256}
// allocInterleaved stress-tests the heap sampling logic by interleaving large and small allocations.
func allocInterleaved(n int) {
	for i := 0; i < n; i++ {
		// Test verification depends on these lines being contiguous.
		a17k = new([17 * 1024]byte)
		a1k = new([1024]byte)
		a18k = new([18 * 1024]byte)
		a512 = new([512]byte)
		a16k = new([16 * 1024]byte)
		a256 = new([256]byte)
		// Test verification depends on these lines being contiguous.
		// Slow down the allocation rate to avoid #52433.
		runtime.Gosched()
	}
}
func allocInterleaved1(n int) {
	allocInterleaved(n)
}
func allocInterleaved2(n int) {
	allocInterleaved(n)
}
func allocInterleaved3(n int) {
	allocInterleaved(n)
}
// Repeatedly exercise a set of allocations and check that the heap
// profile collected by the runtime unsamples to a reasonable
// value. Because sampling is based on randomization, there can be
// significant variability on the unsampled data. To account for that,
// the testcase allows for a 10% margin of error, but only fails if it
// consistently fails across three experiments, avoiding flakes.
func testSmallAllocations() error {
	const iters = 50000
	// Sizes of the allocations performed by each experiment.
	sizes := []int64{1024, 512, 256}
	frames := []string{"main.allocSmall1", "main.allocSmall2", "main.allocSmall3"}
	// Pass if at least one of three experiments has no errors. Use a separate
	// function for each experiment to identify each experiment in the profile.
	allocSmall1(iters)
	if checkAllocations(getMemProfileRecords(), frames[0:1], iters, sizes) == nil {
		// Passed on first try, report no error.
		return nil
	}
	allocSmall2(iters)
	if checkAllocations(getMemProfileRecords(), frames[0:2], iters, sizes) == nil {
		// Passed on second try, report no error.
		return nil
	}
	allocSmall3(iters)
	// If it fails a third time, we may be onto something.
	return checkAllocations(getMemProfileRecords(), frames[0:3], iters, sizes)
}
// allocSmall performs only small allocations for sanity testing.
func allocSmall(n int) {
	for i := 0; i < n; i++ {
		// Test verification depends on these lines being contiguous.
		a1k = new([1024]byte)
		a512 = new([512]byte)
		a256 = new([256]byte)
		// Slow down the allocation rate to avoid #52433.
		runtime.Gosched()
	}
}
// Three separate instances of testing to avoid flakes. Will report an error
// only if they all consistently report failures.
func allocSmall1(n int) {
	allocSmall(n)
}
func allocSmall2(n int) {
	allocSmall(n)
}
func allocSmall3(n int) {
	allocSmall(n)
}
// checkAllocations validates that the profile records collected for
// the named function are consistent with count contiguous allocations
// of the specified sizes.
// Check multiple functions and only report consistent failures across
// multiple tests.
// Look only at samples that include the named frames, and group the
// allocations by their line number. All these allocations are done from
// the same leaf function, so their line numbers are the same.
func checkAllocations(records []runtime.MemProfileRecord, frames []string, count int64, size []int64) error {
	objectsPerLine := map[int][]int64{}
	bytesPerLine := map[int][]int64{}
	totalCount := []int64{}
	// Compute the line number of the first allocation. All the
	// allocations are from the same leaf, so pick the first one.
	var firstLine int
	for ln := range allocObjects(records, frames[0]) {
		if firstLine == 0 || firstLine > ln {
			firstLine = ln
		}
	}
	for _, frame := range frames {
		var objectCount int64
		a := allocObjects(records, frame)
		for s := range size {
			// Allocations of size size[s] should be on line firstLine + s.
			ln := firstLine + s
			objectsPerLine[ln] = append(objectsPerLine[ln], a[ln].objects)
			bytesPerLine[ln] = append(bytesPerLine[ln], a[ln].bytes)
			objectCount += a[ln].objects
		}
		totalCount = append(totalCount, objectCount)
	}
	for i, w := range size {
		ln := firstLine + i
		if err := checkValue(frames[0], ln, "objects", count, objectsPerLine[ln]); err != nil {
			return err
		}
		if err := checkValue(frames[0], ln, "bytes", count*w, bytesPerLine[ln]); err != nil {
			return err
		}
	}
	return checkValue(frames[0], 0, "total", count*int64(len(size)), totalCount)
}
// checkValue checks an unsampled value against its expected value.
// Given that this is a sampled value, it will be unexact and will change
// from run to run. Only report it as a failure if all the values land
// consistently far from the expected value.
func checkValue(fname string, ln int, testName string, want int64, got []int64) error {
	if got == nil {
		return fmt.Errorf("Unexpected empty result")
	}
	min, max := got[0], got[0]
	for _, g := range got[1:] {
		if g < min {
			min = g
		}
		if g > max {
			max = g
		}
	}
	margin := want / 10 // 10% margin.
	if min > want+margin || max < want-margin {
		return fmt.Errorf("%s:%d want %s in [%d: %d], got %v", fname, ln, testName, want-margin, want+margin, got)
	}
	return nil
}
func getMemProfileRecords() []runtime.MemProfileRecord {
	// Force the runtime to update the object and byte counts.
	// This can take up to two GC cycles to get a complete
	// snapshot of the current point in time.
	runtime.GC()
	runtime.GC()
	// Find out how many records there are (MemProfile(nil, true)),
	// allocate that many records, and get the data.
	// There's a race—more records might be added between
	// the two calls—so allocate a few extra records for safety
	// and also try again if we're very unlucky.
	// The loop should only execute one iteration in the common case.
	var p []runtime.MemProfileRecord
	n, ok := runtime.MemProfile(nil, true)
	for {
		// Allocate room for a slightly bigger profile,
		// in case a few more entries have been added
		// since the call to MemProfile.
		p = make([]runtime.MemProfileRecord, n+50)
		n, ok = runtime.MemProfile(p, true)
		if ok {
			p = p[0:n]
			break
		}
		// Profile grew; try again.
	}
	return p
}
type allocStat struct {
	bytes, objects int64
}
// allocObjects examines the profile records for samples including the
// named function and returns the allocation stats aggregated by
// source line number of the allocation (at the leaf frame).
func allocObjects(records []runtime.MemProfileRecord, function string) map[int]allocStat {
	a := make(map[int]allocStat)
	for _, r := range records {
		var pcs []uintptr
		for _, s := range r.Stack0 {
			if s == 0 {
				break
			}
			pcs = append(pcs, s)
		}
		frames := runtime.CallersFrames(pcs)
		line := 0
		for {
			frame, more := frames.Next()
			name := frame.Function
			if line == 0 {
				line = frame.Line
			}
			if name == function {
				allocStat := a[line]
				allocStat.bytes += r.AllocBytes
				allocStat.objects += r.AllocObjects
				a[line] = allocStat
			}
			if !more {
				break
			}
		}
	}
	for line, stats := range a {
		objects, bytes := scaleHeapSample(stats.objects, stats.bytes, int64(runtime.MemProfileRate))
		a[line] = allocStat{bytes, objects}
	}
	return a
}
// scaleHeapSample unsamples heap allocations.
// Taken from src/cmd/pprof/internal/profile/legacy_profile.go
func scaleHeapSample(count, size, rate int64) (int64, int64) {
	if count == 0 || size == 0 {
		return 0, 0
	}
	if rate <= 1 {
		// if rate==1 all samples were collected so no adjustment is needed.
		// if rate<1 treat as unknown and skip scaling.
		return count, size
	}
	avgSize := float64(size) / float64(count)
	scale := 1 / (1 - math.Exp(-avgSize/float64(rate)))
	return int64(float64(count) * scale), int64(float64(size) * scale)
}
 |