123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- package daemon // import "github.com/docker/docker/daemon"
- import (
- "context"
- "encoding/json"
- "errors"
- "runtime"
- "time"
- "github.com/containerd/log"
- "github.com/docker/docker/api/types"
- "github.com/docker/docker/api/types/backend"
- "github.com/docker/docker/api/types/versions"
- "github.com/docker/docker/api/types/versions/v1p20"
- "github.com/docker/docker/container"
- "github.com/docker/docker/errdefs"
- "github.com/docker/docker/pkg/ioutils"
- )
- // 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 {
- // Engine API version (used for backwards compatibility)
- apiVersion := config.Version
- if isWindows && versions.LessThan(apiVersion, "1.21") {
- return errors.New("API versions pre v1.21 do not support stats on Windows")
- }
- ctr, err := daemon.GetContainer(prefixOrName)
- if err != nil {
- return err
- }
- if config.Stream && config.OneShot {
- return errdefs.InvalidParameter(errors.New("cannot have stream=true and one-shot=true"))
- }
- // If the container is either not running or restarting and requires no stream, return an empty stats.
- if (!ctr.IsRunning() || ctr.IsRestarting()) && !config.Stream {
- return json.NewEncoder(config.OutStream).Encode(&types.StatsJSON{
- Name: ctr.Name,
- ID: ctr.ID,
- })
- }
- // Get container stats directly if OneShot is set
- if config.OneShot {
- stats, err := daemon.GetContainerStats(ctr)
- if err != nil {
- return err
- }
- return json.NewEncoder(config.OutStream).Encode(stats)
- }
- outStream := config.OutStream
- if config.Stream {
- wf := ioutils.NewWriteFlusher(outStream)
- defer wf.Close()
- wf.Flush()
- outStream = wf
- }
- var preCPUStats types.CPUStats
- var preRead time.Time
- getStatJSON := func(v interface{}) *types.StatsJSON {
- ss := v.(types.StatsJSON)
- ss.Name = ctr.Name
- ss.ID = ctr.ID
- ss.PreCPUStats = preCPUStats
- ss.PreRead = preRead
- preCPUStats = ss.CPUStats
- preRead = ss.Read
- return &ss
- }
- enc := json.NewEncoder(outStream)
- updates := daemon.subscribeToContainerStats(ctr)
- defer daemon.unsubscribeToContainerStats(ctr, updates)
- noStreamFirstFrame := !config.OneShot
- 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 {
- goto done
- }
- // Sample system CPU usage close to container usage to avoid
- // noise in metric calculations.
- // FIXME: move to containerd on Linux (not Windows)
- stats.CPUStats.SystemUsage, stats.CPUStats.OnlineCPUs, err = getSystemCPUUsage()
- if err != nil {
- goto done
- }
- // We already have the network stats on Windows directly from HCS.
- if !container.Config.NetworkDisabled && runtime.GOOS != "windows" {
- stats.Networks, err = daemon.getNetworkStats(container)
- }
- done:
- switch err.(type) {
- case nil:
- return stats, nil
- case errdefs.ErrConflict, errdefs.ErrNotFound:
- // return empty stats containing only name and ID if not running or not found
- return &types.StatsJSON{
- Name: container.Name,
- ID: container.ID,
- }, nil
- default:
- log.G(context.TODO()).Errorf("collecting stats for container %s: %v", container.Name, err)
- return nil, err
- }
- }
|