4f174aa7 |
package daemon
import (
"encoding/json" |
55268f4e |
"errors" |
bfa0885c |
"fmt" |
55268f4e |
"runtime" |
855a056a |
|
62c9e62e |
"golang.org/x/net/context"
|
06d8f504 |
"github.com/docker/docker/api/types/backend" |
fb48bf51 |
"github.com/docker/docker/container" |
ae4ee974 |
"github.com/docker/docker/pkg/ioutils" |
907407d0 |
"github.com/docker/engine-api/types" |
7534f172 |
"github.com/docker/engine-api/types/versions" |
907407d0 |
"github.com/docker/engine-api/types/versions/v1p20" |
4f174aa7 |
)
|
abd72d40 |
// ContainerStats writes information about the container to the stream
// given in the config object. |
62c9e62e |
func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, config *backend.ContainerStatsConfig) error { |
55268f4e |
if runtime.GOOS == "windows" {
return errors.New("Windows does not support stats")
} |
dd93571c |
// Remote API version (used for backwards compatibility) |
7534f172 |
apiVersion := config.Version |
55268f4e |
|
d7d512bb |
container, err := daemon.GetContainer(prefixOrName) |
2d5d606f |
if err != nil {
return err
}
// If the container is not running and requires no stream, return an empty stats.
if !container.IsRunning() && !config.Stream {
return json.NewEncoder(config.OutStream).Encode(&types.Stats{})
}
|
ae4ee974 |
outStream := config.OutStream |
1cbf5a54 |
if config.Stream { |
ae4ee974 |
wf := ioutils.NewWriteFlusher(outStream)
defer wf.Close()
wf.Flush()
outStream = wf |
1cbf5a54 |
}
|
abd72d40 |
var preCPUStats types.CPUStats |
d3379946 |
getStatJSON := func(v interface{}) *types.StatsJSON { |
d17ee4b5 |
ss := v.(types.StatsJSON) |
abd72d40 |
ss.PreCPUStats = preCPUStats
preCPUStats = ss.CPUStats |
d17ee4b5 |
return &ss |
855a056a |
}
|
ae4ee974 |
enc := json.NewEncoder(outStream) |
855a056a |
|
99065544 |
updates := daemon.subscribeToContainerStats(container) |
dc8a3903 |
defer daemon.unsubscribeToContainerStats(container, updates) |
855a056a |
|
1cbf5a54 |
noStreamFirstFrame := true
for {
select {
case v, ok := <-updates:
if !ok {
return nil
}
|
d2c04f84 |
var statsJSON interface{}
statsJSONPost120 := getStatJSON(v) |
7534f172 |
if versions.LessThan(apiVersion, "1.21") { |
d3379946 |
var (
rxBytes uint64
rxPackets uint64
rxErrors uint64
rxDropped uint64
txBytes uint64
txPackets uint64
txErrors uint64
txDropped uint64
) |
d2c04f84 |
for _, v := range statsJSONPost120.Networks { |
d3379946 |
rxBytes += v.RxBytes
rxPackets += v.RxPackets
rxErrors += v.RxErrors
rxDropped += v.RxDropped
txBytes += v.TxBytes
txPackets += v.TxPackets
txErrors += v.TxErrors
txDropped += v.TxDropped
} |
d2c04f84 |
statsJSON = &v1p20.StatsJSON{
Stats: statsJSONPost120.Stats, |
d3379946 |
Network: types.NetworkStats{
RxBytes: rxBytes,
RxPackets: rxPackets,
RxErrors: rxErrors,
RxDropped: rxDropped,
TxBytes: txBytes,
TxPackets: txPackets,
TxErrors: txErrors,
TxDropped: txDropped,
},
} |
d2c04f84 |
} else {
statsJSON = statsJSONPost120 |
d3379946 |
}
|
1cbf5a54 |
if !config.Stream && noStreamFirstFrame {
// prime the cpu stats so they aren't 0 in the final output
noStreamFirstFrame = false
continue
}
|
d3379946 |
if err := enc.Encode(statsJSON); err != nil { |
1cbf5a54 |
return err
} |
855a056a |
|
1cbf5a54 |
if !config.Stream {
return nil
} |
62c9e62e |
case <-ctx.Done(): |
1cbf5a54 |
return nil |
4f174aa7 |
}
}
} |
fb48bf51 |
func (daemon *Daemon) subscribeToContainerStats(c *container.Container) chan interface{} {
return daemon.statsCollector.collect(c)
}
func (daemon *Daemon) unsubscribeToContainerStats(c *container.Container, ch chan interface{}) {
daemon.statsCollector.unsubscribe(c, ch)
} |
bfa0885c |
// GetContainerStats collects all the stats published by a container
func (daemon *Daemon) GetContainerStats(container *container.Container) (*types.StatsJSON, error) {
stats, err := daemon.stats(container)
if err != nil {
return nil, err
}
if stats.Networks, err = daemon.getNetworkStats(container); err != nil {
return nil, err
}
return stats, nil
}
// Resolve Network SandboxID in case the container reuse another container's network stack
func (daemon *Daemon) getNetworkSandboxID(c *container.Container) (string, error) {
curr := c
for curr.HostConfig.NetworkMode.IsContainer() {
containerID := curr.HostConfig.NetworkMode.ConnectedContainer()
connected, err := daemon.GetContainer(containerID)
if err != nil {
return "", fmt.Errorf("Could not get container for %s", containerID)
}
curr = connected
}
return curr.NetworkSettings.SandboxID, nil
}
func (daemon *Daemon) getNetworkStats(c *container.Container) (map[string]types.NetworkStats, error) {
sandboxID, err := daemon.getNetworkSandboxID(c)
if err != nil {
return nil, err
}
sb, err := daemon.netController.SandboxByID(sandboxID)
if err != nil {
return nil, err
}
lnstats, err := sb.Statistics()
if err != nil {
return nil, err
}
stats := make(map[string]types.NetworkStats)
// Convert libnetwork nw stats into engine-api stats
for ifName, ifStats := range lnstats {
stats[ifName] = types.NetworkStats{
RxBytes: ifStats.RxBytes,
RxPackets: ifStats.RxPackets,
RxErrors: ifStats.RxErrors,
RxDropped: ifStats.RxDropped,
TxBytes: ifStats.TxBytes,
TxPackets: ifStats.TxPackets,
TxErrors: ifStats.TxErrors,
TxDropped: ifStats.TxDropped,
}
}
return stats, nil
} |