Home | History | Annotate | Download | only in test
      1 // run
      2 
      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.
      6 
      7 // Test heap sampling logic.
      8 
      9 package main
     10 
     11 import (
     12 	"fmt"
     13 	"math"
     14 	"runtime"
     15 )
     16 
     17 var a16 *[16]byte
     18 var a512 *[512]byte
     19 var a256 *[256]byte
     20 var a1k *[1024]byte
     21 var a64k *[64 * 1024]byte
     22 
     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.
     27 func main() {
     28 	const countInterleaved = 10000
     29 	allocInterleaved(countInterleaved)
     30 	checkAllocations(getMemProfileRecords(), "main.allocInterleaved", countInterleaved, []int64{256 * 1024, 1024, 256 * 1024, 512, 256 * 1024, 256})
     31 
     32 	const count = 100000
     33 	alloc(count)
     34 	checkAllocations(getMemProfileRecords(), "main.alloc", count, []int64{1024, 512, 256})
     35 }
     36 
     37 // allocInterleaved stress-tests the heap sampling logic by
     38 // interleaving large and small allocations.
     39 func allocInterleaved(n int) {
     40 	for i := 0; i < n; i++ {
     41 		// Test verification depends on these lines being contiguous.
     42 		a64k = new([64 * 1024]byte)
     43 		a1k = new([1024]byte)
     44 		a64k = new([64 * 1024]byte)
     45 		a512 = new([512]byte)
     46 		a64k = new([64 * 1024]byte)
     47 		a256 = new([256]byte)
     48 	}
     49 }
     50 
     51 // alloc performs only small allocations for sanity testing.
     52 func alloc(n int) {
     53 	for i := 0; i < n; i++ {
     54 		// Test verification depends on these lines being contiguous.
     55 		a1k = new([1024]byte)
     56 		a512 = new([512]byte)
     57 		a256 = new([256]byte)
     58 	}
     59 }
     60 
     61 // checkAllocations validates that the profile records collected for
     62 // the named function are consistent with count contiguous allocations
     63 // of the specified sizes.
     64 func checkAllocations(records []runtime.MemProfileRecord, fname string, count int64, size []int64) {
     65 	a := allocObjects(records, fname)
     66 	firstLine := 0
     67 	for ln := range a {
     68 		if firstLine == 0 || firstLine > ln {
     69 			firstLine = ln
     70 		}
     71 	}
     72 	var totalcount int64
     73 	for i, w := range size {
     74 		ln := firstLine + i
     75 		s := a[ln]
     76 		checkValue(fname, ln, "objects", count, s.objects)
     77 		checkValue(fname, ln, "bytes", count*w, s.bytes)
     78 		totalcount += s.objects
     79 	}
     80 	// Check the total number of allocations, to ensure some sampling occurred.
     81 	if totalwant := count * int64(len(size)); totalcount <= 0 || totalcount > totalwant*1024 {
     82 		panic(fmt.Sprintf("%s want total count > 0 && <= %d, got %d", fname, totalwant*1024, totalcount))
     83 	}
     84 }
     85 
     86 // checkValue checks an unsampled value against a range.
     87 func checkValue(fname string, ln int, name string, want, got int64) {
     88 	if got < 0 || got > 1024*want {
     89 		panic(fmt.Sprintf("%s:%d want %s >= 0 && <= %d, got %d", fname, ln, name, 1024*want, got))
     90 	}
     91 }
     92 
     93 func getMemProfileRecords() []runtime.MemProfileRecord {
     94 	// Force the runtime to update the object and byte counts.
     95 	// This can take up to two GC cycles to get a complete
     96 	// snapshot of the current point in time.
     97 	runtime.GC()
     98 	runtime.GC()
     99 
    100 	// Find out how many records there are (MemProfile(nil, true)),
    101 	// allocate that many records, and get the data.
    102 	// There's a racemore records might be added between
    103 	// the two callsso allocate a few extra records for safety
    104 	// and also try again if we're very unlucky.
    105 	// The loop should only execute one iteration in the common case.
    106 	var p []runtime.MemProfileRecord
    107 	n, ok := runtime.MemProfile(nil, true)
    108 	for {
    109 		// Allocate room for a slightly bigger profile,
    110 		// in case a few more entries have been added
    111 		// since the call to MemProfile.
    112 		p = make([]runtime.MemProfileRecord, n+50)
    113 		n, ok = runtime.MemProfile(p, true)
    114 		if ok {
    115 			p = p[0:n]
    116 			break
    117 		}
    118 		// Profile grew; try again.
    119 	}
    120 	return p
    121 }
    122 
    123 type allocStat struct {
    124 	bytes, objects int64
    125 }
    126 
    127 // allocObjects examines the profile records for the named function
    128 // and returns the allocation stats aggregated by source line number.
    129 func allocObjects(records []runtime.MemProfileRecord, function string) map[int]allocStat {
    130 	a := make(map[int]allocStat)
    131 	for _, r := range records {
    132 		for _, s := range r.Stack0 {
    133 			if s == 0 {
    134 				break
    135 			}
    136 			if f := runtime.FuncForPC(s); f != nil {
    137 				name := f.Name()
    138 				_, line := f.FileLine(s)
    139 				if name == function {
    140 					allocStat := a[line]
    141 					allocStat.bytes += r.AllocBytes
    142 					allocStat.objects += r.AllocObjects
    143 					a[line] = allocStat
    144 				}
    145 			}
    146 		}
    147 	}
    148 	for line, stats := range a {
    149 		objects, bytes := scaleHeapSample(stats.objects, stats.bytes, int64(runtime.MemProfileRate))
    150 		a[line] = allocStat{bytes, objects}
    151 	}
    152 	return a
    153 }
    154 
    155 // scaleHeapSample unsamples heap allocations.
    156 // Taken from src/cmd/pprof/internal/profile/legacy_profile.go
    157 func scaleHeapSample(count, size, rate int64) (int64, int64) {
    158 	if count == 0 || size == 0 {
    159 		return 0, 0
    160 	}
    161 
    162 	if rate <= 1 {
    163 		// if rate==1 all samples were collected so no adjustment is needed.
    164 		// if rate<1 treat as unknown and skip scaling.
    165 		return count, size
    166 	}
    167 
    168 	avgSize := float64(size) / float64(count)
    169 	scale := 1 / (1 - math.Exp(-avgSize/float64(rate)))
    170 
    171 	return int64(float64(count) * scale), int64(float64(size) * scale)
    172 }
    173