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