blob: 5dcd6121d440067b76e9e4f8b9b17bf5e6a539d7 [file] [log] [blame]
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/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 {
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
}
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) (*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
}
}