240 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			240 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package ebpf
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"encoding/hex"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"strings"
 | |
| 	"syscall"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/cilium/ebpf/internal"
 | |
| )
 | |
| 
 | |
| // MapInfo describes a map.
 | |
| type MapInfo struct {
 | |
| 	Type       MapType
 | |
| 	id         MapID
 | |
| 	KeySize    uint32
 | |
| 	ValueSize  uint32
 | |
| 	MaxEntries uint32
 | |
| 	Flags      uint32
 | |
| 	// Name as supplied by user space at load time.
 | |
| 	Name string
 | |
| }
 | |
| 
 | |
| func newMapInfoFromFd(fd *internal.FD) (*MapInfo, error) {
 | |
| 	info, err := bpfGetMapInfoByFD(fd)
 | |
| 	if errors.Is(err, syscall.EINVAL) {
 | |
| 		return newMapInfoFromProc(fd)
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &MapInfo{
 | |
| 		MapType(info.map_type),
 | |
| 		MapID(info.id),
 | |
| 		info.key_size,
 | |
| 		info.value_size,
 | |
| 		info.max_entries,
 | |
| 		info.map_flags,
 | |
| 		// name is available from 4.15.
 | |
| 		internal.CString(info.name[:]),
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func newMapInfoFromProc(fd *internal.FD) (*MapInfo, error) {
 | |
| 	var mi MapInfo
 | |
| 	err := scanFdInfo(fd, map[string]interface{}{
 | |
| 		"map_type":    &mi.Type,
 | |
| 		"key_size":    &mi.KeySize,
 | |
| 		"value_size":  &mi.ValueSize,
 | |
| 		"max_entries": &mi.MaxEntries,
 | |
| 		"map_flags":   &mi.Flags,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return &mi, nil
 | |
| }
 | |
| 
 | |
| // ID returns the map ID.
 | |
| //
 | |
| // Available from 4.13.
 | |
| //
 | |
| // The bool return value indicates whether this optional field is available.
 | |
| func (mi *MapInfo) ID() (MapID, bool) {
 | |
| 	return mi.id, mi.id > 0
 | |
| }
 | |
| 
 | |
| // programStats holds statistics of a program.
 | |
| type programStats struct {
 | |
| 	// Total accumulated runtime of the program ins ns.
 | |
| 	runtime time.Duration
 | |
| 	// Total number of times the program was called.
 | |
| 	runCount uint64
 | |
| }
 | |
| 
 | |
| // ProgramInfo describes a program.
 | |
| type ProgramInfo struct {
 | |
| 	Type ProgramType
 | |
| 	id   ProgramID
 | |
| 	// Truncated hash of the BPF bytecode.
 | |
| 	Tag string
 | |
| 	// Name as supplied by user space at load time.
 | |
| 	Name string
 | |
| 
 | |
| 	stats *programStats
 | |
| }
 | |
| 
 | |
| func newProgramInfoFromFd(fd *internal.FD) (*ProgramInfo, error) {
 | |
| 	info, err := bpfGetProgInfoByFD(fd)
 | |
| 	if errors.Is(err, syscall.EINVAL) {
 | |
| 		return newProgramInfoFromProc(fd)
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &ProgramInfo{
 | |
| 		Type: ProgramType(info.prog_type),
 | |
| 		id:   ProgramID(info.id),
 | |
| 		// tag is available if the kernel supports BPF_PROG_GET_INFO_BY_FD.
 | |
| 		Tag: hex.EncodeToString(info.tag[:]),
 | |
| 		// name is available from 4.15.
 | |
| 		Name: internal.CString(info.name[:]),
 | |
| 		stats: &programStats{
 | |
| 			runtime:  time.Duration(info.run_time_ns),
 | |
| 			runCount: info.run_cnt,
 | |
| 		},
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func newProgramInfoFromProc(fd *internal.FD) (*ProgramInfo, error) {
 | |
| 	var info ProgramInfo
 | |
| 	err := scanFdInfo(fd, map[string]interface{}{
 | |
| 		"prog_type": &info.Type,
 | |
| 		"prog_tag":  &info.Tag,
 | |
| 	})
 | |
| 	if errors.Is(err, errMissingFields) {
 | |
| 		return nil, &internal.UnsupportedFeatureError{
 | |
| 			Name:           "reading program info from /proc/self/fdinfo",
 | |
| 			MinimumVersion: internal.Version{4, 10, 0},
 | |
| 		}
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &info, nil
 | |
| }
 | |
| 
 | |
| // ID returns the program ID.
 | |
| //
 | |
| // Available from 4.13.
 | |
| //
 | |
| // The bool return value indicates whether this optional field is available.
 | |
| func (pi *ProgramInfo) ID() (ProgramID, bool) {
 | |
| 	return pi.id, pi.id > 0
 | |
| }
 | |
| 
 | |
| // RunCount returns the total number of times the program was called.
 | |
| //
 | |
| // Can return 0 if the collection of statistics is not enabled. See EnableStats().
 | |
| // The bool return value indicates whether this optional field is available.
 | |
| func (pi *ProgramInfo) RunCount() (uint64, bool) {
 | |
| 	if pi.stats != nil {
 | |
| 		return pi.stats.runCount, true
 | |
| 	}
 | |
| 	return 0, false
 | |
| }
 | |
| 
 | |
| // Runtime returns the total accumulated runtime of the program.
 | |
| //
 | |
| // Can return 0 if the collection of statistics is not enabled. See EnableStats().
 | |
| // The bool return value indicates whether this optional field is available.
 | |
| func (pi *ProgramInfo) Runtime() (time.Duration, bool) {
 | |
| 	if pi.stats != nil {
 | |
| 		return pi.stats.runtime, true
 | |
| 	}
 | |
| 	return time.Duration(0), false
 | |
| }
 | |
| 
 | |
| func scanFdInfo(fd *internal.FD, fields map[string]interface{}) error {
 | |
| 	raw, err := fd.Value()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", raw))
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer fh.Close()
 | |
| 
 | |
| 	if err := scanFdInfoReader(fh, fields); err != nil {
 | |
| 		return fmt.Errorf("%s: %w", fh.Name(), err)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| var errMissingFields = errors.New("missing fields")
 | |
| 
 | |
| func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
 | |
| 	var (
 | |
| 		scanner = bufio.NewScanner(r)
 | |
| 		scanned int
 | |
| 	)
 | |
| 
 | |
| 	for scanner.Scan() {
 | |
| 		parts := strings.SplitN(scanner.Text(), "\t", 2)
 | |
| 		if len(parts) != 2 {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		name := strings.TrimSuffix(parts[0], ":")
 | |
| 		field, ok := fields[string(name)]
 | |
| 		if !ok {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if n, err := fmt.Sscanln(parts[1], field); err != nil || n != 1 {
 | |
| 			return fmt.Errorf("can't parse field %s: %v", name, err)
 | |
| 		}
 | |
| 
 | |
| 		scanned++
 | |
| 	}
 | |
| 
 | |
| 	if err := scanner.Err(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if scanned != len(fields) {
 | |
| 		return errMissingFields
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // EnableStats starts the measuring of the runtime
 | |
| // and run counts of eBPF programs.
 | |
| //
 | |
| // Collecting statistics can have an impact on the performance.
 | |
| //
 | |
| // Requires at least 5.8.
 | |
| func EnableStats(which uint32) (io.Closer, error) {
 | |
| 	attr := internal.BPFEnableStatsAttr{
 | |
| 		StatsType: which,
 | |
| 	}
 | |
| 
 | |
| 	fd, err := internal.BPFEnableStats(&attr)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return fd, nil
 | |
| }
 | 
