mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-29 01:32:25 -05:00
137 lines
3.2 KiB
Go
137 lines
3.2 KiB
Go
|
|
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
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|