Browse Source

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 10 years ago
parent
commit
d3379946ec

+ 13 - 4
api/client/stats.go

@@ -56,7 +56,7 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
 	)
 	)
 	go func() {
 	go func() {
 		for {
 		for {
-			var v *types.Stats
+			var v *types.StatsJSON
 			if err := dec.Decode(&v); err != nil {
 			if err := dec.Decode(&v); err != nil {
 				u <- err
 				u <- err
 				return
 				return
@@ -80,8 +80,7 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
 			s.Memory = float64(v.MemoryStats.Usage)
 			s.Memory = float64(v.MemoryStats.Usage)
 			s.MemoryLimit = float64(v.MemoryStats.Limit)
 			s.MemoryLimit = float64(v.MemoryStats.Limit)
 			s.MemoryPercentage = memPercent
 			s.MemoryPercentage = memPercent
-			s.NetworkRx = float64(v.Network.RxBytes)
-			s.NetworkTx = float64(v.Network.TxBytes)
+			s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks)
 			s.BlockRead = float64(blkRead)
 			s.BlockRead = float64(blkRead)
 			s.BlockWrite = float64(blkWrite)
 			s.BlockWrite = float64(blkWrite)
 			s.mu.Unlock()
 			s.mu.Unlock()
@@ -198,7 +197,7 @@ func (cli *DockerCli) CmdStats(args ...string) error {
 	return nil
 	return nil
 }
 }
 
 
-func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.Stats) float64 {
+func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
 	var (
 	var (
 		cpuPercent = 0.0
 		cpuPercent = 0.0
 		// calculate the change for the cpu usage of the container in between readings
 		// 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)
 	}
 	}
 	return
 	return
 }
 }
+
+func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
+	var rx, tx float64
+
+	for _, v := range network {
+		rx += float64(v.RxBytes)
+		tx += float64(v.TxBytes)
+	}
+	return rx, tx
+}

+ 3 - 0
api/server/container.go

@@ -17,6 +17,7 @@ import (
 	"github.com/docker/docker/daemon"
 	"github.com/docker/docker/daemon"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/signal"
+	"github.com/docker/docker/pkg/version"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
 )
 )
 
 
@@ -81,10 +82,12 @@ func (s *Server) getContainersStats(ctx context.Context, w http.ResponseWriter,
 		closeNotifier = notifier.CloseNotify()
 		closeNotifier = notifier.CloseNotify()
 	}
 	}
 
 
+	version, _ := ctx.Value("api-version").(version.Version)
 	config := &daemon.ContainerStatsConfig{
 	config := &daemon.ContainerStatsConfig{
 		Stream:    stream,
 		Stream:    stream,
 		OutStream: out,
 		OutStream: out,
 		Stop:      closeNotifier,
 		Stop:      closeNotifier,
+		Version:   version,
 	}
 	}
 
 
 	return s.daemon.ContainerStats(container, config)
 	return s.daemon.ContainerStats(container, config)

+ 21 - 6
api/types/stats.go

@@ -89,10 +89,25 @@ type NetworkStats struct {
 
 
 // Stats is Ultimate struct aggregating all types of stats of one container
 // Stats is Ultimate struct aggregating all types of stats of one container
 type Stats struct {
 type Stats struct {
-	Read        time.Time    `json:"read"`
-	Network     NetworkStats `json:"network,omitempty"`
-	PreCPUStats CPUStats     `json:"precpu_stats,omitempty"`
-	CPUStats    CPUStats     `json:"cpu_stats,omitempty"`
-	MemoryStats MemoryStats  `json:"memory_stats,omitempty"`
-	BlkioStats  BlkioStats   `json:"blkio_stats,omitempty"`
+	Read        time.Time   `json:"read"`
+	PreCPUStats CPUStats    `json:"precpu_stats,omitempty"`
+	CPUStats    CPUStats    `json:"cpu_stats,omitempty"`
+	MemoryStats MemoryStats `json:"memory_stats,omitempty"`
+	BlkioStats  BlkioStats  `json:"blkio_stats,omitempty"`
+}
+
+// StatsJSONPre121 is a backcompatibility struct along with ContainerConfig
+type StatsJSONPre121 struct {
+	Stats
+
+	// Network is for fallback stats where API Version < 1.21
+	Network NetworkStats `json:"network,omitempty"`
+}
+
+// StatsJSON is newly used Networks
+type StatsJSON struct {
+	Stats
+
+	// Networks request version >=1.21
+	Networks map[string]NetworkStats `json:"networks,omitempty"`
 }
 }

+ 55 - 3
daemon/stats.go

@@ -6,6 +6,7 @@ import (
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/daemon/execdriver"
 	"github.com/docker/docker/daemon/execdriver"
+	"github.com/docker/docker/pkg/version"
 	"github.com/docker/libnetwork/osl"
 	"github.com/docker/libnetwork/osl"
 	"github.com/opencontainers/runc/libcontainer"
 	"github.com/opencontainers/runc/libcontainer"
 )
 )
@@ -16,6 +17,7 @@ type ContainerStatsConfig struct {
 	Stream    bool
 	Stream    bool
 	OutStream io.Writer
 	OutStream io.Writer
 	Stop      <-chan bool
 	Stop      <-chan bool
+	Version   version.Version
 }
 }
 
 
 // ContainerStats writes information about the container to the stream
 // ContainerStats writes information about the container to the stream
@@ -31,7 +33,7 @@ func (daemon *Daemon) ContainerStats(container *Container, config *ContainerStat
 	}
 	}
 
 
 	var preCPUStats types.CPUStats
 	var preCPUStats types.CPUStats
-	getStat := func(v interface{}) *types.Stats {
+	getStatJSON := func(v interface{}) *types.StatsJSON {
 		update := v.(*execdriver.ResourceStats)
 		update := v.(*execdriver.ResourceStats)
 		// Retrieve the nw statistics from libnetwork and inject them in the Stats
 		// Retrieve the nw statistics from libnetwork and inject them in the Stats
 		if nwStats, err := daemon.getNetworkStats(container); err == nil {
 		if nwStats, err := daemon.getNetworkStats(container); err == nil {
@@ -58,14 +60,64 @@ func (daemon *Daemon) ContainerStats(container *Container, config *ContainerStat
 				return nil
 				return nil
 			}
 			}
 
 
-			s := getStat(v)
+			statsJSON := getStatJSON(v)
+			if config.Version.LessThan("1.21") {
+				var (
+					rxBytes   uint64
+					rxPackets uint64
+					rxErrors  uint64
+					rxDropped uint64
+					txBytes   uint64
+					txPackets uint64
+					txErrors  uint64
+					txDropped uint64
+				)
+				for _, v := range statsJSON.Networks {
+					rxBytes += v.RxBytes
+					rxPackets += v.RxPackets
+					rxErrors += v.RxErrors
+					rxDropped += v.RxDropped
+					txBytes += v.TxBytes
+					txPackets += v.TxPackets
+					txErrors += v.TxErrors
+					txDropped += v.TxDropped
+				}
+				statsJSONPre121 := &types.StatsJSONPre121{
+					Stats: statsJSON.Stats,
+					Network: types.NetworkStats{
+						RxBytes:   rxBytes,
+						RxPackets: rxPackets,
+						RxErrors:  rxErrors,
+						RxDropped: rxDropped,
+						TxBytes:   txBytes,
+						TxPackets: txPackets,
+						TxErrors:  txErrors,
+						TxDropped: txDropped,
+					},
+				}
+
+				if !config.Stream && noStreamFirstFrame {
+					// prime the cpu stats so they aren't 0 in the final output
+					noStreamFirstFrame = false
+					continue
+				}
+
+				if err := enc.Encode(statsJSONPre121); err != nil {
+					return err
+				}
+
+				if !config.Stream {
+					return nil
+				}
+			}
+
 			if !config.Stream && noStreamFirstFrame {
 			if !config.Stream && noStreamFirstFrame {
 				// prime the cpu stats so they aren't 0 in the final output
 				// prime the cpu stats so they aren't 0 in the final output
 				noStreamFirstFrame = false
 				noStreamFirstFrame = false
 				continue
 				continue
 			}
 			}
 
 
-			if err := enc.Encode(s); err != nil {
+			if err := enc.Encode(statsJSON); err != nil {
 				return err
 				return err
 			}
 			}
 
 

+ 3 - 3
daemon/stats_freebsd.go

@@ -6,9 +6,9 @@ import (
 )
 )
 
 
 // convertStatsToAPITypes converts the libcontainer.Stats to the api specific
 // convertStatsToAPITypes converts the libcontainer.Stats to the api specific
-// structs.  This is done to preserve API compatibility and versioning.
-func convertStatsToAPITypes(ls *libcontainer.Stats) *types.Stats {
+// structs. This is done to preserve API compatibility and versioning.
+func convertStatsToAPITypes(ls *libcontainer.Stats) *types.StatsJSON {
 	// TODO FreeBSD. Refactor accordingly to fill in stats.
 	// TODO FreeBSD. Refactor accordingly to fill in stats.
-	s := &types.Stats{}
+	s := &types.StatsJSON{}
 	return s
 	return s
 }
 }

+ 16 - 12
daemon/stats_linux.go

@@ -7,20 +7,24 @@ import (
 )
 )
 
 
 // convertStatsToAPITypes converts the libcontainer.Stats to the api specific
 // convertStatsToAPITypes converts the libcontainer.Stats to the api specific
-// structs.  This is done to preserve API compatibility and versioning.
-func convertStatsToAPITypes(ls *libcontainer.Stats) *types.Stats {
-	s := &types.Stats{}
+// structs. This is done to preserve API compatibility and versioning.
+func convertStatsToAPITypes(ls *libcontainer.Stats) *types.StatsJSON {
+	s := &types.StatsJSON{}
 	if ls.Interfaces != nil {
 	if ls.Interfaces != nil {
-		s.Network = types.NetworkStats{}
+		s.Networks = make(map[string]types.NetworkStats)
 		for _, iface := range ls.Interfaces {
 		for _, iface := range ls.Interfaces {
-			s.Network.RxBytes += iface.RxBytes
-			s.Network.RxPackets += iface.RxPackets
-			s.Network.RxErrors += iface.RxErrors
-			s.Network.RxDropped += iface.RxDropped
-			s.Network.TxBytes += iface.TxBytes
-			s.Network.TxPackets += iface.TxPackets
-			s.Network.TxErrors += iface.TxErrors
-			s.Network.TxDropped += iface.TxDropped
+			// For API Version >= 1.21, the original data of network will
+			// be returned.
+			s.Networks[iface.Name] = types.NetworkStats{
+				RxBytes:   iface.RxBytes,
+				RxPackets: iface.RxPackets,
+				RxErrors:  iface.RxErrors,
+				RxDropped: iface.RxDropped,
+				TxBytes:   iface.TxBytes,
+				TxPackets: iface.TxPackets,
+				TxErrors:  iface.TxErrors,
+				TxDropped: iface.TxDropped,
+			}
 		}
 		}
 	}
 	}
 
 

+ 3 - 3
daemon/stats_windows.go

@@ -6,9 +6,9 @@ import (
 )
 )
 
 
 // convertStatsToAPITypes converts the libcontainer.Stats to the api specific
 // convertStatsToAPITypes converts the libcontainer.Stats to the api specific
-// structs.  This is done to preserve API compatibility and versioning.
-func convertStatsToAPITypes(ls *libcontainer.Stats) *types.Stats {
+// structs. This is done to preserve API compatibility and versioning.
+func convertStatsToAPITypes(ls *libcontainer.Stats) *types.StatsJSON {
 	// TODO Windows. Refactor accordingly to fill in stats.
 	// TODO Windows. Refactor accordingly to fill in stats.
-	s := &types.Stats{}
+	s := &types.StatsJSON{}
 	return s
 	return s
 }
 }

+ 1 - 0
docs/reference/api/docker_remote_api.md

@@ -83,6 +83,7 @@ This section lists each version from latest to oldest.  Each listing includes a
 * `VolumeDriver` has been moved from config to hostConfig to make the configuration portable.
 * `VolumeDriver` has been moved from config to hostConfig to make the configuration portable.
 * `GET /images/(name)/json` now returns information about tags of the image.
 * `GET /images/(name)/json` now returns information about tags of the image.
 * The `config` option now accepts the field `StopSignal`, which specifies the signal to use to kill a container.
 * The `config` option now accepts the field `StopSignal`, which specifies the signal to use to kill a container.
+* `GET /containers/(id)/stats` will return networking information respectively for each interface.
 
 
 
 
 ### v1.20 API changes
 ### v1.20 API changes

+ 21 - 9
docs/reference/api/docker_remote_api_v1.21.md

@@ -631,15 +631,27 @@ This endpoint returns a live stream of a container's resource usage statistics.
 
 
       {
       {
          "read" : "2015-01-08T22:57:31.547920715Z",
          "read" : "2015-01-08T22:57:31.547920715Z",
-         "network" : {
-            "rx_dropped" : 0,
-            "rx_bytes" : 648,
-            "rx_errors" : 0,
-            "tx_packets" : 8,
-            "tx_dropped" : 0,
-            "rx_packets" : 8,
-            "tx_errors" : 0,
-            "tx_bytes" : 648
+         "network": {
+                 "eth0": {
+                     "rx_bytes": 5338,
+                     "rx_dropped": 0,
+                     "rx_errors": 0,
+                     "rx_packets": 36,
+                     "tx_bytes": 648,
+                     "tx_dropped": 0,
+                     "tx_errors": 0,
+                     "tx_packets": 8
+                 },
+                 "eth5": {
+                     "rx_bytes": 4641,
+                     "rx_dropped": 0,
+                     "rx_errors": 0,
+                     "rx_packets": 26,
+                     "tx_bytes": 690,
+                     "tx_dropped": 0,
+                     "tx_errors": 0,
+                     "tx_packets": 9
+                 }
          },
          },
          "memory_stats" : {
          "memory_stats" : {
             "stats" : {
             "stats" : {

+ 23 - 9
integration-cli/docker_api_stats_test.go

@@ -87,8 +87,18 @@ func (s *DockerSuite) TestApiNetworkStats(c *check.C) {
 	contIP := findContainerIP(c, id)
 	contIP := findContainerIP(c, id)
 	numPings := 10
 	numPings := 10
 
 
+	var preRxPackets uint64
+	var preTxPackets uint64
+	var postRxPackets uint64
+	var postTxPackets uint64
+
 	// Get the container networking stats before and after pinging the container
 	// Get the container networking stats before and after pinging the container
 	nwStatsPre := getNetworkStats(c, id)
 	nwStatsPre := getNetworkStats(c, id)
+	for _, v := range nwStatsPre {
+		preRxPackets += v.RxPackets
+		preTxPackets += v.TxPackets
+	}
+
 	countParam := "-c"
 	countParam := "-c"
 	if runtime.GOOS == "windows" {
 	if runtime.GOOS == "windows" {
 		countParam = "-n" // Ping count parameter is -n on Windows
 		countParam = "-n" // Ping count parameter is -n on Windows
@@ -97,18 +107,22 @@ func (s *DockerSuite) TestApiNetworkStats(c *check.C) {
 	pingouts := string(pingout[:])
 	pingouts := string(pingout[:])
 	c.Assert(err, check.IsNil)
 	c.Assert(err, check.IsNil)
 	nwStatsPost := getNetworkStats(c, id)
 	nwStatsPost := getNetworkStats(c, id)
+	for _, v := range nwStatsPost {
+		postRxPackets += v.RxPackets
+		postTxPackets += v.TxPackets
+	}
 
 
 	// Verify the stats contain at least the expected number of packets (account for ARP)
 	// 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. %s", expTxPkts, nwStatsPost.TxPackets, pingouts))
-	c.Assert(nwStatsPost.RxPackets >= expRxPkts, check.Equals, true,
-		check.Commentf("Reported less Txbytes than expected. Expected >= %d. Found %d. %s", expRxPkts, nwStatsPost.RxPackets, pingouts))
+	expRxPkts := 1 + preRxPackets + uint64(numPings)
+	expTxPkts := 1 + preTxPackets + uint64(numPings)
+	c.Assert(postTxPackets >= expTxPkts, check.Equals, true,
+		check.Commentf("Reported less TxPackets than expected. Expected >= %d. Found %d. %s", expTxPkts, postTxPackets, pingouts))
+	c.Assert(postRxPackets >= expRxPkts, check.Equals, true,
+		check.Commentf("Reported less Txbytes than expected. Expected >= %d. Found %d. %s", expRxPkts, postRxPackets, pingouts))
 }
 }
 
 
-func getNetworkStats(c *check.C, id string) types.NetworkStats {
-	var st *types.Stats
+func getNetworkStats(c *check.C, id string) map[string]types.NetworkStats {
+	var st *types.StatsJSON
 
 
 	_, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/stats?stream=false", id), nil, "")
 	_, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/stats?stream=false", id), nil, "")
 	c.Assert(err, check.IsNil)
 	c.Assert(err, check.IsNil)
@@ -117,5 +131,5 @@ func getNetworkStats(c *check.C, id string) types.NetworkStats {
 	c.Assert(err, check.IsNil)
 	c.Assert(err, check.IsNil)
 	body.Close()
 	body.Close()
 
 
-	return st.Network
+	return st.Networks
 }
 }