123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359 |
- //go:build !windows
- package daemon // import "github.com/docker/docker/daemon"
- import (
- "bufio"
- "context"
- "fmt"
- "os"
- "strconv"
- "strings"
- statsV1 "github.com/containerd/cgroups/v3/cgroup1/stats"
- statsV2 "github.com/containerd/cgroups/v3/cgroup2/stats"
- "github.com/docker/docker/api/types"
- "github.com/docker/docker/container"
- "github.com/pkg/errors"
- )
- 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
- }
- // 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 {
- return "", errors.Wrapf(err, "Could not get container for %s", containerID)
- }
- 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)
- // Convert libnetwork nw stats into api stats
- 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
- }
- 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
- }
|