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