github.com/rish1988/moby@v25.0.2+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/api/types/versions"
    14  	"github.com/docker/docker/api/types/versions/v1p20"
    15  	"github.com/docker/docker/container"
    16  	"github.com/docker/docker/errdefs"
    17  	"github.com/docker/docker/pkg/ioutils"
    18  )
    19  
    20  // ContainerStats writes information about the container to the stream
    21  // given in the config object.
    22  func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, config *backend.ContainerStatsConfig) error {
    23  	// Engine API version (used for backwards compatibility)
    24  	apiVersion := config.Version
    25  
    26  	if isWindows && versions.LessThan(apiVersion, "1.21") {
    27  		return errors.New("API versions pre v1.21 do not support stats on Windows")
    28  	}
    29  
    30  	ctr, err := daemon.GetContainer(prefixOrName)
    31  	if err != nil {
    32  		return err
    33  	}
    34  
    35  	if config.Stream && config.OneShot {
    36  		return errdefs.InvalidParameter(errors.New("cannot have stream=true and one-shot=true"))
    37  	}
    38  
    39  	// If the container is either not running or restarting and requires no stream, return an empty stats.
    40  	if (!ctr.IsRunning() || ctr.IsRestarting()) && !config.Stream {
    41  		return json.NewEncoder(config.OutStream).Encode(&types.StatsJSON{
    42  			Name: ctr.Name,
    43  			ID:   ctr.ID,
    44  		})
    45  	}
    46  
    47  	// Get container stats directly if OneShot is set
    48  	if config.OneShot {
    49  		stats, err := daemon.GetContainerStats(ctr)
    50  		if err != nil {
    51  			return err
    52  		}
    53  		return json.NewEncoder(config.OutStream).Encode(stats)
    54  	}
    55  
    56  	outStream := config.OutStream
    57  	if config.Stream {
    58  		wf := ioutils.NewWriteFlusher(outStream)
    59  		defer wf.Close()
    60  		wf.Flush()
    61  		outStream = wf
    62  	}
    63  
    64  	var preCPUStats types.CPUStats
    65  	var preRead time.Time
    66  	getStatJSON := func(v interface{}) *types.StatsJSON {
    67  		ss := v.(types.StatsJSON)
    68  		ss.Name = ctr.Name
    69  		ss.ID = ctr.ID
    70  		ss.PreCPUStats = preCPUStats
    71  		ss.PreRead = preRead
    72  		preCPUStats = ss.CPUStats
    73  		preRead = ss.Read
    74  		return &ss
    75  	}
    76  
    77  	enc := json.NewEncoder(outStream)
    78  
    79  	updates := daemon.subscribeToContainerStats(ctr)
    80  	defer daemon.unsubscribeToContainerStats(ctr, updates)
    81  
    82  	noStreamFirstFrame := !config.OneShot
    83  	for {
    84  		select {
    85  		case v, ok := <-updates:
    86  			if !ok {
    87  				return nil
    88  			}
    89  
    90  			var statsJSON interface{}
    91  			statsJSONPost120 := getStatJSON(v)
    92  			if versions.LessThan(apiVersion, "1.21") {
    93  				var (
    94  					rxBytes   uint64
    95  					rxPackets uint64
    96  					rxErrors  uint64
    97  					rxDropped uint64
    98  					txBytes   uint64
    99  					txPackets uint64
   100  					txErrors  uint64
   101  					txDropped uint64
   102  				)
   103  				for _, v := range statsJSONPost120.Networks {
   104  					rxBytes += v.RxBytes
   105  					rxPackets += v.RxPackets
   106  					rxErrors += v.RxErrors
   107  					rxDropped += v.RxDropped
   108  					txBytes += v.TxBytes
   109  					txPackets += v.TxPackets
   110  					txErrors += v.TxErrors
   111  					txDropped += v.TxDropped
   112  				}
   113  				statsJSON = &v1p20.StatsJSON{
   114  					Stats: statsJSONPost120.Stats,
   115  					Network: types.NetworkStats{
   116  						RxBytes:   rxBytes,
   117  						RxPackets: rxPackets,
   118  						RxErrors:  rxErrors,
   119  						RxDropped: rxDropped,
   120  						TxBytes:   txBytes,
   121  						TxPackets: txPackets,
   122  						TxErrors:  txErrors,
   123  						TxDropped: txDropped,
   124  					},
   125  				}
   126  			} else {
   127  				statsJSON = statsJSONPost120
   128  			}
   129  
   130  			if !config.Stream && noStreamFirstFrame {
   131  				// prime the cpu stats so they aren't 0 in the final output
   132  				noStreamFirstFrame = false
   133  				continue
   134  			}
   135  
   136  			if err := enc.Encode(statsJSON); err != nil {
   137  				return err
   138  			}
   139  
   140  			if !config.Stream {
   141  				return nil
   142  			}
   143  		case <-ctx.Done():
   144  			return nil
   145  		}
   146  	}
   147  }
   148  
   149  func (daemon *Daemon) subscribeToContainerStats(c *container.Container) chan interface{} {
   150  	return daemon.statsCollector.Collect(c)
   151  }
   152  
   153  func (daemon *Daemon) unsubscribeToContainerStats(c *container.Container, ch chan interface{}) {
   154  	daemon.statsCollector.Unsubscribe(c, ch)
   155  }
   156  
   157  // GetContainerStats collects all the stats published by a container
   158  func (daemon *Daemon) GetContainerStats(container *container.Container) (*types.StatsJSON, error) {
   159  	stats, err := daemon.stats(container)
   160  	if err != nil {
   161  		goto done
   162  	}
   163  
   164  	// Sample system CPU usage close to container usage to avoid
   165  	// noise in metric calculations.
   166  	// FIXME: move to containerd on Linux (not Windows)
   167  	stats.CPUStats.SystemUsage, stats.CPUStats.OnlineCPUs, err = getSystemCPUUsage()
   168  	if err != nil {
   169  		goto done
   170  	}
   171  
   172  	// We already have the network stats on Windows directly from HCS.
   173  	if !container.Config.NetworkDisabled && runtime.GOOS != "windows" {
   174  		stats.Networks, err = daemon.getNetworkStats(container)
   175  	}
   176  
   177  done:
   178  	switch err.(type) {
   179  	case nil:
   180  		return stats, nil
   181  	case errdefs.ErrConflict, errdefs.ErrNotFound:
   182  		// return empty stats containing only name and ID if not running or not found
   183  		return &types.StatsJSON{
   184  			Name: container.Name,
   185  			ID:   container.ID,
   186  		}, nil
   187  	default:
   188  		log.G(context.TODO()).Errorf("collecting stats for container %s: %v", container.Name, err)
   189  		return nil, err
   190  	}
   191  }