modernc.org/gc@v1.0.1-0.20240304020402-f0dba7c97c2b/testdata/errchk/test/heapsampling.go (about)

     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 race—more records might be added between
   103  	// the two calls—so 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  }