Black Lives Matter. Support the Equal Justice Initiative.

Source file src/runtime/pprof/mprof_test.go

Documentation: runtime/pprof

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

View as plain text