mirror of https://go.googlesource.com/go
177 lines
5.3 KiB
Go
177 lines
5.3 KiB
Go
// Copyright 2014 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
//go:build !js
|
|
|
|
package pprof
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"internal/profile"
|
|
"reflect"
|
|
"regexp"
|
|
"runtime"
|
|
"testing"
|
|
"unsafe"
|
|
)
|
|
|
|
var memSink any
|
|
|
|
func allocateTransient1M() {
|
|
for i := 0; i < 1024; i++ {
|
|
memSink = &struct{ x [1024]byte }{}
|
|
}
|
|
}
|
|
|
|
//go:noinline
|
|
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++ {
|
|
// Can't use slice because that will introduce implicit allocations.
|
|
obj := &Obj32{link: persistentMemSink}
|
|
persistentMemSink = obj
|
|
}
|
|
}
|
|
|
|
// Allocate transient memory using reflect.Call.
|
|
|
|
func allocateReflectTransient() {
|
|
memSink = make([]byte, 2<<20)
|
|
}
|
|
|
|
func allocateReflect() {
|
|
rv := reflect.ValueOf(allocateReflectTransient)
|
|
rv.Call(nil)
|
|
}
|
|
|
|
var memoryProfilerRun = 0
|
|
|
|
func TestMemoryProfiler(t *testing.T) {
|
|
// Disable sampling, otherwise it's difficult to assert anything.
|
|
oldRate := runtime.MemProfileRate
|
|
runtime.MemProfileRate = 1
|
|
defer func() {
|
|
runtime.MemProfileRate = oldRate
|
|
}()
|
|
|
|
// Allocate a meg to ensure that mcache.nextSample is updated to 1.
|
|
for i := 0; i < 1024; i++ {
|
|
memSink = make([]byte, 1024)
|
|
}
|
|
|
|
// Do the interesting allocations.
|
|
allocateTransient1M()
|
|
allocateTransient2M()
|
|
allocateTransient2MInline()
|
|
allocatePersistent1K()
|
|
allocateReflect()
|
|
memSink = nil
|
|
|
|
runtime.GC() // materialize stats
|
|
|
|
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]+ runtime/pprof\.allocatePersistent1K\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test\.go:47
|
|
# 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test\.go:82
|
|
`, 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]+ runtime/pprof\.allocateTransient1M\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:24
|
|
# 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:79
|
|
`, (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]+ runtime/pprof\.allocateTransient2M\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:30
|
|
# 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:80
|
|
`, 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]+ runtime/pprof\.allocateTransient2MInline\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:34
|
|
# 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*runtime/pprof/mprof_test.go:81
|
|
`, 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:55
|
|
`, 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 := stacks(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
|
|
}
|
|
|
|
// Check the inlined function location is encoded correctly.
|
|
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)
|
|
}
|
|
}
|
|
})
|
|
}
|