From 227bb3a79dc9c4f12db97fbcf4121a02516a1123 Mon Sep 17 00:00:00 2001 From: Tristan Colgate Date: Sun, 2 Oct 2016 17:40:58 +0100 Subject: [PATCH] Add udp and udp6 network statistics --- cadvisor.go | 9 ++- cadvisor_test.go | 6 ++ container/factory.go | 1 + container/libcontainer/helpers.go | 84 ++++++++++++++++++++++ container/libcontainer/helpers_test.go | 25 +++++++ container/libcontainer/testdata/procnetudp | 3 + info/v1/container.go | 18 +++++ info/v2/container.go | 4 ++ 8 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 container/libcontainer/testdata/procnetudp diff --git a/cadvisor.go b/cadvisor.go index 70faa2a2..df8146a4 100644 --- a/cadvisor.go +++ b/cadvisor.go @@ -33,6 +33,7 @@ import ( "github.com/google/cadvisor/version" "crypto/tls" + "github.com/golang/glog" ) @@ -60,13 +61,17 @@ var collectorKey = flag.String("collector_key", "", "Key for the collector's cer var ( // Metrics to be ignored. // Tcp metrics are ignored by default. - ignoreMetrics metricSetValue = metricSetValue{container.MetricSet{container.NetworkTcpUsageMetrics: struct{}{}}} + ignoreMetrics metricSetValue = metricSetValue{container.MetricSet{ + container.NetworkTcpUsageMetrics: struct{}{}, + container.NetworkUdpUsageMetrics: struct{}{}, + }} // List of metrics that can be ignored. ignoreWhitelist = container.MetricSet{ container.DiskUsageMetrics: struct{}{}, container.NetworkUsageMetrics: struct{}{}, container.NetworkTcpUsageMetrics: struct{}{}, + container.NetworkUdpUsageMetrics: struct{}{}, } ) @@ -98,7 +103,7 @@ func (ml *metricSetValue) Set(value string) error { } func init() { - flag.Var(&ignoreMetrics, "disable_metrics", "comma-separated list of `metrics` to be disabled. Options are 'disk', 'network', 'tcp'. Note: tcp is disabled by default due to high CPU usage.") + flag.Var(&ignoreMetrics, "disable_metrics", "comma-separated list of `metrics` to be disabled. Options are 'disk', 'network', 'tcp', 'udp'. Note: tcp and udp are disabled by default due to high CPU usage.") } func main() { diff --git a/cadvisor_test.go b/cadvisor_test.go index 4c326f14..59bbf779 100644 --- a/cadvisor_test.go +++ b/cadvisor_test.go @@ -28,6 +28,12 @@ func TestTcpMetricsAreDisabledByDefault(t *testing.T) { assert.True(t, ignoreMetrics.Has(container.NetworkTcpUsageMetrics)) } +func TestUdpMetricsAreDisabledByDefault(t *testing.T) { + assert.True(t, ignoreMetrics.Has(container.NetworkUdpUsageMetrics)) + flag.Parse() + assert.True(t, ignoreMetrics.Has(container.NetworkUdpUsageMetrics)) +} + func TestIgnoreMetrics(t *testing.T) { tests := []struct { value string diff --git a/container/factory.go b/container/factory.go index 5d0e0007..befb4a9e 100644 --- a/container/factory.go +++ b/container/factory.go @@ -48,6 +48,7 @@ const ( DiskUsageMetrics MetricKind = "disk" NetworkUsageMetrics MetricKind = "network" NetworkTcpUsageMetrics MetricKind = "tcp" + NetworkUdpUsageMetrics MetricKind = "udp" AppMetrics MetricKind = "app" ) diff --git a/container/libcontainer/helpers.go b/container/libcontainer/helpers.go index 69116a35..1c382249 100644 --- a/container/libcontainer/helpers.go +++ b/container/libcontainer/helpers.go @@ -17,6 +17,7 @@ package libcontainer import ( "bufio" "fmt" + "io" "io/ioutil" "os" "path" @@ -118,6 +119,21 @@ func GetStats(cgroupManager cgroups.Manager, rootFs string, pid int, ignoreMetri stats.Network.Tcp6 = t6 } } + if !ignoreMetrics.Has(container.NetworkUdpUsageMetrics) { + u, err := udpStatsFromProc(rootFs, pid, "net/udp") + if err != nil { + glog.V(2).Infof("Unable to get udp stats from pid %d: %v", pid, err) + } else { + stats.Network.Udp = u + } + + u6, err := udpStatsFromProc(rootFs, pid, "net/udp6") + if err != nil { + glog.V(2).Infof("Unable to get udp6 stats from pid %d: %v", pid, err) + } else { + stats.Network.Udp6 = u6 + } + } // For backwards compatibility. if len(stats.Network.Interfaces) > 0 { @@ -291,6 +307,74 @@ func scanTcpStats(tcpStatsFile string) (info.TcpStat, error) { return stats, nil } +func udpStatsFromProc(rootFs string, pid int, file string) (info.UdpStat, error) { + var err error + var udpStats info.UdpStat + + udpStatsFile := path.Join(rootFs, "proc", strconv.Itoa(pid), file) + + r, err := os.Open(udpStatsFile) + if err != nil { + return udpStats, fmt.Errorf("failure opening %s: %v", udpStatsFile, err) + } + + udpStats, err = scanUdpStats(r) + if err != nil { + return udpStats, fmt.Errorf("couldn't read udp stats: %v", err) + } + + return udpStats, nil +} + +func scanUdpStats(r io.Reader) (info.UdpStat, error) { + var stats info.UdpStat + + scanner := bufio.NewScanner(r) + scanner.Split(bufio.ScanLines) + + // Discard header line + if b := scanner.Scan(); !b { + return stats, scanner.Err() + } + + listening := uint64(0) + dropped := uint64(0) + rxQueued := uint64(0) + txQueued := uint64(0) + + for scanner.Scan() { + line := scanner.Text() + // Format: sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops + + listening++ + + fs := strings.Fields(line) + if len(fs) != 13 { + continue + } + + rx, tx := uint64(0), uint64(0) + fmt.Sscanf(fs[4], "%X:%X", &rx, &tx) + rxQueued += rx + txQueued += tx + + d, err := strconv.Atoi(string(fs[12])) + if err != nil { + continue + } + dropped += uint64(d) + } + + stats = info.UdpStat{ + Listen: listening, + Dropped: dropped, + RxQueued: rxQueued, + TxQueued: txQueued, + } + + return stats, nil +} + func GetProcesses(cgroupManager cgroups.Manager) ([]int, error) { pids, err := cgroupManager.GetPids() if err != nil { diff --git a/container/libcontainer/helpers_test.go b/container/libcontainer/helpers_test.go index f0a637c1..c491e287 100644 --- a/container/libcontainer/helpers_test.go +++ b/container/libcontainer/helpers_test.go @@ -15,6 +15,7 @@ package libcontainer import ( + "os" "testing" info "github.com/google/cadvisor/info/v1" @@ -61,3 +62,27 @@ func TestScanInterfaceStats(t *testing.T) { } } } + +func TestScanUDPStats(t *testing.T) { + udpStatsFile := "testdata/procnetudp" + r, err := os.Open(udpStatsFile) + if err != nil { + t.Errorf("failure opening %s: %v", udpStatsFile, err) + } + + stats, err := scanUdpStats(r) + if err != nil { + t.Error(err) + } + + var udpstats = info.UdpStat{ + Listen: 2, + Dropped: 4, + RxQueued: 10, + TxQueued: 11, + } + + if stats != udpstats { + t.Errorf("Expected %#v, got %#v", udpstats, stats) + } +} diff --git a/container/libcontainer/testdata/procnetudp b/container/libcontainer/testdata/procnetudp new file mode 100644 index 00000000..de1718c1 --- /dev/null +++ b/container/libcontainer/testdata/procnetudp @@ -0,0 +1,3 @@ + sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops + 1: 00000000:07D3 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 16583 2 ffff8800b4549400 0 + 21: 00000000:A841 00000000:0000 07 0000000A:0000000B 00:00000000 00000000 1000 0 114299623 2 ffff880338477800 4 diff --git a/info/v1/container.go b/info/v1/container.go index 6c9be7c6..6127d881 100644 --- a/info/v1/container.go +++ b/info/v1/container.go @@ -386,6 +386,10 @@ type NetworkStats struct { Tcp TcpStat `json:"tcp"` // TCP6 connection stats (Established, Listen...) Tcp6 TcpStat `json:"tcp6"` + // UDP connection stats + Udp UdpStat `json:"udp"` + // UDP6 connection stats + Udp6 UdpStat `json:"udp6"` } type TcpStat struct { @@ -413,6 +417,20 @@ type TcpStat struct { Closing uint64 } +type UdpStat struct { + // Count of UDP sockets in state "Listen" + Listen uint64 + + // Count of UDP packets dropped by the IP stack + Dropped uint64 + + // Count of packets Queued for Receieve + RxQueued uint64 + + // Count of packets Queued for Transmit + TxQueued uint64 +} + type FsStats struct { // The block device name associated with the filesystem. Device string `json:"device,omitempty"` diff --git a/info/v2/container.go b/info/v2/container.go index f3f67a55..cda1208a 100644 --- a/info/v2/container.go +++ b/info/v2/container.go @@ -269,6 +269,10 @@ type NetworkStats struct { Tcp TcpStat `json:"tcp"` // TCP6 connection stats (Established, Listen...) Tcp6 TcpStat `json:"tcp6"` + // UDP connection stats + Udp v1.UdpStat `json:"udp"` + // UDP6 connection stats + Udp6 v1.UdpStat `json:"udp6"` } // Instantaneous CPU stats