From 13ec5ee4044889e547df2a21420bdfec68463430 Mon Sep 17 00:00:00 2001 From: Aditya Mukerjee Date: Tue, 17 Oct 2017 17:06:24 -0400 Subject: [PATCH] Add function to expose /proc/softnet_stat data --- networking.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++ networking_test.go | 53 ++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/networking.go b/networking.go index f0412fdbd..63aada593 100644 --- a/networking.go +++ b/networking.go @@ -2,9 +2,13 @@ package veneur import ( "crypto/tls" + "encoding/csv" "fmt" + "io" "net" "os" + "path/filepath" + "strconv" "sync" "github.com/sirupsen/logrus" @@ -191,3 +195,81 @@ func startSSFUnix(s *Server, addr *net.UnixAddr) <-chan struct{} { }() return done } + +type SoftnetData struct { + Processors []SoftnetDataProcessor +} + +type SoftnetDataProcessor struct { + Processed int64 + Dropped int64 + TimeSqueeze int64 + CPUCollision int64 + ReceivedRPS int64 + FlowLimitCount int64 +} + +// SoftnetStat provides information about UDP traffic, as provided +// on Linux machines by /proc/net/softnet_stat. Its behavior on other +// operating systems is undefined. +func SoftnetStat() (SoftnetData, error) { + f, err := os.Open(filepath.Join("/proc", "net", "softnet_stat")) + if err != nil { + return SoftnetData{}, err + } + return parseSoftnet(f) +} + +func parseSoftnet(r io.Reader) (SoftnetData, error) { + // softnet_stat output consists of 11 columns. + // The first two refer to the number of packets processed and dropped. + // The third is the number of times "squeezing" occurred (work was remaining + // when netdev_budget or the time slice expired). + // The next five fields are always 0 for current kernel versions (they previously + // were used for fastroute, which is no longer supported). + // The last three columns provide the number of cpu collisions (when two CPUs attempted + // to acquire the device queue lock simultaneously), the number of times the CPU was woken + // up by an inter-processor interrupt, and the number of times the flow limit has been reached. + const columns = 11 + cr := csv.NewReader(r) + cr.Comma = ' ' + cr.FieldsPerRecord = columns + + records, err := cr.ReadAll() + if err != nil { + return SoftnetData{}, err + } + + sd := SoftnetData{} + sd.Processors = make([]SoftnetDataProcessor, len(records)) + for i, processor := range records { + fields, err := parseSoftnetRecord(processor) + if err != nil { + return SoftnetData{}, err + } + sdp := SoftnetDataProcessor{ + Processed: fields[0], + Dropped: fields[1], + TimeSqueeze: fields[2], + CPUCollision: fields[8], + ReceivedRPS: fields[9], + FlowLimitCount: fields[10], + } + sd.Processors[i] = sdp + } + return sd, nil +} + +// parseSoftnetRecord parses a single row +func parseSoftnetRecord(processorRecord []string) ([]int64, error) { + const base = 16 + r := make([]int64, len(processorRecord)) + for i, c := range processorRecord { + n, err := strconv.ParseInt(c, base, 64) + if err != nil { + return nil, err + } + r[i] = n + } + return r, nil +} diff --git a/networking_test.go b/networking_test.go index 575ae286a..b4eee39d5 100644 --- a/networking_test.go +++ b/networking_test.go @@ -3,6 +3,7 @@ package veneur import ( "fmt" "net" + "strings" "testing" "time" @@ -92,3 +93,55 @@ func TestConnectUNIX(t *testing.T) { } close(srv.shutdown) } + +func TestParseSoftnet(t *testing.T) { + const input = ` +00058bcf 00000001 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 +0004c22d 00000000 00000004 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 +00042d39 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 +00052ffe 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 +00015a54 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 +0001897c 00000000 00000000 00000000 00000000 00000000 00000000 00000000 000000c2 00000000 00000000 +00233be1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 000000d0 00000000 +0025ec50 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000f00` + + expected := SoftnetData{ + Processors: []SoftnetDataProcessor{ + { + Processed: 0x00058bcf, + Dropped: 0x1, + }, + { + Processed: 0x0004c22d, + TimeSqueeze: 0x4, + }, + { + Processed: 0x00042d39, + }, + { + Processed: 0x00052ffe, + }, + { + Processed: 0x00015a54, + }, + { + Processed: 0x0001897c, + CPUCollision: 0xc2, + }, + { + Processed: 0x00233be1, + ReceivedRPS: 0xd0, + }, + { + Processed: 0x0025ec50, + FlowLimitCount: 0xf00, + }, + }, + } + + r := strings.NewReader(input) + sd, err := parseSoftnet(r) + assert.NoError(t, err, "Could not parse valid softnet_stat data") + + assert.Equal(t, sd, expected) +}