1
0
Fork 0
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:
Sebastiaan van Stijn 2015-09-16 20:29:16 +02:00
commit 259a0fb16c
10 changed files with 159 additions and 49 deletions

View file

@ -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
}

View file

@ -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)

View file

@ -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"`
}

View file

@ -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
} }

View file

@ -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
} }

View file

@ -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,
}
} }
} }

View file

@ -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
} }

View file

@ -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

View file

@ -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" : {

View file

@ -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
} }