1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
package caller
import (
"runtime"
"strings"
"sync/atomic"
)
var (
// callerCache caches PC values to string names.
// note this may be a little slower than Caller()
// calls on startup, but after all PCs are cached
// this should be ~3x faster + less GC overhead.
//
// see the following benchmark:
// goos: linux
// goarch: amd64
// pkg: codeberg.org/gruf/go-caller
// cpu: AMD Ryzen 7 7840U w/ Radeon 780M Graphics
// BenchmarkCallerCache
// BenchmarkCallerCache-16 16796982 66.19 ns/op 24 B/op 3 allocs/op
// BenchmarkNoCallerCache
// BenchmarkNoCallerCache-16 5486168 219.9 ns/op 744 B/op 6 allocs/op
callerCache atomic.Pointer[map[uintptr]string]
// stringCache caches strings to minimise string memory use
// by ensuring only 1 instance of the same func name string.
stringCache atomic.Pointer[map[string]string]
)
// Clear will empty the global caller PC -> func names cache.
func Clear() { callerCache.Store(nil); stringCache.Store(nil) }
// Name returns the calling function name for given
// program counter, formatted to be useful for logging.
func Name(pc uintptr) string {
// Get frame iterator for program counter.
frames := runtime.CallersFrames([]uintptr{pc})
if frames == nil {
return "???"
}
// Get func name from frame.
frame, _ := frames.Next()
name := frame.Function
if name == "" {
return "???"
}
// Drop all but package and function name, no path.
if idx := strings.LastIndex(name, "/"); idx >= 0 {
name = name[idx+1:]
}
const params = `[...]`
// Drop any function generic type parameter markers.
if idx := strings.Index(name, params); idx >= 0 {
name = name[:idx] + name[idx+len(params):]
}
return name
}
// Get will return calling func information for given PC value,
// caching func names by their PC values to reduce calls to Caller().
func Get(pc uintptr) string {
var cache map[uintptr]string
for {
// Load caller cache map.
ptr := callerCache.Load()
if ptr != nil {
// Look for stored name.
name, ok := (*ptr)[pc]
if ok {
return name
}
// Make a clone of existing caller cache map.
cache = make(map[uintptr]string, len(*ptr)+1)
for key, value := range *ptr {
cache[key] = value
}
} else {
// Allocate new caller cache map.
cache = make(map[uintptr]string, 1)
}
// Calculate caller
// name for PC value.
name := Name(pc)
name = getString(name)
// Store in map.
cache[pc] = name
// Attempt to update caller cache map pointer.
if callerCache.CompareAndSwap(ptr, &cache) {
return name
}
}
}
func getString(key string) string {
var cache map[string]string
for {
// Load string cache map.
ptr := stringCache.Load()
if ptr != nil {
// Check for existing string.
if str, ok := (*ptr)[key]; ok {
return str
}
// Make a clone of existing string cache map.
cache = make(map[string]string, len(*ptr)+1)
for key, value := range *ptr {
cache[key] = value
}
} else {
// Allocate new string cache map.
cache = make(map[string]string, 1)
}
// Store this str.
cache[key] = key
// Attempt to update string cache map pointer.
if stringCache.CompareAndSwap(ptr, &cache) {
return key
}
}
}
|