Source file src/runtime/pprof/mprof_test.go

     1  // Copyright 2014 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build !js
     6  
     7  package pprof
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"internal/profile"
    13  	"reflect"
    14  	"regexp"
    15  	"runtime"
    16  	"testing"
    17  	"unsafe"
    18  )
    19  
    20  var memSink any
    21  
    22  func allocateTransient1M() {
    23  	for i := 0; i < 1024; i++ {
    24  		memSink = &struct{ x [1024]byte }{}
    25  	}
    26  }
    27  
    28  //go:noinline
    29  func allocateTransient2M() {
    30  	memSink = make([]byte, 2<<20)
    31  }
    32  
    33  func allocateTransient2MInline() {
    34  	memSink = make([]byte, 2<<20)
    35  }
    36  
    37  type Obj32 struct {
    38  	link *Obj32
    39  	pad  [32 - unsafe.Sizeof(uintptr(0))]byte
    40  }
    41  
    42  var persistentMemSink *Obj32
    43  
    44  func allocatePersistent1K() {
    45  	for i := 0; i < 32; i++ {
    46  		// Can't use slice because that will introduce implicit allocations.
    47  		obj := &Obj32{link: persistentMemSink}
    48  		persistentMemSink = obj
    49  	}
    50  }
    51  
    52  // Allocate transient memory using reflect.Call.
    53  
    54  func allocateReflectTransient() {
    55  	memSink = make([]byte, 2<<20)
    56  }
    57  
    58  func allocateReflect() {
    59  	rv := reflect.ValueOf(allocateReflectTransient)
    60  	rv.Call(nil)
    61  }
    62  
    63  var memoryProfilerRun = 0
    64  
    65  func TestMemoryProfiler(t *testing.T) {
    66  	// Disable sampling, otherwise it's difficult to assert anything.
    67  	oldRate := runtime.MemProfileRate
    68  	runtime.MemProfileRate = 1
    69  	defer func() {
    70  		runtime.MemProfileRate = oldRate
    71  	}()
    72  
    73  	// Allocate a meg to ensure that mcache.nextSample is updated to 1.
    74  	for i := 0; i < 1024; i++ {
    75  		memSink = make([]byte, 1024)
    76  	}
    77  
    78  	// Do the interesting allocations.
    79  	allocateTransient1M()
    80  	allocateTransient2M()
    81  	allocateTransient2MInline()
    82  	allocatePersistent1K()
    83  	allocateReflect()
    84  	memSink = nil
    85  
    86  	runtime.GC() // materialize stats
    87  
    88  	memoryProfilerRun++
    89  
    90  	tests := []struct {
    91  		stk    []string
    92  		legacy string
    93  	}{{
    94  		stk: []string{"runtime/pprof.allocatePersistent1K", "runtime/pprof.TestMemoryProfiler"},
    95  		legacy: fmt.Sprintf(`%v: %v \[%v: %v\] @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
    96  #	0x[0-9,a-f]+	runtime/pprof\.allocatePersistent1K\+0x[0-9,a-f]+	.*runtime/pprof/mprof_test\.go:47
    97  #	0x[0-9,a-f]+	runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+	.*runtime/pprof/mprof_test\.go:82
    98  `, 32*memoryProfilerRun, 1024*memoryProfilerRun, 32*memoryProfilerRun, 1024*memoryProfilerRun),
    99  	}, {
   100  		stk: []string{"runtime/pprof.allocateTransient1M", "runtime/pprof.TestMemoryProfiler"},
   101  		legacy: fmt.Sprintf(`0: 0 \[%v: %v\] @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
   102  #	0x[0-9,a-f]+	runtime/pprof\.allocateTransient1M\+0x[0-9,a-f]+	.*runtime/pprof/mprof_test.go:24
   103  #	0x[0-9,a-f]+	runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+	.*runtime/pprof/mprof_test.go:79
   104  `, (1<<10)*memoryProfilerRun, (1<<20)*memoryProfilerRun),
   105  	}, {
   106  		stk: []string{"runtime/pprof.allocateTransient2M", "runtime/pprof.TestMemoryProfiler"},
   107  		legacy: fmt.Sprintf(`0: 0 \[%v: %v\] @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
   108  #	0x[0-9,a-f]+	runtime/pprof\.allocateTransient2M\+0x[0-9,a-f]+	.*runtime/pprof/mprof_test.go:30
   109  #	0x[0-9,a-f]+	runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+	.*runtime/pprof/mprof_test.go:80
   110  `, memoryProfilerRun, (2<<20)*memoryProfilerRun),
   111  	}, {
   112  		stk: []string{"runtime/pprof.allocateTransient2MInline", "runtime/pprof.TestMemoryProfiler"},
   113  		legacy: fmt.Sprintf(`0: 0 \[%v: %v\] @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
   114  #	0x[0-9,a-f]+	runtime/pprof\.allocateTransient2MInline\+0x[0-9,a-f]+	.*runtime/pprof/mprof_test.go:34
   115  #	0x[0-9,a-f]+	runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+	.*runtime/pprof/mprof_test.go:81
   116  `, memoryProfilerRun, (2<<20)*memoryProfilerRun),
   117  	}, {
   118  		stk: []string{"runtime/pprof.allocateReflectTransient"},
   119  		legacy: fmt.Sprintf(`0: 0 \[%v: %v\] @( 0x[0-9,a-f]+)+
   120  #	0x[0-9,a-f]+	runtime/pprof\.allocateReflectTransient\+0x[0-9,a-f]+	.*runtime/pprof/mprof_test.go:55
   121  `, memoryProfilerRun, (2<<20)*memoryProfilerRun),
   122  	}}
   123  
   124  	t.Run("debug=1", func(t *testing.T) {
   125  		var buf bytes.Buffer
   126  		if err := Lookup("heap").WriteTo(&buf, 1); err != nil {
   127  			t.Fatalf("failed to write heap profile: %v", err)
   128  		}
   129  
   130  		for _, test := range tests {
   131  			if !regexp.MustCompile(test.legacy).Match(buf.Bytes()) {
   132  				t.Fatalf("The entry did not match:\n%v\n\nProfile:\n%v\n", test.legacy, buf.String())
   133  			}
   134  		}
   135  	})
   136  
   137  	t.Run("proto", func(t *testing.T) {
   138  		var buf bytes.Buffer
   139  		if err := Lookup("heap").WriteTo(&buf, 0); err != nil {
   140  			t.Fatalf("failed to write heap profile: %v", err)
   141  		}
   142  		p, err := profile.Parse(&buf)
   143  		if err != nil {
   144  			t.Fatalf("failed to parse heap profile: %v", err)
   145  		}
   146  		t.Logf("Profile = %v", p)
   147  
   148  		stks := stacks(p)
   149  		for _, test := range tests {
   150  			if !containsStack(stks, test.stk) {
   151  				t.Fatalf("No matching stack entry for %q\n\nProfile:\n%v\n", test.stk, p)
   152  			}
   153  		}
   154  
   155  		if !containsInlinedCall(TestMemoryProfiler, 4<<10) {
   156  			t.Logf("Can't determine whether allocateTransient2MInline was inlined into TestMemoryProfiler.")
   157  			return
   158  		}
   159  
   160  		// Check the inlined function location is encoded correctly.
   161  		for _, loc := range p.Location {
   162  			inlinedCaller, inlinedCallee := false, false
   163  			for _, line := range loc.Line {
   164  				if line.Function.Name == "runtime/pprof.allocateTransient2MInline" {
   165  					inlinedCallee = true
   166  				}
   167  				if inlinedCallee && line.Function.Name == "runtime/pprof.TestMemoryProfiler" {
   168  					inlinedCaller = true
   169  				}
   170  			}
   171  			if inlinedCallee != inlinedCaller {
   172  				t.Errorf("want allocateTransient2MInline after TestMemoryProfiler in one location, got separate location entries:\n%v", loc)
   173  			}
   174  		}
   175  	})
   176  }
   177  

View as plain text