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