github.com/google/cadvisor@v0.49.1/machine/machine.go (about)

     1  // Copyright 2015 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  // The machine package contains functions that extract machine-level specs.
    16  package machine
    17  
    18  import (
    19  	"fmt"
    20  	"os"
    21  	"path"
    22  	"regexp"
    23  
    24  	// s390/s390x changes
    25  	"runtime"
    26  	"strconv"
    27  	"strings"
    28  
    29  	info "github.com/google/cadvisor/info/v1"
    30  	"github.com/google/cadvisor/utils"
    31  	"github.com/google/cadvisor/utils/sysfs"
    32  	"github.com/google/cadvisor/utils/sysinfo"
    33  
    34  	"k8s.io/klog/v2"
    35  
    36  	"golang.org/x/sys/unix"
    37  )
    38  
    39  var (
    40  	coreRegExp = regexp.MustCompile(`(?m)^core id\s*:\s*([0-9]+)$`)
    41  	nodeRegExp = regexp.MustCompile(`(?m)^physical id\s*:\s*([0-9]+)$`)
    42  	// Power systems have a different format so cater for both
    43  	cpuClockSpeedMHz     = regexp.MustCompile(`(?:cpu MHz|CPU MHz|clock)\s*:\s*([0-9]+\.[0-9]+)(?:MHz)?`)
    44  	memoryCapacityRegexp = regexp.MustCompile(`MemTotal:\s*([0-9]+) kB`)
    45  	swapCapacityRegexp   = regexp.MustCompile(`SwapTotal:\s*([0-9]+) kB`)
    46  	vendorIDRegexp       = regexp.MustCompile(`vendor_id\s*:\s*(\w+)`)
    47  
    48  	cpuAttributesPath  = "/sys/devices/system/cpu/"
    49  	isMemoryController = regexp.MustCompile("mc[0-9]+")
    50  	isDimm             = regexp.MustCompile("dimm[0-9]+")
    51  	machineArch        = getMachineArch()
    52  	maxFreqFile        = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"
    53  )
    54  
    55  const memTypeFileName = "dimm_mem_type"
    56  const sizeFileName = "size"
    57  
    58  // GetCPUVendorID returns "vendor_id" reading /proc/cpuinfo file.
    59  func GetCPUVendorID(procInfo []byte) string {
    60  	vendorID := ""
    61  
    62  	matches := vendorIDRegexp.FindSubmatch(procInfo)
    63  	if len(matches) != 2 {
    64  		klog.V(4).Info("Cannot read vendor id correctly, set empty.")
    65  		return vendorID
    66  	}
    67  
    68  	vendorID = string(matches[1])
    69  
    70  	return vendorID
    71  }
    72  
    73  // GetPhysicalCores returns number of CPU cores reading /proc/cpuinfo file or if needed information from sysfs cpu path
    74  func GetPhysicalCores(procInfo []byte) int {
    75  	numCores := getUniqueMatchesCount(string(procInfo), coreRegExp)
    76  	if numCores == 0 {
    77  		// read number of cores from /sys/bus/cpu/devices/cpu*/topology/core_id to deal with processors
    78  		// for which 'core id' is not available in /proc/cpuinfo
    79  		numCores = sysfs.GetUniqueCPUPropertyCount(cpuAttributesPath, sysfs.CPUCoreID)
    80  	}
    81  	if numCores == 0 {
    82  		klog.Errorf("Cannot read number of physical cores correctly, number of cores set to %d", numCores)
    83  	}
    84  	return numCores
    85  }
    86  
    87  // GetSockets returns number of CPU sockets reading /proc/cpuinfo file or if needed information from sysfs cpu path
    88  func GetSockets(procInfo []byte) int {
    89  	numSocket := getUniqueMatchesCount(string(procInfo), nodeRegExp)
    90  	if numSocket == 0 {
    91  		// read number of sockets from /sys/bus/cpu/devices/cpu*/topology/physical_package_id to deal with processors
    92  		// for which 'physical id' is not available in /proc/cpuinfo
    93  		numSocket = sysfs.GetUniqueCPUPropertyCount(cpuAttributesPath, sysfs.CPUPhysicalPackageID)
    94  	}
    95  	if numSocket == 0 {
    96  		klog.Errorf("Cannot read number of sockets correctly, number of sockets set to %d", numSocket)
    97  	}
    98  	return numSocket
    99  }
   100  
   101  // GetClockSpeed returns the CPU clock speed, given a []byte formatted as the /proc/cpuinfo file.
   102  func GetClockSpeed(procInfo []byte) (uint64, error) {
   103  	// First look through sys to find a max supported cpu frequency.
   104  	if utils.FileExists(maxFreqFile) {
   105  		val, err := os.ReadFile(maxFreqFile)
   106  		if err != nil {
   107  			return 0, err
   108  		}
   109  		var maxFreq uint64
   110  		n, err := fmt.Sscanf(string(val), "%d", &maxFreq)
   111  		if err != nil || n != 1 {
   112  			return 0, fmt.Errorf("could not parse frequency %q", val)
   113  		}
   114  		return maxFreq, nil
   115  	}
   116  	// s390/s390x, mips64, riscv64, aarch64 and arm32 changes
   117  	if isMips64() || isSystemZ() || isAArch64() || isArm32() || isRiscv64() {
   118  		return 0, nil
   119  	}
   120  
   121  	// Fall back to /proc/cpuinfo
   122  	matches := cpuClockSpeedMHz.FindSubmatch(procInfo)
   123  	if len(matches) != 2 {
   124  		return 0, fmt.Errorf("could not detect clock speed from output: %q", string(procInfo))
   125  	}
   126  
   127  	speed, err := strconv.ParseFloat(string(matches[1]), 64)
   128  	if err != nil {
   129  		return 0, err
   130  	}
   131  	// Convert to kHz
   132  	return uint64(speed * 1000), nil
   133  }
   134  
   135  // GetMachineMemoryCapacity returns the machine's total memory from /proc/meminfo.
   136  // Returns the total memory capacity as an uint64 (number of bytes).
   137  func GetMachineMemoryCapacity() (uint64, error) {
   138  	out, err := os.ReadFile("/proc/meminfo")
   139  	if err != nil {
   140  		return 0, err
   141  	}
   142  
   143  	memoryCapacity, err := parseCapacity(out, memoryCapacityRegexp)
   144  	if err != nil {
   145  		return 0, err
   146  	}
   147  	return memoryCapacity, err
   148  }
   149  
   150  // GetMachineMemoryByType returns information about memory capacity and number of DIMMs.
   151  // Information is retrieved from sysfs edac per-DIMM API (/sys/devices/system/edac/mc/)
   152  // introduced in kernel 3.6. Documentation can be found at
   153  // https://www.kernel.org/doc/Documentation/admin-guide/ras.rst.
   154  // Full list of memory types can be found in edac_mc.c
   155  // (https://github.com/torvalds/linux/blob/v5.5/drivers/edac/edac_mc.c#L198)
   156  func GetMachineMemoryByType(edacPath string) (map[string]*info.MemoryInfo, error) {
   157  	memory := map[string]*info.MemoryInfo{}
   158  	names, err := os.ReadDir(edacPath)
   159  	// On some architectures (such as ARM) memory controller device may not exist.
   160  	// If this is the case then we ignore error and return empty slice.
   161  	_, ok := err.(*os.PathError)
   162  	if err != nil && ok {
   163  		return memory, nil
   164  	} else if err != nil {
   165  		return memory, err
   166  	}
   167  	for _, controllerDir := range names {
   168  		controller := controllerDir.Name()
   169  		if !isMemoryController.MatchString(controller) {
   170  			continue
   171  		}
   172  		dimms, err := os.ReadDir(path.Join(edacPath, controllerDir.Name()))
   173  		if err != nil {
   174  			return map[string]*info.MemoryInfo{}, err
   175  		}
   176  		for _, dimmDir := range dimms {
   177  			dimm := dimmDir.Name()
   178  			if !isDimm.MatchString(dimm) {
   179  				continue
   180  			}
   181  			memType, err := os.ReadFile(path.Join(edacPath, controller, dimm, memTypeFileName))
   182  			if err != nil {
   183  				return map[string]*info.MemoryInfo{}, err
   184  			}
   185  			readableMemType := strings.TrimSpace(string(memType))
   186  			if _, exists := memory[readableMemType]; !exists {
   187  				memory[readableMemType] = &info.MemoryInfo{}
   188  			}
   189  			size, err := os.ReadFile(path.Join(edacPath, controller, dimm, sizeFileName))
   190  			if err != nil {
   191  				return map[string]*info.MemoryInfo{}, err
   192  			}
   193  			capacity, err := strconv.Atoi(strings.TrimSpace(string(size)))
   194  			if err != nil {
   195  				return map[string]*info.MemoryInfo{}, err
   196  			}
   197  			memory[readableMemType].Capacity += uint64(mbToBytes(capacity))
   198  			memory[readableMemType].DimmCount++
   199  		}
   200  	}
   201  
   202  	return memory, nil
   203  }
   204  
   205  func mbToBytes(megabytes int) int {
   206  	return megabytes * 1024 * 1024
   207  }
   208  
   209  // GetMachineSwapCapacity returns the machine's total swap from /proc/meminfo.
   210  // Returns the total swap capacity as an uint64 (number of bytes).
   211  func GetMachineSwapCapacity() (uint64, error) {
   212  	out, err := os.ReadFile("/proc/meminfo")
   213  	if err != nil {
   214  		return 0, err
   215  	}
   216  
   217  	swapCapacity, err := parseCapacity(out, swapCapacityRegexp)
   218  	if err != nil {
   219  		return 0, err
   220  	}
   221  	return swapCapacity, err
   222  }
   223  
   224  // GetTopology returns CPU topology reading information from sysfs
   225  func GetTopology(sysFs sysfs.SysFs) ([]info.Node, int, error) {
   226  	// s390/s390x changes
   227  	if isSystemZ() {
   228  		return nil, getNumCores(), nil
   229  	}
   230  	return sysinfo.GetNodesInfo(sysFs)
   231  }
   232  
   233  // parseCapacity matches a Regexp in a []byte, returning the resulting value in bytes.
   234  // Assumes that the value matched by the Regexp is in KB.
   235  func parseCapacity(b []byte, r *regexp.Regexp) (uint64, error) {
   236  	matches := r.FindSubmatch(b)
   237  	if len(matches) != 2 {
   238  		return 0, fmt.Errorf("failed to match regexp in output: %q", string(b))
   239  	}
   240  	m, err := strconv.ParseUint(string(matches[1]), 10, 64)
   241  	if err != nil {
   242  		return 0, err
   243  	}
   244  
   245  	// Convert to bytes.
   246  	return m * 1024, err
   247  }
   248  
   249  // getUniqueMatchesCount returns number of unique matches in given argument using provided regular expression
   250  func getUniqueMatchesCount(s string, r *regexp.Regexp) int {
   251  	matches := r.FindAllString(s, -1)
   252  	uniques := make(map[string]bool)
   253  	for _, match := range matches {
   254  		uniques[match] = true
   255  	}
   256  	return len(uniques)
   257  }
   258  
   259  func getMachineArch() string {
   260  	uname := unix.Utsname{}
   261  	err := unix.Uname(&uname)
   262  	if err != nil {
   263  		klog.Errorf("Cannot get machine architecture, err: %v", err)
   264  		return ""
   265  	}
   266  	return string(uname.Machine[:])
   267  }
   268  
   269  // arm32 changes
   270  func isArm32() bool {
   271  	return strings.Contains(machineArch, "arm")
   272  }
   273  
   274  // aarch64 changes
   275  func isAArch64() bool {
   276  	return strings.Contains(machineArch, "aarch64")
   277  }
   278  
   279  // s390/s390x changes
   280  func isSystemZ() bool {
   281  	return strings.Contains(machineArch, "390")
   282  }
   283  
   284  // riscv64 changes
   285  func isRiscv64() bool {
   286  	return strings.Contains(machineArch, "riscv64")
   287  }
   288  
   289  // mips64 changes
   290  func isMips64() bool {
   291  	return strings.Contains(machineArch, "mips64")
   292  }
   293  
   294  // s390/s390x changes
   295  func getNumCores() int {
   296  	maxProcs := runtime.GOMAXPROCS(0)
   297  	numCPU := runtime.NumCPU()
   298  
   299  	if maxProcs < numCPU {
   300  		return maxProcs
   301  	}
   302  
   303  	return numCPU
   304  }