2015-05-30 17:25:51 +00:00
package main
import (
"encoding/json"
"fmt"
2015-09-17 16:20:25 +00:00
"net/http"
2015-07-01 16:54:26 +00:00
"os/exec"
2015-07-08 20:30:03 +00:00
"runtime"
2015-07-01 16:54:26 +00:00
"strconv"
2015-06-01 20:33:03 +00:00
"strings"
2019-09-09 21:06:12 +00:00
"testing"
2015-06-16 14:33:49 +00:00
"time"
2015-06-01 20:33:03 +00:00
2016-09-06 18:18:12 +00:00
"github.com/docker/docker/api/types"
2023-07-03 11:14:14 +00:00
"github.com/docker/docker/api/types/system"
2017-05-24 03:56:26 +00:00
"github.com/docker/docker/client"
2023-07-27 11:13:00 +00:00
"github.com/docker/docker/integration-cli/cli"
2023-07-14 18:02:38 +00:00
"github.com/docker/docker/testutil"
2019-08-29 20:52:40 +00:00
"github.com/docker/docker/testutil/request"
2020-02-07 13:39:24 +00:00
"gotest.tools/v3/assert"
2020-09-21 19:21:22 +00:00
"gotest.tools/v3/skip"
2015-05-30 17:25:51 +00:00
)
2015-10-30 23:32:09 +00:00
var expectedNetworkInterfaceStats = strings . Split ( "rx_bytes rx_dropped rx_errors rx_packets tx_bytes tx_dropped tx_errors tx_packets" , " " )
2022-06-16 21:32:10 +00:00
func ( s * DockerAPISuite ) TestAPIStatsNoStreamGetCpu ( c * testing . T ) {
2020-09-21 19:21:22 +00:00
skip . If ( c , RuntimeIsWindowsContainerd ( ) , "FIXME: Broken on Windows + containerd combination" )
2023-07-27 11:13:00 +00:00
out := cli . DockerCmd ( c , "run" , "-d" , "busybox" , "/bin/sh" , "-c" , "while true;usleep 100; do echo 'Hello'; done" ) . Stdout ( )
2015-05-30 17:25:51 +00:00
id := strings . TrimSpace ( out )
2023-07-27 11:13:00 +00:00
cli . WaitRun ( c , id )
2023-07-14 18:02:38 +00:00
resp , body , err := request . Get ( testutil . GetContext ( c ) , fmt . Sprintf ( "/containers/%s/stats?stream=false" , id ) )
2019-04-04 13:23:19 +00: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 20:33:03 +00:00
2015-05-30 17:25:51 +00:00
var v * types . Stats
2015-06-01 20:33:03 +00:00
err = json . NewDecoder ( body ) . Decode ( & v )
2019-04-04 13:23:19 +00:00
assert . NilError ( c , err )
2015-07-23 11:24:14 +00:00
body . Close ( )
2015-05-30 17:25:51 +00:00
2022-01-20 12:43:42 +00:00
cpuPercent := 0.0
2016-09-07 23:08:51 +00:00
2023-06-14 09:46:00 +00:00
if testEnv . DaemonInfo . OSType != "windows" {
2016-09-07 23:08:51 +00: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 12:01:58 +00:00
2019-04-04 13:23:19 +00:00
assert . Assert ( c , cpuPercent != 0.0 , "docker stats with no-stream get cpu usage failed: was %v" , cpuPercent )
2015-05-30 17:25:51 +00:00
}
2015-06-16 14:33:49 +00:00
2022-06-16 21:32:10 +00:00
func ( s * DockerAPISuite ) TestAPIStatsStoppedContainerInGoroutines ( c * testing . T ) {
2023-07-27 11:13:00 +00:00
out := cli . DockerCmd ( c , "run" , "-d" , "busybox" , "/bin/sh" , "-c" , "echo 1" ) . Stdout ( )
2015-06-16 14:33:49 +00:00
id := strings . TrimSpace ( out )
getGoRoutines := func ( ) int {
2023-07-14 18:02:38 +00:00
_ , body , err := request . Get ( testutil . GetContext ( c ) , "/info" )
2019-04-04 13:23:19 +00:00
assert . NilError ( c , err )
2023-07-03 11:14:14 +00:00
info := system . Info { }
2015-06-16 14:33:49 +00:00
err = json . NewDecoder ( body ) . Decode ( & info )
2019-04-04 13:23:19 +00:00
assert . NilError ( c , err )
2015-06-16 14:33:49 +00:00
body . Close ( )
return info . NGoroutines
}
// When the HTTP connection is closed, the number of goroutines should not increase.
routines := getGoRoutines ( )
2023-07-14 18:02:38 +00:00
_ , body , err := request . Get ( testutil . GetContext ( c ) , "/containers/" + id + "/stats" )
2019-04-04 13:23:19 +00:00
assert . NilError ( c , err )
2015-06-16 14:33:49 +00:00
body . Close ( )
t := time . After ( 30 * time . Second )
for {
select {
case <- t :
2019-04-04 13:23:19 +00:00
assert . Assert ( c , getGoRoutines ( ) <= routines )
2015-06-16 14:33:49 +00:00
return
default :
if n := getGoRoutines ( ) ; n <= routines {
return
}
time . Sleep ( 200 * time . Millisecond )
}
}
}
2015-07-01 16:54:26 +00:00
2022-06-16 21:32:10 +00:00
func ( s * DockerAPISuite ) TestAPIStatsNetworkStats ( c * testing . T ) {
2020-09-21 19:21:22 +00:00
skip . If ( c , RuntimeIsWindowsContainerd ( ) , "FIXME: Broken on Windows + containerd combination" )
2018-12-24 12:25:53 +00:00
testRequires ( c , testEnv . IsLocalDaemon )
2016-01-28 07:36:31 +00:00
2023-07-27 11:13:00 +00:00
id := runSleepingContainer ( c )
cli . WaitRun ( c , id )
2015-07-01 16:54:26 +00:00
// Retrieve the container address
2016-09-07 23:08:51 +00:00
net := "bridge"
2023-06-14 09:46:00 +00:00
if testEnv . DaemonInfo . OSType == "windows" {
2016-09-07 23:08:51 +00:00
net = "nat"
}
contIP := findContainerIP ( c , id , net )
2016-08-03 20:30:34 +00:00
numPings := 1
2015-07-01 16:54:26 +00:00
2015-08-24 09:17:15 +00:00
var preRxPackets uint64
var preTxPackets uint64
var postRxPackets uint64
var postTxPackets uint64
2015-07-01 16:54:26 +00:00
// Get the container networking stats before and after pinging the container
nwStatsPre := getNetworkStats ( c , id )
2015-08-24 09:17:15 +00:00
for _ , v := range nwStatsPre {
preRxPackets += v . RxPackets
preTxPackets += v . TxPackets
}
2015-07-08 20:30:03 +00:00
countParam := "-c"
if runtime . GOOS == "windows" {
countParam = "-n" // Ping count parameter is -n on Windows
}
2016-05-23 18:20:41 +00: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 13:23:19 +00:00
assert . NilError ( c , err )
2016-05-23 18:20:41 +00:00
pingouts := string ( pingout [ : ] )
2015-07-01 16:54:26 +00:00
nwStatsPost := getNetworkStats ( c , id )
2015-08-24 09:17:15 +00:00
for _ , v := range nwStatsPost {
postRxPackets += v . RxPackets
postTxPackets += v . TxPackets
}
2015-07-01 16:54:26 +00:00
2016-09-07 23:08:51 +00: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 )
2023-06-14 09:46:00 +00:00
if testEnv . DaemonInfo . OSType != "windows" {
2016-09-07 23:08:51 +00:00
expRxPkts ++
expTxPkts ++
}
2019-04-04 13:23:19 +00: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 16:54:26 +00:00
}
2022-06-16 21:32:10 +00:00
func ( s * DockerAPISuite ) TestAPIStatsNetworkStatsVersioning ( c * testing . T ) {
2018-12-24 12:25:53 +00:00
testRequires ( c , testEnv . IsLocalDaemon , DaemonIsLinux )
2016-01-28 07:36:31 +00:00
2023-07-27 11:13:00 +00:00
id := runSleepingContainer ( c )
cli . WaitRun ( c , id )
2024-01-21 17:57:00 +00:00
statsJSONBlob := getStats ( c , id )
assert . Assert ( c , jsonBlobHasGTE121NetworkStats ( statsJSONBlob ) , "Stats JSON blob from API does not look like a >=v1.21 API stats structure" , statsJSONBlob )
2015-10-30 23:32:09 +00:00
}
2019-09-09 21:05:55 +00:00
func getNetworkStats ( c * testing . T , id string ) map [ string ] types . NetworkStats {
2015-08-24 09:17:15 +00:00
var st * types . StatsJSON
2015-07-01 16:54:26 +00:00
2023-07-14 18:02:38 +00:00
_ , body , err := request . Get ( testutil . GetContext ( c ) , "/containers/" + id + "/stats?stream=false" )
2019-04-04 13:23:19 +00:00
assert . NilError ( c , err )
2015-07-01 16:54:26 +00:00
err = json . NewDecoder ( body ) . Decode ( & st )
2019-04-04 13:23:19 +00:00
assert . NilError ( c , err )
2015-07-23 11:24:14 +00:00
body . Close ( )
2015-07-01 16:54:26 +00:00
2015-08-24 09:17:15 +00:00
return st . Networks
2015-07-01 16:54:26 +00:00
}
2015-09-17 16:20:25 +00:00
2024-01-21 17:57:00 +00:00
// getStats returns stats result for the
2016-01-28 07:36:31 +00:00
// container with id using an API call with version apiVersion. Since the
2015-10-30 23:32:09 +00:00
// stats result type differs between API versions, we simply return
2016-01-28 07:36:31 +00:00
// map[string]interface{}.
2024-01-21 17:57:00 +00:00
func getStats ( c * testing . T , id string ) map [ string ] interface { } {
c . Helper ( )
2016-01-28 07:36:31 +00:00
stats := make ( map [ string ] interface { } )
2015-10-30 23:32:09 +00:00
2024-01-21 17:57:00 +00:00
_ , body , err := request . Get ( testutil . GetContext ( c ) , "/containers/" + id + "/stats?stream=false" )
2019-04-04 13:23:19 +00:00
assert . NilError ( c , err )
2015-10-30 23:32:09 +00:00
defer body . Close ( )
2016-01-28 07:36:31 +00:00
err = json . NewDecoder ( body ) . Decode ( & stats )
2019-04-04 13:23:19 +00:00
assert . NilError ( c , err , "failed to decode stat: %s" , err )
2015-10-30 23:32:09 +00:00
return stats
}
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
}
2022-06-16 21:32:10 +00:00
func ( s * DockerAPISuite ) TestAPIStatsContainerNotFound ( c * testing . T ) {
2015-09-17 16:20:25 +00:00
testRequires ( c , DaemonIsLinux )
2023-04-03 11:00:29 +00:00
apiClient , err := client . NewClientWithOpts ( client . FromEnv )
2019-04-04 13:23:19 +00:00
assert . NilError ( c , err )
2023-04-03 11:00:29 +00:00
defer apiClient . Close ( )
2015-09-17 16:20:25 +00:00
2017-05-24 03:56:26 +00:00
expected := "No such container: nonexistent"
2023-07-14 18:02:38 +00:00
_ , err = apiClient . ContainerStats ( testutil . GetContext ( c ) , "nonexistent" , true )
2019-04-04 13:23:19 +00:00
assert . ErrorContains ( c , err , expected )
2023-07-14 18:02:38 +00:00
_ , err = apiClient . ContainerStats ( testutil . GetContext ( c ) , "nonexistent" , false )
2019-04-04 13:23:19 +00:00
assert . ErrorContains ( c , err , expected )
2015-09-17 16:20:25 +00:00
}
2016-04-08 02:09:07 +00:00
2022-06-16 21:32:10 +00:00
func ( s * DockerAPISuite ) TestAPIStatsNoStreamConnectedContainers ( c * testing . T ) {
2016-04-09 06:57:04 +00:00
testRequires ( c , DaemonIsLinux )
2023-07-27 11:13:00 +00:00
id1 := runSleepingContainer ( c )
cli . WaitRun ( c , id1 )
2016-04-09 06:57:04 +00:00
2023-07-27 11:13:00 +00:00
id2 := runSleepingContainer ( c , "--net" , "container:" + id1 )
cli . WaitRun ( c , id2 )
2016-04-09 06:57:04 +00:00
2017-09-22 13:52:41 +00:00
ch := make ( chan error , 1 )
2016-04-09 06:57:04 +00:00
go func ( ) {
2023-07-14 18:02:38 +00:00
resp , body , err := request . Get ( testutil . GetContext ( c ) , "/containers/" + id2 + "/stats?stream=false" )
2016-04-09 06:57:04 +00: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 13:23:19 +00:00
assert . NilError ( c , err , "Error in stats Engine API: %v" , err )
2016-04-09 06:57:04 +00:00
case <- time . After ( 15 * time . Second ) :
c . Fatalf ( "Stats did not return after timeout" )
}
}