codekingpro/portable-devtools
114k
1// run2 3// Copyright 2009 The Go Authors. All rights reserved.4// Use of this source code is governed by a BSD-style5// license that can be found in the LICENSE file.6 7// Test heap sampling logic.8 9package main10 11import (12 "fmt"13 "math"14 "runtime"15)16 17var a16 *[16]byte18var a512 *[512]byte19var a256 *[256]byte20var a1k *[1024]byte21var a16k *[16 * 1024]byte22var a17k *[17 * 1024]byte23var a18k *[18 * 1024]byte24 25// This test checks that heap sampling produces reasonable results.26// Note that heap sampling uses randomization, so the results vary for27// run to run. To avoid flakes, this test performs multiple28// experiments and only complains if all of them consistently fail.29func main() {30 // Sample at 16K instead of default 512K to exercise sampling more heavily.31 runtime.MemProfileRate = 16 * 102432 33 if err := testInterleavedAllocations(); err != nil {34 panic(err.Error())35 }36 if err := testSmallAllocations(); err != nil {37 panic(err.Error())38 }39}40 41// Repeatedly exercise a set of allocations and check that the heap42// profile collected by the runtime unsamples to a reasonable43// value. Because sampling is based on randomization, there can be44// significant variability on the unsampled data. To account for that,45// the testcase allows for a 10% margin of error, but only fails if it46// consistently fails across three experiments, avoiding flakes.47func testInterleavedAllocations() error {48 const iters = 5000049 // Sizes of the allocations performed by each experiment.50 frames := []string{"main.allocInterleaved1", "main.allocInterleaved2", "main.allocInterleaved3"}51 leafFrame := "main.allocInterleaved"52 53 // Pass if at least one of three experiments has no errors. Use a separate54 // function for each experiment to identify each experiment in the profile.55 allocInterleaved1(iters)56 if checkAllocations(getMemProfileRecords(), leafFrame, frames[0:1], iters, allocInterleavedSizes) == nil {57 // Passed on first try, report no error.58 return nil59 }60 allocInterleaved2(iters)61 if checkAllocations(getMemProfileRecords(), leafFrame, frames[0:2], iters, allocInterleavedSizes) == nil {62 // Passed on second try, report no error.63 return nil64 }65 allocInterleaved3(iters)66 // If it fails a third time, we may be onto something.67 return checkAllocations(getMemProfileRecords(), leafFrame, frames[0:3], iters, allocInterleavedSizes)68}69 70var allocInterleavedSizes = []int64{17 * 1024, 1024, 18 * 1024, 512, 16 * 1024, 256}71 72// allocInterleaved stress-tests the heap sampling logic by interleaving large and small allocations.73func allocInterleaved(n int) {74 for i := 0; i < n; i++ {75 // Test verification depends on these lines being contiguous.76 a17k = new([17 * 1024]byte)77 a1k = new([1024]byte)78 a18k = new([18 * 1024]byte)79 a512 = new([512]byte)80 a16k = new([16 * 1024]byte)81 a256 = new([256]byte)82 // Test verification depends on these lines being contiguous.83 84 // Slow down the allocation rate to avoid #52433.85 runtime.Gosched()86 }87}88 89func allocInterleaved1(n int) {90 allocInterleaved(n)91}92 93func allocInterleaved2(n int) {94 allocInterleaved(n)95}96 97func allocInterleaved3(n int) {98 allocInterleaved(n)99}100 101// Repeatedly exercise a set of allocations and check that the heap102// profile collected by the runtime unsamples to a reasonable103// value. Because sampling is based on randomization, there can be104// significant variability on the unsampled data. To account for that,105// the testcase allows for a 10% margin of error, but only fails if it106// consistently fails across three experiments, avoiding flakes.107func testSmallAllocations() error {108 const iters = 50000109 // Sizes of the allocations performed by each experiment.110 sizes := []int64{1024, 512, 256}111 frames := []string{"main.allocSmall1", "main.allocSmall2", "main.allocSmall3"}112 leafFrame := "main.allocSmall"113 114 // Pass if at least one of three experiments has no errors. Use a separate115 // function for each experiment to identify each experiment in the profile.116 allocSmall1(iters)117 if checkAllocations(getMemProfileRecords(), leafFrame, frames[0:1], iters, sizes) == nil {118 // Passed on first try, report no error.119 return nil120 }121 allocSmall2(iters)122 if checkAllocations(getMemProfileRecords(), leafFrame, frames[0:2], iters, sizes) == nil {123 // Passed on second try, report no error.124 return nil125 }126 allocSmall3(iters)127 // If it fails a third time, we may be onto something.128 return checkAllocations(getMemProfileRecords(), leafFrame, frames[0:3], iters, sizes)129}130 131// allocSmall performs only small allocations for sanity testing.132func allocSmall(n int) {133 for i := 0; i < n; i++ {134 // Test verification depends on these lines being contiguous.135 a1k = new([1024]byte)136 a512 = new([512]byte)137 a256 = new([256]byte)138 139 // Slow down the allocation rate to avoid #52433.140 runtime.Gosched()141 }142}143 144// Three separate instances of testing to avoid flakes. Will report an error145// only if they all consistently report failures.146func allocSmall1(n int) {147 allocSmall(n)148}149 150func allocSmall2(n int) {151 allocSmall(n)152}153 154func allocSmall3(n int) {155 allocSmall(n)156}157 158// checkAllocations validates that the profile records collected for159// the named function are consistent with count contiguous allocations160// of the specified sizes.161// Check multiple functions and only report consistent failures across162// multiple tests.163// Look only at samples that include the named frames, and group the164// allocations by their line number. All these allocations are done from165// the same leaf function, so their line numbers are the same.166func checkAllocations(records []runtime.MemProfileRecord, leafFrame string, frames []string, count int64, size []int64) error {167 objectsPerLine := map[int][]int64{}168 bytesPerLine := map[int][]int64{}169 totalCount := []int64{}170 // Compute the line number of the first allocation. All the171 // allocations are from the same leaf, so pick the first one.172 var firstLine int173 for ln := range allocObjects(records, leafFrame, frames[0]) {174 if firstLine == 0 || firstLine > ln {175 firstLine = ln176 }177 }178 for _, frame := range frames {179 var objectCount int64180 a := allocObjects(records, leafFrame, frame)181 for s := range size {182 // Allocations of size size[s] should be on line firstLine + s.183 ln := firstLine + s184 objectsPerLine[ln] = append(objectsPerLine[ln], a[ln].objects)185 bytesPerLine[ln] = append(bytesPerLine[ln], a[ln].bytes)186 objectCount += a[ln].objects187 }188 totalCount = append(totalCount, objectCount)189 }190 for i, w := range size {191 ln := firstLine + i192 if err := checkValue(frames[0], ln, "objects", count, objectsPerLine[ln]); err != nil {193 return err194 }195 if err := checkValue(frames[0], ln, "bytes", count*w, bytesPerLine[ln]); err != nil {196 return err197 }198 }199 return checkValue(frames[0], 0, "total", count*int64(len(size)), totalCount)200}201 202// checkValue checks an unsampled value against its expected value.203// Given that this is a sampled value, it will be unexact and will change204// from run to run. Only report it as a failure if all the values land205// consistently far from the expected value.206func checkValue(fname string, ln int, testName string, want int64, got []int64) error {207 if got == nil {208 return fmt.Errorf("Unexpected empty result")209 }210 min, max := got[0], got[0]211 for _, g := range got[1:] {212 if g < min {213 min = g214 }215 if g > max {216 max = g217 }218 }219 margin := want / 10 // 10% margin.220 if min > want+margin || max < want-margin {221 return fmt.Errorf("%s:%d want %s in [%d: %d], got %v", fname, ln, testName, want-margin, want+margin, got)222 }223 return nil224}225 226func getMemProfileRecords() []runtime.MemProfileRecord {227 // Force the runtime to update the object and byte counts.228 // This can take up to two GC cycles to get a complete229 // snapshot of the current point in time.230 runtime.GC()231 runtime.GC()232 233 // Find out how many records there are (MemProfile(nil, true)),234 // allocate that many records, and get the data.235 // There's a race—more records might be added between236 // the two calls—so allocate a few extra records for safety237 // and also try again if we're very unlucky.238 // The loop should only execute one iteration in the common case.239 var p []runtime.MemProfileRecord240 n, ok := runtime.MemProfile(nil, true)241 for {242 // Allocate room for a slightly bigger profile,243 // in case a few more entries have been added244 // since the call to MemProfile.245 p = make([]runtime.MemProfileRecord, n+50)246 n, ok = runtime.MemProfile(p, true)247 if ok {248 p = p[0:n]249 break250 }251 // Profile grew; try again.252 }253 return p254}255 256type allocStat struct {257 bytes, objects int64258}259 260// allocObjects examines the profile records for samples including the261// named function and returns the allocation stats aggregated by262// source line number of the allocation (at the leaf frame).263func allocObjects(records []runtime.MemProfileRecord, leafFrame, function string) map[int]allocStat {264 a := make(map[int]allocStat)265 for _, r := range records {266 var pcs []uintptr267 for _, s := range r.Stack0 {268 if s == 0 {269 break270 }271 pcs = append(pcs, s)272 }273 frames := runtime.CallersFrames(pcs)274 line := 0275 for {276 frame, more := frames.Next()277 name := frame.Function278 if name == leafFrame && line == 0 {279 line = frame.Line280 }281 if name == function {282 allocStat := a[line]283 allocStat.bytes += r.AllocBytes284 allocStat.objects += r.AllocObjects285 a[line] = allocStat286 }287 if !more {288 break289 }290 }291 }292 for line, stats := range a {293 objects, bytes := scaleHeapSample(stats.objects, stats.bytes, int64(runtime.MemProfileRate))294 a[line] = allocStat{bytes, objects}295 }296 return a297}298 299// scaleHeapSample unsamples heap allocations.300// Taken from src/cmd/pprof/internal/profile/legacy_profile.go301func scaleHeapSample(count, size, rate int64) (int64, int64) {302 if count == 0 || size == 0 {303 return 0, 0304 }305 306 if rate <= 1 {307 // if rate==1 all samples were collected so no adjustment is needed.308 // if rate<1 treat as unknown and skip scaling.309 return count, size310 }311 312 avgSize := float64(size) / float64(count)313 scale := 1 / (1 - math.Exp(-avgSize/float64(rate)))314 315 return int64(float64(count) * scale), int64(float64(size) * scale)316}317 