For now docker stats will sum the rxbytes, txbytes, etc. of all
the interfaces.
It is OK for the output of CLI `docker stats` but not good for
the API response, especially when the container is in sereval
subnets.
It's better to leave these origianl data to user.
Signed-off-by: Hu Keping <hukeping@huawei.com>
| ... | ... |
@@ -56,7 +56,7 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
| 56 | 56 |
) |
| 57 | 57 |
go func() {
|
| 58 | 58 |
for {
|
| 59 |
- var v *types.Stats |
|
| 59 |
+ var v *types.StatsJSON |
|
| 60 | 60 |
if err := dec.Decode(&v); err != nil {
|
| 61 | 61 |
u <- err |
| 62 | 62 |
return |
| ... | ... |
@@ -80,8 +80,7 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
| 80 | 80 |
s.Memory = float64(v.MemoryStats.Usage) |
| 81 | 81 |
s.MemoryLimit = float64(v.MemoryStats.Limit) |
| 82 | 82 |
s.MemoryPercentage = memPercent |
| 83 |
- s.NetworkRx = float64(v.Network.RxBytes) |
|
| 84 |
- s.NetworkTx = float64(v.Network.TxBytes) |
|
| 83 |
+ s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks) |
|
| 85 | 84 |
s.BlockRead = float64(blkRead) |
| 86 | 85 |
s.BlockWrite = float64(blkWrite) |
| 87 | 86 |
s.mu.Unlock() |
| ... | ... |
@@ -198,7 +197,7 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
| 198 | 198 |
return nil |
| 199 | 199 |
} |
| 200 | 200 |
|
| 201 |
-func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.Stats) float64 {
|
|
| 201 |
+func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
|
|
| 202 | 202 |
var ( |
| 203 | 203 |
cpuPercent = 0.0 |
| 204 | 204 |
// calculate the change for the cpu usage of the container in between readings |
| ... | ... |
@@ -224,3 +223,13 @@ func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) |
| 224 | 224 |
} |
| 225 | 225 |
return |
| 226 | 226 |
} |
| 227 |
+ |
|
| 228 |
+func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
|
|
| 229 |
+ var rx, tx float64 |
|
| 230 |
+ |
|
| 231 |
+ for _, v := range network {
|
|
| 232 |
+ rx += float64(v.RxBytes) |
|
| 233 |
+ tx += float64(v.TxBytes) |
|
| 234 |
+ } |
|
| 235 |
+ return rx, tx |
|
| 236 |
+} |
| ... | ... |
@@ -17,6 +17,7 @@ import ( |
| 17 | 17 |
"github.com/docker/docker/daemon" |
| 18 | 18 |
"github.com/docker/docker/pkg/ioutils" |
| 19 | 19 |
"github.com/docker/docker/pkg/signal" |
| 20 |
+ "github.com/docker/docker/pkg/version" |
|
| 20 | 21 |
"github.com/docker/docker/runconfig" |
| 21 | 22 |
) |
| 22 | 23 |
|
| ... | ... |
@@ -81,10 +82,12 @@ func (s *Server) getContainersStats(ctx context.Context, w http.ResponseWriter, |
| 81 | 81 |
closeNotifier = notifier.CloseNotify() |
| 82 | 82 |
} |
| 83 | 83 |
|
| 84 |
+ version, _ := ctx.Value("api-version").(version.Version)
|
|
| 84 | 85 |
config := &daemon.ContainerStatsConfig{
|
| 85 | 86 |
Stream: stream, |
| 86 | 87 |
OutStream: out, |
| 87 | 88 |
Stop: closeNotifier, |
| 89 |
+ Version: version, |
|
| 88 | 90 |
} |
| 89 | 91 |
|
| 90 | 92 |
return s.daemon.ContainerStats(container, config) |
| ... | ... |
@@ -89,10 +89,25 @@ type NetworkStats struct {
|
| 89 | 89 |
|
| 90 | 90 |
// Stats is Ultimate struct aggregating all types of stats of one container |
| 91 | 91 |
type Stats struct {
|
| 92 |
- Read time.Time `json:"read"` |
|
| 93 |
- Network NetworkStats `json:"network,omitempty"` |
|
| 94 |
- PreCPUStats CPUStats `json:"precpu_stats,omitempty"` |
|
| 95 |
- CPUStats CPUStats `json:"cpu_stats,omitempty"` |
|
| 96 |
- MemoryStats MemoryStats `json:"memory_stats,omitempty"` |
|
| 97 |
- BlkioStats BlkioStats `json:"blkio_stats,omitempty"` |
|
| 92 |
+ Read time.Time `json:"read"` |
|
| 93 |
+ PreCPUStats CPUStats `json:"precpu_stats,omitempty"` |
|
| 94 |
+ CPUStats CPUStats `json:"cpu_stats,omitempty"` |
|
| 95 |
+ MemoryStats MemoryStats `json:"memory_stats,omitempty"` |
|
| 96 |
+ BlkioStats BlkioStats `json:"blkio_stats,omitempty"` |
|
| 97 |
+} |
|
| 98 |
+ |
|
| 99 |
+// StatsJSONPre121 is a backcompatibility struct along with ContainerConfig |
|
| 100 |
+type StatsJSONPre121 struct {
|
|
| 101 |
+ Stats |
|
| 102 |
+ |
|
| 103 |
+ // Network is for fallback stats where API Version < 1.21 |
|
| 104 |
+ Network NetworkStats `json:"network,omitempty"` |
|
| 105 |
+} |
|
| 106 |
+ |
|
| 107 |
+// StatsJSON is newly used Networks |
|
| 108 |
+type StatsJSON struct {
|
|
| 109 |
+ Stats |
|
| 110 |
+ |
|
| 111 |
+ // Networks request version >=1.21 |
|
| 112 |
+ Networks map[string]NetworkStats `json:"networks,omitempty"` |
|
| 98 | 113 |
} |
| ... | ... |
@@ -6,6 +6,7 @@ import ( |
| 6 | 6 |
|
| 7 | 7 |
"github.com/docker/docker/api/types" |
| 8 | 8 |
"github.com/docker/docker/daemon/execdriver" |
| 9 |
+ "github.com/docker/docker/pkg/version" |
|
| 9 | 10 |
"github.com/docker/libnetwork/osl" |
| 10 | 11 |
"github.com/opencontainers/runc/libcontainer" |
| 11 | 12 |
) |
| ... | ... |
@@ -16,6 +17,7 @@ type ContainerStatsConfig struct {
|
| 16 | 16 |
Stream bool |
| 17 | 17 |
OutStream io.Writer |
| 18 | 18 |
Stop <-chan bool |
| 19 |
+ Version version.Version |
|
| 19 | 20 |
} |
| 20 | 21 |
|
| 21 | 22 |
// ContainerStats writes information about the container to the stream |
| ... | ... |
@@ -31,7 +33,7 @@ func (daemon *Daemon) ContainerStats(container *Container, config *ContainerStat |
| 31 | 31 |
} |
| 32 | 32 |
|
| 33 | 33 |
var preCPUStats types.CPUStats |
| 34 |
- getStat := func(v interface{}) *types.Stats {
|
|
| 34 |
+ getStatJSON := func(v interface{}) *types.StatsJSON {
|
|
| 35 | 35 |
update := v.(*execdriver.ResourceStats) |
| 36 | 36 |
// Retrieve the nw statistics from libnetwork and inject them in the Stats |
| 37 | 37 |
if nwStats, err := daemon.getNetworkStats(container); err == nil {
|
| ... | ... |
@@ -58,14 +60,64 @@ func (daemon *Daemon) ContainerStats(container *Container, config *ContainerStat |
| 58 | 58 |
return nil |
| 59 | 59 |
} |
| 60 | 60 |
|
| 61 |
- s := getStat(v) |
|
| 61 |
+ statsJSON := getStatJSON(v) |
|
| 62 |
+ if config.Version.LessThan("1.21") {
|
|
| 63 |
+ var ( |
|
| 64 |
+ rxBytes uint64 |
|
| 65 |
+ rxPackets uint64 |
|
| 66 |
+ rxErrors uint64 |
|
| 67 |
+ rxDropped uint64 |
|
| 68 |
+ txBytes uint64 |
|
| 69 |
+ txPackets uint64 |
|
| 70 |
+ txErrors uint64 |
|
| 71 |
+ txDropped uint64 |
|
| 72 |
+ ) |
|
| 73 |
+ for _, v := range statsJSON.Networks {
|
|
| 74 |
+ rxBytes += v.RxBytes |
|
| 75 |
+ rxPackets += v.RxPackets |
|
| 76 |
+ rxErrors += v.RxErrors |
|
| 77 |
+ rxDropped += v.RxDropped |
|
| 78 |
+ txBytes += v.TxBytes |
|
| 79 |
+ txPackets += v.TxPackets |
|
| 80 |
+ txErrors += v.TxErrors |
|
| 81 |
+ txDropped += v.TxDropped |
|
| 82 |
+ } |
|
| 83 |
+ statsJSONPre121 := &types.StatsJSONPre121{
|
|
| 84 |
+ Stats: statsJSON.Stats, |
|
| 85 |
+ Network: types.NetworkStats{
|
|
| 86 |
+ RxBytes: rxBytes, |
|
| 87 |
+ RxPackets: rxPackets, |
|
| 88 |
+ RxErrors: rxErrors, |
|
| 89 |
+ RxDropped: rxDropped, |
|
| 90 |
+ TxBytes: txBytes, |
|
| 91 |
+ TxPackets: txPackets, |
|
| 92 |
+ TxErrors: txErrors, |
|
| 93 |
+ TxDropped: txDropped, |
|
| 94 |
+ }, |
|
| 95 |
+ } |
|
| 96 |
+ |
|
| 97 |
+ if !config.Stream && noStreamFirstFrame {
|
|
| 98 |
+ // prime the cpu stats so they aren't 0 in the final output |
|
| 99 |
+ noStreamFirstFrame = false |
|
| 100 |
+ continue |
|
| 101 |
+ } |
|
| 102 |
+ |
|
| 103 |
+ if err := enc.Encode(statsJSONPre121); err != nil {
|
|
| 104 |
+ return err |
|
| 105 |
+ } |
|
| 106 |
+ |
|
| 107 |
+ if !config.Stream {
|
|
| 108 |
+ return nil |
|
| 109 |
+ } |
|
| 110 |
+ } |
|
| 111 |
+ |
|
| 62 | 112 |
if !config.Stream && noStreamFirstFrame {
|
| 63 | 113 |
// prime the cpu stats so they aren't 0 in the final output |
| 64 | 114 |
noStreamFirstFrame = false |
| 65 | 115 |
continue |
| 66 | 116 |
} |
| 67 | 117 |
|
| 68 |
- if err := enc.Encode(s); err != nil {
|
|
| 118 |
+ if err := enc.Encode(statsJSON); err != nil {
|
|
| 69 | 119 |
return err |
| 70 | 120 |
} |
| 71 | 121 |
|
| ... | ... |
@@ -6,9 +6,9 @@ import ( |
| 6 | 6 |
) |
| 7 | 7 |
|
| 8 | 8 |
// convertStatsToAPITypes converts the libcontainer.Stats to the api specific |
| 9 |
-// structs. This is done to preserve API compatibility and versioning. |
|
| 10 |
-func convertStatsToAPITypes(ls *libcontainer.Stats) *types.Stats {
|
|
| 9 |
+// structs. This is done to preserve API compatibility and versioning. |
|
| 10 |
+func convertStatsToAPITypes(ls *libcontainer.Stats) *types.StatsJSON {
|
|
| 11 | 11 |
// TODO FreeBSD. Refactor accordingly to fill in stats. |
| 12 |
- s := &types.Stats{}
|
|
| 12 |
+ s := &types.StatsJSON{}
|
|
| 13 | 13 |
return s |
| 14 | 14 |
} |
| ... | ... |
@@ -7,20 +7,24 @@ import ( |
| 7 | 7 |
) |
| 8 | 8 |
|
| 9 | 9 |
// convertStatsToAPITypes converts the libcontainer.Stats to the api specific |
| 10 |
-// structs. This is done to preserve API compatibility and versioning. |
|
| 11 |
-func convertStatsToAPITypes(ls *libcontainer.Stats) *types.Stats {
|
|
| 12 |
- s := &types.Stats{}
|
|
| 10 |
+// structs. This is done to preserve API compatibility and versioning. |
|
| 11 |
+func convertStatsToAPITypes(ls *libcontainer.Stats) *types.StatsJSON {
|
|
| 12 |
+ s := &types.StatsJSON{}
|
|
| 13 | 13 |
if ls.Interfaces != nil {
|
| 14 |
- s.Network = types.NetworkStats{}
|
|
| 14 |
+ s.Networks = make(map[string]types.NetworkStats) |
|
| 15 | 15 |
for _, iface := range ls.Interfaces {
|
| 16 |
- s.Network.RxBytes += iface.RxBytes |
|
| 17 |
- s.Network.RxPackets += iface.RxPackets |
|
| 18 |
- s.Network.RxErrors += iface.RxErrors |
|
| 19 |
- s.Network.RxDropped += iface.RxDropped |
|
| 20 |
- s.Network.TxBytes += iface.TxBytes |
|
| 21 |
- s.Network.TxPackets += iface.TxPackets |
|
| 22 |
- s.Network.TxErrors += iface.TxErrors |
|
| 23 |
- s.Network.TxDropped += iface.TxDropped |
|
| 16 |
+ // For API Version >= 1.21, the original data of network will |
|
| 17 |
+ // be returned. |
|
| 18 |
+ s.Networks[iface.Name] = types.NetworkStats{
|
|
| 19 |
+ RxBytes: iface.RxBytes, |
|
| 20 |
+ RxPackets: iface.RxPackets, |
|
| 21 |
+ RxErrors: iface.RxErrors, |
|
| 22 |
+ RxDropped: iface.RxDropped, |
|
| 23 |
+ TxBytes: iface.TxBytes, |
|
| 24 |
+ TxPackets: iface.TxPackets, |
|
| 25 |
+ TxErrors: iface.TxErrors, |
|
| 26 |
+ TxDropped: iface.TxDropped, |
|
| 27 |
+ } |
|
| 24 | 28 |
} |
| 25 | 29 |
} |
| 26 | 30 |
|
| ... | ... |
@@ -6,9 +6,9 @@ import ( |
| 6 | 6 |
) |
| 7 | 7 |
|
| 8 | 8 |
// convertStatsToAPITypes converts the libcontainer.Stats to the api specific |
| 9 |
-// structs. This is done to preserve API compatibility and versioning. |
|
| 10 |
-func convertStatsToAPITypes(ls *libcontainer.Stats) *types.Stats {
|
|
| 9 |
+// structs. This is done to preserve API compatibility and versioning. |
|
| 10 |
+func convertStatsToAPITypes(ls *libcontainer.Stats) *types.StatsJSON {
|
|
| 11 | 11 |
// TODO Windows. Refactor accordingly to fill in stats. |
| 12 |
- s := &types.Stats{}
|
|
| 12 |
+ s := &types.StatsJSON{}
|
|
| 13 | 13 |
return s |
| 14 | 14 |
} |
| ... | ... |
@@ -83,6 +83,7 @@ This section lists each version from latest to oldest. Each listing includes a |
| 83 | 83 |
* `VolumeDriver` has been moved from config to hostConfig to make the configuration portable. |
| 84 | 84 |
* `GET /images/(name)/json` now returns information about tags of the image. |
| 85 | 85 |
* The `config` option now accepts the field `StopSignal`, which specifies the signal to use to kill a container. |
| 86 |
+* `GET /containers/(id)/stats` will return networking information respectively for each interface. |
|
| 86 | 87 |
|
| 87 | 88 |
|
| 88 | 89 |
### v1.20 API changes |
| ... | ... |
@@ -631,15 +631,27 @@ This endpoint returns a live stream of a container's resource usage statistics. |
| 631 | 631 |
|
| 632 | 632 |
{
|
| 633 | 633 |
"read" : "2015-01-08T22:57:31.547920715Z", |
| 634 |
- "network" : {
|
|
| 635 |
- "rx_dropped" : 0, |
|
| 636 |
- "rx_bytes" : 648, |
|
| 637 |
- "rx_errors" : 0, |
|
| 638 |
- "tx_packets" : 8, |
|
| 639 |
- "tx_dropped" : 0, |
|
| 640 |
- "rx_packets" : 8, |
|
| 641 |
- "tx_errors" : 0, |
|
| 642 |
- "tx_bytes" : 648 |
|
| 634 |
+ "network": {
|
|
| 635 |
+ "eth0": {
|
|
| 636 |
+ "rx_bytes": 5338, |
|
| 637 |
+ "rx_dropped": 0, |
|
| 638 |
+ "rx_errors": 0, |
|
| 639 |
+ "rx_packets": 36, |
|
| 640 |
+ "tx_bytes": 648, |
|
| 641 |
+ "tx_dropped": 0, |
|
| 642 |
+ "tx_errors": 0, |
|
| 643 |
+ "tx_packets": 8 |
|
| 644 |
+ }, |
|
| 645 |
+ "eth5": {
|
|
| 646 |
+ "rx_bytes": 4641, |
|
| 647 |
+ "rx_dropped": 0, |
|
| 648 |
+ "rx_errors": 0, |
|
| 649 |
+ "rx_packets": 26, |
|
| 650 |
+ "tx_bytes": 690, |
|
| 651 |
+ "tx_dropped": 0, |
|
| 652 |
+ "tx_errors": 0, |
|
| 653 |
+ "tx_packets": 9 |
|
| 654 |
+ } |
|
| 643 | 655 |
}, |
| 644 | 656 |
"memory_stats" : {
|
| 645 | 657 |
"stats" : {
|
| ... | ... |
@@ -87,8 +87,18 @@ func (s *DockerSuite) TestApiNetworkStats(c *check.C) {
|
| 87 | 87 |
contIP := findContainerIP(c, id) |
| 88 | 88 |
numPings := 10 |
| 89 | 89 |
|
| 90 |
+ var preRxPackets uint64 |
|
| 91 |
+ var preTxPackets uint64 |
|
| 92 |
+ var postRxPackets uint64 |
|
| 93 |
+ var postTxPackets uint64 |
|
| 94 |
+ |
|
| 90 | 95 |
// Get the container networking stats before and after pinging the container |
| 91 | 96 |
nwStatsPre := getNetworkStats(c, id) |
| 97 |
+ for _, v := range nwStatsPre {
|
|
| 98 |
+ preRxPackets += v.RxPackets |
|
| 99 |
+ preTxPackets += v.TxPackets |
|
| 100 |
+ } |
|
| 101 |
+ |
|
| 92 | 102 |
countParam := "-c" |
| 93 | 103 |
if runtime.GOOS == "windows" {
|
| 94 | 104 |
countParam = "-n" // Ping count parameter is -n on Windows |
| ... | ... |
@@ -97,18 +107,22 @@ func (s *DockerSuite) TestApiNetworkStats(c *check.C) {
|
| 97 | 97 |
pingouts := string(pingout[:]) |
| 98 | 98 |
c.Assert(err, check.IsNil) |
| 99 | 99 |
nwStatsPost := getNetworkStats(c, id) |
| 100 |
+ for _, v := range nwStatsPost {
|
|
| 101 |
+ postRxPackets += v.RxPackets |
|
| 102 |
+ postTxPackets += v.TxPackets |
|
| 103 |
+ } |
|
| 100 | 104 |
|
| 101 | 105 |
// Verify the stats contain at least the expected number of packets (account for ARP) |
| 102 |
- expRxPkts := 1 + nwStatsPre.RxPackets + uint64(numPings) |
|
| 103 |
- expTxPkts := 1 + nwStatsPre.TxPackets + uint64(numPings) |
|
| 104 |
- c.Assert(nwStatsPost.TxPackets >= expTxPkts, check.Equals, true, |
|
| 105 |
- check.Commentf("Reported less TxPackets than expected. Expected >= %d. Found %d. %s", expTxPkts, nwStatsPost.TxPackets, pingouts))
|
|
| 106 |
- c.Assert(nwStatsPost.RxPackets >= expRxPkts, check.Equals, true, |
|
| 107 |
- check.Commentf("Reported less Txbytes than expected. Expected >= %d. Found %d. %s", expRxPkts, nwStatsPost.RxPackets, pingouts))
|
|
| 106 |
+ expRxPkts := 1 + preRxPackets + uint64(numPings) |
|
| 107 |
+ expTxPkts := 1 + preTxPackets + uint64(numPings) |
|
| 108 |
+ c.Assert(postTxPackets >= expTxPkts, check.Equals, true, |
|
| 109 |
+ check.Commentf("Reported less TxPackets than expected. Expected >= %d. Found %d. %s", expTxPkts, postTxPackets, pingouts))
|
|
| 110 |
+ c.Assert(postRxPackets >= expRxPkts, check.Equals, true, |
|
| 111 |
+ check.Commentf("Reported less Txbytes than expected. Expected >= %d. Found %d. %s", expRxPkts, postRxPackets, pingouts))
|
|
| 108 | 112 |
} |
| 109 | 113 |
|
| 110 |
-func getNetworkStats(c *check.C, id string) types.NetworkStats {
|
|
| 111 |
- var st *types.Stats |
|
| 114 |
+func getNetworkStats(c *check.C, id string) map[string]types.NetworkStats {
|
|
| 115 |
+ var st *types.StatsJSON |
|
| 112 | 116 |
|
| 113 | 117 |
_, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/stats?stream=false", id), nil, "")
|
| 114 | 118 |
c.Assert(err, check.IsNil) |
| ... | ... |
@@ -117,5 +131,5 @@ func getNetworkStats(c *check.C, id string) types.NetworkStats {
|
| 117 | 117 |
c.Assert(err, check.IsNil) |
| 118 | 118 |
body.Close() |
| 119 | 119 |
|
| 120 |
- return st.Network |
|
| 120 |
+ return st.Networks |
|
| 121 | 121 |
} |