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