Files
moby/daemon/stats.go
Sebastiaan van Stijn 83510a26b3 api/types: move backend types to daemon/server
The "backend" types in API were designed to decouple the API server
implementation from the daemon, or other parts of the code that
back the API server. This would allow the daemon to evolve (e.g.
functionality moved to different subsystems) without that impacting
the API server's implementation.

Now that the API server is no longer part of the API package (module),
there is no benefit to having it in the API module. The API server
may evolve (and require changes in the backend), which has no direct
relation with the API module (types, responses); the backend definition
is, however, coupled to the API server implementation.

It's worth noting that, while "technically" possible to use the API
server package, and implement an alternative backend implementation,
this has never been a prime objective. The backend definition was
never considered "stable", and we don't expect external users to
(attempt) to use it as such.

This patch moves the backend types to the daemon/server package,
so that they can evolve with the daemon and API server implementation
without that impacting the API module (which we intend to be stable,
following SemVer).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-28 00:03:04 +02:00

136 lines
3.5 KiB
Go

package daemon
import (
"context"
"encoding/json"
"errors"
"runtime"
"time"
"github.com/containerd/log"
"github.com/docker/docker/daemon/container"
"github.com/docker/docker/daemon/server/backend"
"github.com/docker/docker/errdefs"
containertypes "github.com/moby/moby/api/types/container"
)
// 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
}
}