dotcloud/docker

View on GitHub
daemon/stats.go

Summary

Maintainability
A
3 hrs
Test Coverage
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/backend"
    containertypes "github.com/docker/docker/api/types/container"
    "github.com/docker/docker/container"
    "github.com/docker/docker/errdefs"
)

// 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 {
    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"))
    }

    enc := json.NewEncoder(config.OutStream())

    // 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 enc.Encode(&containertypes.StatsResponse{
            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 enc.Encode(stats)
    }

    var preCPUStats containertypes.CPUStats
    var preRead time.Time
    getStatJSON := func(v interface{}) *containertypes.StatsResponse {
        ss := v.(containertypes.StatsResponse)
        ss.Name = ctr.Name
        ss.ID = ctr.ID
        ss.PreCPUStats = preCPUStats
        ss.PreRead = preRead
        preCPUStats = ss.CPUStats
        preRead = ss.Read
        return &ss
    }

    updates := daemon.subscribeToContainerStats(ctr)
    defer daemon.unsubscribeToContainerStats(ctr, updates)

    noStreamFirstFrame := !config.OneShot

    for {
        select {
        case v, ok := <-updates:
            if !ok {
                return nil
            }

            statsJSON := getStatJSON(v)
            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) (*containertypes.StatsResponse, 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 &containertypes.StatsResponse{
            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
    }
}