diff options
Diffstat (limited to 'vendor/codeberg.org/gruf/go-caller/caller.go')
| -rw-r--r-- | vendor/codeberg.org/gruf/go-caller/caller.go | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/vendor/codeberg.org/gruf/go-caller/caller.go b/vendor/codeberg.org/gruf/go-caller/caller.go new file mode 100644 index 000000000..b07e05169 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-caller/caller.go @@ -0,0 +1,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 + } + } +} |
