Skip to content

Commit

Permalink
Refactor the statistics of network in docker stats
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
HuKeping committed Sep 15, 2015
1 parent 58d6919 commit d337994
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 49 deletions.
17 changes: 13 additions & 4 deletions api/client/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand All @@ -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
}
3 changes: 3 additions & 0 deletions api/server/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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)
Expand Down
27 changes: 21 additions & 6 deletions api/types/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
58 changes: 55 additions & 3 deletions daemon/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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
}

Expand Down
6 changes: 3 additions & 3 deletions daemon/stats_freebsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
28 changes: 16 additions & 12 deletions daemon/stats_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
}

Expand Down
6 changes: 3 additions & 3 deletions daemon/stats_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
1 change: 1 addition & 0 deletions docs/reference/api/docker_remote_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 21 additions & 9 deletions docs/reference/api/docker_remote_api_v1.21.md
Original file line number Diff line number Diff line change
Expand Up @@ -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" : {
Expand Down
32 changes: 23 additions & 9 deletions integration-cli/docker_api_stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
}

0 comments on commit d337994

Please sign in to comment.