github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/integration-cli/docker_api_stats_test.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "os/exec" 8 "runtime" 9 "strconv" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/docker/docker/api/types" 15 "github.com/docker/docker/api/types/versions" 16 "github.com/docker/docker/integration-cli/checker" 17 "github.com/docker/docker/integration-cli/request" 18 "github.com/go-check/check" 19 ) 20 21 var expectedNetworkInterfaceStats = strings.Split("rx_bytes rx_dropped rx_errors rx_packets tx_bytes tx_dropped tx_errors tx_packets", " ") 22 23 func (s *DockerSuite) TestAPIStatsNoStreamGetCpu(c *check.C) { 24 out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "while true;do echo 'Hello'; usleep 100000; done") 25 26 id := strings.TrimSpace(out) 27 c.Assert(waitRun(id), checker.IsNil) 28 29 resp, body, err := request.SockRequestRaw("GET", fmt.Sprintf("/containers/%s/stats?stream=false", id), nil, "", daemonHost()) 30 c.Assert(err, checker.IsNil) 31 c.Assert(resp.StatusCode, checker.Equals, http.StatusOK) 32 c.Assert(resp.Header.Get("Content-Type"), checker.Equals, "application/json") 33 34 var v *types.Stats 35 err = json.NewDecoder(body).Decode(&v) 36 c.Assert(err, checker.IsNil) 37 body.Close() 38 39 var cpuPercent = 0.0 40 41 if testEnv.DaemonPlatform() != "windows" { 42 cpuDelta := float64(v.CPUStats.CPUUsage.TotalUsage - v.PreCPUStats.CPUUsage.TotalUsage) 43 systemDelta := float64(v.CPUStats.SystemUsage - v.PreCPUStats.SystemUsage) 44 cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0 45 } else { 46 // Max number of 100ns intervals between the previous time read and now 47 possIntervals := uint64(v.Read.Sub(v.PreRead).Nanoseconds()) // Start with number of ns intervals 48 possIntervals /= 100 // Convert to number of 100ns intervals 49 possIntervals *= uint64(v.NumProcs) // Multiple by the number of processors 50 51 // Intervals used 52 intervalsUsed := v.CPUStats.CPUUsage.TotalUsage - v.PreCPUStats.CPUUsage.TotalUsage 53 54 // Percentage avoiding divide-by-zero 55 if possIntervals > 0 { 56 cpuPercent = float64(intervalsUsed) / float64(possIntervals) * 100.0 57 } 58 } 59 60 c.Assert(cpuPercent, check.Not(checker.Equals), 0.0, check.Commentf("docker stats with no-stream get cpu usage failed: was %v", cpuPercent)) 61 } 62 63 func (s *DockerSuite) TestAPIStatsStoppedContainerInGoroutines(c *check.C) { 64 out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo 1") 65 id := strings.TrimSpace(out) 66 67 getGoRoutines := func() int { 68 _, body, err := request.SockRequestRaw("GET", fmt.Sprintf("/info"), nil, "", daemonHost()) 69 c.Assert(err, checker.IsNil) 70 info := types.Info{} 71 err = json.NewDecoder(body).Decode(&info) 72 c.Assert(err, checker.IsNil) 73 body.Close() 74 return info.NGoroutines 75 } 76 77 // When the HTTP connection is closed, the number of goroutines should not increase. 78 routines := getGoRoutines() 79 _, body, err := request.SockRequestRaw("GET", fmt.Sprintf("/containers/%s/stats", id), nil, "", daemonHost()) 80 c.Assert(err, checker.IsNil) 81 body.Close() 82 83 t := time.After(30 * time.Second) 84 for { 85 select { 86 case <-t: 87 c.Assert(getGoRoutines(), checker.LessOrEqualThan, routines) 88 return 89 default: 90 if n := getGoRoutines(); n <= routines { 91 return 92 } 93 time.Sleep(200 * time.Millisecond) 94 } 95 } 96 } 97 98 func (s *DockerSuite) TestAPIStatsNetworkStats(c *check.C) { 99 testRequires(c, SameHostDaemon) 100 101 out, _ := runSleepingContainer(c) 102 id := strings.TrimSpace(out) 103 c.Assert(waitRun(id), checker.IsNil) 104 105 // Retrieve the container address 106 net := "bridge" 107 if testEnv.DaemonPlatform() == "windows" { 108 net = "nat" 109 } 110 contIP := findContainerIP(c, id, net) 111 numPings := 1 112 113 var preRxPackets uint64 114 var preTxPackets uint64 115 var postRxPackets uint64 116 var postTxPackets uint64 117 118 // Get the container networking stats before and after pinging the container 119 nwStatsPre := getNetworkStats(c, id) 120 for _, v := range nwStatsPre { 121 preRxPackets += v.RxPackets 122 preTxPackets += v.TxPackets 123 } 124 125 countParam := "-c" 126 if runtime.GOOS == "windows" { 127 countParam = "-n" // Ping count parameter is -n on Windows 128 } 129 pingout, err := exec.Command("ping", contIP, countParam, strconv.Itoa(numPings)).CombinedOutput() 130 if err != nil && runtime.GOOS == "linux" { 131 // If it fails then try a work-around, but just for linux. 132 // If this fails too then go back to the old error for reporting. 133 // 134 // The ping will sometimes fail due to an apparmor issue where it 135 // denies access to the libc.so.6 shared library - running it 136 // via /lib64/ld-linux-x86-64.so.2 seems to work around it. 137 pingout2, err2 := exec.Command("/lib64/ld-linux-x86-64.so.2", "/bin/ping", contIP, "-c", strconv.Itoa(numPings)).CombinedOutput() 138 if err2 == nil { 139 pingout = pingout2 140 err = err2 141 } 142 } 143 c.Assert(err, checker.IsNil) 144 pingouts := string(pingout[:]) 145 nwStatsPost := getNetworkStats(c, id) 146 for _, v := range nwStatsPost { 147 postRxPackets += v.RxPackets 148 postTxPackets += v.TxPackets 149 } 150 151 // Verify the stats contain at least the expected number of packets 152 // On Linux, account for ARP. 153 expRxPkts := preRxPackets + uint64(numPings) 154 expTxPkts := preTxPackets + uint64(numPings) 155 if testEnv.DaemonPlatform() != "windows" { 156 expRxPkts++ 157 expTxPkts++ 158 } 159 c.Assert(postTxPackets, checker.GreaterOrEqualThan, expTxPkts, 160 check.Commentf("Reported less TxPackets than expected. Expected >= %d. Found %d. %s", expTxPkts, postTxPackets, pingouts)) 161 c.Assert(postRxPackets, checker.GreaterOrEqualThan, expRxPkts, 162 check.Commentf("Reported less Txbytes than expected. Expected >= %d. Found %d. %s", expRxPkts, postRxPackets, pingouts)) 163 } 164 165 func (s *DockerSuite) TestAPIStatsNetworkStatsVersioning(c *check.C) { 166 // Windows doesn't support API versions less than 1.25, so no point testing 1.17 .. 1.21 167 testRequires(c, SameHostDaemon, DaemonIsLinux) 168 169 out, _ := runSleepingContainer(c) 170 id := strings.TrimSpace(out) 171 c.Assert(waitRun(id), checker.IsNil) 172 wg := sync.WaitGroup{} 173 174 for i := 17; i <= 21; i++ { 175 wg.Add(1) 176 go func(i int) { 177 defer wg.Done() 178 apiVersion := fmt.Sprintf("v1.%d", i) 179 statsJSONBlob := getVersionedStats(c, id, apiVersion) 180 if versions.LessThan(apiVersion, "v1.21") { 181 c.Assert(jsonBlobHasLTv121NetworkStats(statsJSONBlob), checker.Equals, true, 182 check.Commentf("Stats JSON blob from API %s %#v does not look like a <v1.21 API stats structure", apiVersion, statsJSONBlob)) 183 } else { 184 c.Assert(jsonBlobHasGTE121NetworkStats(statsJSONBlob), checker.Equals, true, 185 check.Commentf("Stats JSON blob from API %s %#v does not look like a >=v1.21 API stats structure", apiVersion, statsJSONBlob)) 186 } 187 }(i) 188 } 189 wg.Wait() 190 } 191 192 func getNetworkStats(c *check.C, id string) map[string]types.NetworkStats { 193 var st *types.StatsJSON 194 195 _, body, err := request.SockRequestRaw("GET", fmt.Sprintf("/containers/%s/stats?stream=false", id), nil, "", daemonHost()) 196 c.Assert(err, checker.IsNil) 197 198 err = json.NewDecoder(body).Decode(&st) 199 c.Assert(err, checker.IsNil) 200 body.Close() 201 202 return st.Networks 203 } 204 205 // getVersionedStats returns stats result for the 206 // container with id using an API call with version apiVersion. Since the 207 // stats result type differs between API versions, we simply return 208 // map[string]interface{}. 209 func getVersionedStats(c *check.C, id string, apiVersion string) map[string]interface{} { 210 stats := make(map[string]interface{}) 211 212 _, body, err := request.SockRequestRaw("GET", fmt.Sprintf("/%s/containers/%s/stats?stream=false", apiVersion, id), nil, "", daemonHost()) 213 c.Assert(err, checker.IsNil) 214 defer body.Close() 215 216 err = json.NewDecoder(body).Decode(&stats) 217 c.Assert(err, checker.IsNil, check.Commentf("failed to decode stat: %s", err)) 218 219 return stats 220 } 221 222 func jsonBlobHasLTv121NetworkStats(blob map[string]interface{}) bool { 223 networkStatsIntfc, ok := blob["network"] 224 if !ok { 225 return false 226 } 227 networkStats, ok := networkStatsIntfc.(map[string]interface{}) 228 if !ok { 229 return false 230 } 231 for _, expectedKey := range expectedNetworkInterfaceStats { 232 if _, ok := networkStats[expectedKey]; !ok { 233 return false 234 } 235 } 236 return true 237 } 238 239 func jsonBlobHasGTE121NetworkStats(blob map[string]interface{}) bool { 240 networksStatsIntfc, ok := blob["networks"] 241 if !ok { 242 return false 243 } 244 networksStats, ok := networksStatsIntfc.(map[string]interface{}) 245 if !ok { 246 return false 247 } 248 for _, networkInterfaceStatsIntfc := range networksStats { 249 networkInterfaceStats, ok := networkInterfaceStatsIntfc.(map[string]interface{}) 250 if !ok { 251 return false 252 } 253 for _, expectedKey := range expectedNetworkInterfaceStats { 254 if _, ok := networkInterfaceStats[expectedKey]; !ok { 255 return false 256 } 257 } 258 } 259 return true 260 } 261 262 func (s *DockerSuite) TestAPIStatsContainerNotFound(c *check.C) { 263 testRequires(c, DaemonIsLinux) 264 265 status, _, err := request.SockRequest("GET", "/containers/nonexistent/stats", nil, daemonHost()) 266 c.Assert(err, checker.IsNil) 267 c.Assert(status, checker.Equals, http.StatusNotFound) 268 269 status, _, err = request.SockRequest("GET", "/containers/nonexistent/stats?stream=0", nil, daemonHost()) 270 c.Assert(err, checker.IsNil) 271 c.Assert(status, checker.Equals, http.StatusNotFound) 272 } 273 274 func (s *DockerSuite) TestAPIStatsNoStreamConnectedContainers(c *check.C) { 275 testRequires(c, DaemonIsLinux) 276 277 out1, _ := runSleepingContainer(c) 278 id1 := strings.TrimSpace(out1) 279 c.Assert(waitRun(id1), checker.IsNil) 280 281 out2, _ := runSleepingContainer(c, "--net", "container:"+id1) 282 id2 := strings.TrimSpace(out2) 283 c.Assert(waitRun(id2), checker.IsNil) 284 285 ch := make(chan error) 286 go func() { 287 resp, body, err := request.SockRequestRaw("GET", fmt.Sprintf("/containers/%s/stats?stream=false", id2), nil, "", daemonHost()) 288 defer body.Close() 289 if err != nil { 290 ch <- err 291 } 292 if resp.StatusCode != http.StatusOK { 293 ch <- fmt.Errorf("Invalid StatusCode %v", resp.StatusCode) 294 } 295 if resp.Header.Get("Content-Type") != "application/json" { 296 ch <- fmt.Errorf("Invalid 'Content-Type' %v", resp.Header.Get("Content-Type")) 297 } 298 var v *types.Stats 299 if err := json.NewDecoder(body).Decode(&v); err != nil { 300 ch <- err 301 } 302 ch <- nil 303 }() 304 305 select { 306 case err := <-ch: 307 c.Assert(err, checker.IsNil, check.Commentf("Error in stats Engine API: %v", err)) 308 case <-time.After(15 * time.Second): 309 c.Fatalf("Stats did not return after timeout") 310 } 311 }