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 }