123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- package daemon
- import (
- "encoding/json"
- "errors"
- "fmt"
- "runtime"
- "golang.org/x/net/context"
- "github.com/docker/docker/api/types/backend"
- "github.com/docker/docker/container"
- "github.com/docker/docker/pkg/ioutils"
- "github.com/docker/engine-api/types"
- "github.com/docker/engine-api/types/versions"
- "github.com/docker/engine-api/types/versions/v1p20"
- )
- // ContainerStats writes information about the container to the stream
- // given in the config object.
- func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, config *backend.ContainerStatsConfig) error {
- if runtime.GOOS == "windows" {
- return errors.New("Windows does not support stats")
- }
- // Remote API version (used for backwards compatibility)
- apiVersion := config.Version
- container, err := daemon.GetContainer(prefixOrName)
- 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{})
- }
- outStream := config.OutStream
- if config.Stream {
- wf := ioutils.NewWriteFlusher(outStream)
- defer wf.Close()
- wf.Flush()
- outStream = wf
- }
- var preCPUStats types.CPUStats
- getStatJSON := func(v interface{}) *types.StatsJSON {
- ss := v.(types.StatsJSON)
- ss.PreCPUStats = preCPUStats
- preCPUStats = ss.CPUStats
- return &ss
- }
- enc := json.NewEncoder(outStream)
- updates := daemon.subscribeToContainerStats(container)
- defer daemon.unsubscribeToContainerStats(container, updates)
- noStreamFirstFrame := true
- for {
- select {
- case v, ok := <-updates:
- if !ok {
- return nil
- }
- var statsJSON interface{}
- statsJSONPost120 := getStatJSON(v)
- if versions.LessThan(apiVersion, "1.21") {
- var (
- rxBytes uint64
- rxPackets uint64
- rxErrors uint64
- rxDropped uint64
- txBytes uint64
- txPackets uint64
- txErrors uint64
- txDropped uint64
- )
- for _, v := range statsJSONPost120.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
- }
- statsJSON = &v1p20.StatsJSON{
- Stats: statsJSONPost120.Stats,
- Network: types.NetworkStats{
- RxBytes: rxBytes,
- RxPackets: rxPackets,
- RxErrors: rxErrors,
- RxDropped: rxDropped,
- TxBytes: txBytes,
- TxPackets: txPackets,
- TxErrors: txErrors,
- TxDropped: txDropped,
- },
- }
- } else {
- statsJSON = statsJSONPost120
- }
- 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
- }
- if !config.Stream {
- return nil
- }
- case <-ctx.Done():
- return nil
- }
- }
- }
- 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)
- }
- // 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
- }
|