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>
This commit is contained in:
Hu Keping 2015-08-24 17:17:15 +08:00
parent 58d6919183
commit d3379946ec
10 changed files with 159 additions and 49 deletions

View File

@ -56,7 +56,7 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
)
go func() {
for {
var v *types.Stats
var v *types.StatsJSON
if err := dec.Decode(&v); err != nil {
u <- err
return
@ -80,8 +80,7 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
s.Memory = float64(v.MemoryStats.Usage)
s.MemoryLimit = float64(v.MemoryStats.Limit)
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.BlockWrite = float64(blkWrite)
s.mu.Unlock()
@ -198,7 +197,7 @@ func (cli *DockerCli) CmdStats(args ...string) error {
return nil
}
func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.Stats) float64 {
func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
var (
cpuPercent = 0.0
// 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
}
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
}

View File

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

View File

@ -89,10 +89,25 @@ type NetworkStats struct {
// Stats is Ultimate struct aggregating all types of stats of one container
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"`
}

View File

@ -6,6 +6,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/pkg/version"
"github.com/docker/libnetwork/osl"
"github.com/opencontainers/runc/libcontainer"
)
@ -16,6 +17,7 @@ type ContainerStatsConfig struct {
Stream bool
OutStream io.Writer
Stop <-chan bool
Version version.Version
}
// 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
getStat := func(v interface{}) *types.Stats {
getStatJSON := func(v interface{}) *types.StatsJSON {
update := v.(*execdriver.ResourceStats)
// Retrieve the nw statistics from libnetwork and inject them in the Stats
if nwStats, err := daemon.getNetworkStats(container); err == nil {
@ -58,14 +60,64 @@ func (daemon *Daemon) ContainerStats(container *Container, config *ContainerStat
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 {
// prime the cpu stats so they aren't 0 in the final output
noStreamFirstFrame = false
continue
}
if err := enc.Encode(s); err != nil {
if err := enc.Encode(statsJSON); err != nil {
return err
}

View File

@ -6,9 +6,9 @@ import (
)
// 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.
s := &types.Stats{}
s := &types.StatsJSON{}
return s
}

View File

@ -7,20 +7,24 @@ import (
)
// 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 {
s.Network = types.NetworkStats{}
s.Networks = make(map[string]types.NetworkStats)
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,
}
}
}

View File

@ -6,9 +6,9 @@ import (
)
// 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.
s := &types.Stats{}
s := &types.StatsJSON{}
return s
}

View File

@ -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.
* `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.
* `GET /containers/(id)/stats` will return networking information respectively for each interface.
### v1.20 API changes

View File

@ -631,15 +631,27 @@ This endpoint returns a live stream of a container's resource usage statistics.
{
"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" : {
"stats" : {

View File

@ -87,8 +87,18 @@ func (s *DockerSuite) TestApiNetworkStats(c *check.C) {
contIP := findContainerIP(c, id)
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
nwStatsPre := getNetworkStats(c, id)
for _, v := range nwStatsPre {
preRxPackets += v.RxPackets
preTxPackets += v.TxPackets
}
countParam := "-c"
if runtime.GOOS == "windows" {
countParam = "-n" // Ping count parameter is -n on Windows
@ -97,18 +107,22 @@ func (s *DockerSuite) TestApiNetworkStats(c *check.C) {
pingouts := string(pingout[:])
c.Assert(err, check.IsNil)
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)
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, "")
c.Assert(err, check.IsNil)
@ -117,5 +131,5 @@ func getNetworkStats(c *check.C, id string) types.NetworkStats {
c.Assert(err, check.IsNil)
body.Close()
return st.Network
return st.Networks
}