| | |
| | |
| | |
| |
|
| | |
| |
|
| | package pprof |
| |
|
| | import ( |
| | "bytes" |
| | "fmt" |
| | "internal/asan" |
| | "internal/profile" |
| | "reflect" |
| | "regexp" |
| | "runtime" |
| | "testing" |
| | "unsafe" |
| | ) |
| |
|
| | var memSink any |
| |
|
| | func allocateTransient1M() { |
| | for i := 0; i < 1024; i++ { |
| | memSink = &struct{ x [1024]byte }{} |
| | } |
| | } |
| |
|
| | |
| | func allocateTransient2M() { |
| | memSink = make([]byte, 2<<20) |
| | } |
| |
|
| | func allocateTransient2MInline() { |
| | memSink = make([]byte, 2<<20) |
| | } |
| |
|
| | type Obj32 struct { |
| | link *Obj32 |
| | pad [32 - unsafe.Sizeof(uintptr(0))]byte |
| | } |
| |
|
| | var persistentMemSink *Obj32 |
| |
|
| | func allocatePersistent1K() { |
| | for i := 0; i < 32; i++ { |
| | |
| | obj := &Obj32{link: persistentMemSink} |
| | persistentMemSink = obj |
| | } |
| | } |
| |
|
| | |
| |
|
| | func allocateReflectTransient() { |
| | memSink = make([]byte, 2<<20) |
| | } |
| |
|
| | func allocateReflect() { |
| | rv := reflect.ValueOf(allocateReflectTransient) |
| | rv.Call(nil) |
| | } |
| |
|
| | var memoryProfilerRun = 0 |
| |
|
| | func TestMemoryProfiler(t *testing.T) { |
| | if asan.Enabled { |
| | t.Skip("extra allocations with -asan throw off the test; see #70079") |
| | } |
| |
|
| | |
| | oldRate := runtime.MemProfileRate |
| | runtime.MemProfileRate = 1 |
| | defer func() { |
| | runtime.MemProfileRate = oldRate |
| | }() |
| |
|
| | |
| | for i := 0; i < 1024; i++ { |
| | memSink = make([]byte, 1024) |
| | } |
| |
|
| | |
| | allocateTransient1M() |
| | allocateTransient2M() |
| | allocateTransient2MInline() |
| | allocatePersistent1K() |
| | allocateReflect() |
| | memSink = nil |
| |
|
| | runtime.GC() |
| |
|
| | memoryProfilerRun++ |
| |
|
| | tests := []struct { |
| | stk []string |
| | legacy string |
| | }{{ |
| | stk: []string{"runtime/pprof.allocatePersistent1K", "runtime/pprof.TestMemoryProfiler"}, |
| | 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]+( 0x[0-9,a-f]+ 0x[0-9,a-f]+)? |
| | # 0x[0-9,a-f]+ runtime/pprof\.allocatePersistent1K\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test\.go:48 |
| | # 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test\.go:87 |
| | `, 32*memoryProfilerRun, 1024*memoryProfilerRun, 32*memoryProfilerRun, 1024*memoryProfilerRun), |
| | }, { |
| | stk: []string{"runtime/pprof.allocateTransient1M", "runtime/pprof.TestMemoryProfiler"}, |
| | 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]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ |
| | # 0x[0-9,a-f]+ runtime/pprof\.allocateTransient1M\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:25 |
| | # 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:84 |
| | `, (1<<10)*memoryProfilerRun, (1<<20)*memoryProfilerRun), |
| | }, { |
| | stk: []string{"runtime/pprof.allocateTransient2M", "runtime/pprof.TestMemoryProfiler"}, |
| | 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]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ |
| | # 0x[0-9,a-f]+ runtime/pprof\.allocateTransient2M\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:31 |
| | # 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:85 |
| | `, memoryProfilerRun, (2<<20)*memoryProfilerRun), |
| | }, { |
| | stk: []string{"runtime/pprof.allocateTransient2MInline", "runtime/pprof.TestMemoryProfiler"}, |
| | 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]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ |
| | # 0x[0-9,a-f]+ runtime/pprof\.allocateTransient2MInline\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:35 |
| | # 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:86 |
| | `, memoryProfilerRun, (2<<20)*memoryProfilerRun), |
| | }, { |
| | stk: []string{"runtime/pprof.allocateReflectTransient"}, |
| | legacy: fmt.Sprintf(`0: 0 \[%v: %v\] @( 0x[0-9,a-f]+)+ |
| | # 0x[0-9,a-f]+ runtime/pprof\.allocateReflectTransient\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:56 |
| | `, memoryProfilerRun, (2<<20)*memoryProfilerRun), |
| | }} |
| |
|
| | t.Run("debug=1", func(t *testing.T) { |
| | var buf bytes.Buffer |
| | if err := Lookup("heap").WriteTo(&buf, 1); err != nil { |
| | t.Fatalf("failed to write heap profile: %v", err) |
| | } |
| |
|
| | for _, test := range tests { |
| | if !regexp.MustCompile(test.legacy).Match(buf.Bytes()) { |
| | t.Fatalf("The entry did not match:\n%v\n\nProfile:\n%v\n", test.legacy, buf.String()) |
| | } |
| | } |
| | }) |
| |
|
| | t.Run("proto", func(t *testing.T) { |
| | var buf bytes.Buffer |
| | if err := Lookup("heap").WriteTo(&buf, 0); err != nil { |
| | t.Fatalf("failed to write heap profile: %v", err) |
| | } |
| | p, err := profile.Parse(&buf) |
| | if err != nil { |
| | t.Fatalf("failed to parse heap profile: %v", err) |
| | } |
| | t.Logf("Profile = %v", p) |
| |
|
| | stks := profileStacks(p) |
| | for _, test := range tests { |
| | if !containsStack(stks, test.stk) { |
| | t.Fatalf("No matching stack entry for %q\n\nProfile:\n%v\n", test.stk, p) |
| | } |
| | } |
| |
|
| | if !containsInlinedCall(TestMemoryProfiler, 4<<10) { |
| | t.Logf("Can't determine whether allocateTransient2MInline was inlined into TestMemoryProfiler.") |
| | return |
| | } |
| |
|
| | |
| | for _, loc := range p.Location { |
| | inlinedCaller, inlinedCallee := false, false |
| | for _, line := range loc.Line { |
| | if line.Function.Name == "runtime/pprof.allocateTransient2MInline" { |
| | inlinedCallee = true |
| | } |
| | if inlinedCallee && line.Function.Name == "runtime/pprof.TestMemoryProfiler" { |
| | inlinedCaller = true |
| | } |
| | } |
| | if inlinedCallee != inlinedCaller { |
| | t.Errorf("want allocateTransient2MInline after TestMemoryProfiler in one location, got separate location entries:\n%v", loc) |
| | } |
| | } |
| | }) |
| | } |
| |
|