github.com/google/cadvisor@v0.49.1/container/libcontainer/handler.go (about)

     1  // Copyright 2018 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package libcontainer
    16  
    17  import (
    18  	"bufio"
    19  	"bytes"
    20  	"encoding/json"
    21  	"flag"
    22  	"fmt"
    23  	"io"
    24  	"os"
    25  	"path"
    26  	"regexp"
    27  	"strconv"
    28  	"strings"
    29  	"time"
    30  
    31  	"github.com/opencontainers/runc/libcontainer"
    32  	"github.com/opencontainers/runc/libcontainer/cgroups"
    33  	"github.com/opencontainers/runc/libcontainer/cgroups/fs2"
    34  	"k8s.io/klog/v2"
    35  
    36  	"github.com/google/cadvisor/container"
    37  	"github.com/google/cadvisor/container/common"
    38  	info "github.com/google/cadvisor/info/v1"
    39  )
    40  
    41  var (
    42  	referencedResetInterval = flag.Uint64("referenced_reset_interval", 0,
    43  		"Reset interval for referenced bytes (container_referenced_bytes metric), number of measurement cycles after which referenced bytes are cleared, if set to 0 referenced bytes are never cleared (default: 0)")
    44  
    45  	smapsFilePathPattern     = "/proc/%d/smaps"
    46  	clearRefsFilePathPattern = "/proc/%d/clear_refs"
    47  
    48  	referencedRegexp = regexp.MustCompile(`Referenced:\s*([0-9]+)\s*kB`)
    49  )
    50  
    51  type Handler struct {
    52  	cgroupManager   cgroups.Manager
    53  	rootFs          string
    54  	pid             int
    55  	includedMetrics container.MetricSet
    56  	// pidMetricsCache holds CPU scheduler stats for existing processes (map key is PID) between calls to schedulerStatsFromProcs.
    57  	pidMetricsCache map[int]*info.CpuSchedstat
    58  	// pidMetricsSaved holds accumulated CPU scheduler stats for processes that no longer exist.
    59  	pidMetricsSaved info.CpuSchedstat
    60  	cycles          uint64
    61  }
    62  
    63  func NewHandler(cgroupManager cgroups.Manager, rootFs string, pid int, includedMetrics container.MetricSet) *Handler {
    64  	return &Handler{
    65  		cgroupManager:   cgroupManager,
    66  		rootFs:          rootFs,
    67  		pid:             pid,
    68  		includedMetrics: includedMetrics,
    69  		pidMetricsCache: make(map[int]*info.CpuSchedstat),
    70  	}
    71  }
    72  
    73  // Get cgroup and networking stats of the specified container
    74  func (h *Handler) GetStats() (*info.ContainerStats, error) {
    75  	ignoreStatsError := false
    76  	if cgroups.IsCgroup2UnifiedMode() {
    77  		// On cgroup v2 the root cgroup stats have been introduced in recent kernel versions,
    78  		// so not all kernel versions have all the data. This means that stat fetching can fail
    79  		// due to lacking cgroup stat files, but that some data is provided.
    80  		if h.cgroupManager.Path("") == fs2.UnifiedMountpoint {
    81  			ignoreStatsError = true
    82  		}
    83  	}
    84  
    85  	cgroupStats, err := h.cgroupManager.GetStats()
    86  	if err != nil {
    87  		if !ignoreStatsError {
    88  			return nil, err
    89  		}
    90  		klog.V(4).Infof("Ignoring errors when gathering stats for root cgroup since some controllers don't have stats on the root cgroup: %v", err)
    91  	}
    92  	libcontainerStats := &libcontainer.Stats{
    93  		CgroupStats: cgroupStats,
    94  	}
    95  	stats := newContainerStats(libcontainerStats, h.includedMetrics)
    96  
    97  	if h.includedMetrics.Has(container.ProcessSchedulerMetrics) {
    98  		stats.Cpu.Schedstat, err = h.schedulerStatsFromProcs()
    99  		if err != nil {
   100  			klog.V(4).Infof("Unable to get Process Scheduler Stats: %v", err)
   101  		}
   102  	}
   103  
   104  	if h.includedMetrics.Has(container.ReferencedMemoryMetrics) {
   105  		h.cycles++
   106  		pids, err := h.cgroupManager.GetPids()
   107  		if err != nil {
   108  			klog.V(4).Infof("Could not get PIDs for container %d: %v", h.pid, err)
   109  		} else {
   110  			stats.ReferencedMemory, err = referencedBytesStat(pids, h.cycles, *referencedResetInterval)
   111  			if err != nil {
   112  				klog.V(4).Infof("Unable to get referenced bytes: %v", err)
   113  			}
   114  		}
   115  	}
   116  
   117  	// If we know the pid then get network stats from /proc/<pid>/net/dev
   118  	if h.pid > 0 {
   119  		if h.includedMetrics.Has(container.NetworkUsageMetrics) {
   120  			netStats, err := networkStatsFromProc(h.rootFs, h.pid)
   121  			if err != nil {
   122  				klog.V(4).Infof("Unable to get network stats from pid %d: %v", h.pid, err)
   123  			} else {
   124  				stats.Network.Interfaces = append(stats.Network.Interfaces, netStats...)
   125  			}
   126  		}
   127  		if h.includedMetrics.Has(container.NetworkTcpUsageMetrics) {
   128  			t, err := tcpStatsFromProc(h.rootFs, h.pid, "net/tcp")
   129  			if err != nil {
   130  				klog.V(4).Infof("Unable to get tcp stats from pid %d: %v", h.pid, err)
   131  			} else {
   132  				stats.Network.Tcp = t
   133  			}
   134  
   135  			t6, err := tcpStatsFromProc(h.rootFs, h.pid, "net/tcp6")
   136  			if err != nil {
   137  				klog.V(4).Infof("Unable to get tcp6 stats from pid %d: %v", h.pid, err)
   138  			} else {
   139  				stats.Network.Tcp6 = t6
   140  			}
   141  
   142  		}
   143  		if h.includedMetrics.Has(container.NetworkAdvancedTcpUsageMetrics) {
   144  			ta, err := advancedTCPStatsFromProc(h.rootFs, h.pid, "net/netstat", "net/snmp")
   145  			if err != nil {
   146  				klog.V(4).Infof("Unable to get advanced tcp stats from pid %d: %v", h.pid, err)
   147  			} else {
   148  				stats.Network.TcpAdvanced = ta
   149  			}
   150  		}
   151  		if h.includedMetrics.Has(container.NetworkUdpUsageMetrics) {
   152  			u, err := udpStatsFromProc(h.rootFs, h.pid, "net/udp")
   153  			if err != nil {
   154  				klog.V(4).Infof("Unable to get udp stats from pid %d: %v", h.pid, err)
   155  			} else {
   156  				stats.Network.Udp = u
   157  			}
   158  
   159  			u6, err := udpStatsFromProc(h.rootFs, h.pid, "net/udp6")
   160  			if err != nil {
   161  				klog.V(4).Infof("Unable to get udp6 stats from pid %d: %v", h.pid, err)
   162  			} else {
   163  				stats.Network.Udp6 = u6
   164  			}
   165  		}
   166  	}
   167  	// some process metrics are per container ( number of processes, number of
   168  	// file descriptors etc.) and not required a proper container's
   169  	// root PID (systemd services don't have the root PID atm)
   170  	if h.includedMetrics.Has(container.ProcessMetrics) {
   171  		path, ok := common.GetControllerPath(h.cgroupManager.GetPaths(), "cpu", cgroups.IsCgroup2UnifiedMode())
   172  		if !ok {
   173  			klog.V(4).Infof("Could not find cgroups CPU for container %d", h.pid)
   174  		} else {
   175  			stats.Processes, err = processStatsFromProcs(h.rootFs, path, h.pid)
   176  			if err != nil {
   177  				klog.V(4).Infof("Unable to get Process Stats: %v", err)
   178  			}
   179  		}
   180  
   181  		// if include processes metrics, just set threads metrics if exist, and has no relationship with cpu path
   182  		setThreadsStats(cgroupStats, stats)
   183  	}
   184  
   185  	// For backwards compatibility.
   186  	if len(stats.Network.Interfaces) > 0 {
   187  		stats.Network.InterfaceStats = stats.Network.Interfaces[0]
   188  	}
   189  
   190  	return stats, nil
   191  }
   192  
   193  func parseUlimit(value string) (int64, error) {
   194  	num, err := strconv.ParseInt(value, 10, 64)
   195  	if err != nil {
   196  		if strings.EqualFold(value, "unlimited") {
   197  			// -1 implies unlimited except for priority and nice; man limits.conf
   198  			num = -1
   199  		} else {
   200  			// Value is not a number or "unlimited"; return an error
   201  			return 0, fmt.Errorf("unable to parse limit: %s", value)
   202  		}
   203  	}
   204  	return num, nil
   205  }
   206  
   207  func processLimitsFile(fileData string) []info.UlimitSpec {
   208  	const maxOpenFilesLinePrefix = "Max open files"
   209  
   210  	limits := strings.Split(fileData, "\n")
   211  	ulimits := make([]info.UlimitSpec, 0, len(limits))
   212  	for _, lim := range limits {
   213  		// Skip any headers/footers
   214  		if strings.HasPrefix(lim, "Max open files") {
   215  			// Remove line prefix
   216  			ulimit, err := processMaxOpenFileLimitLine(
   217  				"max_open_files",
   218  				lim[len(maxOpenFilesLinePrefix):],
   219  			)
   220  			if err == nil {
   221  				ulimits = append(ulimits, ulimit)
   222  			}
   223  		}
   224  	}
   225  	return ulimits
   226  }
   227  
   228  // Any caller of processMaxOpenFileLimitLine must ensure that the name prefix is already removed from the limit line.
   229  // with the "Max open files" prefix.
   230  func processMaxOpenFileLimitLine(name, line string) (info.UlimitSpec, error) {
   231  	// Remove any leading whitespace
   232  	line = strings.TrimSpace(line)
   233  	// Split on whitespace
   234  	fields := strings.Fields(line)
   235  	if len(fields) != 3 {
   236  		return info.UlimitSpec{}, fmt.Errorf("unable to parse max open files line: %s", line)
   237  	}
   238  	// The first field is the soft limit, the second is the hard limit
   239  	soft, err := parseUlimit(fields[0])
   240  	if err != nil {
   241  		return info.UlimitSpec{}, err
   242  	}
   243  	hard, err := parseUlimit(fields[1])
   244  	if err != nil {
   245  		return info.UlimitSpec{}, err
   246  	}
   247  	return info.UlimitSpec{
   248  		Name:      name,
   249  		SoftLimit: soft,
   250  		HardLimit: hard,
   251  	}, nil
   252  }
   253  
   254  func processRootProcUlimits(rootFs string, rootPid int) []info.UlimitSpec {
   255  	filePath := path.Join(rootFs, "/proc", strconv.Itoa(rootPid), "limits")
   256  	out, err := os.ReadFile(filePath)
   257  	if err != nil {
   258  		klog.V(4).Infof("error while listing directory %q to read ulimits: %v", filePath, err)
   259  		return []info.UlimitSpec{}
   260  	}
   261  	return processLimitsFile(string(out))
   262  }
   263  
   264  func processStatsFromProcs(rootFs string, cgroupPath string, rootPid int) (info.ProcessStats, error) {
   265  	var fdCount, socketCount uint64
   266  	filePath := path.Join(cgroupPath, "cgroup.procs")
   267  	out, err := os.ReadFile(filePath)
   268  	if err != nil {
   269  		return info.ProcessStats{}, fmt.Errorf("couldn't open cpu cgroup procs file %v : %v", filePath, err)
   270  	}
   271  
   272  	pids := strings.Split(string(out), "\n")
   273  
   274  	// EOL is also treated as a new line while reading "cgroup.procs" file with os.ReadFile.
   275  	// The last value is an empty string "". Ex: pids = ["22", "1223", ""]
   276  	// Trim the last value
   277  	if len(pids) != 0 && pids[len(pids)-1] == "" {
   278  		pids = pids[:len(pids)-1]
   279  	}
   280  
   281  	for _, pid := range pids {
   282  		dirPath := path.Join(rootFs, "/proc", pid, "fd")
   283  		fds, err := os.ReadDir(dirPath)
   284  		if err != nil {
   285  			klog.V(4).Infof("error while listing directory %q to measure fd count: %v", dirPath, err)
   286  			continue
   287  		}
   288  		fdCount += uint64(len(fds))
   289  		for _, fd := range fds {
   290  			fdPath := path.Join(dirPath, fd.Name())
   291  			linkName, err := os.Readlink(fdPath)
   292  			if err != nil {
   293  				klog.V(4).Infof("error while reading %q link: %v", fdPath, err)
   294  				continue
   295  			}
   296  			if strings.HasPrefix(linkName, "socket") {
   297  				socketCount++
   298  			}
   299  		}
   300  	}
   301  
   302  	processStats := info.ProcessStats{
   303  		ProcessCount: uint64(len(pids)),
   304  		FdCount:      fdCount,
   305  		SocketCount:  socketCount,
   306  	}
   307  
   308  	if rootPid > 0 {
   309  		processStats.Ulimits = processRootProcUlimits(rootFs, rootPid)
   310  	}
   311  
   312  	return processStats, nil
   313  }
   314  
   315  func (h *Handler) schedulerStatsFromProcs() (info.CpuSchedstat, error) {
   316  	pids, err := h.cgroupManager.GetAllPids()
   317  	if err != nil {
   318  		return info.CpuSchedstat{}, fmt.Errorf("Could not get PIDs for container %d: %w", h.pid, err)
   319  	}
   320  	alivePids := make(map[int]struct{}, len(pids))
   321  	for _, pid := range pids {
   322  		f, err := os.Open(path.Join(h.rootFs, "proc", strconv.Itoa(pid), "schedstat"))
   323  		if err != nil {
   324  			return info.CpuSchedstat{}, fmt.Errorf("couldn't open scheduler statistics for process %d: %v", pid, err)
   325  		}
   326  		defer f.Close()
   327  		contents, err := io.ReadAll(f)
   328  		if err != nil {
   329  			return info.CpuSchedstat{}, fmt.Errorf("couldn't read scheduler statistics for process %d: %v", pid, err)
   330  		}
   331  		alivePids[pid] = struct{}{}
   332  		rawMetrics := bytes.Split(bytes.TrimRight(contents, "\n"), []byte(" "))
   333  		if len(rawMetrics) != 3 {
   334  			return info.CpuSchedstat{}, fmt.Errorf("unexpected number of metrics in schedstat file for process %d", pid)
   335  		}
   336  		cacheEntry, ok := h.pidMetricsCache[pid]
   337  		if !ok {
   338  			cacheEntry = &info.CpuSchedstat{}
   339  			h.pidMetricsCache[pid] = cacheEntry
   340  		}
   341  		for i, rawMetric := range rawMetrics {
   342  			metric, err := strconv.ParseUint(string(rawMetric), 10, 64)
   343  			if err != nil {
   344  				return info.CpuSchedstat{}, fmt.Errorf("parsing error while reading scheduler statistics for process: %d: %v", pid, err)
   345  			}
   346  			switch i {
   347  			case 0:
   348  				cacheEntry.RunTime = metric
   349  			case 1:
   350  				cacheEntry.RunqueueTime = metric
   351  			case 2:
   352  				cacheEntry.RunPeriods = metric
   353  			}
   354  		}
   355  	}
   356  	schedstats := h.pidMetricsSaved // copy
   357  	for p, v := range h.pidMetricsCache {
   358  		schedstats.RunPeriods += v.RunPeriods
   359  		schedstats.RunqueueTime += v.RunqueueTime
   360  		schedstats.RunTime += v.RunTime
   361  		if _, alive := alivePids[p]; !alive {
   362  			// PID p is gone: accumulate its stats ...
   363  			h.pidMetricsSaved.RunPeriods += v.RunPeriods
   364  			h.pidMetricsSaved.RunqueueTime += v.RunqueueTime
   365  			h.pidMetricsSaved.RunTime += v.RunTime
   366  			// ... and remove its cache entry, to prevent
   367  			// pidMetricsCache from growing.
   368  			delete(h.pidMetricsCache, p)
   369  		}
   370  	}
   371  	return schedstats, nil
   372  }
   373  
   374  // referencedBytesStat gets and clears referenced bytes
   375  // see: https://github.com/brendangregg/wss#wsspl-referenced-page-flag
   376  func referencedBytesStat(pids []int, cycles uint64, resetInterval uint64) (uint64, error) {
   377  	referencedKBytes, err := getReferencedKBytes(pids)
   378  	if err != nil {
   379  		return uint64(0), err
   380  	}
   381  
   382  	err = clearReferencedBytes(pids, cycles, resetInterval)
   383  	if err != nil {
   384  		return uint64(0), err
   385  	}
   386  	return referencedKBytes * 1024, nil
   387  }
   388  
   389  func getReferencedKBytes(pids []int) (uint64, error) {
   390  	referencedKBytes := uint64(0)
   391  	readSmapsContent := false
   392  	foundMatch := false
   393  	for _, pid := range pids {
   394  		smapsFilePath := fmt.Sprintf(smapsFilePathPattern, pid)
   395  		smapsContent, err := os.ReadFile(smapsFilePath)
   396  		if err != nil {
   397  			klog.V(5).Infof("Cannot read %s file, err: %s", smapsFilePath, err)
   398  			if os.IsNotExist(err) {
   399  				continue // smaps file does not exists for all PIDs
   400  			}
   401  			return 0, err
   402  		}
   403  		readSmapsContent = true
   404  
   405  		allMatches := referencedRegexp.FindAllSubmatch(smapsContent, -1)
   406  		if len(allMatches) == 0 {
   407  			klog.V(5).Infof("Not found any information about referenced bytes in %s file", smapsFilePath)
   408  			continue // referenced bytes may not exist in smaps file
   409  		}
   410  
   411  		for _, matches := range allMatches {
   412  			if len(matches) != 2 {
   413  				return 0, fmt.Errorf("failed to match regexp in output: %s", string(smapsContent))
   414  			}
   415  			foundMatch = true
   416  			referenced, err := strconv.ParseUint(string(matches[1]), 10, 64)
   417  			if err != nil {
   418  				return 0, err
   419  			}
   420  			referencedKBytes += referenced
   421  		}
   422  	}
   423  
   424  	if len(pids) != 0 {
   425  		if !readSmapsContent {
   426  			klog.Warningf("Cannot read smaps files for any PID from %s", "CONTAINER")
   427  		} else if !foundMatch {
   428  			klog.Warningf("Not found any information about referenced bytes in smaps files for any PID from %s", "CONTAINER")
   429  		}
   430  	}
   431  	return referencedKBytes, nil
   432  }
   433  
   434  func clearReferencedBytes(pids []int, cycles uint64, resetInterval uint64) error {
   435  	if resetInterval == 0 {
   436  		return nil
   437  	}
   438  
   439  	if cycles%resetInterval == 0 {
   440  		for _, pid := range pids {
   441  			clearRefsFilePath := fmt.Sprintf(clearRefsFilePathPattern, pid)
   442  			clerRefsFile, err := os.OpenFile(clearRefsFilePath, os.O_WRONLY, 0o644)
   443  			if err != nil {
   444  				// clear_refs file may not exist for all PIDs
   445  				continue
   446  			}
   447  			_, err = clerRefsFile.WriteString("1\n")
   448  			if err != nil {
   449  				return err
   450  			}
   451  			err = clerRefsFile.Close()
   452  			if err != nil {
   453  				return err
   454  			}
   455  		}
   456  	}
   457  	return nil
   458  }
   459  
   460  func networkStatsFromProc(rootFs string, pid int) ([]info.InterfaceStats, error) {
   461  	netStatsFile := path.Join(rootFs, "proc", strconv.Itoa(pid), "/net/dev")
   462  
   463  	ifaceStats, err := scanInterfaceStats(netStatsFile)
   464  	if err != nil {
   465  		return []info.InterfaceStats{}, fmt.Errorf("couldn't read network stats: %v", err)
   466  	}
   467  
   468  	return ifaceStats, nil
   469  }
   470  
   471  var ignoredDevicePrefixes = []string{"lo", "veth", "docker", "nerdctl"}
   472  
   473  func isIgnoredDevice(ifName string) bool {
   474  	for _, prefix := range ignoredDevicePrefixes {
   475  		if strings.HasPrefix(strings.ToLower(ifName), prefix) {
   476  			return true
   477  		}
   478  	}
   479  	return false
   480  }
   481  
   482  func scanInterfaceStats(netStatsFile string) ([]info.InterfaceStats, error) {
   483  	file, err := os.Open(netStatsFile)
   484  	if err != nil {
   485  		return nil, fmt.Errorf("failure opening %s: %v", netStatsFile, err)
   486  	}
   487  	defer file.Close()
   488  
   489  	scanner := bufio.NewScanner(file)
   490  
   491  	// Discard header lines
   492  	for i := 0; i < 2; i++ {
   493  		if b := scanner.Scan(); !b {
   494  			return nil, scanner.Err()
   495  		}
   496  	}
   497  
   498  	stats := []info.InterfaceStats{}
   499  	for scanner.Scan() {
   500  		line := scanner.Text()
   501  		line = strings.Replace(line, ":", "", -1)
   502  
   503  		fields := strings.Fields(line)
   504  		// If the format of the  line is invalid then don't trust any of the stats
   505  		// in this file.
   506  		if len(fields) != 17 {
   507  			return nil, fmt.Errorf("invalid interface stats line: %v", line)
   508  		}
   509  
   510  		devName := fields[0]
   511  		if isIgnoredDevice(devName) {
   512  			continue
   513  		}
   514  
   515  		i := info.InterfaceStats{
   516  			Name: devName,
   517  		}
   518  
   519  		statFields := append(fields[1:5], fields[9:13]...)
   520  		statPointers := []*uint64{
   521  			&i.RxBytes, &i.RxPackets, &i.RxErrors, &i.RxDropped,
   522  			&i.TxBytes, &i.TxPackets, &i.TxErrors, &i.TxDropped,
   523  		}
   524  
   525  		err := setInterfaceStatValues(statFields, statPointers)
   526  		if err != nil {
   527  			return nil, fmt.Errorf("cannot parse interface stats (%v): %v", err, line)
   528  		}
   529  
   530  		stats = append(stats, i)
   531  	}
   532  
   533  	return stats, nil
   534  }
   535  
   536  func setInterfaceStatValues(fields []string, pointers []*uint64) error {
   537  	for i, v := range fields {
   538  		val, err := strconv.ParseUint(v, 10, 64)
   539  		if err != nil {
   540  			return err
   541  		}
   542  		*pointers[i] = val
   543  	}
   544  	return nil
   545  }
   546  
   547  func tcpStatsFromProc(rootFs string, pid int, file string) (info.TcpStat, error) {
   548  	tcpStatsFile := path.Join(rootFs, "proc", strconv.Itoa(pid), file)
   549  
   550  	tcpStats, err := scanTCPStats(tcpStatsFile)
   551  	if err != nil {
   552  		return tcpStats, fmt.Errorf("couldn't read tcp stats: %v", err)
   553  	}
   554  
   555  	return tcpStats, nil
   556  }
   557  
   558  func advancedTCPStatsFromProc(rootFs string, pid int, file1, file2 string) (info.TcpAdvancedStat, error) {
   559  	var advancedStats info.TcpAdvancedStat
   560  	var err error
   561  
   562  	netstatFile := path.Join(rootFs, "proc", strconv.Itoa(pid), file1)
   563  	err = scanAdvancedTCPStats(&advancedStats, netstatFile)
   564  	if err != nil {
   565  		return advancedStats, err
   566  	}
   567  
   568  	snmpFile := path.Join(rootFs, "proc", strconv.Itoa(pid), file2)
   569  	err = scanAdvancedTCPStats(&advancedStats, snmpFile)
   570  	if err != nil {
   571  		return advancedStats, err
   572  	}
   573  
   574  	return advancedStats, nil
   575  }
   576  
   577  func scanAdvancedTCPStats(advancedStats *info.TcpAdvancedStat, advancedTCPStatsFile string) error {
   578  	data, err := os.ReadFile(advancedTCPStatsFile)
   579  	if err != nil {
   580  		return fmt.Errorf("failure opening %s: %v", advancedTCPStatsFile, err)
   581  	}
   582  
   583  	reader := strings.NewReader(string(data))
   584  	scanner := bufio.NewScanner(reader)
   585  	scanner.Split(bufio.ScanLines)
   586  
   587  	advancedTCPStats := make(map[string]interface{})
   588  	for scanner.Scan() {
   589  		nameParts := strings.Split(scanner.Text(), " ")
   590  		scanner.Scan()
   591  		valueParts := strings.Split(scanner.Text(), " ")
   592  		// Remove trailing :. and ignore non-tcp
   593  		protocol := nameParts[0][:len(nameParts[0])-1]
   594  		if protocol != "TcpExt" && protocol != "Tcp" {
   595  			continue
   596  		}
   597  		if len(nameParts) != len(valueParts) {
   598  			return fmt.Errorf("mismatch field count mismatch in %s: %s",
   599  				advancedTCPStatsFile, protocol)
   600  		}
   601  		for i := 1; i < len(nameParts); i++ {
   602  			if strings.Contains(valueParts[i], "-") {
   603  				vInt64, err := strconv.ParseInt(valueParts[i], 10, 64)
   604  				if err != nil {
   605  					return fmt.Errorf("decode value: %s to int64 error: %s", valueParts[i], err)
   606  				}
   607  				advancedTCPStats[nameParts[i]] = vInt64
   608  			} else {
   609  				vUint64, err := strconv.ParseUint(valueParts[i], 10, 64)
   610  				if err != nil {
   611  					return fmt.Errorf("decode value: %s to uint64 error: %s", valueParts[i], err)
   612  				}
   613  				advancedTCPStats[nameParts[i]] = vUint64
   614  			}
   615  		}
   616  	}
   617  
   618  	b, err := json.Marshal(advancedTCPStats)
   619  	if err != nil {
   620  		return err
   621  	}
   622  
   623  	err = json.Unmarshal(b, advancedStats)
   624  	if err != nil {
   625  		return err
   626  	}
   627  
   628  	return scanner.Err()
   629  }
   630  
   631  func scanTCPStats(tcpStatsFile string) (info.TcpStat, error) {
   632  	var stats info.TcpStat
   633  
   634  	data, err := os.ReadFile(tcpStatsFile)
   635  	if err != nil {
   636  		return stats, fmt.Errorf("failure opening %s: %v", tcpStatsFile, err)
   637  	}
   638  
   639  	tcpStateMap := map[string]uint64{
   640  		"01": 0, // ESTABLISHED
   641  		"02": 0, // SYN_SENT
   642  		"03": 0, // SYN_RECV
   643  		"04": 0, // FIN_WAIT1
   644  		"05": 0, // FIN_WAIT2
   645  		"06": 0, // TIME_WAIT
   646  		"07": 0, // CLOSE
   647  		"08": 0, // CLOSE_WAIT
   648  		"09": 0, // LAST_ACK
   649  		"0A": 0, // LISTEN
   650  		"0B": 0, // CLOSING
   651  	}
   652  
   653  	reader := strings.NewReader(string(data))
   654  	scanner := bufio.NewScanner(reader)
   655  
   656  	scanner.Split(bufio.ScanLines)
   657  
   658  	// Discard header line
   659  	if b := scanner.Scan(); !b {
   660  		return stats, scanner.Err()
   661  	}
   662  
   663  	for scanner.Scan() {
   664  		line := scanner.Text()
   665  
   666  		state := strings.Fields(line)
   667  		// TCP state is the 4th field.
   668  		// Format: sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt  uid timeout inode
   669  		tcpState := state[3]
   670  		_, ok := tcpStateMap[tcpState]
   671  		if !ok {
   672  			return stats, fmt.Errorf("invalid TCP stats line: %v", line)
   673  		}
   674  		tcpStateMap[tcpState]++
   675  	}
   676  
   677  	stats = info.TcpStat{
   678  		Established: tcpStateMap["01"],
   679  		SynSent:     tcpStateMap["02"],
   680  		SynRecv:     tcpStateMap["03"],
   681  		FinWait1:    tcpStateMap["04"],
   682  		FinWait2:    tcpStateMap["05"],
   683  		TimeWait:    tcpStateMap["06"],
   684  		Close:       tcpStateMap["07"],
   685  		CloseWait:   tcpStateMap["08"],
   686  		LastAck:     tcpStateMap["09"],
   687  		Listen:      tcpStateMap["0A"],
   688  		Closing:     tcpStateMap["0B"],
   689  	}
   690  
   691  	return stats, nil
   692  }
   693  
   694  func udpStatsFromProc(rootFs string, pid int, file string) (info.UdpStat, error) {
   695  	var err error
   696  	var udpStats info.UdpStat
   697  
   698  	udpStatsFile := path.Join(rootFs, "proc", strconv.Itoa(pid), file)
   699  
   700  	r, err := os.Open(udpStatsFile)
   701  	if err != nil {
   702  		return udpStats, fmt.Errorf("failure opening %s: %v", udpStatsFile, err)
   703  	}
   704  
   705  	udpStats, err = scanUDPStats(r)
   706  	if err != nil {
   707  		return udpStats, fmt.Errorf("couldn't read udp stats: %v", err)
   708  	}
   709  
   710  	return udpStats, nil
   711  }
   712  
   713  func scanUDPStats(r io.Reader) (info.UdpStat, error) {
   714  	var stats info.UdpStat
   715  
   716  	scanner := bufio.NewScanner(r)
   717  	scanner.Split(bufio.ScanLines)
   718  
   719  	// Discard header line
   720  	if b := scanner.Scan(); !b {
   721  		return stats, scanner.Err()
   722  	}
   723  
   724  	listening := uint64(0)
   725  	dropped := uint64(0)
   726  	rxQueued := uint64(0)
   727  	txQueued := uint64(0)
   728  
   729  	for scanner.Scan() {
   730  		line := scanner.Text()
   731  		// Format: sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt  uid timeout inode ref pointer drops
   732  
   733  		listening++
   734  
   735  		fs := strings.Fields(line)
   736  		if len(fs) != 13 {
   737  			continue
   738  		}
   739  
   740  		rx, tx := uint64(0), uint64(0)
   741  		fmt.Sscanf(fs[4], "%X:%X", &rx, &tx)
   742  		rxQueued += rx
   743  		txQueued += tx
   744  
   745  		d, err := strconv.Atoi(string(fs[12]))
   746  		if err != nil {
   747  			continue
   748  		}
   749  		dropped += uint64(d)
   750  	}
   751  
   752  	stats = info.UdpStat{
   753  		Listen:   listening,
   754  		Dropped:  dropped,
   755  		RxQueued: rxQueued,
   756  		TxQueued: txQueued,
   757  	}
   758  
   759  	return stats, nil
   760  }
   761  
   762  func (h *Handler) GetProcesses() ([]int, error) {
   763  	pids, err := h.cgroupManager.GetPids()
   764  	if err != nil {
   765  		return nil, err
   766  	}
   767  	return pids, nil
   768  }
   769  
   770  // Convert libcontainer stats to info.ContainerStats.
   771  func setCPUStats(s *cgroups.Stats, ret *info.ContainerStats, withPerCPU bool) {
   772  	ret.Cpu.Usage.User = s.CpuStats.CpuUsage.UsageInUsermode
   773  	ret.Cpu.Usage.System = s.CpuStats.CpuUsage.UsageInKernelmode
   774  	ret.Cpu.Usage.Total = s.CpuStats.CpuUsage.TotalUsage
   775  	ret.Cpu.CFS.Periods = s.CpuStats.ThrottlingData.Periods
   776  	ret.Cpu.CFS.ThrottledPeriods = s.CpuStats.ThrottlingData.ThrottledPeriods
   777  	ret.Cpu.CFS.ThrottledTime = s.CpuStats.ThrottlingData.ThrottledTime
   778  
   779  	if !withPerCPU {
   780  		return
   781  	}
   782  	if len(s.CpuStats.CpuUsage.PercpuUsage) == 0 {
   783  		// libcontainer's 'GetStats' can leave 'PercpuUsage' nil if it skipped the
   784  		// cpuacct subsystem.
   785  		return
   786  	}
   787  	ret.Cpu.Usage.PerCpu = s.CpuStats.CpuUsage.PercpuUsage
   788  }
   789  
   790  func setDiskIoStats(s *cgroups.Stats, ret *info.ContainerStats) {
   791  	ret.DiskIo.IoServiceBytes = diskStatsCopy(s.BlkioStats.IoServiceBytesRecursive)
   792  	ret.DiskIo.IoServiced = diskStatsCopy(s.BlkioStats.IoServicedRecursive)
   793  	ret.DiskIo.IoQueued = diskStatsCopy(s.BlkioStats.IoQueuedRecursive)
   794  	ret.DiskIo.Sectors = diskStatsCopy(s.BlkioStats.SectorsRecursive)
   795  	ret.DiskIo.IoServiceTime = diskStatsCopy(s.BlkioStats.IoServiceTimeRecursive)
   796  	ret.DiskIo.IoWaitTime = diskStatsCopy(s.BlkioStats.IoWaitTimeRecursive)
   797  	ret.DiskIo.IoMerged = diskStatsCopy(s.BlkioStats.IoMergedRecursive)
   798  	ret.DiskIo.IoTime = diskStatsCopy(s.BlkioStats.IoTimeRecursive)
   799  }
   800  
   801  func setMemoryStats(s *cgroups.Stats, ret *info.ContainerStats) {
   802  	ret.Memory.Usage = s.MemoryStats.Usage.Usage
   803  	ret.Memory.MaxUsage = s.MemoryStats.Usage.MaxUsage
   804  	ret.Memory.Failcnt = s.MemoryStats.Usage.Failcnt
   805  	ret.Memory.KernelUsage = s.MemoryStats.KernelUsage.Usage
   806  
   807  	if cgroups.IsCgroup2UnifiedMode() {
   808  		ret.Memory.Cache = s.MemoryStats.Stats["file"]
   809  		ret.Memory.RSS = s.MemoryStats.Stats["anon"]
   810  		ret.Memory.Swap = s.MemoryStats.SwapUsage.Usage - s.MemoryStats.Usage.Usage
   811  		ret.Memory.MappedFile = s.MemoryStats.Stats["file_mapped"]
   812  	} else if s.MemoryStats.UseHierarchy {
   813  		ret.Memory.Cache = s.MemoryStats.Stats["total_cache"]
   814  		ret.Memory.RSS = s.MemoryStats.Stats["total_rss"]
   815  		ret.Memory.Swap = s.MemoryStats.Stats["total_swap"]
   816  		ret.Memory.MappedFile = s.MemoryStats.Stats["total_mapped_file"]
   817  	} else {
   818  		ret.Memory.Cache = s.MemoryStats.Stats["cache"]
   819  		ret.Memory.RSS = s.MemoryStats.Stats["rss"]
   820  		ret.Memory.Swap = s.MemoryStats.Stats["swap"]
   821  		ret.Memory.MappedFile = s.MemoryStats.Stats["mapped_file"]
   822  	}
   823  	if v, ok := s.MemoryStats.Stats["pgfault"]; ok {
   824  		ret.Memory.ContainerData.Pgfault = v
   825  		ret.Memory.HierarchicalData.Pgfault = v
   826  	}
   827  	if v, ok := s.MemoryStats.Stats["pgmajfault"]; ok {
   828  		ret.Memory.ContainerData.Pgmajfault = v
   829  		ret.Memory.HierarchicalData.Pgmajfault = v
   830  	}
   831  
   832  	inactiveFileKeyName := "total_inactive_file"
   833  	if cgroups.IsCgroup2UnifiedMode() {
   834  		inactiveFileKeyName = "inactive_file"
   835  	}
   836  
   837  	workingSet := ret.Memory.Usage
   838  	if v, ok := s.MemoryStats.Stats[inactiveFileKeyName]; ok {
   839  		if workingSet < v {
   840  			workingSet = 0
   841  		} else {
   842  			workingSet -= v
   843  		}
   844  	}
   845  	ret.Memory.WorkingSet = workingSet
   846  }
   847  
   848  func setCPUSetStats(s *cgroups.Stats, ret *info.ContainerStats) {
   849  	ret.CpuSet.MemoryMigrate = s.CPUSetStats.MemoryMigrate
   850  }
   851  
   852  func getNumaStats(memoryStats map[uint8]uint64) map[uint8]uint64 {
   853  	stats := make(map[uint8]uint64, len(memoryStats))
   854  	for node, usage := range memoryStats {
   855  		stats[node] = usage
   856  	}
   857  	return stats
   858  }
   859  
   860  func setMemoryNumaStats(s *cgroups.Stats, ret *info.ContainerStats) {
   861  	ret.Memory.ContainerData.NumaStats.File = getNumaStats(s.MemoryStats.PageUsageByNUMA.File.Nodes)
   862  	ret.Memory.ContainerData.NumaStats.Anon = getNumaStats(s.MemoryStats.PageUsageByNUMA.Anon.Nodes)
   863  	ret.Memory.ContainerData.NumaStats.Unevictable = getNumaStats(s.MemoryStats.PageUsageByNUMA.Unevictable.Nodes)
   864  
   865  	ret.Memory.HierarchicalData.NumaStats.File = getNumaStats(s.MemoryStats.PageUsageByNUMA.Hierarchical.File.Nodes)
   866  	ret.Memory.HierarchicalData.NumaStats.Anon = getNumaStats(s.MemoryStats.PageUsageByNUMA.Hierarchical.Anon.Nodes)
   867  	ret.Memory.HierarchicalData.NumaStats.Unevictable = getNumaStats(s.MemoryStats.PageUsageByNUMA.Hierarchical.Unevictable.Nodes)
   868  }
   869  
   870  func setHugepageStats(s *cgroups.Stats, ret *info.ContainerStats) {
   871  	ret.Hugetlb = make(map[string]info.HugetlbStats)
   872  	for k, v := range s.HugetlbStats {
   873  		ret.Hugetlb[k] = info.HugetlbStats{
   874  			Usage:    v.Usage,
   875  			MaxUsage: v.MaxUsage,
   876  			Failcnt:  v.Failcnt,
   877  		}
   878  	}
   879  }
   880  
   881  func setNetworkStats(libcontainerStats *libcontainer.Stats, ret *info.ContainerStats) {
   882  	ret.Network.Interfaces = make([]info.InterfaceStats, len(libcontainerStats.Interfaces))
   883  	for i := range libcontainerStats.Interfaces {
   884  		ret.Network.Interfaces[i] = info.InterfaceStats{
   885  			Name:      libcontainerStats.Interfaces[i].Name,
   886  			RxBytes:   libcontainerStats.Interfaces[i].RxBytes,
   887  			RxPackets: libcontainerStats.Interfaces[i].RxPackets,
   888  			RxErrors:  libcontainerStats.Interfaces[i].RxErrors,
   889  			RxDropped: libcontainerStats.Interfaces[i].RxDropped,
   890  			TxBytes:   libcontainerStats.Interfaces[i].TxBytes,
   891  			TxPackets: libcontainerStats.Interfaces[i].TxPackets,
   892  			TxErrors:  libcontainerStats.Interfaces[i].TxErrors,
   893  			TxDropped: libcontainerStats.Interfaces[i].TxDropped,
   894  		}
   895  	}
   896  
   897  	// Add to base struct for backwards compatibility.
   898  	if len(ret.Network.Interfaces) > 0 {
   899  		ret.Network.InterfaceStats = ret.Network.Interfaces[0]
   900  	}
   901  }
   902  
   903  // read from pids path not cpu
   904  func setThreadsStats(s *cgroups.Stats, ret *info.ContainerStats) {
   905  	if s != nil {
   906  		ret.Processes.ThreadsCurrent = s.PidsStats.Current
   907  		ret.Processes.ThreadsMax = s.PidsStats.Limit
   908  	}
   909  }
   910  
   911  func newContainerStats(libcontainerStats *libcontainer.Stats, includedMetrics container.MetricSet) *info.ContainerStats {
   912  	ret := &info.ContainerStats{
   913  		Timestamp: time.Now(),
   914  	}
   915  
   916  	if s := libcontainerStats.CgroupStats; s != nil {
   917  		setCPUStats(s, ret, includedMetrics.Has(container.PerCpuUsageMetrics))
   918  		if includedMetrics.Has(container.DiskIOMetrics) {
   919  			setDiskIoStats(s, ret)
   920  		}
   921  		setMemoryStats(s, ret)
   922  		if includedMetrics.Has(container.MemoryNumaMetrics) {
   923  			setMemoryNumaStats(s, ret)
   924  		}
   925  		if includedMetrics.Has(container.HugetlbUsageMetrics) {
   926  			setHugepageStats(s, ret)
   927  		}
   928  		if includedMetrics.Has(container.CPUSetMetrics) {
   929  			setCPUSetStats(s, ret)
   930  		}
   931  	}
   932  	if len(libcontainerStats.Interfaces) > 0 {
   933  		setNetworkStats(libcontainerStats, ret)
   934  	}
   935  	return ret
   936  }