3 // Copyright 2009 The Go Authors. All rights reserved.
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file.
7 // Test heap sampling logic.
21 var a64k *[64 * 1024]byte
23 // This test checks that heap sampling produces reasonable
24 // results. Note that heap sampling uses randomization, so the results
25 // vary for run to run. This test only checks that the resulting
26 // values appear reasonable.
28 return // TODO: fix this flaky test; golang.org/issue/13098
30 const countInterleaved = 10000
31 allocInterleaved(countInterleaved)
32 checkAllocations(getMemProfileRecords(), "main.allocInterleaved", countInterleaved, []int64{256 * 1024, 1024, 256 * 1024, 512, 256 * 1024, 256})
36 checkAllocations(getMemProfileRecords(), "main.alloc", count, []int64{1024, 512, 256})
39 // allocInterleaved stress-tests the heap sampling logic by
40 // interleaving large and small allocations.
41 func allocInterleaved(n int) {
42 for i := 0; i < n; i++ {
43 // Test verification depends on these lines being contiguous.
44 a64k = new([64 * 1024]byte)
46 a64k = new([64 * 1024]byte)
48 a64k = new([64 * 1024]byte)
53 // alloc performs only small allocations for sanity testing.
55 for i := 0; i < n; i++ {
56 // Test verification depends on these lines being contiguous.
63 // checkAllocations validates that the profile records collected for
64 // the named function are consistent with count contiguous allocations
65 // of the specified sizes.
66 func checkAllocations(records []runtime.MemProfileRecord, fname string, count int64, size []int64) {
67 a := allocObjects(records, fname)
70 if firstLine == 0 || firstLine > ln {
75 for i, w := range size {
78 checkValue(fname, ln, "objects", count, s.objects)
79 checkValue(fname, ln, "bytes", count*w, s.bytes)
80 totalcount += s.objects
82 // Check the total number of allocations, to ensure some sampling occurred.
83 if totalwant := count * int64(len(size)); totalcount <= 0 || totalcount > totalwant*1024 {
84 panic(fmt.Sprintf("%s want total count > 0 && <= %d, got %d", fname, totalwant*1024, totalcount))
88 // checkValue checks an unsampled value against a range.
89 func checkValue(fname string, ln int, name string, want, got int64) {
90 if got < 0 || got > 1024*want {
91 panic(fmt.Sprintf("%s:%d want %s >= 0 && <= %d, got %d", fname, ln, name, 1024*want, got))
95 func getMemProfileRecords() []runtime.MemProfileRecord {
96 // Find out how many records there are (MemProfile(nil, true)),
97 // allocate that many records, and get the data.
98 // There's a race—more records might be added between
99 // the two calls—so allocate a few extra records for safety
100 // and also try again if we're very unlucky.
101 // The loop should only execute one iteration in the common case.
102 var p []runtime.MemProfileRecord
103 n, ok := runtime.MemProfile(nil, true)
105 // Allocate room for a slightly bigger profile,
106 // in case a few more entries have been added
107 // since the call to MemProfile.
108 p = make([]runtime.MemProfileRecord, n+50)
109 n, ok = runtime.MemProfile(p, true)
114 // Profile grew; try again.
119 type allocStat struct {
123 // allocObjects examines the profile records for the named function
124 // and returns the allocation stats aggregated by source line number.
125 func allocObjects(records []runtime.MemProfileRecord, function string) map[int]allocStat {
126 a := make(map[int]allocStat)
127 for _, r := range records {
128 for _, s := range r.Stack0 {
132 if f := runtime.FuncForPC(s); f != nil {
134 _, line := f.FileLine(s)
135 if name == function {
137 allocStat.bytes += r.AllocBytes
138 allocStat.objects += r.AllocObjects
144 for line, stats := range a {
145 objects, bytes := scaleHeapSample(stats.objects, stats.bytes, int64(runtime.MemProfileRate))
146 a[line] = allocStat{bytes, objects}
151 // scaleHeapSample unsamples heap allocations.
152 // Taken from src/cmd/pprof/internal/profile/legacy_profile.go
153 func scaleHeapSample(count, size, rate int64) (int64, int64) {
154 if count == 0 || size == 0 {
159 // if rate==1 all samples were collected so no adjustment is needed.
160 // if rate<1 treat as unknown and skip scaling.
164 avgSize := float64(size) / float64(count)
165 scale := 1 / (1 - math.Exp(-avgSize/float64(rate)))
167 return int64(float64(count) * scale), int64(float64(size) * scale)