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() {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" : {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue