gotosocial/vendor/codeberg.org/gruf/go-caller/caller.go

137 lines
3.2 KiB
Go
Raw Normal View History

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
}
}
}