2015-05-30 13:25:51 -04:00
package main
import (
2018-04-19 18:30:59 -04:00
"context"
2015-05-30 13:25:51 -04:00
"encoding/json"
"fmt"
2015-09-17 12:20:25 -04:00
"net/http"
2015-07-01 12:54:26 -04:00
"os/exec"
2015-07-08 16:30:03 -04:00
"runtime"
2015-07-01 12:54:26 -04:00
"strconv"
2015-06-01 16:33:03 -04:00
"strings"
2016-08-03 16:24:55 -04:00
"sync"
2019-09-09 17:06:12 -04:00
"testing"
2015-06-16 10:33:49 -04:00
"time"
2015-06-01 16:33:03 -04:00
2016-09-06 14:18:12 -04:00
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions"
2017-05-23 23:56:26 -04:00
"github.com/docker/docker/client"
2018-04-17 04:22:04 -04:00
"github.com/docker/docker/internal/test/request"
2019-04-04 09:23:19 -04:00
"gotest.tools/assert"
2015-05-30 13:25:51 -04:00
)
2015-10-30 19:32:09 -04:00
var expectedNetworkInterfaceStats = strings . Split ( "rx_bytes rx_dropped rx_errors rx_packets tx_bytes tx_dropped tx_errors tx_packets" , " " )
2019-09-09 17:05:55 -04:00
func ( s * DockerSuite ) TestAPIStatsNoStreamGetCpu ( c * testing . T ) {
2017-05-10 16:26:04 -04:00
out , _ := dockerCmd ( c , "run" , "-d" , "busybox" , "/bin/sh" , "-c" , "while true;usleep 100; do echo 'Hello'; done" )
2015-05-30 13:25:51 -04:00
id := strings . TrimSpace ( out )
2019-04-04 09:23:19 -04:00
assert . NilError ( c , waitRun ( id ) )
2017-03-06 10:35:27 -05:00
resp , body , err := request . Get ( fmt . Sprintf ( "/containers/%s/stats?stream=false" , id ) )
2019-04-04 09:23:19 -04:00
assert . NilError ( c , err )
assert . Equal ( c , resp . StatusCode , http . StatusOK )
assert . Equal ( c , resp . Header . Get ( "Content-Type" ) , "application/json" )
assert . Equal ( c , resp . Header . Get ( "Content-Type" ) , "application/json" )
2015-06-01 16:33:03 -04:00
2015-05-30 13:25:51 -04:00
var v * types . Stats
2015-06-01 16:33:03 -04:00
err = json . NewDecoder ( body ) . Decode ( & v )
2019-04-04 09:23:19 -04:00
assert . NilError ( c , err )
2015-07-23 07:24:14 -04:00
body . Close ( )
2015-05-30 13:25:51 -04:00
2015-06-01 16:33:03 -04:00
var cpuPercent = 0.0
2016-09-07 19:08:51 -04:00
2018-01-15 09:32:06 -05:00
if testEnv . OSType != "windows" {
2016-09-07 19:08:51 -04:00
cpuDelta := float64 ( v . CPUStats . CPUUsage . TotalUsage - v . PreCPUStats . CPUUsage . TotalUsage )
systemDelta := float64 ( v . CPUStats . SystemUsage - v . PreCPUStats . SystemUsage )
cpuPercent = ( cpuDelta / systemDelta ) * float64 ( len ( v . CPUStats . CPUUsage . PercpuUsage ) ) * 100.0
} else {
// Max number of 100ns intervals between the previous time read and now
possIntervals := uint64 ( v . Read . Sub ( v . PreRead ) . Nanoseconds ( ) ) // Start with number of ns intervals
possIntervals /= 100 // Convert to number of 100ns intervals
possIntervals *= uint64 ( v . NumProcs ) // Multiple by the number of processors
// Intervals used
intervalsUsed := v . CPUStats . CPUUsage . TotalUsage - v . PreCPUStats . CPUUsage . TotalUsage
// Percentage avoiding divide-by-zero
if possIntervals > 0 {
cpuPercent = float64 ( intervalsUsed ) / float64 ( possIntervals ) * 100.0
}
}
2015-10-13 08:01:58 -04:00
2019-04-04 09:23:19 -04:00
assert . Assert ( c , cpuPercent != 0.0 , "docker stats with no-stream get cpu usage failed: was %v" , cpuPercent )
2015-05-30 13:25:51 -04:00
}
2015-06-16 10:33:49 -04:00
2019-09-09 17:05:55 -04:00
func ( s * DockerSuite ) TestAPIStatsStoppedContainerInGoroutines ( c * testing . T ) {
2015-06-16 10:33:49 -04:00
out , _ := dockerCmd ( c , "run" , "-d" , "busybox" , "/bin/sh" , "-c" , "echo 1" )
id := strings . TrimSpace ( out )
getGoRoutines := func ( ) int {
2017-03-06 10:35:27 -05:00
_ , body , err := request . Get ( fmt . Sprintf ( "/info" ) )
2019-04-04 09:23:19 -04:00
assert . NilError ( c , err )
2015-06-16 10:33:49 -04:00
info := types . Info { }
err = json . NewDecoder ( body ) . Decode ( & info )
2019-04-04 09:23:19 -04:00
assert . NilError ( c , err )
2015-06-16 10:33:49 -04:00
body . Close ( )
return info . NGoroutines
}
// When the HTTP connection is closed, the number of goroutines should not increase.
routines := getGoRoutines ( )
2017-03-06 10:35:27 -05:00
_ , body , err := request . Get ( fmt . Sprintf ( "/containers/%s/stats" , id ) )
2019-04-04 09:23:19 -04:00
assert . NilError ( c , err )
2015-06-16 10:33:49 -04:00
body . Close ( )
t := time . After ( 30 * time . Second )
for {
select {
case <- t :
2019-04-04 09:23:19 -04:00
assert . Assert ( c , getGoRoutines ( ) <= routines )
2015-06-16 10:33:49 -04:00
return
default :
if n := getGoRoutines ( ) ; n <= routines {
return
}
time . Sleep ( 200 * time . Millisecond )
}
}
}
2015-07-01 12:54:26 -04:00
2019-09-09 17:05:55 -04:00
func ( s * DockerSuite ) TestAPIStatsNetworkStats ( c * testing . T ) {
2018-12-24 07:25:53 -05:00
testRequires ( c , testEnv . IsLocalDaemon )
2016-01-28 02:36:31 -05:00
2017-04-16 17:39:30 -04:00
out := runSleepingContainer ( c )
2015-07-01 12:54:26 -04:00
id := strings . TrimSpace ( out )
2019-04-04 09:23:19 -04:00
assert . NilError ( c , waitRun ( id ) )
2015-07-01 12:54:26 -04:00
// Retrieve the container address
2016-09-07 19:08:51 -04:00
net := "bridge"
2018-01-15 09:32:06 -05:00
if testEnv . OSType == "windows" {
2016-09-07 19:08:51 -04:00
net = "nat"
}
contIP := findContainerIP ( c , id , net )
2016-08-03 16:30:34 -04:00
numPings := 1
2015-07-01 12:54:26 -04:00
2015-08-24 05:17:15 -04:00
var preRxPackets uint64
var preTxPackets uint64
var postRxPackets uint64
var postTxPackets uint64
2015-07-01 12:54:26 -04:00
// Get the container networking stats before and after pinging the container
nwStatsPre := getNetworkStats ( c , id )
2015-08-24 05:17:15 -04:00
for _ , v := range nwStatsPre {
preRxPackets += v . RxPackets
preTxPackets += v . TxPackets
}
2015-07-08 16:30:03 -04:00
countParam := "-c"
if runtime . GOOS == "windows" {
countParam = "-n" // Ping count parameter is -n on Windows
}
2016-05-23 14:20:41 -04:00
pingout , err := exec . Command ( "ping" , contIP , countParam , strconv . Itoa ( numPings ) ) . CombinedOutput ( )
if err != nil && runtime . GOOS == "linux" {
// If it fails then try a work-around, but just for linux.
// If this fails too then go back to the old error for reporting.
//
// The ping will sometimes fail due to an apparmor issue where it
// denies access to the libc.so.6 shared library - running it
// via /lib64/ld-linux-x86-64.so.2 seems to work around it.
pingout2 , err2 := exec . Command ( "/lib64/ld-linux-x86-64.so.2" , "/bin/ping" , contIP , "-c" , strconv . Itoa ( numPings ) ) . CombinedOutput ( )
if err2 == nil {
pingout = pingout2
err = err2
}
}
2019-04-04 09:23:19 -04:00
assert . NilError ( c , err )
2016-05-23 14:20:41 -04:00
pingouts := string ( pingout [ : ] )
2015-07-01 12:54:26 -04:00
nwStatsPost := getNetworkStats ( c , id )
2015-08-24 05:17:15 -04:00
for _ , v := range nwStatsPost {
postRxPackets += v . RxPackets
postTxPackets += v . TxPackets
}
2015-07-01 12:54:26 -04:00
2016-09-07 19:08:51 -04:00
// Verify the stats contain at least the expected number of packets
// On Linux, account for ARP.
expRxPkts := preRxPackets + uint64 ( numPings )
expTxPkts := preTxPackets + uint64 ( numPings )
2018-01-15 09:32:06 -05:00
if testEnv . OSType != "windows" {
2016-09-07 19:08:51 -04:00
expRxPkts ++
expTxPkts ++
}
2019-04-04 09:23:19 -04:00
assert . Assert ( c , postTxPackets >= expTxPkts , "Reported less TxPackets than expected. Expected >= %d. Found %d. %s" , expTxPkts , postTxPackets , pingouts )
assert . Assert ( c , postRxPackets >= expRxPkts , "Reported less RxPackets than expected. Expected >= %d. Found %d. %s" , expRxPkts , postRxPackets , pingouts )
2015-07-01 12:54:26 -04:00
}
2019-09-09 17:05:55 -04:00
func ( s * DockerSuite ) TestAPIStatsNetworkStatsVersioning ( c * testing . T ) {
2016-10-31 13:15:43 -04:00
// Windows doesn't support API versions less than 1.25, so no point testing 1.17 .. 1.21
2018-12-24 07:25:53 -05:00
testRequires ( c , testEnv . IsLocalDaemon , DaemonIsLinux )
2016-01-28 02:36:31 -05:00
2017-04-16 17:39:30 -04:00
out := runSleepingContainer ( c )
2015-10-30 19:32:09 -04:00
id := strings . TrimSpace ( out )
2019-04-04 09:23:19 -04:00
assert . NilError ( c , waitRun ( id ) )
2016-08-03 16:24:55 -04:00
wg := sync . WaitGroup { }
2015-10-30 19:32:09 -04:00
2016-10-31 13:15:43 -04:00
for i := 17 ; i <= 21 ; i ++ {
2016-08-03 16:24:55 -04:00
wg . Add ( 1 )
2016-09-29 13:56:43 -04:00
go func ( i int ) {
2016-08-03 16:24:55 -04:00
defer wg . Done ( )
apiVersion := fmt . Sprintf ( "v1.%d" , i )
statsJSONBlob := getVersionedStats ( c , id , apiVersion )
if versions . LessThan ( apiVersion , "v1.21" ) {
2019-04-04 09:23:19 -04:00
assert . Assert ( c , jsonBlobHasLTv121NetworkStats ( statsJSONBlob ) , "Stats JSON blob from API %s %#v does not look like a <v1.21 API stats structure" , apiVersion , statsJSONBlob )
2016-08-03 16:24:55 -04:00
} else {
2019-04-04 09:23:19 -04:00
assert . Assert ( c , jsonBlobHasGTE121NetworkStats ( statsJSONBlob ) , "Stats JSON blob from API %s %#v does not look like a >=v1.21 API stats structure" , apiVersion , statsJSONBlob )
2016-08-03 16:24:55 -04:00
}
2016-09-29 13:56:43 -04:00
} ( i )
2015-10-30 19:32:09 -04:00
}
2016-08-03 16:24:55 -04:00
wg . Wait ( )
2015-10-30 19:32:09 -04:00
}
2019-09-09 17:05:55 -04:00
func getNetworkStats ( c * testing . T , id string ) map [ string ] types . NetworkStats {
2015-08-24 05:17:15 -04:00
var st * types . StatsJSON
2015-07-01 12:54:26 -04:00
2017-03-06 10:35:27 -05:00
_ , body , err := request . Get ( fmt . Sprintf ( "/containers/%s/stats?stream=false" , id ) )
2019-04-04 09:23:19 -04:00
assert . NilError ( c , err )
2015-07-01 12:54:26 -04:00
err = json . NewDecoder ( body ) . Decode ( & st )
2019-04-04 09:23:19 -04:00
assert . NilError ( c , err )
2015-07-23 07:24:14 -04:00
body . Close ( )
2015-07-01 12:54:26 -04:00
2015-08-24 05:17:15 -04:00
return st . Networks
2015-07-01 12:54:26 -04:00
}
2015-09-17 12:20:25 -04:00
2016-01-28 02:36:31 -05:00
// getVersionedStats returns stats result for the
// container with id using an API call with version apiVersion. Since the
2015-10-30 19:32:09 -04:00
// stats result type differs between API versions, we simply return
2016-01-28 02:36:31 -05:00
// map[string]interface{}.
2019-09-09 17:05:55 -04:00
func getVersionedStats ( c * testing . T , id string , apiVersion string ) map [ string ] interface { } {
2016-01-28 02:36:31 -05:00
stats := make ( map [ string ] interface { } )
2015-10-30 19:32:09 -04:00
2017-03-06 10:35:27 -05:00
_ , body , err := request . Get ( fmt . Sprintf ( "/%s/containers/%s/stats?stream=false" , apiVersion , id ) )
2019-04-04 09:23:19 -04:00
assert . NilError ( c , err )
2015-10-30 19:32:09 -04:00
defer body . Close ( )
2016-01-28 02:36:31 -05:00
err = json . NewDecoder ( body ) . Decode ( & stats )
2019-04-04 09:23:19 -04:00
assert . NilError ( c , err , "failed to decode stat: %s" , err )
2015-10-30 19:32:09 -04:00
return stats
}
func jsonBlobHasLTv121NetworkStats ( blob map [ string ] interface { } ) bool {
networkStatsIntfc , ok := blob [ "network" ]
if ! ok {
return false
}
networkStats , ok := networkStatsIntfc . ( map [ string ] interface { } )
if ! ok {
return false
}
for _ , expectedKey := range expectedNetworkInterfaceStats {
if _ , ok := networkStats [ expectedKey ] ; ! ok {
return false
}
}
return true
}
func jsonBlobHasGTE121NetworkStats ( blob map [ string ] interface { } ) bool {
networksStatsIntfc , ok := blob [ "networks" ]
if ! ok {
return false
}
networksStats , ok := networksStatsIntfc . ( map [ string ] interface { } )
if ! ok {
return false
}
for _ , networkInterfaceStatsIntfc := range networksStats {
networkInterfaceStats , ok := networkInterfaceStatsIntfc . ( map [ string ] interface { } )
if ! ok {
return false
}
for _ , expectedKey := range expectedNetworkInterfaceStats {
if _ , ok := networkInterfaceStats [ expectedKey ] ; ! ok {
return false
}
}
}
return true
}
2019-09-09 17:05:55 -04:00
func ( s * DockerSuite ) TestAPIStatsContainerNotFound ( c * testing . T ) {
2015-09-17 12:20:25 -04:00
testRequires ( c , DaemonIsLinux )
2019-01-03 16:49:00 -05:00
cli , err := client . NewClientWithOpts ( client . FromEnv )
2019-04-04 09:23:19 -04:00
assert . NilError ( c , err )
2017-05-23 23:56:26 -04:00
defer cli . Close ( )
2015-09-17 12:20:25 -04:00
2017-05-23 23:56:26 -04:00
expected := "No such container: nonexistent"
_ , err = cli . ContainerStats ( context . Background ( ) , "nonexistent" , true )
2019-04-04 09:23:19 -04:00
assert . ErrorContains ( c , err , expected )
2017-05-23 23:56:26 -04:00
_ , err = cli . ContainerStats ( context . Background ( ) , "nonexistent" , false )
2019-04-04 09:23:19 -04:00
assert . ErrorContains ( c , err , expected )
2015-09-17 12:20:25 -04:00
}
2016-04-07 22:09:07 -04:00
2019-09-09 17:05:55 -04:00
func ( s * DockerSuite ) TestAPIStatsNoStreamConnectedContainers ( c * testing . T ) {
2016-04-09 02:57:04 -04:00
testRequires ( c , DaemonIsLinux )
2017-04-16 17:39:30 -04:00
out1 := runSleepingContainer ( c )
2016-04-09 02:57:04 -04:00
id1 := strings . TrimSpace ( out1 )
2019-04-04 09:23:19 -04:00
assert . NilError ( c , waitRun ( id1 ) )
2016-04-09 02:57:04 -04:00
2017-04-16 17:39:30 -04:00
out2 := runSleepingContainer ( c , "--net" , "container:" + id1 )
2016-04-09 02:57:04 -04:00
id2 := strings . TrimSpace ( out2 )
2019-04-04 09:23:19 -04:00
assert . NilError ( c , waitRun ( id2 ) )
2016-04-09 02:57:04 -04:00
2017-09-22 09:52:41 -04:00
ch := make ( chan error , 1 )
2016-04-09 02:57:04 -04:00
go func ( ) {
2017-03-06 10:35:27 -05:00
resp , body , err := request . Get ( fmt . Sprintf ( "/containers/%s/stats?stream=false" , id2 ) )
2016-04-09 02:57:04 -04:00
defer body . Close ( )
if err != nil {
ch <- err
}
if resp . StatusCode != http . StatusOK {
ch <- fmt . Errorf ( "Invalid StatusCode %v" , resp . StatusCode )
}
if resp . Header . Get ( "Content-Type" ) != "application/json" {
ch <- fmt . Errorf ( "Invalid 'Content-Type' %v" , resp . Header . Get ( "Content-Type" ) )
}
var v * types . Stats
if err := json . NewDecoder ( body ) . Decode ( & v ) ; err != nil {
ch <- err
}
ch <- nil
} ( )
select {
case err := <- ch :
2019-04-04 09:23:19 -04:00
assert . NilError ( c , err , "Error in stats Engine API: %v" , err )
2016-04-09 02:57:04 -04:00
case <- time . After ( 15 * time . Second ) :
c . Fatalf ( "Stats did not return after timeout" )
}
}