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  }