From 908ab87a159e8893d8a9f15d34d2cb0e2aeceb5e Mon Sep 17 00:00:00 2001 From: Alessandro Boch Date: Wed, 1 Jul 2015 09:54:26 -0700 Subject: [PATCH] Stats API to retrieve nw stats from libnetwork - Container networking statistics are no longer retrievable from libcontainer after the introduction of libnetwork. This change adds the missing code for docker daemon to retireve the nw stats from Endpoint. Signed-off-by: Alessandro Boch Upstream-commit: 8b40e44c395fb307cceb6f179ffe02030b316535 Component: engine --- components/engine/daemon/stats.go | 49 +++++++++++++++++++ .../integration-cli/docker_api_stats_test.go | 40 +++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/components/engine/daemon/stats.go b/components/engine/daemon/stats.go index 646d7820bb..a444c6027b 100644 --- a/components/engine/daemon/stats.go +++ b/components/engine/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/components/engine/integration-cli/docker_api_stats_test.go b/components/engine/integration-cli/docker_api_stats_test.go index 1d5d563c18..9f83f87e3a 100644 --- a/components/engine/integration-cli/docker_api_stats_test.go +++ b/components/engine/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 +}