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