github.com/vincentwoo/docker@v0.7.3-0.20160116130405-82401a4b13c0/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/docker/pkg/version"
    15  	"github.com/docker/engine-api/types"
    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.ContentLength, checker.GreaterThan, int64(0), check.Commentf("should not use chunked encoding"))
    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  	// Run container for 30 secs
    86  	out, _ := dockerCmd(c, "run", "-d", "busybox", "top")
    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 := 10
    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)).Output()
   111  	pingouts := string(pingout[:])
   112  	c.Assert(err, checker.IsNil)
   113  	nwStatsPost := getNetworkStats(c, id)
   114  	for _, v := range nwStatsPost {
   115  		postRxPackets += v.RxPackets
   116  		postTxPackets += v.TxPackets
   117  	}
   118  
   119  	// Verify the stats contain at least the expected number of packets (account for ARP)
   120  	expRxPkts := 1 + preRxPackets + uint64(numPings)
   121  	expTxPkts := 1 + preTxPackets + uint64(numPings)
   122  	c.Assert(postTxPackets, checker.GreaterOrEqualThan, expTxPkts,
   123  		check.Commentf("Reported less TxPackets than expected. Expected >= %d. Found %d. %s", expTxPkts, postTxPackets, pingouts))
   124  	c.Assert(postRxPackets, checker.GreaterOrEqualThan, expRxPkts,
   125  		check.Commentf("Reported less Txbytes than expected. Expected >= %d. Found %d. %s", expRxPkts, postRxPackets, pingouts))
   126  }
   127  
   128  func (s *DockerSuite) TestApiStatsNetworkStatsVersioning(c *check.C) {
   129  	testRequires(c, SameHostDaemon)
   130  	testRequires(c, DaemonIsLinux)
   131  	// Run container for 30 secs
   132  	out, _ := dockerCmd(c, "run", "-d", "busybox", "top")
   133  	id := strings.TrimSpace(out)
   134  	c.Assert(waitRun(id), checker.IsNil)
   135  
   136  	for i := 17; i <= 21; i++ {
   137  		apiVersion := fmt.Sprintf("v1.%d", i)
   138  		for _, statsJSONBlob := range getVersionedStats(c, id, 3, apiVersion) {
   139  			if version.Version(apiVersion).LessThan("v1.21") {
   140  				c.Assert(jsonBlobHasLTv121NetworkStats(statsJSONBlob), checker.Equals, true,
   141  					check.Commentf("Stats JSON blob from API %s %#v does not look like a <v1.21 API stats structure", apiVersion, statsJSONBlob))
   142  			} else {
   143  				c.Assert(jsonBlobHasGTE121NetworkStats(statsJSONBlob), checker.Equals, true,
   144  					check.Commentf("Stats JSON blob from API %s %#v does not look like a >=v1.21 API stats structure", apiVersion, statsJSONBlob))
   145  			}
   146  		}
   147  	}
   148  }
   149  
   150  func getNetworkStats(c *check.C, id string) map[string]types.NetworkStats {
   151  	var st *types.StatsJSON
   152  
   153  	_, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/stats?stream=false", id), nil, "")
   154  	c.Assert(err, checker.IsNil)
   155  
   156  	err = json.NewDecoder(body).Decode(&st)
   157  	c.Assert(err, checker.IsNil)
   158  	body.Close()
   159  
   160  	return st.Networks
   161  }
   162  
   163  // getVersionedNetworkStats returns a slice of numStats stats results for the
   164  // container with id id using an API call with version apiVersion. Since the
   165  // stats result type differs between API versions, we simply return
   166  // []map[string]interface{}.
   167  func getVersionedStats(c *check.C, id string, numStats int, apiVersion string) []map[string]interface{} {
   168  	stats := make([]map[string]interface{}, numStats)
   169  
   170  	requestPath := fmt.Sprintf("/%s/containers/%s/stats?stream=true", apiVersion, id)
   171  	_, body, err := sockRequestRaw("GET", requestPath, nil, "")
   172  	c.Assert(err, checker.IsNil)
   173  	defer body.Close()
   174  
   175  	statsDecoder := json.NewDecoder(body)
   176  	for i := range stats {
   177  		err = statsDecoder.Decode(&stats[i])
   178  		c.Assert(err, checker.IsNil, check.Commentf("failed to decode %dth stat: %s", i, err))
   179  	}
   180  
   181  	return stats
   182  }
   183  
   184  func jsonBlobHasLTv121NetworkStats(blob map[string]interface{}) bool {
   185  	networkStatsIntfc, ok := blob["network"]
   186  	if !ok {
   187  		return false
   188  	}
   189  	networkStats, ok := networkStatsIntfc.(map[string]interface{})
   190  	if !ok {
   191  		return false
   192  	}
   193  	for _, expectedKey := range expectedNetworkInterfaceStats {
   194  		if _, ok := networkStats[expectedKey]; !ok {
   195  			return false
   196  		}
   197  	}
   198  	return true
   199  }
   200  
   201  func jsonBlobHasGTE121NetworkStats(blob map[string]interface{}) bool {
   202  	networksStatsIntfc, ok := blob["networks"]
   203  	if !ok {
   204  		return false
   205  	}
   206  	networksStats, ok := networksStatsIntfc.(map[string]interface{})
   207  	if !ok {
   208  		return false
   209  	}
   210  	for _, networkInterfaceStatsIntfc := range networksStats {
   211  		networkInterfaceStats, ok := networkInterfaceStatsIntfc.(map[string]interface{})
   212  		if !ok {
   213  			return false
   214  		}
   215  		for _, expectedKey := range expectedNetworkInterfaceStats {
   216  			if _, ok := networkInterfaceStats[expectedKey]; !ok {
   217  				return false
   218  			}
   219  		}
   220  	}
   221  	return true
   222  }
   223  
   224  func (s *DockerSuite) TestApiStatsContainerNotFound(c *check.C) {
   225  	testRequires(c, DaemonIsLinux)
   226  
   227  	status, _, err := sockRequest("GET", "/containers/nonexistent/stats", nil)
   228  	c.Assert(err, checker.IsNil)
   229  	c.Assert(status, checker.Equals, http.StatusNotFound)
   230  
   231  	status, _, err = sockRequest("GET", "/containers/nonexistent/stats?stream=0", nil)
   232  	c.Assert(err, checker.IsNil)
   233  	c.Assert(status, checker.Equals, http.StatusNotFound)
   234  }