Skip to content

Commit d337994

Browse files
committed
Refactor the statistics of network in docker stats
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 <[email protected]>
1 parent 58d6919 commit d337994

10 files changed

Lines changed: 159 additions & 49 deletions

File tree

api/client/stats.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
5656
)
5757
go func() {
5858
for {
59-
var v *types.Stats
59+
var v *types.StatsJSON
6060
if err := dec.Decode(&v); err != nil {
6161
u <- err
6262
return
@@ -80,8 +80,7 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
8080
s.Memory = float64(v.MemoryStats.Usage)
8181
s.MemoryLimit = float64(v.MemoryStats.Limit)
8282
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)
8584
s.BlockRead = float64(blkRead)
8685
s.BlockWrite = float64(blkWrite)
8786
s.mu.Unlock()
@@ -198,7 +197,7 @@ func (cli *DockerCli) CmdStats(args ...string) error {
198197
return nil
199198
}
200199

201-
func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.Stats) float64 {
200+
func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
202201
var (
203202
cpuPercent = 0.0
204203
// 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)
224223
}
225224
return
226225
}
226+
227+
func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
228+
var rx, tx float64
229+
230+
for _, v := range network {
231+
rx += float64(v.RxBytes)
232+
tx += float64(v.TxBytes)
233+
}
234+
return rx, tx
235+
}

api/server/container.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/docker/docker/daemon"
1818
"github.com/docker/docker/pkg/ioutils"
1919
"github.com/docker/docker/pkg/signal"
20+
"github.com/docker/docker/pkg/version"
2021
"github.com/docker/docker/runconfig"
2122
)
2223

@@ -81,10 +82,12 @@ func (s *Server) getContainersStats(ctx context.Context, w http.ResponseWriter,
8182
closeNotifier = notifier.CloseNotify()
8283
}
8384

85+
version, _ := ctx.Value("api-version").(version.Version)
8486
config := &daemon.ContainerStatsConfig{
8587
Stream: stream,
8688
OutStream: out,
8789
Stop: closeNotifier,
90+
Version: version,
8891
}
8992

9093
return s.daemon.ContainerStats(container, config)

api/types/stats.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,25 @@ type NetworkStats struct {
8989

9090
// Stats is Ultimate struct aggregating all types of stats of one container
9191
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"`
98113
}

daemon/stats.go

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/docker/docker/api/types"
88
"github.com/docker/docker/daemon/execdriver"
9+
"github.com/docker/docker/pkg/version"
910
"github.com/docker/libnetwork/osl"
1011
"github.com/opencontainers/runc/libcontainer"
1112
)
@@ -16,6 +17,7 @@ type ContainerStatsConfig struct {
1617
Stream bool
1718
OutStream io.Writer
1819
Stop <-chan bool
20+
Version version.Version
1921
}
2022

2123
// ContainerStats writes information about the container to the stream
@@ -31,7 +33,7 @@ func (daemon *Daemon) ContainerStats(container *Container, config *ContainerStat
3133
}
3234

3335
var preCPUStats types.CPUStats
34-
getStat := func(v interface{}) *types.Stats {
36+
getStatJSON := func(v interface{}) *types.StatsJSON {
3537
update := v.(*execdriver.ResourceStats)
3638
// Retrieve the nw statistics from libnetwork and inject them in the Stats
3739
if nwStats, err := daemon.getNetworkStats(container); err == nil {
@@ -58,14 +60,64 @@ func (daemon *Daemon) ContainerStats(container *Container, config *ContainerStat
5860
return nil
5961
}
6062

61-
s := getStat(v)
63+
statsJSON := getStatJSON(v)
64+
if config.Version.LessThan("1.21") {
65+
var (
66+
rxBytes uint64
67+
rxPackets uint64
68+
rxErrors uint64
69+
rxDropped uint64
70+
txBytes uint64
71+
txPackets uint64
72+
txErrors uint64
73+
txDropped uint64
74+
)
75+
for _, v := range statsJSON.Networks {
76+
rxBytes += v.RxBytes
77+
rxPackets += v.RxPackets
78+
rxErrors += v.RxErrors
79+
rxDropped += v.RxDropped
80+
txBytes += v.TxBytes
81+
txPackets += v.TxPackets
82+
txErrors += v.TxErrors
83+
txDropped += v.TxDropped
84+
}
85+
statsJSONPre121 := &types.StatsJSONPre121{
86+
Stats: statsJSON.Stats,
87+
Network: types.NetworkStats{
88+
RxBytes: rxBytes,
89+
RxPackets: rxPackets,
90+
RxErrors: rxErrors,
91+
RxDropped: rxDropped,
92+
TxBytes: txBytes,
93+
TxPackets: txPackets,
94+
TxErrors: txErrors,
95+
TxDropped: txDropped,
96+
},
97+
}
98+
99+
if !config.Stream && noStreamFirstFrame {
100+
// prime the cpu stats so they aren't 0 in the final output
101+
noStreamFirstFrame = false
102+
continue
103+
}
104+
105+
if err := enc.Encode(statsJSONPre121); err != nil {
106+
return err
107+
}
108+
109+
if !config.Stream {
110+
return nil
111+
}
112+
}
113+
62114
if !config.Stream && noStreamFirstFrame {
63115
// prime the cpu stats so they aren't 0 in the final output
64116
noStreamFirstFrame = false
65117
continue
66118
}
67119

68-
if err := enc.Encode(s); err != nil {
120+
if err := enc.Encode(statsJSON); err != nil {
69121
return err
70122
}
71123

daemon/stats_freebsd.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import (
66
)
77

88
// 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 {
1111
// TODO FreeBSD. Refactor accordingly to fill in stats.
12-
s := &types.Stats{}
12+
s := &types.StatsJSON{}
1313
return s
1414
}

daemon/stats_linux.go

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,24 @@ import (
77
)
88

99
// 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{}
1313
if ls.Interfaces != nil {
14-
s.Network = types.NetworkStats{}
14+
s.Networks = make(map[string]types.NetworkStats)
1515
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+
}
2428
}
2529
}
2630

daemon/stats_windows.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import (
66
)
77

88
// 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 {
1111
// TODO Windows. Refactor accordingly to fill in stats.
12-
s := &types.Stats{}
12+
s := &types.StatsJSON{}
1313
return s
1414
}

docs/reference/api/docker_remote_api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ This section lists each version from latest to oldest. Each listing includes a
8383
* `VolumeDriver` has been moved from config to hostConfig to make the configuration portable.
8484
* `GET /images/(name)/json` now returns information about tags of the image.
8585
* 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.
8687

8788

8889
### v1.20 API changes

docs/reference/api/docker_remote_api_v1.21.md

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -631,15 +631,27 @@ This endpoint returns a live stream of a container's resource usage statistics.
631631

632632
{
633633
"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+
}
643655
},
644656
"memory_stats" : {
645657
"stats" : {

integration-cli/docker_api_stats_test.go

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,18 @@ func (s *DockerSuite) TestApiNetworkStats(c *check.C) {
8787
contIP := findContainerIP(c, id)
8888
numPings := 10
8989

90+
var preRxPackets uint64
91+
var preTxPackets uint64
92+
var postRxPackets uint64
93+
var postTxPackets uint64
94+
9095
// Get the container networking stats before and after pinging the container
9196
nwStatsPre := getNetworkStats(c, id)
97+
for _, v := range nwStatsPre {
98+
preRxPackets += v.RxPackets
99+
preTxPackets += v.TxPackets
100+
}
101+
92102
countParam := "-c"
93103
if runtime.GOOS == "windows" {
94104
countParam = "-n" // Ping count parameter is -n on Windows
@@ -97,18 +107,22 @@ func (s *DockerSuite) TestApiNetworkStats(c *check.C) {
97107
pingouts := string(pingout[:])
98108
c.Assert(err, check.IsNil)
99109
nwStatsPost := getNetworkStats(c, id)
110+
for _, v := range nwStatsPost {
111+
postRxPackets += v.RxPackets
112+
postTxPackets += v.TxPackets
113+
}
100114

101115
// 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))
116+
expRxPkts := 1 + preRxPackets + uint64(numPings)
117+
expTxPkts := 1 + preTxPackets + uint64(numPings)
118+
c.Assert(postTxPackets >= expTxPkts, check.Equals, true,
119+
check.Commentf("Reported less TxPackets than expected. Expected >= %d. Found %d. %s", expTxPkts, postTxPackets, pingouts))
120+
c.Assert(postRxPackets >= expRxPkts, check.Equals, true,
121+
check.Commentf("Reported less Txbytes than expected. Expected >= %d. Found %d. %s", expRxPkts, postRxPackets, pingouts))
108122
}
109123

110-
func getNetworkStats(c *check.C, id string) types.NetworkStats {
111-
var st *types.Stats
124+
func getNetworkStats(c *check.C, id string) map[string]types.NetworkStats {
125+
var st *types.StatsJSON
112126

113127
_, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/stats?stream=false", id), nil, "")
114128
c.Assert(err, check.IsNil)
@@ -117,5 +131,5 @@ func getNetworkStats(c *check.C, id string) types.NetworkStats {
117131
c.Assert(err, check.IsNil)
118132
body.Close()
119133

120-
return st.Network
134+
return st.Networks
121135
}

0 commit comments

Comments
 (0)