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 }