Source file 
test/heapsampling.go
     1  
     2  
     3  
     4  
     5  
     6  
     7  
     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 a16k *[16 * 1024]byte
    22  var a17k *[17 * 1024]byte
    23  var a18k *[18 * 1024]byte
    24  
    25  
    26  
    27  
    28  
    29  func main() {
    30  	
    31  	runtime.MemProfileRate = 16 * 1024
    32  
    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  
    42  
    43  
    44  
    45  
    46  
    47  func testInterleavedAllocations() error {
    48  	const iters = 50000
    49  	
    50  	frames := []string{"main.allocInterleaved1", "main.allocInterleaved2", "main.allocInterleaved3"}
    51  
    52  	
    53  	
    54  	allocInterleaved1(iters)
    55  	if checkAllocations(getMemProfileRecords(), frames[0:1], iters, allocInterleavedSizes) == nil {
    56  		
    57  		return nil
    58  	}
    59  	allocInterleaved2(iters)
    60  	if checkAllocations(getMemProfileRecords(), frames[0:2], iters, allocInterleavedSizes) == nil {
    61  		
    62  		return nil
    63  	}
    64  	allocInterleaved3(iters)
    65  	
    66  	return checkAllocations(getMemProfileRecords(), frames[0:3], iters, allocInterleavedSizes)
    67  }
    68  
    69  var allocInterleavedSizes = []int64{17 * 1024, 1024, 18 * 1024, 512, 16 * 1024, 256}
    70  
    71  
    72  func allocInterleaved(n int) {
    73  	for i := 0; i < n; i++ {
    74  		
    75  		a17k = new([17 * 1024]byte)
    76  		a1k = new([1024]byte)
    77  		a18k = new([18 * 1024]byte)
    78  		a512 = new([512]byte)
    79  		a16k = new([16 * 1024]byte)
    80  		a256 = new([256]byte)
    81  		
    82  
    83  		
    84  		runtime.Gosched()
    85  	}
    86  }
    87  
    88  func allocInterleaved1(n int) {
    89  	allocInterleaved(n)
    90  }
    91  
    92  func allocInterleaved2(n int) {
    93  	allocInterleaved(n)
    94  }
    95  
    96  func allocInterleaved3(n int) {
    97  	allocInterleaved(n)
    98  }
    99  
   100  
   101  
   102  
   103  
   104  
   105  
   106  func testSmallAllocations() error {
   107  	const iters = 50000
   108  	
   109  	sizes := []int64{1024, 512, 256}
   110  	frames := []string{"main.allocSmall1", "main.allocSmall2", "main.allocSmall3"}
   111  
   112  	
   113  	
   114  	allocSmall1(iters)
   115  	if checkAllocations(getMemProfileRecords(), frames[0:1], iters, sizes) == nil {
   116  		
   117  		return nil
   118  	}
   119  	allocSmall2(iters)
   120  	if checkAllocations(getMemProfileRecords(), frames[0:2], iters, sizes) == nil {
   121  		
   122  		return nil
   123  	}
   124  	allocSmall3(iters)
   125  	
   126  	return checkAllocations(getMemProfileRecords(), frames[0:3], iters, sizes)
   127  }
   128  
   129  
   130  func allocSmall(n int) {
   131  	for i := 0; i < n; i++ {
   132  		
   133  		a1k = new([1024]byte)
   134  		a512 = new([512]byte)
   135  		a256 = new([256]byte)
   136  
   137  		
   138  		runtime.Gosched()
   139  	}
   140  }
   141  
   142  
   143  
   144  func allocSmall1(n int) {
   145  	allocSmall(n)
   146  }
   147  
   148  func allocSmall2(n int) {
   149  	allocSmall(n)
   150  }
   151  
   152  func allocSmall3(n int) {
   153  	allocSmall(n)
   154  }
   155  
   156  
   157  
   158  
   159  
   160  
   161  
   162  
   163  
   164  func checkAllocations(records []runtime.MemProfileRecord, frames []string, count int64, size []int64) error {
   165  	objectsPerLine := map[int][]int64{}
   166  	bytesPerLine := map[int][]int64{}
   167  	totalCount := []int64{}
   168  	
   169  	
   170  	var firstLine int
   171  	for ln := range allocObjects(records, frames[0]) {
   172  		if firstLine == 0 || firstLine > ln {
   173  			firstLine = ln
   174  		}
   175  	}
   176  	for _, frame := range frames {
   177  		var objectCount int64
   178  		a := allocObjects(records, frame)
   179  		for s := range size {
   180  			
   181  			ln := firstLine + s
   182  			objectsPerLine[ln] = append(objectsPerLine[ln], a[ln].objects)
   183  			bytesPerLine[ln] = append(bytesPerLine[ln], a[ln].bytes)
   184  			objectCount += a[ln].objects
   185  		}
   186  		totalCount = append(totalCount, objectCount)
   187  	}
   188  	for i, w := range size {
   189  		ln := firstLine + i
   190  		if err := checkValue(frames[0], ln, "objects", count, objectsPerLine[ln]); err != nil {
   191  			return err
   192  		}
   193  		if err := checkValue(frames[0], ln, "bytes", count*w, bytesPerLine[ln]); err != nil {
   194  			return err
   195  		}
   196  	}
   197  	return checkValue(frames[0], 0, "total", count*int64(len(size)), totalCount)
   198  }
   199  
   200  
   201  
   202  
   203  
   204  func checkValue(fname string, ln int, testName string, want int64, got []int64) error {
   205  	if got == nil {
   206  		return fmt.Errorf("Unexpected empty result")
   207  	}
   208  	min, max := got[0], got[0]
   209  	for _, g := range got[1:] {
   210  		if g < min {
   211  			min = g
   212  		}
   213  		if g > max {
   214  			max = g
   215  		}
   216  	}
   217  	margin := want / 10 
   218  	if min > want+margin || max < want-margin {
   219  		return fmt.Errorf("%s:%d want %s in [%d: %d], got %v", fname, ln, testName, want-margin, want+margin, got)
   220  	}
   221  	return nil
   222  }
   223  
   224  func getMemProfileRecords() []runtime.MemProfileRecord {
   225  	
   226  	
   227  	
   228  	runtime.GC()
   229  	runtime.GC()
   230  
   231  	
   232  	
   233  	
   234  	
   235  	
   236  	
   237  	var p []runtime.MemProfileRecord
   238  	n, ok := runtime.MemProfile(nil, true)
   239  	for {
   240  		
   241  		
   242  		
   243  		p = make([]runtime.MemProfileRecord, n+50)
   244  		n, ok = runtime.MemProfile(p, true)
   245  		if ok {
   246  			p = p[0:n]
   247  			break
   248  		}
   249  		
   250  	}
   251  	return p
   252  }
   253  
   254  type allocStat struct {
   255  	bytes, objects int64
   256  }
   257  
   258  
   259  
   260  
   261  func allocObjects(records []runtime.MemProfileRecord, function string) map[int]allocStat {
   262  	a := make(map[int]allocStat)
   263  	for _, r := range records {
   264  		var pcs []uintptr
   265  		for _, s := range r.Stack0 {
   266  			if s == 0 {
   267  				break
   268  			}
   269  			pcs = append(pcs, s)
   270  		}
   271  		frames := runtime.CallersFrames(pcs)
   272  		line := 0
   273  		for {
   274  			frame, more := frames.Next()
   275  			name := frame.Function
   276  			if line == 0 {
   277  				line = frame.Line
   278  			}
   279  			if name == function {
   280  				allocStat := a[line]
   281  				allocStat.bytes += r.AllocBytes
   282  				allocStat.objects += r.AllocObjects
   283  				a[line] = allocStat
   284  			}
   285  			if !more {
   286  				break
   287  			}
   288  		}
   289  	}
   290  	for line, stats := range a {
   291  		objects, bytes := scaleHeapSample(stats.objects, stats.bytes, int64(runtime.MemProfileRate))
   292  		a[line] = allocStat{bytes, objects}
   293  	}
   294  	return a
   295  }
   296  
   297  
   298  
   299  func scaleHeapSample(count, size, rate int64) (int64, int64) {
   300  	if count == 0 || size == 0 {
   301  		return 0, 0
   302  	}
   303  
   304  	if rate <= 1 {
   305  		
   306  		
   307  		return count, size
   308  	}
   309  
   310  	avgSize := float64(size) / float64(count)
   311  	scale := 1 / (1 - math.Exp(-avgSize/float64(rate)))
   312  
   313  	return int64(float64(count) * scale), int64(float64(size) * scale)
   314  }
   315  
View as plain text