github.com/docker/engine@v22.0.0-20211208180946-d456264580cf+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 }