summaryrefslogtreecommitdiff
path: root/vendor/codeberg.org/gruf/go-caller/caller.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/codeberg.org/gruf/go-caller/caller.go')
-rw-r--r--vendor/codeberg.org/gruf/go-caller/caller.go136
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
+ }
+ }
+}