Browse code

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 <hukeping@huawei.com>

Hu Keping authored on 2015/08/24 18:17:15
Showing 10 changed files
... ...
@@ -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
 }