mirror of
https://github.com/golang/go
synced 2024-11-19 14:24:47 -07:00
runtime/pprof: use new profile format for block/mutex profiles
Unlike the legacy text format that outputs the count and the number of cycles, the pprof tool expects contention profiles to include the count and the delay time measured in nanoseconds. printCountCycleProfile performs the conversion from cycles to nanoseconds. (See parseContention function in cmd/vendor/github.com/google/pprof/profile/legacy_profile.go) Fixes #21474 Change-Id: I8e8fb6ea803822d7eaaf9ecf1df3e236ad225a7b Reviewed-on: https://go-review.googlesource.com/64410 Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Russ Cox <rsc@golang.org>
This commit is contained in:
parent
14f2bfd369
commit
f99d14e0de
@ -346,6 +346,41 @@ type countProfile interface {
|
|||||||
Stack(i int) []uintptr
|
Stack(i int) []uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// printCountCycleProfile outputs block profile records (for block or mutex profiles)
|
||||||
|
// as the pprof-proto format output. Translations from cycle count to time duration
|
||||||
|
// are done because The proto expects count and time (nanoseconds) instead of count
|
||||||
|
// and the number of cycles for block, contention profiles.
|
||||||
|
func printCountCycleProfile(w io.Writer, countName, cycleName string, records []runtime.BlockProfileRecord) error {
|
||||||
|
// Output profile in protobuf form.
|
||||||
|
b := newProfileBuilder(w)
|
||||||
|
b.pbValueType(tagProfile_PeriodType, countName, "count")
|
||||||
|
b.pb.int64Opt(tagProfile_Period, 1)
|
||||||
|
b.pbValueType(tagProfile_SampleType, countName, "count")
|
||||||
|
b.pbValueType(tagProfile_SampleType, cycleName, "nanoseconds")
|
||||||
|
|
||||||
|
cpuGHz := float64(runtime_cyclesPerSecond()) / 1e9
|
||||||
|
|
||||||
|
values := []int64{0, 0}
|
||||||
|
var locs []uint64
|
||||||
|
for _, r := range records {
|
||||||
|
values[0] = int64(r.Count)
|
||||||
|
values[1] = int64(float64(r.Cycles) / cpuGHz) // to nanoseconds
|
||||||
|
locs = locs[:0]
|
||||||
|
for _, addr := range r.Stack() {
|
||||||
|
// For count profiles, all stack addresses are
|
||||||
|
// return PCs, which is what locForPC expects.
|
||||||
|
l := b.locForPC(addr)
|
||||||
|
if l == 0 { // runtime.goexit
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
locs = append(locs, l)
|
||||||
|
}
|
||||||
|
b.pbSample(values, locs, nil)
|
||||||
|
}
|
||||||
|
b.build()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// printCountProfile prints a countProfile at the specified debug level.
|
// printCountProfile prints a countProfile at the specified debug level.
|
||||||
// The profile will be in compressed proto format unless debug is nonzero.
|
// The profile will be in compressed proto format unless debug is nonzero.
|
||||||
func printCountProfile(w io.Writer, debug int, name string, p countProfile) error {
|
func printCountProfile(w io.Writer, debug int, name string, p countProfile) error {
|
||||||
@ -763,14 +798,14 @@ func writeBlock(w io.Writer, debug int) error {
|
|||||||
|
|
||||||
sort.Slice(p, func(i, j int) bool { return p[i].Cycles > p[j].Cycles })
|
sort.Slice(p, func(i, j int) bool { return p[i].Cycles > p[j].Cycles })
|
||||||
|
|
||||||
b := bufio.NewWriter(w)
|
if debug <= 0 {
|
||||||
var tw *tabwriter.Writer
|
return printCountCycleProfile(w, "contentions", "delay", p)
|
||||||
w = b
|
|
||||||
if debug > 0 {
|
|
||||||
tw = tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
|
|
||||||
w = tw
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b := bufio.NewWriter(w)
|
||||||
|
tw := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
|
||||||
|
w = tw
|
||||||
|
|
||||||
fmt.Fprintf(w, "--- contention:\n")
|
fmt.Fprintf(w, "--- contention:\n")
|
||||||
fmt.Fprintf(w, "cycles/second=%v\n", runtime_cyclesPerSecond())
|
fmt.Fprintf(w, "cycles/second=%v\n", runtime_cyclesPerSecond())
|
||||||
for i := range p {
|
for i := range p {
|
||||||
@ -807,14 +842,14 @@ func writeMutex(w io.Writer, debug int) error {
|
|||||||
|
|
||||||
sort.Slice(p, func(i, j int) bool { return p[i].Cycles > p[j].Cycles })
|
sort.Slice(p, func(i, j int) bool { return p[i].Cycles > p[j].Cycles })
|
||||||
|
|
||||||
b := bufio.NewWriter(w)
|
if debug <= 0 {
|
||||||
var tw *tabwriter.Writer
|
return printCountCycleProfile(w, "contentions", "delay", p)
|
||||||
w = b
|
|
||||||
if debug > 0 {
|
|
||||||
tw = tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
|
|
||||||
w = tw
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b := bufio.NewWriter(w)
|
||||||
|
tw := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
|
||||||
|
w = tw
|
||||||
|
|
||||||
fmt.Fprintf(w, "--- mutex:\n")
|
fmt.Fprintf(w, "--- mutex:\n")
|
||||||
fmt.Fprintf(w, "cycles/second=%v\n", runtime_cyclesPerSecond())
|
fmt.Fprintf(w, "cycles/second=%v\n", runtime_cyclesPerSecond())
|
||||||
fmt.Fprintf(w, "sampling period=%d\n", runtime.SetMutexProfileFraction(-1))
|
fmt.Fprintf(w, "sampling period=%d\n", runtime.SetMutexProfileFraction(-1))
|
||||||
|
@ -397,46 +397,103 @@ func TestBlockProfile(t *testing.T) {
|
|||||||
type TestCase struct {
|
type TestCase struct {
|
||||||
name string
|
name string
|
||||||
f func()
|
f func()
|
||||||
|
stk []string
|
||||||
re string
|
re string
|
||||||
}
|
}
|
||||||
tests := [...]TestCase{
|
tests := [...]TestCase{
|
||||||
{"chan recv", blockChanRecv, `
|
{
|
||||||
|
name: "chan recv",
|
||||||
|
f: blockChanRecv,
|
||||||
|
stk: []string{
|
||||||
|
"runtime.chanrecv1",
|
||||||
|
"runtime/pprof.blockChanRecv",
|
||||||
|
"runtime/pprof.TestBlockProfile",
|
||||||
|
},
|
||||||
|
re: `
|
||||||
[0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
|
[0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
|
||||||
# 0x[0-9a-f]+ runtime\.chanrecv1\+0x[0-9a-f]+ .*/src/runtime/chan.go:[0-9]+
|
# 0x[0-9a-f]+ runtime\.chanrecv1\+0x[0-9a-f]+ .*/src/runtime/chan.go:[0-9]+
|
||||||
# 0x[0-9a-f]+ runtime/pprof\.blockChanRecv\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
# 0x[0-9a-f]+ runtime/pprof\.blockChanRecv\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
||||||
# 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
# 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
||||||
`},
|
`},
|
||||||
{"chan send", blockChanSend, `
|
{
|
||||||
|
name: "chan send",
|
||||||
|
f: blockChanSend,
|
||||||
|
stk: []string{
|
||||||
|
"runtime.chansend1",
|
||||||
|
"runtime/pprof.blockChanSend",
|
||||||
|
"runtime/pprof.TestBlockProfile",
|
||||||
|
},
|
||||||
|
re: `
|
||||||
[0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
|
[0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
|
||||||
# 0x[0-9a-f]+ runtime\.chansend1\+0x[0-9a-f]+ .*/src/runtime/chan.go:[0-9]+
|
# 0x[0-9a-f]+ runtime\.chansend1\+0x[0-9a-f]+ .*/src/runtime/chan.go:[0-9]+
|
||||||
# 0x[0-9a-f]+ runtime/pprof\.blockChanSend\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
# 0x[0-9a-f]+ runtime/pprof\.blockChanSend\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
||||||
# 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
# 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
||||||
`},
|
`},
|
||||||
{"chan close", blockChanClose, `
|
{
|
||||||
|
name: "chan close",
|
||||||
|
f: blockChanClose,
|
||||||
|
stk: []string{
|
||||||
|
"runtime.chanrecv1",
|
||||||
|
"runtime/pprof.blockChanClose",
|
||||||
|
"runtime/pprof.TestBlockProfile",
|
||||||
|
},
|
||||||
|
re: `
|
||||||
[0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
|
[0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
|
||||||
# 0x[0-9a-f]+ runtime\.chanrecv1\+0x[0-9a-f]+ .*/src/runtime/chan.go:[0-9]+
|
# 0x[0-9a-f]+ runtime\.chanrecv1\+0x[0-9a-f]+ .*/src/runtime/chan.go:[0-9]+
|
||||||
# 0x[0-9a-f]+ runtime/pprof\.blockChanClose\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
# 0x[0-9a-f]+ runtime/pprof\.blockChanClose\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
||||||
# 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
# 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
||||||
`},
|
`},
|
||||||
{"select recv async", blockSelectRecvAsync, `
|
{
|
||||||
|
name: "select recv async",
|
||||||
|
f: blockSelectRecvAsync,
|
||||||
|
stk: []string{
|
||||||
|
"runtime.selectgo",
|
||||||
|
"runtime/pprof.blockSelectRecvAsync",
|
||||||
|
"runtime/pprof.TestBlockProfile",
|
||||||
|
},
|
||||||
|
re: `
|
||||||
[0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
|
[0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
|
||||||
# 0x[0-9a-f]+ runtime\.selectgo\+0x[0-9a-f]+ .*/src/runtime/select.go:[0-9]+
|
# 0x[0-9a-f]+ runtime\.selectgo\+0x[0-9a-f]+ .*/src/runtime/select.go:[0-9]+
|
||||||
# 0x[0-9a-f]+ runtime/pprof\.blockSelectRecvAsync\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
# 0x[0-9a-f]+ runtime/pprof\.blockSelectRecvAsync\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
||||||
# 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
# 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
||||||
`},
|
`},
|
||||||
{"select send sync", blockSelectSendSync, `
|
{
|
||||||
|
name: "select send sync",
|
||||||
|
f: blockSelectSendSync,
|
||||||
|
stk: []string{
|
||||||
|
"runtime.selectgo",
|
||||||
|
"runtime/pprof.blockSelectSendSync",
|
||||||
|
"runtime/pprof.TestBlockProfile",
|
||||||
|
},
|
||||||
|
re: `
|
||||||
[0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
|
[0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
|
||||||
# 0x[0-9a-f]+ runtime\.selectgo\+0x[0-9a-f]+ .*/src/runtime/select.go:[0-9]+
|
# 0x[0-9a-f]+ runtime\.selectgo\+0x[0-9a-f]+ .*/src/runtime/select.go:[0-9]+
|
||||||
# 0x[0-9a-f]+ runtime/pprof\.blockSelectSendSync\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
# 0x[0-9a-f]+ runtime/pprof\.blockSelectSendSync\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
||||||
# 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
# 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
||||||
`},
|
`},
|
||||||
{"mutex", blockMutex, `
|
{
|
||||||
|
name: "mutex",
|
||||||
|
f: blockMutex,
|
||||||
|
stk: []string{
|
||||||
|
"sync.(*Mutex).Lock",
|
||||||
|
"runtime/pprof.blockMutex",
|
||||||
|
"runtime/pprof.TestBlockProfile",
|
||||||
|
},
|
||||||
|
re: `
|
||||||
[0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
|
[0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
|
||||||
# 0x[0-9a-f]+ sync\.\(\*Mutex\)\.Lock\+0x[0-9a-f]+ .*/src/sync/mutex\.go:[0-9]+
|
# 0x[0-9a-f]+ sync\.\(\*Mutex\)\.Lock\+0x[0-9a-f]+ .*/src/sync/mutex\.go:[0-9]+
|
||||||
# 0x[0-9a-f]+ runtime/pprof\.blockMutex\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
# 0x[0-9a-f]+ runtime/pprof\.blockMutex\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
||||||
# 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
# 0x[0-9a-f]+ runtime/pprof\.TestBlockProfile\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
||||||
`},
|
`},
|
||||||
{"cond", blockCond, `
|
{
|
||||||
|
name: "cond",
|
||||||
|
f: blockCond,
|
||||||
|
stk: []string{
|
||||||
|
"sync.(*Cond).Wait",
|
||||||
|
"runtime/pprof.blockCond",
|
||||||
|
"runtime/pprof.TestBlockProfile",
|
||||||
|
},
|
||||||
|
re: `
|
||||||
[0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
|
[0-9]+ [0-9]+ @( 0x[[:xdigit:]]+)+
|
||||||
# 0x[0-9a-f]+ sync\.\(\*Cond\)\.Wait\+0x[0-9a-f]+ .*/src/sync/cond\.go:[0-9]+
|
# 0x[0-9a-f]+ sync\.\(\*Cond\)\.Wait\+0x[0-9a-f]+ .*/src/sync/cond\.go:[0-9]+
|
||||||
# 0x[0-9a-f]+ runtime/pprof\.blockCond\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
# 0x[0-9a-f]+ runtime/pprof\.blockCond\+0x[0-9a-f]+ .*/src/runtime/pprof/pprof_test.go:[0-9]+
|
||||||
@ -444,11 +501,14 @@ func TestBlockProfile(t *testing.T) {
|
|||||||
`},
|
`},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate block profile
|
||||||
runtime.SetBlockProfileRate(1)
|
runtime.SetBlockProfileRate(1)
|
||||||
defer runtime.SetBlockProfileRate(0)
|
defer runtime.SetBlockProfileRate(0)
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
test.f()
|
test.f()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Run("debug=1", func(t *testing.T) {
|
||||||
var w bytes.Buffer
|
var w bytes.Buffer
|
||||||
Lookup("block").WriteTo(&w, 1)
|
Lookup("block").WriteTo(&w, 1)
|
||||||
prof := w.String()
|
prof := w.String()
|
||||||
@ -463,9 +523,62 @@ func TestBlockProfile(t *testing.T) {
|
|||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
if !regexp.MustCompile(strings.Replace(test.re, "\t", "\t+", -1)).MatchString(prof) {
|
if !regexp.MustCompile(strings.Replace(test.re, "\t", "\t+", -1)).MatchString(prof) {
|
||||||
t.Fatalf("Bad %v entry, expect:\n%v\ngot:\n%v", test.name, test.re, prof)
|
t.Errorf("Bad %v entry, expect:\n%v\ngot:\n%v", test.name, test.re, prof)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("proto", func(t *testing.T) {
|
||||||
|
// proto format
|
||||||
|
var w bytes.Buffer
|
||||||
|
Lookup("block").WriteTo(&w, 0)
|
||||||
|
p, err := profile.Parse(&w)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse profile: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("parsed proto: %s", p)
|
||||||
|
if err := p.CheckValid(); err != nil {
|
||||||
|
t.Fatalf("invalid profile: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stks := stacks(p)
|
||||||
|
for _, test := range tests {
|
||||||
|
if !containsStack(stks, test.stk) {
|
||||||
|
t.Errorf("No matching stack entry for %v, want %+v", test.name, test.stk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func stacks(p *profile.Profile) (res [][]string) {
|
||||||
|
for _, s := range p.Sample {
|
||||||
|
var stk []string
|
||||||
|
for _, l := range s.Location {
|
||||||
|
for _, line := range l.Line {
|
||||||
|
stk = append(stk, line.Function.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res = append(res, stk)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsStack(got [][]string, want []string) bool {
|
||||||
|
for _, stk := range got {
|
||||||
|
if len(stk) < len(want) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i, f := range want {
|
||||||
|
if f != stk[i] {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if i == len(want)-1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const blockDelay = 10 * time.Millisecond
|
const blockDelay = 10 * time.Millisecond
|
||||||
@ -557,6 +670,8 @@ func blockCond() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMutexProfile(t *testing.T) {
|
func TestMutexProfile(t *testing.T) {
|
||||||
|
// Generate mutex profile
|
||||||
|
|
||||||
old := runtime.SetMutexProfileFraction(1)
|
old := runtime.SetMutexProfileFraction(1)
|
||||||
defer runtime.SetMutexProfileFraction(old)
|
defer runtime.SetMutexProfileFraction(old)
|
||||||
if old != 0 {
|
if old != 0 {
|
||||||
@ -565,9 +680,11 @@ func TestMutexProfile(t *testing.T) {
|
|||||||
|
|
||||||
blockMutex()
|
blockMutex()
|
||||||
|
|
||||||
|
t.Run("debug=1", func(t *testing.T) {
|
||||||
var w bytes.Buffer
|
var w bytes.Buffer
|
||||||
Lookup("mutex").WriteTo(&w, 1)
|
Lookup("mutex").WriteTo(&w, 1)
|
||||||
prof := w.String()
|
prof := w.String()
|
||||||
|
t.Logf("received profile: %v", prof)
|
||||||
|
|
||||||
if !strings.HasPrefix(prof, "--- mutex:\ncycles/second=") {
|
if !strings.HasPrefix(prof, "--- mutex:\ncycles/second=") {
|
||||||
t.Errorf("Bad profile header:\n%v", prof)
|
t.Errorf("Bad profile header:\n%v", prof)
|
||||||
@ -590,6 +707,30 @@ func TestMutexProfile(t *testing.T) {
|
|||||||
if ok, err := regexp.MatchString(r3, lines[5]); err != nil || !ok {
|
if ok, err := regexp.MatchString(r3, lines[5]); err != nil || !ok {
|
||||||
t.Errorf("%q didn't match %q", lines[5], r3)
|
t.Errorf("%q didn't match %q", lines[5], r3)
|
||||||
}
|
}
|
||||||
|
t.Logf(prof)
|
||||||
|
})
|
||||||
|
t.Run("proto", func(t *testing.T) {
|
||||||
|
// proto format
|
||||||
|
var w bytes.Buffer
|
||||||
|
Lookup("mutex").WriteTo(&w, 0)
|
||||||
|
p, err := profile.Parse(&w)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse profile: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("parsed proto: %s", p)
|
||||||
|
if err := p.CheckValid(); err != nil {
|
||||||
|
t.Fatalf("invalid profile: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stks := stacks(p)
|
||||||
|
for _, want := range [][]string{
|
||||||
|
{"sync.(*Mutex).Unlock", "runtime/pprof.blockMutex.func1"},
|
||||||
|
} {
|
||||||
|
if !containsStack(stks, want) {
|
||||||
|
t.Errorf("No matching stack entry for %+v", want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func func1(c chan int) { <-c }
|
func func1(c chan int) { <-c }
|
||||||
|
Loading…
Reference in New Issue
Block a user