...
Run Format

Source file test/heapsampling.go

Documentation: test

  // 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)
  }
  

View as plain text