diff --git a/README.rst b/README.rst index bd93152..f4e8328 100644 --- a/README.rst +++ b/README.rst @@ -119,6 +119,11 @@ Several methods have been added which are not present in psutil, but will provid - various status +- net_protocols (linux only) + + - system wide stats on network protocols (i.e IP, TCP, UDP, etc.) + - sourced from /proc/net/snmp + Some codes are ported from Ohai. many thanks. @@ -145,6 +150,7 @@ users x x x x pids x x x x pid_exists x x x x net_connections x x +net_protocols x net_if_addrs net_if_stats ================= ====== ======= ====== ======= diff --git a/net/net.go b/net/net.go index e8a1035..61f9abf 100644 --- a/net/net.go +++ b/net/net.go @@ -45,6 +45,12 @@ type NetConnectionStat struct { Pid int32 `json:"pid"` } +// System wide stats about different network protocols +type NetProtoCountersStat struct { + Protocol string `json:"protocol"` + Stats map[string]int64 `json:"stats"` +} + // NetInterfaceAddr is designed for represent interface addresses type NetInterfaceAddr struct { Addr string `json:"addr"` @@ -75,6 +81,11 @@ func (n NetConnectionStat) String() string { return string(s) } +func (n NetProtoCountersStat) String() string { + s, _ := json.Marshal(n) + return string(s) +} + func (a Addr) String() string { s, _ := json.Marshal(a) return string(s) diff --git a/net/net_darwin.go b/net/net_darwin.go index 26ef705..7786f7c 100644 --- a/net/net_darwin.go +++ b/net/net_darwin.go @@ -3,6 +3,7 @@ package net import ( + "errors" "os/exec" "strconv" "strings" @@ -90,3 +91,11 @@ func NetIOCounters(pernic bool) ([]NetIOCountersStat, error) { return ret, nil } + +// NetProtoCounters returns network statistics for the entire system +// If protocols is empty then all protocols are returned, otherwise +// just the protocols in the list are returned. +// Not Implemented for Darwin +func NetProtoCounters(protocols []string) ([]NetProtoCountersStat, error) { + return nil, errors.New("NetProtoCounters not implemented for darwin") +} diff --git a/net/net_freebsd.go b/net/net_freebsd.go index 482f187..6dc7a23 100644 --- a/net/net_freebsd.go +++ b/net/net_freebsd.go @@ -3,6 +3,7 @@ package net import ( + "errors" "os/exec" "strconv" "strings" @@ -81,3 +82,11 @@ func NetIOCounters(pernic bool) ([]NetIOCountersStat, error) { return ret, nil } + +// NetProtoCounters returns network statistics for the entire system +// If protocols is empty then all protocols are returned, otherwise +// just the protocols in the list are returned. +// Not Implemented for FreeBSD +func NetProtoCounters(protocols []string) ([]NetProtoCountersStat, error) { + return nil, errors.New("NetProtoCounters not implemented for freebsd") +} diff --git a/net/net_linux.go b/net/net_linux.go index 4218373..51d8091 100644 --- a/net/net_linux.go +++ b/net/net_linux.go @@ -3,6 +3,7 @@ package net import ( + "errors" "strconv" "strings" @@ -89,3 +90,73 @@ func NetIOCounters(pernic bool) ([]NetIOCountersStat, error) { return ret, nil } + +var netProtocols = []string{ + "ip", + "icmp", + "icmpmsg", + "tcp", + "udp", + "udplite", +} + +// NetProtoCounters returns network statistics for the entire system +// If protocols is empty then all protocols are returned, otherwise +// just the protocols in the list are returned. +// Available protocols: +// ip,icmp,icmpmsg,tcp,udp,udplite +func NetProtoCounters(protocols []string) ([]NetProtoCountersStat, error) { + if len(protocols) == 0 { + protocols = netProtocols + } + + stats := make([]NetProtoCountersStat, 0, len(protocols)) + protos := make(map[string]bool, len(protocols)) + for _, p := range protocols { + protos[p] = true + } + + filename := "/proc/net/snmp" + lines, err := common.ReadLines(filename) + if err != nil { + return nil, err + } + + linecount := len(lines) + for i := 0; i < linecount; i++ { + line := lines[i] + r := strings.IndexRune(line, ':') + if r == -1 { + return nil, errors.New(filename + " is not fomatted correctly, expected ':'.") + } + proto := strings.ToLower(line[:r]) + if !protos[proto] { + // skip protocol and data line + i++ + continue + } + + // Read header line + statNames := strings.Split(line[r+2:], " ") + + // Read data line + i++ + statValues := strings.Split(lines[i][r+2:], " ") + if len(statNames) != len(statValues) { + return nil, errors.New(filename + " is not fomatted correctly, expected same number of columns.") + } + stat := NetProtoCountersStat{ + Protocol: proto, + Stats: make(map[string]int64, len(statNames)), + } + for j := range statNames { + value, err := strconv.ParseInt(statValues[j], 10, 64) + if err != nil { + return nil, err + } + stat.Stats[statNames[j]] = value + } + stats = append(stats, stat) + } + return stats, nil +} diff --git a/net/net_test.go b/net/net_test.go index 9337e53..187c320 100644 --- a/net/net_test.go +++ b/net/net_test.go @@ -26,6 +26,22 @@ func TestNetIOCountersStatString(t *testing.T) { } } +func TestNetProtoCountersStatString(t *testing.T) { + v := NetProtoCountersStat{ + Protocol: "tcp", + Stats: map[string]int64{ + "MaxConn": -1, + "ActiveOpens": 4000, + "PassiveOpens": 3000, + }, + } + e := `{"protocol":"tcp","stats":{"ActiveOpens":4000,"MaxConn":-1,"PassiveOpens":3000}}` + if e != fmt.Sprintf("%v", v) { + t.Errorf("NetProtoCountersStat string is invalid: %v", v) + } + +} + func TestNetConnectionStatString(t *testing.T) { v := NetConnectionStat{ Fd: 10, @@ -122,6 +138,45 @@ func TestNetInterfaces(t *testing.T) { } } +func TestNetProtoCountersStatsAll(t *testing.T) { + v, err := NetProtoCounters(nil) + if err != nil { + t.Fatalf("Could not get NetProtoCounters: %v", err) + } + if len(v) == 0 { + t.Fatalf("Could not get NetProtoCounters: %v", err) + } + for _, vv := range v { + if vv.Protocol == "" { + t.Errorf("Invalid NetProtoCountersStat: %v", vv) + } + if len(vv.Stats) == 0 { + t.Errorf("Invalid NetProtoCountersStat: %v", vv) + } + } +} + +func TestNetProtoCountersStats(t *testing.T) { + v, err := NetProtoCounters([]string{"tcp", "ip"}) + if err != nil { + t.Fatalf("Could not get NetProtoCounters: %v", err) + } + if len(v) == 0 { + t.Fatalf("Could not get NetProtoCounters: %v", err) + } + if len(v) != 2 { + t.Fatalf("Go incorrect number of NetProtoCounters: %v", err) + } + for _, vv := range v { + if vv.Protocol != "tcp" && vv.Protocol != "ip" { + t.Errorf("Invalid NetProtoCountersStat: %v", vv) + } + if len(vv.Stats) == 0 { + t.Errorf("Invalid NetProtoCountersStat: %v", vv) + } + } +} + func TestNetConnections(t *testing.T) { if ci := os.Getenv("CI"); ci != "" { // skip if test on drone.io return diff --git a/net/net_windows.go b/net/net_windows.go index 3edaf1d..33aceb6 100644 --- a/net/net_windows.go +++ b/net/net_windows.go @@ -3,6 +3,7 @@ package net import ( + "errors" "net" "os" "syscall" @@ -96,3 +97,11 @@ func getAdapterList() (*syscall.IpAdapterInfo, error) { } return a, nil } + +// NetProtoCounters returns network statistics for the entire system +// If protocols is empty then all protocols are returned, otherwise +// just the protocols in the list are returned. +// Not Implemented for Windows +func NetProtoCounters(protocols []string) ([]NetProtoCountersStat, error) { + return nil, errors.New("NetProtoCounters not implemented for windows") +}