mirror of https://github.com/docker/docs.git
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:
parent
58d6919183
commit
d3379946ec
|
@ -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.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks)
|
||||||
s.NetworkTx = float64(v.Network.TxBytes)
|
|
||||||
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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -90,9 +90,24 @@ 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"`
|
Read time.Time `json:"read"`
|
||||||
Network NetworkStats `json:"network,omitempty"`
|
|
||||||
PreCPUStats CPUStats `json:"precpu_stats,omitempty"`
|
PreCPUStats CPUStats `json:"precpu_stats,omitempty"`
|
||||||
CPUStats CPUStats `json:"cpu_stats,omitempty"`
|
CPUStats CPUStats `json:"cpu_stats,omitempty"`
|
||||||
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
|
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
|
||||||
BlkioStats BlkioStats `json:"blkio_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"`
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
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(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(statsJSON); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@ 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.
|
// structs. This is done to preserve API compatibility and versioning.
|
||||||
func convertStatsToAPITypes(ls *libcontainer.Stats) *types.Stats {
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,19 +8,23 @@ 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.
|
// structs. This is done to preserve API compatibility and versioning.
|
||||||
func convertStatsToAPITypes(ls *libcontainer.Stats) *types.Stats {
|
func convertStatsToAPITypes(ls *libcontainer.Stats) *types.StatsJSON {
|
||||||
s := &types.Stats{}
|
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
|
// For API Version >= 1.21, the original data of network will
|
||||||
s.Network.RxPackets += iface.RxPackets
|
// be returned.
|
||||||
s.Network.RxErrors += iface.RxErrors
|
s.Networks[iface.Name] = types.NetworkStats{
|
||||||
s.Network.RxDropped += iface.RxDropped
|
RxBytes: iface.RxBytes,
|
||||||
s.Network.TxBytes += iface.TxBytes
|
RxPackets: iface.RxPackets,
|
||||||
s.Network.TxPackets += iface.TxPackets
|
RxErrors: iface.RxErrors,
|
||||||
s.Network.TxErrors += iface.TxErrors
|
RxDropped: iface.RxDropped,
|
||||||
s.Network.TxDropped += iface.TxDropped
|
TxBytes: iface.TxBytes,
|
||||||
|
TxPackets: iface.TxPackets,
|
||||||
|
TxErrors: iface.TxErrors,
|
||||||
|
TxDropped: iface.TxDropped,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@ 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.
|
// structs. This is done to preserve API compatibility and versioning.
|
||||||
func convertStatsToAPITypes(ls *libcontainer.Stats) *types.Stats {
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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" : {
|
"network": {
|
||||||
"rx_dropped" : 0,
|
"eth0": {
|
||||||
"rx_bytes" : 648,
|
"rx_bytes": 5338,
|
||||||
"rx_errors" : 0,
|
"rx_dropped": 0,
|
||||||
"tx_packets" : 8,
|
"rx_errors": 0,
|
||||||
"tx_dropped" : 0,
|
"rx_packets": 36,
|
||||||
"rx_packets" : 8,
|
"tx_bytes": 648,
|
||||||
"tx_errors" : 0,
|
"tx_dropped": 0,
|
||||||
"tx_bytes" : 648
|
"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" : {
|
||||||
|
|
|
@ -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)
|
expRxPkts := 1 + preRxPackets + uint64(numPings)
|
||||||
expTxPkts := 1 + nwStatsPre.TxPackets + uint64(numPings)
|
expTxPkts := 1 + preTxPackets + uint64(numPings)
|
||||||
c.Assert(nwStatsPost.TxPackets >= expTxPkts, check.Equals, true,
|
c.Assert(postTxPackets >= expTxPkts, check.Equals, true,
|
||||||
check.Commentf("Reported less TxPackets than expected. Expected >= %d. Found %d. %s", expTxPkts, nwStatsPost.TxPackets, pingouts))
|
check.Commentf("Reported less TxPackets than expected. Expected >= %d. Found %d. %s", expTxPkts, postTxPackets, pingouts))
|
||||||
c.Assert(nwStatsPost.RxPackets >= expRxPkts, check.Equals, true,
|
c.Assert(postRxPackets >= expRxPkts, check.Equals, true,
|
||||||
check.Commentf("Reported less Txbytes than expected. Expected >= %d. Found %d. %s", expRxPkts, nwStatsPost.RxPackets, pingouts))
|
check.Commentf("Reported less Txbytes than expected. Expected >= %d. Found %d. %s", expRxPkts, postRxPackets, pingouts))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNetworkStats(c *check.C, id string) types.NetworkStats {
|
func getNetworkStats(c *check.C, id string) map[string]types.NetworkStats {
|
||||||
var st *types.Stats
|
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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue