mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #15786 from HuKeping/stats-network
Refactor the statistics of network in docker stats
This commit is contained in:
commit
259a0fb16c
10 changed files with 159 additions and 49 deletions
|
@ -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 a new issue