diff --git a/daemon/stats.go b/daemon/stats.go index 646d7820bb..a444c6027b 100644 --- a/daemon/stats.go +++ b/daemon/stats.go @@ -6,6 +6,8 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/daemon/execdriver" + "github.com/docker/libcontainer" + "github.com/docker/libnetwork/sandbox" ) type ContainerStatsConfig struct { @@ -27,6 +29,10 @@ func (daemon *Daemon) ContainerStats(name string, config *ContainerStatsConfig) var preCpuStats types.CpuStats getStat := func(v interface{}) *types.Stats { update := v.(*execdriver.ResourceStats) + // Retrieve the nw statistics from libnetwork and inject them in the Stats + if nwStats, err := daemon.getNetworkStats(name); err == nil { + update.Stats.Interfaces = nwStats + } ss := convertStatsToAPITypes(update.Stats) ss.PreCpuStats = preCpuStats ss.MemoryStats.Limit = uint64(update.MemoryLimit) @@ -67,3 +73,46 @@ func (daemon *Daemon) ContainerStats(name string, config *ContainerStatsConfig) } } } + +func (daemon *Daemon) getNetworkStats(name string) ([]*libcontainer.NetworkInterface, error) { + var list []*libcontainer.NetworkInterface + + c, err := daemon.Get(name) + if err != nil { + return list, err + } + + nw, err := daemon.netController.NetworkByID(c.NetworkSettings.NetworkID) + if err != nil { + return list, err + } + ep, err := nw.EndpointByID(c.NetworkSettings.EndpointID) + if err != nil { + return list, err + } + + stats, err := ep.Statistics() + if err != nil { + return list, err + } + + // Convert libnetwork nw stats into libcontainer nw stats + for ifName, ifStats := range stats { + list = append(list, convertLnNetworkStats(ifName, ifStats)) + } + + return list, nil +} + +func convertLnNetworkStats(name string, stats *sandbox.InterfaceStatistics) *libcontainer.NetworkInterface { + n := &libcontainer.NetworkInterface{Name: name} + n.RxBytes = stats.RxBytes + n.RxPackets = stats.RxPackets + n.RxErrors = stats.RxErrors + n.RxDropped = stats.RxDropped + n.TxBytes = stats.TxBytes + n.TxPackets = stats.TxPackets + n.TxErrors = stats.TxErrors + n.TxDropped = stats.TxDropped + return n +} diff --git a/integration-cli/docker_api_stats_test.go b/integration-cli/docker_api_stats_test.go index 1d5d563c18..9f83f87e3a 100644 --- a/integration-cli/docker_api_stats_test.go +++ b/integration-cli/docker_api_stats_test.go @@ -3,6 +3,8 @@ package main import ( "encoding/json" "fmt" + "os/exec" + "strconv" "strings" "time" @@ -69,3 +71,41 @@ func (s *DockerSuite) TestStoppedContainerStatsGoroutines(c *check.C) { } } } + +func (s *DockerSuite) TestApiNetworkStats(c *check.C) { + // Run container for 30 secs + out, _ := dockerCmd(c, "run", "-d", "busybox", "top") + id := strings.TrimSpace(out) + err := waitRun(id) + c.Assert(err, check.IsNil) + + // Retrieve the container address + contIP := findContainerIP(c, id) + numPings := 10 + + // Get the container networking stats before and after pinging the container + nwStatsPre := getNetworkStats(c, id) + _, err = exec.Command("ping", contIP, "-c", strconv.Itoa(numPings)).Output() + c.Assert(err, check.IsNil) + nwStatsPost := getNetworkStats(c, id) + + // Verify the stats contain at least the expected number of packets (account for ARP) + expRxPkts := 1 + nwStatsPre.RxPackets + uint64(numPings) + expTxPkts := 1 + nwStatsPre.TxPackets + uint64(numPings) + c.Assert(nwStatsPost.TxPackets >= expTxPkts, check.Equals, true, + check.Commentf("Reported less TxPackets than expected. Expected >= %d. Found %d", expTxPkts, nwStatsPost.TxPackets)) + c.Assert(nwStatsPost.RxPackets >= expRxPkts, check.Equals, true, + check.Commentf("Reported less Txbytes than expected. Expected >= %d. Found %d", expRxPkts, nwStatsPost.RxPackets)) +} + +func getNetworkStats(c *check.C, id string) types.Network { + var st *types.Stats + + _, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/stats?stream=false", id), nil, "") + c.Assert(err, check.IsNil) + + err = json.NewDecoder(body).Decode(&st) + c.Assert(err, check.IsNil) + + return st.Network +}