diff --git a/libnetwork/endpoint.go b/libnetwork/endpoint.go index dc90596e44..f5868e8609 100644 --- a/libnetwork/endpoint.go +++ b/libnetwork/endpoint.go @@ -51,6 +51,9 @@ type Endpoint interface { // Delete and detaches this endpoint from the network. Delete() error + + // Retrieve the interfaces' statistics from the sandbox + Statistics() (map[string]*sandbox.InterfaceStatistics, error) } // EndpointOption is a option setter function type used to pass varios options to Network @@ -557,6 +560,33 @@ func (ep *endpoint) Delete() error { return nil } +func (ep *endpoint) Statistics() (map[string]*sandbox.InterfaceStatistics, error) { + m := make(map[string]*sandbox.InterfaceStatistics) + + ep.Lock() + n := ep.network + skey := ep.container.data.SandboxKey + ep.Unlock() + + n.Lock() + c := n.ctrlr + n.Unlock() + + sbox := c.sandboxGet(skey) + if sbox == nil { + return m, nil + } + + var err error + for _, i := range sbox.Info().Interfaces() { + if m[i.DstName()], err = i.Statistics(); err != nil { + return m, err + } + } + + return m, nil +} + func (ep *endpoint) deleteEndpoint() error { ep.Lock() n := ep.network diff --git a/libnetwork/libnetwork_test.go b/libnetwork/libnetwork_test.go index dbdac358b5..b890f07613 100644 --- a/libnetwork/libnetwork_test.go +++ b/libnetwork/libnetwork_test.go @@ -1042,6 +1042,15 @@ func TestEndpointJoin(t *testing.T) { t.Fatalf("Endpoint ContainerInfo returned unexpected id: %s", ep1.ContainerInfo().ID()) } + // Attempt retrieval of endpoint interfaces statistics + stats, err := ep1.Statistics() + if err != nil { + t.Fatal(err) + } + if _, ok := stats["eth0"]; !ok { + t.Fatalf("Did not find eth0 statistics") + } + // Now test the container joining another network n2, err := createTestNetwork(bridgeNetType, "testnetwork2", options.Generic{ diff --git a/libnetwork/sandbox/interface_linux.go b/libnetwork/sandbox/interface_linux.go index a89b3e7824..4bc6de57c1 100644 --- a/libnetwork/sandbox/interface_linux.go +++ b/libnetwork/sandbox/interface_linux.go @@ -2,7 +2,9 @@ package sandbox import ( "fmt" + "io/ioutil" "net" + "regexp" "sync" "github.com/docker/libnetwork/types" @@ -153,6 +155,33 @@ func (i *nwIface) Remove() error { }) } +// Returns the sandbox's side veth interface statistics +func (i *nwIface) Statistics() (*InterfaceStatistics, error) { + i.Lock() + n := i.ns + i.Unlock() + + n.Lock() + path := n.path + n.Unlock() + + s := &InterfaceStatistics{} + + err := nsInvoke(path, func(nsFD int) error { return nil }, func(callerFD int) error { + data, err := ioutil.ReadFile(netStatsFile) + if err != nil { + return fmt.Errorf("failed to open %s: %v", netStatsFile, err) + } + return scanInterfaceStats(string(data), i.DstName(), s) + }) + + if err != nil { + err = fmt.Errorf("failed to retrieve the statistics for %s in netns %s: %v", i.DstName(), path, err) + } + + return s, err +} + func (n *networkNamespace) findDst(srcName string, isBridge bool) string { n.Lock() defer n.Unlock() @@ -311,3 +340,28 @@ func setInterfaceRoutes(iface netlink.Link, i *nwIface) error { } return nil } + +// In older kernels (like the one in Centos 6.6 distro) sysctl does not have netns support. Therefore +// we cannot gather the statistics from /sys/class/net//statistics/ files. Per-netns stats +// are naturally found in /proc/net/dev in kernels which support netns (ifconfig relyes on that). +const ( + netStatsFile = "/proc/net/dev" + base = "[ ]*%s:([ ]+[0-9]+){16}" +) + +func scanInterfaceStats(data, ifName string, i *InterfaceStatistics) error { + var ( + bktStr string + bkt uint64 + ) + + regex := fmt.Sprintf(base, ifName) + re := regexp.MustCompile(regex) + line := re.FindString(data) + + _, err := fmt.Sscanf(line, "%s %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", + &bktStr, &i.RxBytes, &i.RxPackets, &i.RxErrors, &i.RxDropped, &bkt, &bkt, &bkt, + &bkt, &i.TxBytes, &i.TxPackets, &i.TxErrors, &i.TxDropped, &bkt, &bkt, &bkt, &bkt) + + return err +} diff --git a/libnetwork/sandbox/sandbox.go b/libnetwork/sandbox/sandbox.go index 13d940d7aa..f32bcfe30a 100644 --- a/libnetwork/sandbox/sandbox.go +++ b/libnetwork/sandbox/sandbox.go @@ -1,6 +1,7 @@ package sandbox import ( + "fmt" "net" "github.com/docker/libnetwork/types" @@ -146,4 +147,24 @@ type Interface interface { // Remove an interface from the sandbox by renaming to original name // and moving it out of the sandbox. Remove() error + + // Statistics returns the statistics for this interface + Statistics() (*InterfaceStatistics, error) +} + +// InterfaceStatistics represents the interface's statistics +type InterfaceStatistics struct { + RxBytes uint64 + RxPackets uint64 + RxErrors uint64 + RxDropped uint64 + TxBytes uint64 + TxPackets uint64 + TxErrors uint64 + TxDropped uint64 +} + +func (is *InterfaceStatistics) String() string { + return fmt.Sprintf("\nRxBytes: %d, RxPackets: %d, RxErrors: %d, RxDropped: %d, TxBytes: %d, TxPackets: %d, TxErrors: %d, TxDropped: %d", + is.RxBytes, is.RxPackets, is.RxErrors, is.RxDropped, is.TxBytes, is.TxPackets, is.TxErrors, is.TxDropped) } diff --git a/libnetwork/sandbox/sandbox_linux_test.go b/libnetwork/sandbox/sandbox_linux_test.go index 7f769c9978..e5ad326cf5 100644 --- a/libnetwork/sandbox/sandbox_linux_test.go +++ b/libnetwork/sandbox/sandbox_linux_test.go @@ -152,3 +152,29 @@ func verifyCleanup(t *testing.T, s Sandbox, wait bool) { } } } + +func TestScanStatistics(t *testing.T) { + data := + "Inter-| Receive | Transmit\n" + + " face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed\n" + + " eth0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" + + " wlan0: 7787685 11141 0 0 0 0 0 0 1681390 7220 0 0 0 0 0 0\n" + + " lo: 783782 1853 0 0 0 0 0 0 783782 1853 0 0 0 0 0 0\n" + + "lxcbr0: 0 0 0 0 0 0 0 0 9006 61 0 0 0 0 0 0\n" + + i := &InterfaceStatistics{} + + if err := scanInterfaceStats(data, "wlan0", i); err != nil { + t.Fatal(err) + } + if i.TxBytes != 1681390 || i.TxPackets != 7220 || i.RxBytes != 7787685 || i.RxPackets != 11141 { + t.Fatalf("Error scanning the statistics") + } + + if err := scanInterfaceStats(data, "lxcbr0", i); err != nil { + t.Fatal(err) + } + if i.TxBytes != 9006 || i.TxPackets != 61 || i.RxBytes != 0 || i.RxPackets != 0 { + t.Fatalf("Error scanning the statistics") + } +}