Signed-off-by: Alessandro Boch <aboch@docker.com>
| ... | ... |
@@ -6,6 +6,8 @@ import ( |
| 6 | 6 |
|
| 7 | 7 |
"github.com/docker/docker/api/types" |
| 8 | 8 |
"github.com/docker/docker/daemon/execdriver" |
| 9 |
+ "github.com/docker/libcontainer" |
|
| 10 |
+ "github.com/docker/libnetwork/sandbox" |
|
| 9 | 11 |
) |
| 10 | 12 |
|
| 11 | 13 |
type ContainerStatsConfig struct {
|
| ... | ... |
@@ -27,6 +29,10 @@ func (daemon *Daemon) ContainerStats(name string, config *ContainerStatsConfig) |
| 27 | 27 |
var preCpuStats types.CpuStats |
| 28 | 28 |
getStat := func(v interface{}) *types.Stats {
|
| 29 | 29 |
update := v.(*execdriver.ResourceStats) |
| 30 |
+ // Retrieve the nw statistics from libnetwork and inject them in the Stats |
|
| 31 |
+ if nwStats, err := daemon.getNetworkStats(name); err == nil {
|
|
| 32 |
+ update.Stats.Interfaces = nwStats |
|
| 33 |
+ } |
|
| 30 | 34 |
ss := convertStatsToAPITypes(update.Stats) |
| 31 | 35 |
ss.PreCpuStats = preCpuStats |
| 32 | 36 |
ss.MemoryStats.Limit = uint64(update.MemoryLimit) |
| ... | ... |
@@ -67,3 +73,46 @@ func (daemon *Daemon) ContainerStats(name string, config *ContainerStatsConfig) |
| 67 | 67 |
} |
| 68 | 68 |
} |
| 69 | 69 |
} |
| 70 |
+ |
|
| 71 |
+func (daemon *Daemon) getNetworkStats(name string) ([]*libcontainer.NetworkInterface, error) {
|
|
| 72 |
+ var list []*libcontainer.NetworkInterface |
|
| 73 |
+ |
|
| 74 |
+ c, err := daemon.Get(name) |
|
| 75 |
+ if err != nil {
|
|
| 76 |
+ return list, err |
|
| 77 |
+ } |
|
| 78 |
+ |
|
| 79 |
+ nw, err := daemon.netController.NetworkByID(c.NetworkSettings.NetworkID) |
|
| 80 |
+ if err != nil {
|
|
| 81 |
+ return list, err |
|
| 82 |
+ } |
|
| 83 |
+ ep, err := nw.EndpointByID(c.NetworkSettings.EndpointID) |
|
| 84 |
+ if err != nil {
|
|
| 85 |
+ return list, err |
|
| 86 |
+ } |
|
| 87 |
+ |
|
| 88 |
+ stats, err := ep.Statistics() |
|
| 89 |
+ if err != nil {
|
|
| 90 |
+ return list, err |
|
| 91 |
+ } |
|
| 92 |
+ |
|
| 93 |
+ // Convert libnetwork nw stats into libcontainer nw stats |
|
| 94 |
+ for ifName, ifStats := range stats {
|
|
| 95 |
+ list = append(list, convertLnNetworkStats(ifName, ifStats)) |
|
| 96 |
+ } |
|
| 97 |
+ |
|
| 98 |
+ return list, nil |
|
| 99 |
+} |
|
| 100 |
+ |
|
| 101 |
+func convertLnNetworkStats(name string, stats *sandbox.InterfaceStatistics) *libcontainer.NetworkInterface {
|
|
| 102 |
+ n := &libcontainer.NetworkInterface{Name: name}
|
|
| 103 |
+ n.RxBytes = stats.RxBytes |
|
| 104 |
+ n.RxPackets = stats.RxPackets |
|
| 105 |
+ n.RxErrors = stats.RxErrors |
|
| 106 |
+ n.RxDropped = stats.RxDropped |
|
| 107 |
+ n.TxBytes = stats.TxBytes |
|
| 108 |
+ n.TxPackets = stats.TxPackets |
|
| 109 |
+ n.TxErrors = stats.TxErrors |
|
| 110 |
+ n.TxDropped = stats.TxDropped |
|
| 111 |
+ return n |
|
| 112 |
+} |
| ... | ... |
@@ -3,6 +3,8 @@ package main |
| 3 | 3 |
import ( |
| 4 | 4 |
"encoding/json" |
| 5 | 5 |
"fmt" |
| 6 |
+ "os/exec" |
|
| 7 |
+ "strconv" |
|
| 6 | 8 |
"strings" |
| 7 | 9 |
"time" |
| 8 | 10 |
|
| ... | ... |
@@ -69,3 +71,41 @@ func (s *DockerSuite) TestStoppedContainerStatsGoroutines(c *check.C) {
|
| 69 | 69 |
} |
| 70 | 70 |
} |
| 71 | 71 |
} |
| 72 |
+ |
|
| 73 |
+func (s *DockerSuite) TestApiNetworkStats(c *check.C) {
|
|
| 74 |
+ // Run container for 30 secs |
|
| 75 |
+ out, _ := dockerCmd(c, "run", "-d", "busybox", "top") |
|
| 76 |
+ id := strings.TrimSpace(out) |
|
| 77 |
+ err := waitRun(id) |
|
| 78 |
+ c.Assert(err, check.IsNil) |
|
| 79 |
+ |
|
| 80 |
+ // Retrieve the container address |
|
| 81 |
+ contIP := findContainerIP(c, id) |
|
| 82 |
+ numPings := 10 |
|
| 83 |
+ |
|
| 84 |
+ // Get the container networking stats before and after pinging the container |
|
| 85 |
+ nwStatsPre := getNetworkStats(c, id) |
|
| 86 |
+ _, err = exec.Command("ping", contIP, "-c", strconv.Itoa(numPings)).Output()
|
|
| 87 |
+ c.Assert(err, check.IsNil) |
|
| 88 |
+ nwStatsPost := getNetworkStats(c, id) |
|
| 89 |
+ |
|
| 90 |
+ // Verify the stats contain at least the expected number of packets (account for ARP) |
|
| 91 |
+ expRxPkts := 1 + nwStatsPre.RxPackets + uint64(numPings) |
|
| 92 |
+ expTxPkts := 1 + nwStatsPre.TxPackets + uint64(numPings) |
|
| 93 |
+ c.Assert(nwStatsPost.TxPackets >= expTxPkts, check.Equals, true, |
|
| 94 |
+ check.Commentf("Reported less TxPackets than expected. Expected >= %d. Found %d", expTxPkts, nwStatsPost.TxPackets))
|
|
| 95 |
+ c.Assert(nwStatsPost.RxPackets >= expRxPkts, check.Equals, true, |
|
| 96 |
+ check.Commentf("Reported less Txbytes than expected. Expected >= %d. Found %d", expRxPkts, nwStatsPost.RxPackets))
|
|
| 97 |
+} |
|
| 98 |
+ |
|
| 99 |
+func getNetworkStats(c *check.C, id string) types.Network {
|
|
| 100 |
+ var st *types.Stats |
|
| 101 |
+ |
|
| 102 |
+ _, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/stats?stream=false", id), nil, "")
|
|
| 103 |
+ c.Assert(err, check.IsNil) |
|
| 104 |
+ |
|
| 105 |
+ err = json.NewDecoder(body).Decode(&st) |
|
| 106 |
+ c.Assert(err, check.IsNil) |
|
| 107 |
+ |
|
| 108 |
+ return st.Network |
|
| 109 |
+} |