/usr/share/go-1.7/test/heapsampling.go is in golang-1.7-src 1.7.4-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 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 | // 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 a64k *[64 * 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. This test only checks that the resulting
// values appear reasonable.
func main() {
const countInterleaved = 10000
allocInterleaved(countInterleaved)
checkAllocations(getMemProfileRecords(), "main.allocInterleaved", countInterleaved, []int64{256 * 1024, 1024, 256 * 1024, 512, 256 * 1024, 256})
const count = 100000
alloc(count)
checkAllocations(getMemProfileRecords(), "main.alloc", count, []int64{1024, 512, 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.
a64k = new([64 * 1024]byte)
a1k = new([1024]byte)
a64k = new([64 * 1024]byte)
a512 = new([512]byte)
a64k = new([64 * 1024]byte)
a256 = new([256]byte)
}
}
// alloc performs only small allocations for sanity testing.
func alloc(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)
}
}
// checkAllocations validates that the profile records collected for
// the named function are consistent with count contiguous allocations
// of the specified sizes.
func checkAllocations(records []runtime.MemProfileRecord, fname string, count int64, size []int64) {
a := allocObjects(records, fname)
firstLine := 0
for ln := range a {
if firstLine == 0 || firstLine > ln {
firstLine = ln
}
}
var totalcount int64
for i, w := range size {
ln := firstLine + i
s := a[ln]
checkValue(fname, ln, "objects", count, s.objects)
checkValue(fname, ln, "bytes", count*w, s.bytes)
totalcount += s.objects
}
// Check the total number of allocations, to ensure some sampling occurred.
if totalwant := count * int64(len(size)); totalcount <= 0 || totalcount > totalwant*1024 {
panic(fmt.Sprintf("%s want total count > 0 && <= %d, got %d", fname, totalwant*1024, totalcount))
}
}
// checkValue checks an unsampled value against a range.
func checkValue(fname string, ln int, name string, want, got int64) {
if got < 0 || got > 1024*want {
panic(fmt.Sprintf("%s:%d want %s >= 0 && <= %d, got %d", fname, ln, name, 1024*want, got))
}
}
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 the named function
// and returns the allocation stats aggregated by source line number.
func allocObjects(records []runtime.MemProfileRecord, function string) map[int]allocStat {
a := make(map[int]allocStat)
for _, r := range records {
for _, s := range r.Stack0 {
if s == 0 {
break
}
if f := runtime.FuncForPC(s); f != nil {
name := f.Name()
_, line := f.FileLine(s)
if name == function {
allocStat := a[line]
allocStat.bytes += r.AllocBytes
allocStat.objects += r.AllocObjects
a[line] = allocStat
}
}
}
}
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)
}
|