github.com/kobeld/docker@v1.12.0-rc1/daemon/stats.go (about) 1 package daemon 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "runtime" 8 9 "golang.org/x/net/context" 10 11 "github.com/docker/docker/api/types/backend" 12 "github.com/docker/docker/container" 13 "github.com/docker/docker/pkg/ioutils" 14 "github.com/docker/engine-api/types" 15 "github.com/docker/engine-api/types/versions" 16 "github.com/docker/engine-api/types/versions/v1p20" 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 if runtime.GOOS == "windows" { 23 return errors.New("Windows does not support stats") 24 } 25 // Remote API version (used for backwards compatibility) 26 apiVersion := config.Version 27 28 container, err := daemon.GetContainer(prefixOrName) 29 if err != nil { 30 return err 31 } 32 33 // If the container is not running and requires no stream, return an empty stats. 34 if !container.IsRunning() && !config.Stream { 35 return json.NewEncoder(config.OutStream).Encode(&types.Stats{}) 36 } 37 38 outStream := config.OutStream 39 if config.Stream { 40 wf := ioutils.NewWriteFlusher(outStream) 41 defer wf.Close() 42 wf.Flush() 43 outStream = wf 44 } 45 46 var preCPUStats types.CPUStats 47 getStatJSON := func(v interface{}) *types.StatsJSON { 48 ss := v.(types.StatsJSON) 49 ss.PreCPUStats = preCPUStats 50 preCPUStats = ss.CPUStats 51 return &ss 52 } 53 54 enc := json.NewEncoder(outStream) 55 56 updates := daemon.subscribeToContainerStats(container) 57 defer daemon.unsubscribeToContainerStats(container, updates) 58 59 noStreamFirstFrame := true 60 for { 61 select { 62 case v, ok := <-updates: 63 if !ok { 64 return nil 65 } 66 67 var statsJSON interface{} 68 statsJSONPost120 := getStatJSON(v) 69 if versions.LessThan(apiVersion, "1.21") { 70 var ( 71 rxBytes uint64 72 rxPackets uint64 73 rxErrors uint64 74 rxDropped uint64 75 txBytes uint64 76 txPackets uint64 77 txErrors uint64 78 txDropped uint64 79 ) 80 for _, v := range statsJSONPost120.Networks { 81 rxBytes += v.RxBytes 82 rxPackets += v.RxPackets 83 rxErrors += v.RxErrors 84 rxDropped += v.RxDropped 85 txBytes += v.TxBytes 86 txPackets += v.TxPackets 87 txErrors += v.TxErrors 88 txDropped += v.TxDropped 89 } 90 statsJSON = &v1p20.StatsJSON{ 91 Stats: statsJSONPost120.Stats, 92 Network: types.NetworkStats{ 93 RxBytes: rxBytes, 94 RxPackets: rxPackets, 95 RxErrors: rxErrors, 96 RxDropped: rxDropped, 97 TxBytes: txBytes, 98 TxPackets: txPackets, 99 TxErrors: txErrors, 100 TxDropped: txDropped, 101 }, 102 } 103 } else { 104 statsJSON = statsJSONPost120 105 } 106 107 if !config.Stream && noStreamFirstFrame { 108 // prime the cpu stats so they aren't 0 in the final output 109 noStreamFirstFrame = false 110 continue 111 } 112 113 if err := enc.Encode(statsJSON); err != nil { 114 return err 115 } 116 117 if !config.Stream { 118 return nil 119 } 120 case <-ctx.Done(): 121 return nil 122 } 123 } 124 } 125 126 func (daemon *Daemon) subscribeToContainerStats(c *container.Container) chan interface{} { 127 return daemon.statsCollector.collect(c) 128 } 129 130 func (daemon *Daemon) unsubscribeToContainerStats(c *container.Container, ch chan interface{}) { 131 daemon.statsCollector.unsubscribe(c, ch) 132 } 133 134 // GetContainerStats collects all the stats published by a container 135 func (daemon *Daemon) GetContainerStats(container *container.Container) (*types.StatsJSON, error) { 136 stats, err := daemon.stats(container) 137 if err != nil { 138 return nil, err 139 } 140 141 if stats.Networks, err = daemon.getNetworkStats(container); err != nil { 142 return nil, err 143 } 144 145 return stats, nil 146 } 147 148 // Resolve Network SandboxID in case the container reuse another container's network stack 149 func (daemon *Daemon) getNetworkSandboxID(c *container.Container) (string, error) { 150 curr := c 151 for curr.HostConfig.NetworkMode.IsContainer() { 152 containerID := curr.HostConfig.NetworkMode.ConnectedContainer() 153 connected, err := daemon.GetContainer(containerID) 154 if err != nil { 155 return "", fmt.Errorf("Could not get container for %s", containerID) 156 } 157 curr = connected 158 } 159 return curr.NetworkSettings.SandboxID, nil 160 } 161 162 func (daemon *Daemon) getNetworkStats(c *container.Container) (map[string]types.NetworkStats, error) { 163 sandboxID, err := daemon.getNetworkSandboxID(c) 164 if err != nil { 165 return nil, err 166 } 167 168 sb, err := daemon.netController.SandboxByID(sandboxID) 169 if err != nil { 170 return nil, err 171 } 172 173 lnstats, err := sb.Statistics() 174 if err != nil { 175 return nil, err 176 } 177 178 stats := make(map[string]types.NetworkStats) 179 // Convert libnetwork nw stats into engine-api stats 180 for ifName, ifStats := range lnstats { 181 stats[ifName] = types.NetworkStats{ 182 RxBytes: ifStats.RxBytes, 183 RxPackets: ifStats.RxPackets, 184 RxErrors: ifStats.RxErrors, 185 RxDropped: ifStats.RxDropped, 186 TxBytes: ifStats.TxBytes, 187 TxPackets: ifStats.TxPackets, 188 TxErrors: ifStats.TxErrors, 189 TxDropped: ifStats.TxDropped, 190 } 191 } 192 193 return stats, nil 194 }