github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/daemon/stats_unix.go (about)

     1  //go:build !windows
     2  
     3  package daemon // import "github.com/docker/docker/daemon"
     4  
     5  import (
     6  	"bufio"
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"strconv"
    11  	"strings"
    12  
    13  	statsV1 "github.com/containerd/cgroups/v3/cgroup1/stats"
    14  	statsV2 "github.com/containerd/cgroups/v3/cgroup2/stats"
    15  	"github.com/docker/docker/api/types"
    16  	"github.com/docker/docker/container"
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  func copyBlkioEntry(entries []*statsV1.BlkIOEntry) []types.BlkioStatEntry {
    21  	out := make([]types.BlkioStatEntry, len(entries))
    22  	for i, re := range entries {
    23  		out[i] = types.BlkioStatEntry{
    24  			Major: re.Major,
    25  			Minor: re.Minor,
    26  			Op:    re.Op,
    27  			Value: re.Value,
    28  		}
    29  	}
    30  	return out
    31  }
    32  
    33  func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) {
    34  	c.Lock()
    35  	task, err := c.GetRunningTask()
    36  	c.Unlock()
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  	cs, err := task.Stats(context.Background())
    41  	if err != nil {
    42  		if strings.Contains(err.Error(), "container not found") {
    43  			return nil, containerNotFound(c.ID)
    44  		}
    45  		return nil, err
    46  	}
    47  	s := &types.StatsJSON{}
    48  	s.Read = cs.Read
    49  	stats := cs.Metrics
    50  	switch t := stats.(type) {
    51  	case *statsV1.Metrics:
    52  		return daemon.statsV1(s, t)
    53  	case *statsV2.Metrics:
    54  		return daemon.statsV2(s, t)
    55  	default:
    56  		return nil, errors.Errorf("unexpected type of metrics %+v", t)
    57  	}
    58  }
    59  
    60  func (daemon *Daemon) statsV1(s *types.StatsJSON, stats *statsV1.Metrics) (*types.StatsJSON, error) {
    61  	if stats.Blkio != nil {
    62  		s.BlkioStats = types.BlkioStats{
    63  			IoServiceBytesRecursive: copyBlkioEntry(stats.Blkio.IoServiceBytesRecursive),
    64  			IoServicedRecursive:     copyBlkioEntry(stats.Blkio.IoServicedRecursive),
    65  			IoQueuedRecursive:       copyBlkioEntry(stats.Blkio.IoQueuedRecursive),
    66  			IoServiceTimeRecursive:  copyBlkioEntry(stats.Blkio.IoServiceTimeRecursive),
    67  			IoWaitTimeRecursive:     copyBlkioEntry(stats.Blkio.IoWaitTimeRecursive),
    68  			IoMergedRecursive:       copyBlkioEntry(stats.Blkio.IoMergedRecursive),
    69  			IoTimeRecursive:         copyBlkioEntry(stats.Blkio.IoTimeRecursive),
    70  			SectorsRecursive:        copyBlkioEntry(stats.Blkio.SectorsRecursive),
    71  		}
    72  	}
    73  	if stats.CPU != nil {
    74  		s.CPUStats = types.CPUStats{
    75  			CPUUsage: types.CPUUsage{
    76  				TotalUsage:        stats.CPU.Usage.Total,
    77  				PercpuUsage:       stats.CPU.Usage.PerCPU,
    78  				UsageInKernelmode: stats.CPU.Usage.Kernel,
    79  				UsageInUsermode:   stats.CPU.Usage.User,
    80  			},
    81  			ThrottlingData: types.ThrottlingData{
    82  				Periods:          stats.CPU.Throttling.Periods,
    83  				ThrottledPeriods: stats.CPU.Throttling.ThrottledPeriods,
    84  				ThrottledTime:    stats.CPU.Throttling.ThrottledTime,
    85  			},
    86  		}
    87  	}
    88  
    89  	if stats.Memory != nil {
    90  		raw := map[string]uint64{
    91  			"cache":                     stats.Memory.Cache,
    92  			"rss":                       stats.Memory.RSS,
    93  			"rss_huge":                  stats.Memory.RSSHuge,
    94  			"mapped_file":               stats.Memory.MappedFile,
    95  			"dirty":                     stats.Memory.Dirty,
    96  			"writeback":                 stats.Memory.Writeback,
    97  			"pgpgin":                    stats.Memory.PgPgIn,
    98  			"pgpgout":                   stats.Memory.PgPgOut,
    99  			"pgfault":                   stats.Memory.PgFault,
   100  			"pgmajfault":                stats.Memory.PgMajFault,
   101  			"inactive_anon":             stats.Memory.InactiveAnon,
   102  			"active_anon":               stats.Memory.ActiveAnon,
   103  			"inactive_file":             stats.Memory.InactiveFile,
   104  			"active_file":               stats.Memory.ActiveFile,
   105  			"unevictable":               stats.Memory.Unevictable,
   106  			"hierarchical_memory_limit": stats.Memory.HierarchicalMemoryLimit,
   107  			"hierarchical_memsw_limit":  stats.Memory.HierarchicalSwapLimit,
   108  			"total_cache":               stats.Memory.TotalCache,
   109  			"total_rss":                 stats.Memory.TotalRSS,
   110  			"total_rss_huge":            stats.Memory.TotalRSSHuge,
   111  			"total_mapped_file":         stats.Memory.TotalMappedFile,
   112  			"total_dirty":               stats.Memory.TotalDirty,
   113  			"total_writeback":           stats.Memory.TotalWriteback,
   114  			"total_pgpgin":              stats.Memory.TotalPgPgIn,
   115  			"total_pgpgout":             stats.Memory.TotalPgPgOut,
   116  			"total_pgfault":             stats.Memory.TotalPgFault,
   117  			"total_pgmajfault":          stats.Memory.TotalPgMajFault,
   118  			"total_inactive_anon":       stats.Memory.TotalInactiveAnon,
   119  			"total_active_anon":         stats.Memory.TotalActiveAnon,
   120  			"total_inactive_file":       stats.Memory.TotalInactiveFile,
   121  			"total_active_file":         stats.Memory.TotalActiveFile,
   122  			"total_unevictable":         stats.Memory.TotalUnevictable,
   123  		}
   124  		if stats.Memory.Usage != nil {
   125  			s.MemoryStats = types.MemoryStats{
   126  				Stats:    raw,
   127  				Usage:    stats.Memory.Usage.Usage,
   128  				MaxUsage: stats.Memory.Usage.Max,
   129  				Limit:    stats.Memory.Usage.Limit,
   130  				Failcnt:  stats.Memory.Usage.Failcnt,
   131  			}
   132  		} else {
   133  			s.MemoryStats = types.MemoryStats{
   134  				Stats: raw,
   135  			}
   136  		}
   137  
   138  		// if the container does not set memory limit, use the machineMemory
   139  		if s.MemoryStats.Limit > daemon.machineMemory && daemon.machineMemory > 0 {
   140  			s.MemoryStats.Limit = daemon.machineMemory
   141  		}
   142  	}
   143  
   144  	if stats.Pids != nil {
   145  		s.PidsStats = types.PidsStats{
   146  			Current: stats.Pids.Current,
   147  			Limit:   stats.Pids.Limit,
   148  		}
   149  	}
   150  
   151  	return s, nil
   152  }
   153  
   154  func (daemon *Daemon) statsV2(s *types.StatsJSON, stats *statsV2.Metrics) (*types.StatsJSON, error) {
   155  	if stats.Io != nil {
   156  		var isbr []types.BlkioStatEntry
   157  		for _, re := range stats.Io.Usage {
   158  			isbr = append(isbr,
   159  				types.BlkioStatEntry{
   160  					Major: re.Major,
   161  					Minor: re.Minor,
   162  					Op:    "read",
   163  					Value: re.Rbytes,
   164  				},
   165  				types.BlkioStatEntry{
   166  					Major: re.Major,
   167  					Minor: re.Minor,
   168  					Op:    "write",
   169  					Value: re.Wbytes,
   170  				},
   171  			)
   172  		}
   173  		s.BlkioStats = types.BlkioStats{
   174  			IoServiceBytesRecursive: isbr,
   175  			// Other fields are unsupported
   176  		}
   177  	}
   178  
   179  	if stats.CPU != nil {
   180  		s.CPUStats = types.CPUStats{
   181  			CPUUsage: types.CPUUsage{
   182  				TotalUsage: stats.CPU.UsageUsec * 1000,
   183  				// PercpuUsage is not supported
   184  				UsageInKernelmode: stats.CPU.SystemUsec * 1000,
   185  				UsageInUsermode:   stats.CPU.UserUsec * 1000,
   186  			},
   187  			ThrottlingData: types.ThrottlingData{
   188  				Periods:          stats.CPU.NrPeriods,
   189  				ThrottledPeriods: stats.CPU.NrThrottled,
   190  				ThrottledTime:    stats.CPU.ThrottledUsec * 1000,
   191  			},
   192  		}
   193  	}
   194  
   195  	if stats.Memory != nil {
   196  		s.MemoryStats = types.MemoryStats{
   197  			// Stats is not compatible with v1
   198  			Stats: map[string]uint64{
   199  				"anon":                   stats.Memory.Anon,
   200  				"file":                   stats.Memory.File,
   201  				"kernel_stack":           stats.Memory.KernelStack,
   202  				"slab":                   stats.Memory.Slab,
   203  				"sock":                   stats.Memory.Sock,
   204  				"shmem":                  stats.Memory.Shmem,
   205  				"file_mapped":            stats.Memory.FileMapped,
   206  				"file_dirty":             stats.Memory.FileDirty,
   207  				"file_writeback":         stats.Memory.FileWriteback,
   208  				"anon_thp":               stats.Memory.AnonThp,
   209  				"inactive_anon":          stats.Memory.InactiveAnon,
   210  				"active_anon":            stats.Memory.ActiveAnon,
   211  				"inactive_file":          stats.Memory.InactiveFile,
   212  				"active_file":            stats.Memory.ActiveFile,
   213  				"unevictable":            stats.Memory.Unevictable,
   214  				"slab_reclaimable":       stats.Memory.SlabReclaimable,
   215  				"slab_unreclaimable":     stats.Memory.SlabUnreclaimable,
   216  				"pgfault":                stats.Memory.Pgfault,
   217  				"pgmajfault":             stats.Memory.Pgmajfault,
   218  				"workingset_refault":     stats.Memory.WorkingsetRefault,
   219  				"workingset_activate":    stats.Memory.WorkingsetActivate,
   220  				"workingset_nodereclaim": stats.Memory.WorkingsetNodereclaim,
   221  				"pgrefill":               stats.Memory.Pgrefill,
   222  				"pgscan":                 stats.Memory.Pgscan,
   223  				"pgsteal":                stats.Memory.Pgsteal,
   224  				"pgactivate":             stats.Memory.Pgactivate,
   225  				"pgdeactivate":           stats.Memory.Pgdeactivate,
   226  				"pglazyfree":             stats.Memory.Pglazyfree,
   227  				"pglazyfreed":            stats.Memory.Pglazyfreed,
   228  				"thp_fault_alloc":        stats.Memory.ThpFaultAlloc,
   229  				"thp_collapse_alloc":     stats.Memory.ThpCollapseAlloc,
   230  			},
   231  			Usage: stats.Memory.Usage,
   232  			// MaxUsage is not supported
   233  			Limit: stats.Memory.UsageLimit,
   234  		}
   235  		// if the container does not set memory limit, use the machineMemory
   236  		if s.MemoryStats.Limit > daemon.machineMemory && daemon.machineMemory > 0 {
   237  			s.MemoryStats.Limit = daemon.machineMemory
   238  		}
   239  		if stats.MemoryEvents != nil {
   240  			// Failcnt is set to the "oom" field of the "memory.events" file.
   241  			// See https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
   242  			s.MemoryStats.Failcnt = stats.MemoryEvents.Oom
   243  		}
   244  	}
   245  
   246  	if stats.Pids != nil {
   247  		s.PidsStats = types.PidsStats{
   248  			Current: stats.Pids.Current,
   249  			Limit:   stats.Pids.Limit,
   250  		}
   251  	}
   252  
   253  	return s, nil
   254  }
   255  
   256  // Resolve Network SandboxID in case the container reuse another container's network stack
   257  func (daemon *Daemon) getNetworkSandboxID(c *container.Container) (string, error) {
   258  	curr := c
   259  	for curr.HostConfig.NetworkMode.IsContainer() {
   260  		containerID := curr.HostConfig.NetworkMode.ConnectedContainer()
   261  		connected, err := daemon.GetContainer(containerID)
   262  		if err != nil {
   263  			return "", errors.Wrapf(err, "Could not get container for %s", containerID)
   264  		}
   265  		curr = connected
   266  	}
   267  	return curr.NetworkSettings.SandboxID, nil
   268  }
   269  
   270  func (daemon *Daemon) getNetworkStats(c *container.Container) (map[string]types.NetworkStats, error) {
   271  	sandboxID, err := daemon.getNetworkSandboxID(c)
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  
   276  	sb, err := daemon.netController.SandboxByID(sandboxID)
   277  	if err != nil {
   278  		return nil, err
   279  	}
   280  
   281  	lnstats, err := sb.Statistics()
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  
   286  	stats := make(map[string]types.NetworkStats)
   287  	// Convert libnetwork nw stats into api stats
   288  	for ifName, ifStats := range lnstats {
   289  		stats[ifName] = types.NetworkStats{
   290  			RxBytes:   ifStats.RxBytes,
   291  			RxPackets: ifStats.RxPackets,
   292  			RxErrors:  ifStats.RxErrors,
   293  			RxDropped: ifStats.RxDropped,
   294  			TxBytes:   ifStats.TxBytes,
   295  			TxPackets: ifStats.TxPackets,
   296  			TxErrors:  ifStats.TxErrors,
   297  			TxDropped: ifStats.TxDropped,
   298  		}
   299  	}
   300  
   301  	return stats, nil
   302  }
   303  
   304  const (
   305  	// The value comes from `C.sysconf(C._SC_CLK_TCK)`, and
   306  	// on Linux it's a constant which is safe to be hard coded,
   307  	// so we can avoid using cgo here. For details, see:
   308  	// https://github.com/containerd/cgroups/pull/12
   309  	clockTicksPerSecond  = 100
   310  	nanoSecondsPerSecond = 1e9
   311  )
   312  
   313  // getSystemCPUUsage returns the host system's cpu usage in
   314  // nanoseconds and number of online CPUs. An error is returned
   315  // if the format of the underlying file does not match.
   316  //
   317  // Uses /proc/stat defined by POSIX. Looks for the cpu
   318  // statistics line and then sums up the first seven fields
   319  // provided. See `man 5 proc` for details on specific field
   320  // information.
   321  func getSystemCPUUsage() (cpuUsage uint64, cpuNum uint32, err error) {
   322  	f, err := os.Open("/proc/stat")
   323  	if err != nil {
   324  		return 0, 0, err
   325  	}
   326  	defer f.Close()
   327  
   328  	scanner := bufio.NewScanner(f)
   329  	for scanner.Scan() {
   330  		line := scanner.Text()
   331  		if len(line) < 4 || line[:3] != "cpu" {
   332  			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
   333  		}
   334  		if line[3] == ' ' {
   335  			parts := strings.Fields(line)
   336  			if len(parts) < 8 {
   337  				return 0, 0, fmt.Errorf("invalid number of cpu fields")
   338  			}
   339  			var totalClockTicks uint64
   340  			for _, i := range parts[1:8] {
   341  				v, err := strconv.ParseUint(i, 10, 64)
   342  				if err != nil {
   343  					return 0, 0, fmt.Errorf("Unable to convert value %s to int: %w", i, err)
   344  				}
   345  				totalClockTicks += v
   346  			}
   347  			cpuUsage = (totalClockTicks * nanoSecondsPerSecond) /
   348  				clockTicksPerSecond
   349  		}
   350  		if '0' <= line[3] && line[3] <= '9' {
   351  			cpuNum++
   352  		}
   353  	}
   354  
   355  	if err := scanner.Err(); err != nil {
   356  		return 0, 0, fmt.Errorf("error scanning '/proc/stat' file: %w", err)
   357  	}
   358  	return
   359  }