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  }