Endpoint to expose interfaces' statistics

Signed-off-by: Alessandro Boch <aboch@docker.com>
This commit is contained in:
Alessandro Boch 2015-06-29 13:32:07 -07:00
parent 88f16a11d2
commit 5ac330aca2
5 changed files with 140 additions and 0 deletions

View File

@ -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

View File

@ -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{

View File

@ -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/<dev>/statistics/<counter> 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
}

View File

@ -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)
}

View File

@ -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")
}
}