Skip to content

Commit a7b7c69

Browse files
committed
Change Call to a struct to cache the results of runtime.FuncForPC and add func Caller().
1 parent 2ac150b commit a7b7c69

File tree

2 files changed

+125
-154
lines changed

2 files changed

+125
-154
lines changed

stack.go

Lines changed: 74 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,32 @@ import (
1010
"sync"
1111
)
1212

13-
// Call records a single function invocation from a goroutine stack. It is a
14-
// wrapper for the program counter values returned by runtime.Caller and
15-
// runtime.Callers and consumed by runtime.FuncForPC.
16-
type Call uintptr
13+
// Call records a single function invocation from a goroutine stack.
14+
type Call struct {
15+
fn *runtime.Func
16+
pc uintptr
17+
}
18+
19+
// Caller returns a Call from the stack of the current goroutine. The argument
20+
// skip is the number of stack frames to ascend, with 0 identifying the
21+
// calling function.
22+
func Caller(skip int) Call {
23+
var pcs [2]uintptr
24+
n := runtime.Callers(skip+1, pcs[:])
25+
26+
var c Call
27+
28+
if n < 2 {
29+
return c
30+
}
31+
32+
c.pc = pcs[1]
33+
if runtime.FuncForPC(pcs[0]) != sigpanic {
34+
c.pc--
35+
}
36+
c.fn = runtime.FuncForPC(c.pc)
37+
return c
38+
}
1739

1840
// Format implements fmt.Formatter with support for the following verbs.
1941
//
@@ -29,22 +51,21 @@ type Call uintptr
2951
// %+n import path qualified function name
3052
// %+v equivalent to %+s:%d
3153
// %#v equivalent to %#s:%d
32-
func (pc Call) Format(s fmt.State, c rune) {
33-
fn := runtime.FuncForPC(uintptr(pc))
34-
if fn == nil {
35-
fmt.Fprintf(s, "%%!%c(NOFUNC)", c)
54+
func (c Call) Format(s fmt.State, verb rune) {
55+
if c.fn == nil {
56+
fmt.Fprintf(s, "%%!%c(NOFUNC)", verb)
3657
return
3758
}
3859

39-
switch c {
60+
switch verb {
4061
case 's', 'v':
41-
file, line := fn.FileLine(uintptr(pc))
62+
file, line := c.fn.FileLine(uintptr(c.pc))
4263
switch {
4364
case s.Flag('#'):
4465
// done
4566
case s.Flag('+'):
4667
// Here we want to get the source file path relative to the
47-
// compile time GOPATH. As of Go 1.3.x there is no direct way to
68+
// compile time GOPATH. As of Go 1.4.x there is no direct way to
4869
// know the compiled GOPATH at runtime, but we can infer the
4970
// number of path segments in the GOPATH. We note that fn.Name()
5071
// returns the function name qualified by the import path, which
@@ -64,7 +85,7 @@ func (pc Call) Format(s fmt.State, c rune) {
6485
// From this we can easily see that fn.Name() has one less path
6586
// separator than our desired output.
6687
const sep = "/"
67-
impCnt := strings.Count(fn.Name(), sep) + 1
88+
impCnt := strings.Count(c.fn.Name(), sep) + 1
6889
pathCnt := strings.Count(file, sep)
6990
for pathCnt > impCnt {
7091
i := strings.Index(file, sep)
@@ -81,16 +102,16 @@ func (pc Call) Format(s fmt.State, c rune) {
81102
}
82103
}
83104
fmt.Fprint(s, file)
84-
if c == 'v' {
105+
if verb == 'v' {
85106
fmt.Fprint(s, ":", line)
86107
}
87108

88109
case 'd':
89-
_, line := fn.FileLine(uintptr(pc))
110+
_, line := c.fn.FileLine(uintptr(c.pc))
90111
fmt.Fprint(s, line)
91112

92113
case 'n':
93-
name := fn.Name()
114+
name := c.fn.Name()
94115
if !s.Flag('+') {
95116
const pathSep = "/"
96117
if i := strings.LastIndex(name, pathSep); i != -1 {
@@ -107,42 +128,41 @@ func (pc Call) Format(s fmt.State, c rune) {
107128

108129
// name returns the import path qualified name of the function containing the
109130
// call.
110-
func (pc Call) name() string {
111-
fn := runtime.FuncForPC(uintptr(pc))
112-
if fn == nil {
131+
func (c Call) name() string {
132+
if c.fn == nil {
113133
return "???"
114134
}
115-
return fn.Name()
135+
return c.fn.Name()
116136
}
117137

118-
func (pc Call) file() string {
119-
fn := runtime.FuncForPC(uintptr(pc))
120-
if fn == nil {
138+
func (c Call) file() string {
139+
if c.fn == nil {
121140
return "???"
122141
}
123-
file, _ := fn.FileLine(uintptr(pc))
142+
file, _ := c.fn.FileLine(uintptr(c.pc))
124143
return file
125144
}
126145

127-
// CallStack records a sequence of function invocations from a goroutine stack.
146+
// CallStack records a sequence of function invocations from a goroutine
147+
// stack.
128148
type CallStack []Call
129149

130-
// Format implements fmt.Formatter by printing the CallStack as square brackes ([,
131-
// ]) surrounding a space separated list of Calls each formatted with the
150+
// Format implements fmt.Formatter by printing the CallStack as square brackes
151+
// ([, ]) surrounding a space separated list of Calls each formatted with the
132152
// supplied verb and options.
133-
func (pcs CallStack) Format(s fmt.State, c rune) {
153+
func (cs CallStack) Format(s fmt.State, verb rune) {
134154
s.Write([]byte("["))
135-
for i, pc := range pcs {
155+
for i, pc := range cs {
136156
if i > 0 {
137157
s.Write([]byte(" "))
138158
}
139-
pc.Format(s, c)
159+
pc.Format(s, verb)
140160
}
141161
s.Write([]byte("]"))
142162
}
143163

144-
// findSigpanic intentially executes faulting code to generate a stack
145-
// trace containing an entry for runtime.sigpanic.
164+
// findSigpanic intentially executes faulting code to generate a stack trace
165+
// containing an entry for runtime.sigpanic.
146166
func findSigpanic() *runtime.Func {
147167
var fn *runtime.Func
148168
func() int {
@@ -190,53 +210,38 @@ func Trace() CallStack {
190210
n := runtime.Callers(2, pcs)
191211
cs := make([]Call, n)
192212

193-
var prevFn *runtime.Func
194213
for i, pc := range pcs[:n] {
195214
pcFix := pc
196-
if prevFn != sigpanic {
215+
if i > 0 && cs[i-1].fn != sigpanic {
197216
pcFix--
198217
}
199-
cs[i] = Call(pcFix)
200-
prevFn = runtime.FuncForPC(pc)
218+
cs[i] = Call{
219+
fn: runtime.FuncForPC(pcFix),
220+
pc: pcFix,
221+
}
201222
}
202223

203224
pcStackPool.Put(pcs)
204225

205226
return cs
206227
}
207228

208-
// TrimBelow returns a slice of the CallStack with all entries below pc removed.
209-
func (pcs CallStack) TrimBelow(pc Call) CallStack {
210-
for len(pcs) > 0 && pcs[0] != pc {
211-
pcs = pcs[1:]
229+
// TrimBelow returns a slice of the CallStack with all entries below pc
230+
// removed.
231+
func (cs CallStack) TrimBelow(c Call) CallStack {
232+
for len(cs) > 0 && cs[0].pc != c.pc {
233+
cs = cs[1:]
212234
}
213-
return pcs
214-
}
215-
216-
// TrimAbove returns a slice of the CallStack with all entries above pc removed.
217-
func (pcs CallStack) TrimAbove(pc Call) CallStack {
218-
for len(pcs) > 0 && pcs[len(pcs)-1] != pc {
219-
pcs = pcs[:len(pcs)-1]
220-
}
221-
return pcs
222-
}
223-
224-
// TrimBelowName returns a slice of the CallStack with all entries below the
225-
// lowest with function name name removed.
226-
func (pcs CallStack) TrimBelowName(name string) CallStack {
227-
for len(pcs) > 0 && pcs[0].name() != name {
228-
pcs = pcs[1:]
229-
}
230-
return pcs
235+
return cs
231236
}
232237

233-
// TrimAboveName returns a slice of the CallStack with all entries above the
234-
// highest with function name name removed.
235-
func (pcs CallStack) TrimAboveName(name string) CallStack {
236-
for len(pcs) > 0 && pcs[len(pcs)-1].name() != name {
237-
pcs = pcs[:len(pcs)-1]
238+
// TrimAbove returns a slice of the CallStack with all entries above pc
239+
// removed.
240+
func (cs CallStack) TrimAbove(c Call) CallStack {
241+
for len(cs) > 0 && cs[len(cs)-1].pc != c.pc {
242+
cs = cs[:len(cs)-1]
238243
}
239-
return pcs
244+
return cs
240245
}
241246

242247
var goroot string
@@ -255,12 +260,12 @@ func inGoroot(path string) bool {
255260
return strings.HasPrefix(path, goroot)
256261
}
257262

258-
// TrimRuntime returns a slice of the CallStack with the topmost entries from the
259-
// go runtime removed. It considers any calls originating from files under
263+
// TrimRuntime returns a slice of the CallStack with the topmost entries from
264+
// the go runtime removed. It considers any calls originating from files under
260265
// GOROOT as part of the runtime.
261-
func (pcs CallStack) TrimRuntime() CallStack {
262-
for len(pcs) > 0 && inGoroot(pcs[len(pcs)-1].file()) {
263-
pcs = pcs[:len(pcs)-1]
266+
func (cs CallStack) TrimRuntime() CallStack {
267+
for len(cs) > 0 && inGoroot(cs[len(cs)-1].file()) {
268+
cs = cs[:len(cs)-1]
264269
}
265-
return pcs
270+
return cs
266271
}

0 commit comments

Comments
 (0)