Skip to content

Commit e25de44

Browse files
committed
runtime/pprof: correct inlined function location encoding for non-CPU profiles
Change-Id: Id270a3477bf1a581755c4311eb12f990aa2260b5 Reviewed-on: https://go-review.googlesource.com/c/go/+/205097 Run-TryBot: Hyang-Ah Hana Kim <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent f5e89c2 commit e25de44

File tree

4 files changed

+166
-119
lines changed

4 files changed

+166
-119
lines changed

src/runtime/pprof/mprof_test.go

Lines changed: 82 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5+
// +build !js
6+
57
package pprof
68

79
import (
@@ -10,6 +12,7 @@ import (
1012
"reflect"
1113
"regexp"
1214
"runtime"
15+
"runtime/pprof/internal/profile"
1316
"testing"
1417
"unsafe"
1518
)
@@ -27,6 +30,10 @@ func allocateTransient2M() {
2730
memSink = make([]byte, 2<<20)
2831
}
2932

33+
func allocateTransient2MInline() {
34+
memSink = make([]byte, 2<<20)
35+
}
36+
3037
type Obj32 struct {
3138
link *Obj32
3239
pad [32 - unsafe.Sizeof(uintptr(0))]byte
@@ -71,42 +78,93 @@ func TestMemoryProfiler(t *testing.T) {
7178
// Do the interesting allocations.
7279
allocateTransient1M()
7380
allocateTransient2M()
81+
allocateTransient2MInline()
7482
allocatePersistent1K()
7583
allocateReflect()
7684
memSink = nil
7785

7886
runtime.GC() // materialize stats
79-
var buf bytes.Buffer
80-
if err := Lookup("heap").WriteTo(&buf, 1); err != nil {
81-
t.Fatalf("failed to write heap profile: %v", err)
82-
}
8387

8488
memoryProfilerRun++
8589

86-
tests := []string{
87-
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]+
88-
# 0x[0-9,a-f]+ runtime/pprof\.allocatePersistent1K\+0x[0-9,a-f]+ .*/runtime/pprof/mprof_test\.go:40
89-
# 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*/runtime/pprof/mprof_test\.go:74
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
9098
`, 32*memoryProfilerRun, 1024*memoryProfilerRun, 32*memoryProfilerRun, 1024*memoryProfilerRun),
91-
92-
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]+
93-
# 0x[0-9,a-f]+ runtime/pprof\.allocateTransient1M\+0x[0-9,a-f]+ .*/runtime/pprof/mprof_test.go:21
94-
# 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*/runtime/pprof/mprof_test.go:72
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
95104
`, (1<<10)*memoryProfilerRun, (1<<20)*memoryProfilerRun),
96-
97-
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]+
98-
# 0x[0-9,a-f]+ runtime/pprof\.allocateTransient2M\+0x[0-9,a-f]+ .*/runtime/pprof/mprof_test.go:27
99-
# 0x[0-9,a-f]+ runtime/pprof\.TestMemoryProfiler\+0x[0-9,a-f]+ .*/runtime/pprof/mprof_test.go:73
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
100110
`, memoryProfilerRun, (2<<20)*memoryProfilerRun),
101-
102-
fmt.Sprintf(`0: 0 \[%v: %v\] @( 0x[0-9,a-f]+)+
103-
# 0x[0-9,a-f]+ runtime/pprof\.allocateReflectTransient\+0x[0-9,a-f]+ .*/runtime/pprof/mprof_test.go:48
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
104116
`, memoryProfilerRun, (2<<20)*memoryProfilerRun),
105-
}
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+
}}
106123

107-
for _, test := range tests {
108-
if !regexp.MustCompile(test).Match(buf.Bytes()) {
109-
t.Fatalf("The entry did not match:\n%v\n\nProfile:\n%v\n", test, buf.String())
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)
110128
}
111-
}
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+
// Check the inlined function location is encoded correctly.
155+
for _, loc := range p.Location {
156+
inlinedCaller, inlinedCallee := false, false
157+
for _, line := range loc.Line {
158+
if line.Function.Name == "runtime/pprof.allocateTransient2MInline" {
159+
inlinedCallee = true
160+
}
161+
if inlinedCallee && line.Function.Name == "runtime/pprof.TestMemoryProfiler" {
162+
inlinedCaller = true
163+
}
164+
}
165+
if inlinedCallee != inlinedCaller {
166+
t.Errorf("want allocateTransient2MInline after TestMemoryProfiler in one location, got separate location entries:\n%v", loc)
167+
}
168+
}
169+
})
112170
}

src/runtime/pprof/pprof.go

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -386,16 +386,9 @@ func printCountCycleProfile(w io.Writer, countName, cycleName string, scaler fun
386386
count, nanosec := scaler(r.Count, float64(r.Cycles)/cpuGHz)
387387
values[0] = count
388388
values[1] = int64(nanosec)
389-
locs = locs[:0]
390-
for _, addr := range r.Stack() {
391-
// For count profiles, all stack addresses are
392-
// return PCs, which is what locForPC expects.
393-
l := b.locForPC(addr)
394-
if l == 0 { // runtime.goexit
395-
continue
396-
}
397-
locs = append(locs, l)
398-
}
389+
// For count profiles, all stack addresses are
390+
// return PCs, which is what appendLocsForStack expects.
391+
locs = b.appendLocsForStack(locs[:0], r.Stack())
399392
b.pbSample(values, locs, nil)
400393
}
401394
b.build()
@@ -451,16 +444,9 @@ func printCountProfile(w io.Writer, debug int, name string, p countProfile) erro
451444
var locs []uint64
452445
for _, k := range keys {
453446
values[0] = int64(count[k])
454-
locs = locs[:0]
455-
for _, addr := range p.Stack(index[k]) {
456-
// For count profiles, all stack addresses are
457-
// return PCs, which is what locForPC expects.
458-
l := b.locForPC(addr)
459-
if l == 0 { // runtime.goexit
460-
continue
461-
}
462-
locs = append(locs, l)
463-
}
447+
// For count profiles, all stack addresses are
448+
// return PCs, which is what appendLocsForStack expects.
449+
locs = b.appendLocsForStack(locs[:0], p.Stack(index[k]))
464450
b.pbSample(values, locs, nil)
465451
}
466452
b.build()

src/runtime/pprof/proto.go

Lines changed: 68 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type profileBuilder struct {
4444
locs map[uintptr]locInfo // list of locInfo starting with the given PC.
4545
funcs map[string]int // Package path-qualified function name to Function.ID
4646
mem []memMap
47+
deck pcDeck
4748
}
4849

4950
type memMap struct {
@@ -428,8 +429,6 @@ func (b *profileBuilder) build() {
428429
}
429430

430431
values := []int64{0, 0}
431-
432-
var deck = &pcDeck{}
433432
var locs []uint64
434433

435434
for e := b.m.all; e != nil; e = e.nextAll {
@@ -445,61 +444,13 @@ func (b *profileBuilder) build() {
445444
}
446445
}
447446

448-
deck.reset()
449-
locs = locs[:0]
450-
451447
// Addresses from stack traces point to the next instruction after each call,
452448
// except for the leaf, which points to where the signal occurred.
453-
// deck.add+emitLocation expects return PCs so increment the leaf address to
449+
// appendLocsForStack expects return PCs so increment the leaf address to
454450
// look like a return PC.
455451
e.stk[0] += 1
456-
for stk := e.stk; len(stk) > 0; {
457-
addr := stk[0]
458-
if l, ok := b.locs[addr]; ok {
459-
// first record the location if there is any pending accumulated info.
460-
if id := b.emitLocation(deck); id > 0 {
461-
locs = append(locs, id)
462-
}
463-
464-
// then, record the cached location.
465-
locs = append(locs, l.id)
466-
stk = stk[len(l.pcs):] // skip the matching pcs.
467-
continue
468-
}
469-
470-
frames, symbolizeResult := allFrames(addr)
471-
if len(frames) == 0 { // runtime.goexit.
472-
if id := b.emitLocation(deck); id > 0 {
473-
locs = append(locs, id)
474-
}
475-
stk = stk[1:]
476-
continue
477-
}
478-
479-
if added := deck.tryAdd(addr, frames, symbolizeResult); added {
480-
stk = stk[1:]
481-
continue
482-
}
483-
// add failed because this addr is not inlined with
484-
// the existing PCs in the deck. Flush the deck and retry to
485-
// handle this pc.
486-
if id := b.emitLocation(deck); id > 0 {
487-
locs = append(locs, id)
488-
}
489-
490-
// check cache again - previous emitLocation added a new entry
491-
if l, ok := b.locs[addr]; ok {
492-
locs = append(locs, l.id)
493-
stk = stk[len(l.pcs):] // skip the matching pcs.
494-
} else {
495-
deck.tryAdd(addr, frames, symbolizeResult) // must succeed.
496-
stk = stk[1:]
497-
}
498-
}
499-
if id := b.emitLocation(deck); id > 0 { // emit remaining location.
500-
locs = append(locs, id)
501-
}
502-
e.stk[0] -= 1 // undo the adjustment on the leaf done before the loop.
452+
locs = b.appendLocsForStack(locs[:0], e.stk)
453+
e.stk[0] -= 1 // undo the adjustment on the leaf.
503454

504455
b.pbSample(values, locs, labels)
505456
}
@@ -517,6 +468,62 @@ func (b *profileBuilder) build() {
517468
b.zw.Close()
518469
}
519470

471+
// appendLocsForStack appends the location IDs for the given stack trace to the given
472+
// location ID slice, locs. The addresses in the stack are return PCs or 1 + the PC of
473+
// an inline marker as the runtime traceback function returns.
474+
//
475+
// It may emit to b.pb, so there must be no message encoding in progress.
476+
func (b *profileBuilder) appendLocsForStack(locs []uint64, stk []uintptr) (newLocs []uint64) {
477+
b.deck.reset()
478+
for len(stk) > 0 {
479+
addr := stk[0]
480+
if l, ok := b.locs[addr]; ok {
481+
// first record the location if there is any pending accumulated info.
482+
if id := b.emitLocation(); id > 0 {
483+
locs = append(locs, id)
484+
}
485+
486+
// then, record the cached location.
487+
locs = append(locs, l.id)
488+
stk = stk[len(l.pcs):] // skip the matching pcs.
489+
continue
490+
}
491+
492+
frames, symbolizeResult := allFrames(addr)
493+
if len(frames) == 0 { // runtime.goexit.
494+
if id := b.emitLocation(); id > 0 {
495+
locs = append(locs, id)
496+
}
497+
stk = stk[1:]
498+
continue
499+
}
500+
501+
if added := b.deck.tryAdd(addr, frames, symbolizeResult); added {
502+
stk = stk[1:]
503+
continue
504+
}
505+
// add failed because this addr is not inlined with
506+
// the existing PCs in the deck. Flush the deck and retry to
507+
// handle this pc.
508+
if id := b.emitLocation(); id > 0 {
509+
locs = append(locs, id)
510+
}
511+
512+
// check cache again - previous emitLocation added a new entry
513+
if l, ok := b.locs[addr]; ok {
514+
locs = append(locs, l.id)
515+
stk = stk[len(l.pcs):] // skip the matching pcs.
516+
} else {
517+
b.deck.tryAdd(addr, frames, symbolizeResult) // must succeed.
518+
stk = stk[1:]
519+
}
520+
}
521+
if id := b.emitLocation(); id > 0 { // emit remaining location.
522+
locs = append(locs, id)
523+
}
524+
return locs
525+
}
526+
520527
// pcDeck is a helper to detect a sequence of inlined functions from
521528
// a stack trace returned by the runtime.
522529
//
@@ -583,15 +590,14 @@ func (d *pcDeck) tryAdd(pc uintptr, frames []runtime.Frame, symbolizeResult symb
583590
// and returns the location ID encoded in the profile protobuf.
584591
// It emits to b.pb, so there must be no message encoding in progress.
585592
// It resets the deck.
586-
func (b *profileBuilder) emitLocation(deck *pcDeck) uint64 {
587-
defer deck.reset()
588-
589-
if len(deck.pcs) == 0 {
593+
func (b *profileBuilder) emitLocation() uint64 {
594+
if len(b.deck.pcs) == 0 {
590595
return 0
591596
}
597+
defer b.deck.reset()
592598

593-
addr := deck.pcs[0]
594-
firstFrame := deck.frames[0]
599+
addr := b.deck.pcs[0]
600+
firstFrame := b.deck.frames[0]
595601

596602
// We can't write out functions while in the middle of the
597603
// Location message, so record new functions we encounter and
@@ -603,12 +609,12 @@ func (b *profileBuilder) emitLocation(deck *pcDeck) uint64 {
603609
newFuncs := make([]newFunc, 0, 8)
604610

605611
id := uint64(len(b.locs)) + 1
606-
b.locs[addr] = locInfo{id: id, pcs: append([]uintptr{}, deck.pcs...)}
612+
b.locs[addr] = locInfo{id: id, pcs: append([]uintptr{}, b.deck.pcs...)}
607613

608614
start := b.pb.startMessage()
609615
b.pb.uint64Opt(tagLocation_ID, id)
610616
b.pb.uint64Opt(tagLocation_Address, uint64(firstFrame.PC))
611-
for _, frame := range deck.frames {
617+
for _, frame := range b.deck.frames {
612618
// Write out each line in frame expansion.
613619
funcID := uint64(b.funcs[frame.Function])
614620
if funcID == 0 {
@@ -623,7 +629,7 @@ func (b *profileBuilder) emitLocation(deck *pcDeck) uint64 {
623629
b.pb.uint64Opt(tagLocation_MappingID, uint64(i+1))
624630

625631
m := b.mem[i]
626-
m.funcs |= deck.symbolizeResult
632+
m.funcs |= b.deck.symbolizeResult
627633
b.mem[i] = m
628634
break
629635
}

0 commit comments

Comments
 (0)