github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/internal/sys/fd_trace.go (about) 1 package sys 2 3 import ( 4 "bytes" 5 "fmt" 6 "runtime" 7 "sync" 8 ) 9 10 // OnLeakFD controls tracing [FD] lifetime to detect resources that are not 11 // closed by Close(). 12 // 13 // If fn is not nil, tracing is enabled for all FDs created going forward. fn is 14 // invoked for all FDs that are closed by the garbage collector instead of an 15 // explicit Close() by a caller. Calling OnLeakFD twice with a non-nil fn 16 // (without disabling tracing in the meantime) will cause a panic. 17 // 18 // If fn is nil, tracing will be disabled. Any FDs that have not been closed are 19 // considered to be leaked, fn will be invoked for them, and the process will be 20 // terminated. 21 // 22 // fn will be invoked at most once for every unique sys.FD allocation since a 23 // runtime.Frames can only be unwound once. 24 func OnLeakFD(fn func(*runtime.Frames)) { 25 // Enable leak tracing if new fn is provided. 26 if fn != nil { 27 if onLeakFD != nil { 28 panic("OnLeakFD called twice with non-nil fn") 29 } 30 31 onLeakFD = fn 32 return 33 } 34 35 // fn is nil past this point. 36 37 if onLeakFD == nil { 38 return 39 } 40 41 // Call onLeakFD for all open fds. 42 if fs := flushFrames(); len(fs) != 0 { 43 for _, f := range fs { 44 onLeakFD(f) 45 } 46 } 47 48 onLeakFD = nil 49 } 50 51 var onLeakFD func(*runtime.Frames) 52 53 // fds is a registry of all file descriptors wrapped into sys.fds that were 54 // created while an fd tracer was active. 55 var fds sync.Map // map[int]*runtime.Frames 56 57 // flushFrames removes all elements from fds and returns them as a slice. This 58 // deals with the fact that a runtime.Frames can only be unwound once using 59 // Next(). 60 func flushFrames() []*runtime.Frames { 61 var frames []*runtime.Frames 62 fds.Range(func(key, value any) bool { 63 frames = append(frames, value.(*runtime.Frames)) 64 fds.Delete(key) 65 return true 66 }) 67 return frames 68 } 69 70 func callersFrames() *runtime.Frames { 71 c := make([]uintptr, 32) 72 73 // Skip runtime.Callers and this function. 74 i := runtime.Callers(2, c) 75 if i == 0 { 76 return nil 77 } 78 79 return runtime.CallersFrames(c) 80 } 81 82 // FormatFrames formats a runtime.Frames as a human-readable string. 83 func FormatFrames(fs *runtime.Frames) string { 84 var b bytes.Buffer 85 for { 86 f, more := fs.Next() 87 b.WriteString(fmt.Sprintf("\t%s+%#x\n\t\t%s:%d\n", f.Function, f.PC-f.Entry, f.File, f.Line)) 88 if !more { 89 break 90 } 91 } 92 return b.String() 93 }