mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 05: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 | ||
|  | 		} | ||
|  | 	} | ||
|  | } |