Tizen_4.0 base
[platform/upstream/docker-engine.git] / integration-cli / docker_api_stats_test.go
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;usleep 100; do echo 'Hello'; done")
25
26         id := strings.TrimSpace(out)
27         c.Assert(waitRun(id), checker.IsNil)
28         resp, body, err := request.Get(fmt.Sprintf("/containers/%s/stats?stream=false", id))
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 testEnv.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 := request.Get(fmt.Sprintf("/info"))
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 := request.Get(fmt.Sprintf("/containers/%s/stats", id))
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 testEnv.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 testEnv.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 RxPackets than expected. Expected >= %d. Found %d. %s", expRxPkts, postRxPackets, pingouts))
162 }
163
164 func (s *DockerSuite) TestAPIStatsNetworkStatsVersioning(c *check.C) {
165         // Windows doesn't support API versions less than 1.25, so no point testing 1.17 .. 1.21
166         testRequires(c, SameHostDaemon, DaemonIsLinux)
167
168         out := runSleepingContainer(c)
169         id := strings.TrimSpace(out)
170         c.Assert(waitRun(id), checker.IsNil)
171         wg := sync.WaitGroup{}
172
173         for i := 17; i <= 21; i++ {
174                 wg.Add(1)
175                 go func(i int) {
176                         defer wg.Done()
177                         apiVersion := fmt.Sprintf("v1.%d", i)
178                         statsJSONBlob := getVersionedStats(c, id, apiVersion)
179                         if versions.LessThan(apiVersion, "v1.21") {
180                                 c.Assert(jsonBlobHasLTv121NetworkStats(statsJSONBlob), checker.Equals, true,
181                                         check.Commentf("Stats JSON blob from API %s %#v does not look like a <v1.21 API stats structure", apiVersion, statsJSONBlob))
182                         } else {
183                                 c.Assert(jsonBlobHasGTE121NetworkStats(statsJSONBlob), checker.Equals, true,
184                                         check.Commentf("Stats JSON blob from API %s %#v does not look like a >=v1.21 API stats structure", apiVersion, statsJSONBlob))
185                         }
186                 }(i)
187         }
188         wg.Wait()
189 }
190
191 func getNetworkStats(c *check.C, id string) map[string]types.NetworkStats {
192         var st *types.StatsJSON
193
194         _, body, err := request.Get(fmt.Sprintf("/containers/%s/stats?stream=false", id))
195         c.Assert(err, checker.IsNil)
196
197         err = json.NewDecoder(body).Decode(&st)
198         c.Assert(err, checker.IsNil)
199         body.Close()
200
201         return st.Networks
202 }
203
204 // getVersionedStats returns stats result for the
205 // container with id using an API call with version apiVersion. Since the
206 // stats result type differs between API versions, we simply return
207 // map[string]interface{}.
208 func getVersionedStats(c *check.C, id string, apiVersion string) map[string]interface{} {
209         stats := make(map[string]interface{})
210
211         _, body, err := request.Get(fmt.Sprintf("/%s/containers/%s/stats?stream=false", apiVersion, id))
212         c.Assert(err, checker.IsNil)
213         defer body.Close()
214
215         err = json.NewDecoder(body).Decode(&stats)
216         c.Assert(err, checker.IsNil, check.Commentf("failed to decode stat: %s", err))
217
218         return stats
219 }
220
221 func jsonBlobHasLTv121NetworkStats(blob map[string]interface{}) bool {
222         networkStatsIntfc, ok := blob["network"]
223         if !ok {
224                 return false
225         }
226         networkStats, ok := networkStatsIntfc.(map[string]interface{})
227         if !ok {
228                 return false
229         }
230         for _, expectedKey := range expectedNetworkInterfaceStats {
231                 if _, ok := networkStats[expectedKey]; !ok {
232                         return false
233                 }
234         }
235         return true
236 }
237
238 func jsonBlobHasGTE121NetworkStats(blob map[string]interface{}) bool {
239         networksStatsIntfc, ok := blob["networks"]
240         if !ok {
241                 return false
242         }
243         networksStats, ok := networksStatsIntfc.(map[string]interface{})
244         if !ok {
245                 return false
246         }
247         for _, networkInterfaceStatsIntfc := range networksStats {
248                 networkInterfaceStats, ok := networkInterfaceStatsIntfc.(map[string]interface{})
249                 if !ok {
250                         return false
251                 }
252                 for _, expectedKey := range expectedNetworkInterfaceStats {
253                         if _, ok := networkInterfaceStats[expectedKey]; !ok {
254                                 return false
255                         }
256                 }
257         }
258         return true
259 }
260
261 func (s *DockerSuite) TestAPIStatsContainerNotFound(c *check.C) {
262         testRequires(c, DaemonIsLinux)
263
264         status, _, err := request.SockRequest("GET", "/containers/nonexistent/stats", nil, daemonHost())
265         c.Assert(err, checker.IsNil)
266         c.Assert(status, checker.Equals, http.StatusNotFound)
267
268         status, _, err = request.SockRequest("GET", "/containers/nonexistent/stats?stream=0", nil, daemonHost())
269         c.Assert(err, checker.IsNil)
270         c.Assert(status, checker.Equals, http.StatusNotFound)
271 }
272
273 func (s *DockerSuite) TestAPIStatsNoStreamConnectedContainers(c *check.C) {
274         testRequires(c, DaemonIsLinux)
275
276         out1 := runSleepingContainer(c)
277         id1 := strings.TrimSpace(out1)
278         c.Assert(waitRun(id1), checker.IsNil)
279
280         out2 := runSleepingContainer(c, "--net", "container:"+id1)
281         id2 := strings.TrimSpace(out2)
282         c.Assert(waitRun(id2), checker.IsNil)
283
284         ch := make(chan error)
285         go func() {
286                 resp, body, err := request.Get(fmt.Sprintf("/containers/%s/stats?stream=false", id2))
287                 defer body.Close()
288                 if err != nil {
289                         ch <- err
290                 }
291                 if resp.StatusCode != http.StatusOK {
292                         ch <- fmt.Errorf("Invalid StatusCode %v", resp.StatusCode)
293                 }
294                 if resp.Header.Get("Content-Type") != "application/json" {
295                         ch <- fmt.Errorf("Invalid 'Content-Type' %v", resp.Header.Get("Content-Type"))
296                 }
297                 var v *types.Stats
298                 if err := json.NewDecoder(body).Decode(&v); err != nil {
299                         ch <- err
300                 }
301                 ch <- nil
302         }()
303
304         select {
305         case err := <-ch:
306                 c.Assert(err, checker.IsNil, check.Commentf("Error in stats Engine API: %v", err))
307         case <-time.After(15 * time.Second):
308                 c.Fatalf("Stats did not return after timeout")
309         }
310 }