2021-08-23 13:14:53 +00:00
//go:build !windows
2016-09-07 23:08:51 +00:00
2018-02-05 21:05:59 +00:00
package daemon // import "github.com/docker/docker/daemon"
2016-09-07 23:08:51 +00:00
import (
2023-09-26 04:44:04 +00:00
"bufio"
2023-04-08 17:00:01 +00:00
"context"
2023-09-26 04:44:04 +00:00
"fmt"
"os"
"strconv"
2023-04-08 17:00:01 +00:00
"strings"
statsV1 "github.com/containerd/cgroups/v3/cgroup1/stats"
statsV2 "github.com/containerd/cgroups/v3/cgroup2/stats"
2016-09-07 23:08:51 +00:00
"github.com/docker/docker/api/types"
"github.com/docker/docker/container"
2017-07-19 14:20:13 +00:00
"github.com/pkg/errors"
2016-09-07 23:08:51 +00:00
)
2023-04-08 17:00:01 +00:00
func copyBlkioEntry ( entries [ ] * statsV1 . BlkIOEntry ) [ ] types . BlkioStatEntry {
out := make ( [ ] types . BlkioStatEntry , len ( entries ) )
for i , re := range entries {
out [ i ] = types . BlkioStatEntry {
Major : re . Major ,
Minor : re . Minor ,
Op : re . Op ,
Value : re . Value ,
}
}
return out
}
func ( daemon * Daemon ) stats ( c * container . Container ) ( * types . StatsJSON , error ) {
c . Lock ( )
task , err := c . GetRunningTask ( )
c . Unlock ( )
if err != nil {
return nil , err
}
cs , err := task . Stats ( context . Background ( ) )
if err != nil {
if strings . Contains ( err . Error ( ) , "container not found" ) {
return nil , containerNotFound ( c . ID )
}
return nil , err
}
s := & types . StatsJSON { }
s . Read = cs . Read
stats := cs . Metrics
switch t := stats . ( type ) {
case * statsV1 . Metrics :
return daemon . statsV1 ( s , t )
case * statsV2 . Metrics :
return daemon . statsV2 ( s , t )
default :
return nil , errors . Errorf ( "unexpected type of metrics %+v" , t )
}
}
func ( daemon * Daemon ) statsV1 ( s * types . StatsJSON , stats * statsV1 . Metrics ) ( * types . StatsJSON , error ) {
if stats . Blkio != nil {
s . BlkioStats = types . BlkioStats {
IoServiceBytesRecursive : copyBlkioEntry ( stats . Blkio . IoServiceBytesRecursive ) ,
IoServicedRecursive : copyBlkioEntry ( stats . Blkio . IoServicedRecursive ) ,
IoQueuedRecursive : copyBlkioEntry ( stats . Blkio . IoQueuedRecursive ) ,
IoServiceTimeRecursive : copyBlkioEntry ( stats . Blkio . IoServiceTimeRecursive ) ,
IoWaitTimeRecursive : copyBlkioEntry ( stats . Blkio . IoWaitTimeRecursive ) ,
IoMergedRecursive : copyBlkioEntry ( stats . Blkio . IoMergedRecursive ) ,
IoTimeRecursive : copyBlkioEntry ( stats . Blkio . IoTimeRecursive ) ,
SectorsRecursive : copyBlkioEntry ( stats . Blkio . SectorsRecursive ) ,
}
}
if stats . CPU != nil {
s . CPUStats = types . CPUStats {
CPUUsage : types . CPUUsage {
TotalUsage : stats . CPU . Usage . Total ,
PercpuUsage : stats . CPU . Usage . PerCPU ,
UsageInKernelmode : stats . CPU . Usage . Kernel ,
UsageInUsermode : stats . CPU . Usage . User ,
} ,
ThrottlingData : types . ThrottlingData {
Periods : stats . CPU . Throttling . Periods ,
ThrottledPeriods : stats . CPU . Throttling . ThrottledPeriods ,
ThrottledTime : stats . CPU . Throttling . ThrottledTime ,
} ,
}
}
if stats . Memory != nil {
raw := map [ string ] uint64 {
"cache" : stats . Memory . Cache ,
"rss" : stats . Memory . RSS ,
"rss_huge" : stats . Memory . RSSHuge ,
"mapped_file" : stats . Memory . MappedFile ,
"dirty" : stats . Memory . Dirty ,
"writeback" : stats . Memory . Writeback ,
"pgpgin" : stats . Memory . PgPgIn ,
"pgpgout" : stats . Memory . PgPgOut ,
"pgfault" : stats . Memory . PgFault ,
"pgmajfault" : stats . Memory . PgMajFault ,
"inactive_anon" : stats . Memory . InactiveAnon ,
"active_anon" : stats . Memory . ActiveAnon ,
"inactive_file" : stats . Memory . InactiveFile ,
"active_file" : stats . Memory . ActiveFile ,
"unevictable" : stats . Memory . Unevictable ,
"hierarchical_memory_limit" : stats . Memory . HierarchicalMemoryLimit ,
"hierarchical_memsw_limit" : stats . Memory . HierarchicalSwapLimit ,
"total_cache" : stats . Memory . TotalCache ,
"total_rss" : stats . Memory . TotalRSS ,
"total_rss_huge" : stats . Memory . TotalRSSHuge ,
"total_mapped_file" : stats . Memory . TotalMappedFile ,
"total_dirty" : stats . Memory . TotalDirty ,
"total_writeback" : stats . Memory . TotalWriteback ,
"total_pgpgin" : stats . Memory . TotalPgPgIn ,
"total_pgpgout" : stats . Memory . TotalPgPgOut ,
"total_pgfault" : stats . Memory . TotalPgFault ,
"total_pgmajfault" : stats . Memory . TotalPgMajFault ,
"total_inactive_anon" : stats . Memory . TotalInactiveAnon ,
"total_active_anon" : stats . Memory . TotalActiveAnon ,
"total_inactive_file" : stats . Memory . TotalInactiveFile ,
"total_active_file" : stats . Memory . TotalActiveFile ,
"total_unevictable" : stats . Memory . TotalUnevictable ,
}
if stats . Memory . Usage != nil {
s . MemoryStats = types . MemoryStats {
Stats : raw ,
Usage : stats . Memory . Usage . Usage ,
MaxUsage : stats . Memory . Usage . Max ,
Limit : stats . Memory . Usage . Limit ,
Failcnt : stats . Memory . Usage . Failcnt ,
}
} else {
s . MemoryStats = types . MemoryStats {
Stats : raw ,
}
}
// if the container does not set memory limit, use the machineMemory
if s . MemoryStats . Limit > daemon . machineMemory && daemon . machineMemory > 0 {
s . MemoryStats . Limit = daemon . machineMemory
}
}
if stats . Pids != nil {
s . PidsStats = types . PidsStats {
Current : stats . Pids . Current ,
Limit : stats . Pids . Limit ,
}
}
return s , nil
}
func ( daemon * Daemon ) statsV2 ( s * types . StatsJSON , stats * statsV2 . Metrics ) ( * types . StatsJSON , error ) {
if stats . Io != nil {
var isbr [ ] types . BlkioStatEntry
for _ , re := range stats . Io . Usage {
isbr = append ( isbr ,
types . BlkioStatEntry {
Major : re . Major ,
Minor : re . Minor ,
Op : "read" ,
Value : re . Rbytes ,
} ,
types . BlkioStatEntry {
Major : re . Major ,
Minor : re . Minor ,
Op : "write" ,
Value : re . Wbytes ,
} ,
)
}
s . BlkioStats = types . BlkioStats {
IoServiceBytesRecursive : isbr ,
// Other fields are unsupported
}
}
if stats . CPU != nil {
s . CPUStats = types . CPUStats {
CPUUsage : types . CPUUsage {
TotalUsage : stats . CPU . UsageUsec * 1000 ,
// PercpuUsage is not supported
UsageInKernelmode : stats . CPU . SystemUsec * 1000 ,
UsageInUsermode : stats . CPU . UserUsec * 1000 ,
} ,
ThrottlingData : types . ThrottlingData {
Periods : stats . CPU . NrPeriods ,
ThrottledPeriods : stats . CPU . NrThrottled ,
ThrottledTime : stats . CPU . ThrottledUsec * 1000 ,
} ,
}
}
if stats . Memory != nil {
s . MemoryStats = types . MemoryStats {
// Stats is not compatible with v1
Stats : map [ string ] uint64 {
"anon" : stats . Memory . Anon ,
"file" : stats . Memory . File ,
"kernel_stack" : stats . Memory . KernelStack ,
"slab" : stats . Memory . Slab ,
"sock" : stats . Memory . Sock ,
"shmem" : stats . Memory . Shmem ,
"file_mapped" : stats . Memory . FileMapped ,
"file_dirty" : stats . Memory . FileDirty ,
"file_writeback" : stats . Memory . FileWriteback ,
"anon_thp" : stats . Memory . AnonThp ,
"inactive_anon" : stats . Memory . InactiveAnon ,
"active_anon" : stats . Memory . ActiveAnon ,
"inactive_file" : stats . Memory . InactiveFile ,
"active_file" : stats . Memory . ActiveFile ,
"unevictable" : stats . Memory . Unevictable ,
"slab_reclaimable" : stats . Memory . SlabReclaimable ,
"slab_unreclaimable" : stats . Memory . SlabUnreclaimable ,
"pgfault" : stats . Memory . Pgfault ,
"pgmajfault" : stats . Memory . Pgmajfault ,
"workingset_refault" : stats . Memory . WorkingsetRefault ,
"workingset_activate" : stats . Memory . WorkingsetActivate ,
"workingset_nodereclaim" : stats . Memory . WorkingsetNodereclaim ,
"pgrefill" : stats . Memory . Pgrefill ,
"pgscan" : stats . Memory . Pgscan ,
"pgsteal" : stats . Memory . Pgsteal ,
"pgactivate" : stats . Memory . Pgactivate ,
"pgdeactivate" : stats . Memory . Pgdeactivate ,
"pglazyfree" : stats . Memory . Pglazyfree ,
"pglazyfreed" : stats . Memory . Pglazyfreed ,
"thp_fault_alloc" : stats . Memory . ThpFaultAlloc ,
"thp_collapse_alloc" : stats . Memory . ThpCollapseAlloc ,
} ,
Usage : stats . Memory . Usage ,
// MaxUsage is not supported
Limit : stats . Memory . UsageLimit ,
}
// if the container does not set memory limit, use the machineMemory
if s . MemoryStats . Limit > daemon . machineMemory && daemon . machineMemory > 0 {
s . MemoryStats . Limit = daemon . machineMemory
}
if stats . MemoryEvents != nil {
// Failcnt is set to the "oom" field of the "memory.events" file.
// See https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
s . MemoryStats . Failcnt = stats . MemoryEvents . Oom
}
}
if stats . Pids != nil {
s . PidsStats = types . PidsStats {
Current : stats . Pids . Current ,
Limit : stats . Pids . Limit ,
}
}
return s , nil
}
2016-09-07 23:08:51 +00:00
// Resolve Network SandboxID in case the container reuse another container's network stack
func ( daemon * Daemon ) getNetworkSandboxID ( c * container . Container ) ( string , error ) {
curr := c
for curr . HostConfig . NetworkMode . IsContainer ( ) {
containerID := curr . HostConfig . NetworkMode . ConnectedContainer ( )
connected , err := daemon . GetContainer ( containerID )
if err != nil {
2017-07-19 14:20:13 +00:00
return "" , errors . Wrapf ( err , "Could not get container for %s" , containerID )
2016-09-07 23:08:51 +00:00
}
curr = connected
}
return curr . NetworkSettings . SandboxID , nil
}
func ( daemon * Daemon ) getNetworkStats ( c * container . Container ) ( map [ string ] types . NetworkStats , error ) {
sandboxID , err := daemon . getNetworkSandboxID ( c )
if err != nil {
return nil , err
}
sb , err := daemon . netController . SandboxByID ( sandboxID )
if err != nil {
return nil , err
}
lnstats , err := sb . Statistics ( )
if err != nil {
return nil , err
}
stats := make ( map [ string ] types . NetworkStats )
2016-09-21 10:36:52 +00:00
// Convert libnetwork nw stats into api stats
2016-09-07 23:08:51 +00:00
for ifName , ifStats := range lnstats {
stats [ ifName ] = types . NetworkStats {
RxBytes : ifStats . RxBytes ,
RxPackets : ifStats . RxPackets ,
RxErrors : ifStats . RxErrors ,
RxDropped : ifStats . RxDropped ,
TxBytes : ifStats . TxBytes ,
TxPackets : ifStats . TxPackets ,
TxErrors : ifStats . TxErrors ,
TxDropped : ifStats . TxDropped ,
}
}
return stats , nil
}
2023-09-26 04:44:04 +00:00
const (
// The value comes from `C.sysconf(C._SC_CLK_TCK)`, and
// on Linux it's a constant which is safe to be hard coded,
// so we can avoid using cgo here. For details, see:
// https://github.com/containerd/cgroups/pull/12
clockTicksPerSecond = 100
nanoSecondsPerSecond = 1e9
)
// getSystemCPUUsage returns the host system's cpu usage in
// nanoseconds and number of online CPUs. An error is returned
// if the format of the underlying file does not match.
//
// Uses /proc/stat defined by POSIX. Looks for the cpu
// statistics line and then sums up the first seven fields
// provided. See `man 5 proc` for details on specific field
// information.
func getSystemCPUUsage ( ) ( cpuUsage uint64 , cpuNum uint32 , err error ) {
f , err := os . Open ( "/proc/stat" )
if err != nil {
return 0 , 0 , err
}
defer f . Close ( )
scanner := bufio . NewScanner ( f )
for scanner . Scan ( ) {
line := scanner . Text ( )
if len ( line ) < 4 || line [ : 3 ] != "cpu" {
break // Assume all cpu* records are at the front, like glibc https://github.com/bminor/glibc/blob/5d00c201b9a2da768a79ea8d5311f257871c0b43/sysdeps/unix/sysv/linux/getsysstats.c#L108-L135
}
if line [ 3 ] == ' ' {
parts := strings . Fields ( line )
if len ( parts ) < 8 {
return 0 , 0 , fmt . Errorf ( "invalid number of cpu fields" )
}
var totalClockTicks uint64
for _ , i := range parts [ 1 : 8 ] {
v , err := strconv . ParseUint ( i , 10 , 64 )
if err != nil {
return 0 , 0 , fmt . Errorf ( "Unable to convert value %s to int: %w" , i , err )
}
totalClockTicks += v
}
cpuUsage = ( totalClockTicks * nanoSecondsPerSecond ) /
clockTicksPerSecond
}
if '0' <= line [ 3 ] && line [ 3 ] <= '9' {
cpuNum ++
}
}
if err := scanner . Err ( ) ; err != nil {
return 0 , 0 , fmt . Errorf ( "error scanning '/proc/stat' file: %w" , err )
}
return
}