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 }