github.com/moby/docker@v26.1.3+incompatible/daemon/stats.go (about)

     1  package daemon // import "github.com/docker/docker/daemon"
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"runtime"
     8  	"time"
     9  
    10  	"github.com/containerd/log"
    11  	"github.com/docker/docker/api/types"
    12  	"github.com/docker/docker/api/types/backend"
    13  	"github.com/docker/docker/container"
    14  	"github.com/docker/docker/errdefs"
    15  	"github.com/docker/docker/pkg/ioutils"
    16  )
    17  
    18  // ContainerStats writes information about the container to the stream
    19  // given in the config object.
    20  func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, config *backend.ContainerStatsConfig) error {
    21  	ctr, err := daemon.GetContainer(prefixOrName)
    22  	if err != nil {
    23  		return err
    24  	}
    25  
    26  	if config.Stream && config.OneShot {
    27  		return errdefs.InvalidParameter(errors.New("cannot have stream=true and one-shot=true"))
    28  	}
    29  
    30  	// If the container is either not running or restarting and requires no stream, return an empty stats.
    31  	if (!ctr.IsRunning() || ctr.IsRestarting()) && !config.Stream {
    32  		return json.NewEncoder(config.OutStream).Encode(&types.StatsJSON{
    33  			Name: ctr.Name,
    34  			ID:   ctr.ID,
    35  		})
    36  	}
    37  
    38  	// Get container stats directly if OneShot is set
    39  	if config.OneShot {
    40  		stats, err := daemon.GetContainerStats(ctr)
    41  		if err != nil {
    42  			return err
    43  		}
    44  		return json.NewEncoder(config.OutStream).Encode(stats)
    45  	}
    46  
    47  	outStream := config.OutStream
    48  	if config.Stream {
    49  		wf := ioutils.NewWriteFlusher(outStream)
    50  		defer wf.Close()
    51  		wf.Flush()
    52  		outStream = wf
    53  	}
    54  
    55  	var preCPUStats types.CPUStats
    56  	var preRead time.Time
    57  	getStatJSON := func(v interface{}) *types.StatsJSON {
    58  		ss := v.(types.StatsJSON)
    59  		ss.Name = ctr.Name
    60  		ss.ID = ctr.ID
    61  		ss.PreCPUStats = preCPUStats
    62  		ss.PreRead = preRead
    63  		preCPUStats = ss.CPUStats
    64  		preRead = ss.Read
    65  		return &ss
    66  	}
    67  
    68  	enc := json.NewEncoder(outStream)
    69  
    70  	updates := daemon.subscribeToContainerStats(ctr)
    71  	defer daemon.unsubscribeToContainerStats(ctr, updates)
    72  
    73  	noStreamFirstFrame := !config.OneShot
    74  	for {
    75  		select {
    76  		case v, ok := <-updates:
    77  			if !ok {
    78  				return nil
    79  			}
    80  
    81  			statsJSON := getStatJSON(v)
    82  			if !config.Stream && noStreamFirstFrame {
    83  				// prime the cpu stats so they aren't 0 in the final output
    84  				noStreamFirstFrame = false
    85  				continue
    86  			}
    87  
    88  			if err := enc.Encode(statsJSON); err != nil {
    89  				return err
    90  			}
    91  
    92  			if !config.Stream {
    93  				return nil
    94  			}
    95  		case <-ctx.Done():
    96  			return nil
    97  		}
    98  	}
    99  }
   100  
   101  func (daemon *Daemon) subscribeToContainerStats(c *container.Container) chan interface{} {
   102  	return daemon.statsCollector.Collect(c)
   103  }
   104  
   105  func (daemon *Daemon) unsubscribeToContainerStats(c *container.Container, ch chan interface{}) {
   106  	daemon.statsCollector.Unsubscribe(c, ch)
   107  }
   108  
   109  // GetContainerStats collects all the stats published by a container
   110  func (daemon *Daemon) GetContainerStats(container *container.Container) (*types.StatsJSON, error) {
   111  	stats, err := daemon.stats(container)
   112  	if err != nil {
   113  		goto done
   114  	}
   115  
   116  	// Sample system CPU usage close to container usage to avoid
   117  	// noise in metric calculations.
   118  	// FIXME: move to containerd on Linux (not Windows)
   119  	stats.CPUStats.SystemUsage, stats.CPUStats.OnlineCPUs, err = getSystemCPUUsage()
   120  	if err != nil {
   121  		goto done
   122  	}
   123  
   124  	// We already have the network stats on Windows directly from HCS.
   125  	if !container.Config.NetworkDisabled && runtime.GOOS != "windows" {
   126  		stats.Networks, err = daemon.getNetworkStats(container)
   127  	}
   128  
   129  done:
   130  	switch err.(type) {
   131  	case nil:
   132  		return stats, nil
   133  	case errdefs.ErrConflict, errdefs.ErrNotFound:
   134  		// return empty stats containing only name and ID if not running or not found
   135  		return &types.StatsJSON{
   136  			Name: container.Name,
   137  			ID:   container.ID,
   138  		}, nil
   139  	default:
   140  		log.G(context.TODO()).Errorf("collecting stats for container %s: %v", container.Name, err)
   141  		return nil, err
   142  	}
   143  }