pkg/exporter/probe/procsoftnet/procsoftnet.go (123 lines of code) (raw):

package procsoftnet import ( "bufio" "context" "fmt" "github.com/alibaba/kubeskoop/pkg/exporter/probe" "io" "os" "strconv" "strings" "github.com/alibaba/kubeskoop/pkg/exporter/nettop" "golang.org/x/exp/slog" ) const ( SNProcessed = "processed" SNDropped = "dropped" probeName = "softnet" ) var ( softnetMetrics = []probe.LegacyMetric{ {Name: SNProcessed, Help: "The total number of packets processed by the softnet layer"}, {Name: SNDropped, Help: "The total number of packets dropped by the softnet layer"}, } ) func init() { probe.MustRegisterMetricsProbe(probeName, softNetProbeCreator) } func softNetProbeCreator() (probe.MetricsProbe, error) { p := &ProcSoftnet{} batchMetrics := probe.NewLegacyBatchMetrics(probeName, softnetMetrics, p.CollectOnce) return probe.NewMetricsProbe(probeName, p, batchMetrics), nil } type ProcSoftnet struct { } func (s *ProcSoftnet) Start(_ context.Context) error { return nil } func (s *ProcSoftnet) Stop(_ context.Context) error { return nil } func (s *ProcSoftnet) CollectOnce() (map[string]map[uint32]uint64, error) { ets := nettop.GetAllUniqueNetnsEntity() if len(ets) == 0 { slog.Info("collect", "mod", probeName, "ignore", "no entity found") } return collect(ets) } func collect(nslist []*nettop.Entity) (map[string]map[uint32]uint64, error) { resMap := make(map[string]map[uint32]uint64) for _, m := range softnetMetrics { resMap[m.Name] = map[uint32]uint64{} } for _, ns := range nslist { stat, err := getSoftnetStatByPid(uint32(ns.GetPid())) if err != nil { continue } for _, m := range softnetMetrics { resMap[m.Name][uint32(ns.GetNetns())] = stat[m.Name] } } return resMap, nil } type SoftnetStat struct { // Number of processed packets. Processed uint32 // Number of dropped packets. Dropped uint32 // Number of times processing packets ran out of quota. TimeSqueezed uint32 } func getSoftnetStatByPid(pid uint32) (map[string]uint64, error) { snfile := fmt.Sprintf("/proc/%d/net/softnet_stat", pid) if _, err := os.Stat(snfile); os.IsNotExist(err) { return nil, err } sns, err := parseSoftnet(snfile) if err != nil { return nil, err } res := map[string]uint64{} for _, ns := range sns { res[SNProcessed] += uint64(ns.Processed) res[SNDropped] += uint64(ns.Dropped) } return res, nil } func parseSoftnet(file string) ([]SoftnetStat, error) { f, err := os.Open(file) if err != nil { return nil, err } defer f.Close() reader := io.LimitReader(f, 1024*512) var minColumns = 9 s := bufio.NewScanner(reader) var stats []SoftnetStat for s.Scan() { columns := strings.Fields(s.Text()) width := len(columns) if width < minColumns { return nil, fmt.Errorf("%d columns were detected, but at least %d were expected", width, minColumns) } // We only parse the first three columns at the moment. us, err := parseHexUint32s(columns[0:3]) if err != nil { return nil, err } stats = append(stats, SoftnetStat{ Processed: us[0], Dropped: us[1], TimeSqueezed: us[2], }) } return stats, nil } func parseHexUint32s(ss []string) ([]uint32, error) { us := make([]uint32, 0, len(ss)) for _, s := range ss { u, err := strconv.ParseUint(s, 16, 32) if err != nil { return nil, err } us = append(us, uint32(u)) } return us, nil }