mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
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
10 changed files with 159 additions and 49 deletions
|
@ -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 a new issue