2019-01-12 04:58:27 +00:00
|
|
|
// Copyright 2015 Google Inc. All Rights Reserved.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
// The machine package contains functions that extract machine-level specs.
|
|
|
|
package machine
|
|
|
|
|
|
|
|
import (
|
2019-04-07 17:07:55 +00:00
|
|
|
"bytes"
|
2019-01-12 04:58:27 +00:00
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
2020-08-10 17:43:49 +00:00
|
|
|
"os"
|
|
|
|
"path"
|
2019-04-07 17:07:55 +00:00
|
|
|
"path/filepath"
|
2019-01-12 04:58:27 +00:00
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2019-12-12 01:27:03 +00:00
|
|
|
|
2019-01-12 04:58:27 +00:00
|
|
|
// s390/s390x changes
|
|
|
|
"runtime"
|
|
|
|
|
|
|
|
info "github.com/google/cadvisor/info/v1"
|
|
|
|
"github.com/google/cadvisor/utils"
|
|
|
|
"github.com/google/cadvisor/utils/sysfs"
|
|
|
|
"github.com/google/cadvisor/utils/sysinfo"
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
"k8s.io/klog/v2"
|
2019-01-12 04:58:27 +00:00
|
|
|
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2020-08-10 17:43:49 +00:00
|
|
|
coreRegExp = regexp.MustCompile(`(?m)^core id\s*:\s*([0-9]+)$`)
|
|
|
|
nodeRegExp = regexp.MustCompile(`(?m)^physical id\s*:\s*([0-9]+)$`)
|
2019-01-12 04:58:27 +00:00
|
|
|
// Power systems have a different format so cater for both
|
2020-08-10 17:43:49 +00:00
|
|
|
cpuClockSpeedMHz = regexp.MustCompile(`(?:cpu MHz|CPU MHz|clock)\s*:\s*([0-9]+\.[0-9]+)(?:MHz)?`)
|
2019-01-12 04:58:27 +00:00
|
|
|
memoryCapacityRegexp = regexp.MustCompile(`MemTotal:\s*([0-9]+) kB`)
|
|
|
|
swapCapacityRegexp = regexp.MustCompile(`SwapTotal:\s*([0-9]+) kB`)
|
2020-08-10 17:43:49 +00:00
|
|
|
|
|
|
|
cpuBusPath = "/sys/bus/cpu/devices/"
|
|
|
|
isMemoryController = regexp.MustCompile("mc[0-9]+")
|
|
|
|
isDimm = regexp.MustCompile("dimm[0-9]+")
|
|
|
|
machineArch = getMachineArch()
|
|
|
|
maxFreqFile = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"
|
2019-01-12 04:58:27 +00:00
|
|
|
)
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
const sysFsCPUCoreID = "core_id"
|
|
|
|
const sysFsCPUPhysicalPackageID = "physical_package_id"
|
|
|
|
const sysFsCPUTopology = "topology"
|
|
|
|
const memTypeFileName = "dimm_mem_type"
|
|
|
|
const sizeFileName = "size"
|
|
|
|
|
|
|
|
// GetPhysicalCores returns number of CPU cores reading /proc/cpuinfo file or if needed information from sysfs cpu path
|
|
|
|
func GetPhysicalCores(procInfo []byte) int {
|
|
|
|
numCores := getUniqueMatchesCount(string(procInfo), coreRegExp)
|
|
|
|
if numCores == 0 {
|
|
|
|
// read number of cores from /sys/bus/cpu/devices/cpu*/topology/core_id to deal with processors
|
|
|
|
// for which 'core id' is not available in /proc/cpuinfo
|
|
|
|
numCores = getUniqueCPUPropertyCount(cpuBusPath, sysFsCPUCoreID)
|
|
|
|
}
|
|
|
|
if numCores == 0 {
|
|
|
|
klog.Errorf("Cannot read number of physical cores correctly, number of cores set to %d", numCores)
|
|
|
|
}
|
|
|
|
return numCores
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetSockets returns number of CPU sockets reading /proc/cpuinfo file or if needed information from sysfs cpu path
|
|
|
|
func GetSockets(procInfo []byte) int {
|
|
|
|
numSocket := getUniqueMatchesCount(string(procInfo), nodeRegExp)
|
|
|
|
if numSocket == 0 {
|
|
|
|
// read number of sockets from /sys/bus/cpu/devices/cpu*/topology/physical_package_id to deal with processors
|
|
|
|
// for which 'physical id' is not available in /proc/cpuinfo
|
|
|
|
numSocket = getUniqueCPUPropertyCount(cpuBusPath, sysFsCPUPhysicalPackageID)
|
|
|
|
}
|
|
|
|
if numSocket == 0 {
|
|
|
|
klog.Errorf("Cannot read number of sockets correctly, number of sockets set to %d", numSocket)
|
|
|
|
}
|
|
|
|
return numSocket
|
|
|
|
}
|
2019-01-12 04:58:27 +00:00
|
|
|
|
|
|
|
// GetClockSpeed returns the CPU clock speed, given a []byte formatted as the /proc/cpuinfo file.
|
|
|
|
func GetClockSpeed(procInfo []byte) (uint64, error) {
|
|
|
|
// First look through sys to find a max supported cpu frequency.
|
|
|
|
if utils.FileExists(maxFreqFile) {
|
|
|
|
val, err := ioutil.ReadFile(maxFreqFile)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
var maxFreq uint64
|
|
|
|
n, err := fmt.Sscanf(string(val), "%d", &maxFreq)
|
|
|
|
if err != nil || n != 1 {
|
|
|
|
return 0, fmt.Errorf("could not parse frequency %q", val)
|
|
|
|
}
|
|
|
|
return maxFreq, nil
|
|
|
|
}
|
2020-12-01 01:06:26 +00:00
|
|
|
// s390/s390x, mips64, riscv64, aarch64 and arm32 changes
|
|
|
|
if isMips64() || isSystemZ() || isAArch64() || isArm32() || isRiscv64() {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
2019-01-12 04:58:27 +00:00
|
|
|
// Fall back to /proc/cpuinfo
|
|
|
|
matches := cpuClockSpeedMHz.FindSubmatch(procInfo)
|
|
|
|
if len(matches) != 2 {
|
|
|
|
return 0, fmt.Errorf("could not detect clock speed from output: %q", string(procInfo))
|
|
|
|
}
|
|
|
|
|
|
|
|
speed, err := strconv.ParseFloat(string(matches[1]), 64)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
// Convert to kHz
|
|
|
|
return uint64(speed * 1000), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetMachineMemoryCapacity returns the machine's total memory from /proc/meminfo.
|
|
|
|
// Returns the total memory capacity as an uint64 (number of bytes).
|
|
|
|
func GetMachineMemoryCapacity() (uint64, error) {
|
|
|
|
out, err := ioutil.ReadFile("/proc/meminfo")
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
memoryCapacity, err := parseCapacity(out, memoryCapacityRegexp)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return memoryCapacity, err
|
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
// GetMachineMemoryByType returns information about memory capacity and number of DIMMs.
|
|
|
|
// Information is retrieved from sysfs edac per-DIMM API (/sys/devices/system/edac/mc/)
|
|
|
|
// introduced in kernel 3.6. Documentation can be found at
|
|
|
|
// https://www.kernel.org/doc/Documentation/admin-guide/ras.rst.
|
|
|
|
// Full list of memory types can be found in edac_mc.c
|
|
|
|
// (https://github.com/torvalds/linux/blob/v5.5/drivers/edac/edac_mc.c#L198)
|
|
|
|
func GetMachineMemoryByType(edacPath string) (map[string]*info.MemoryInfo, error) {
|
|
|
|
memory := map[string]*info.MemoryInfo{}
|
|
|
|
names, err := ioutil.ReadDir(edacPath)
|
|
|
|
// On some architectures (such as ARM) memory controller device may not exist.
|
|
|
|
// If this is the case then we ignore error and return empty slice.
|
|
|
|
_, ok := err.(*os.PathError)
|
|
|
|
if err != nil && ok {
|
|
|
|
return memory, nil
|
|
|
|
} else if err != nil {
|
|
|
|
return memory, err
|
|
|
|
}
|
|
|
|
for _, controllerDir := range names {
|
|
|
|
controller := controllerDir.Name()
|
|
|
|
if !isMemoryController.MatchString(controller) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
dimms, err := ioutil.ReadDir(path.Join(edacPath, controllerDir.Name()))
|
|
|
|
if err != nil {
|
|
|
|
return map[string]*info.MemoryInfo{}, err
|
|
|
|
}
|
|
|
|
for _, dimmDir := range dimms {
|
|
|
|
dimm := dimmDir.Name()
|
|
|
|
if !isDimm.MatchString(dimm) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
memType, err := ioutil.ReadFile(path.Join(edacPath, controller, dimm, memTypeFileName))
|
|
|
|
if err != nil {
|
|
|
|
return map[string]*info.MemoryInfo{}, err
|
|
|
|
}
|
|
|
|
readableMemType := strings.TrimSpace(string(memType))
|
|
|
|
if _, exists := memory[readableMemType]; !exists {
|
|
|
|
memory[readableMemType] = &info.MemoryInfo{}
|
|
|
|
}
|
|
|
|
size, err := ioutil.ReadFile(path.Join(edacPath, controller, dimm, sizeFileName))
|
|
|
|
if err != nil {
|
|
|
|
return map[string]*info.MemoryInfo{}, err
|
|
|
|
}
|
|
|
|
capacity, err := strconv.Atoi(strings.TrimSpace(string(size)))
|
|
|
|
if err != nil {
|
|
|
|
return map[string]*info.MemoryInfo{}, err
|
|
|
|
}
|
|
|
|
memory[readableMemType].Capacity += uint64(mbToBytes(capacity))
|
|
|
|
memory[readableMemType].DimmCount++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return memory, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func mbToBytes(megabytes int) int {
|
|
|
|
return megabytes * 1024 * 1024
|
|
|
|
}
|
|
|
|
|
2019-01-12 04:58:27 +00:00
|
|
|
// GetMachineSwapCapacity returns the machine's total swap from /proc/meminfo.
|
|
|
|
// Returns the total swap capacity as an uint64 (number of bytes).
|
|
|
|
func GetMachineSwapCapacity() (uint64, error) {
|
|
|
|
out, err := ioutil.ReadFile("/proc/meminfo")
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
swapCapacity, err := parseCapacity(out, swapCapacityRegexp)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return swapCapacity, err
|
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
// GetTopology returns CPU topology reading information from sysfs
|
|
|
|
func GetTopology(sysFs sysfs.SysFs) ([]info.Node, int, error) {
|
|
|
|
// s390/s390x changes
|
|
|
|
if isSystemZ() {
|
|
|
|
return nil, getNumCores(), nil
|
|
|
|
}
|
|
|
|
return sysinfo.GetNodesInfo(sysFs)
|
|
|
|
}
|
|
|
|
|
2019-01-12 04:58:27 +00:00
|
|
|
// parseCapacity matches a Regexp in a []byte, returning the resulting value in bytes.
|
|
|
|
// Assumes that the value matched by the Regexp is in KB.
|
|
|
|
func parseCapacity(b []byte, r *regexp.Regexp) (uint64, error) {
|
|
|
|
matches := r.FindSubmatch(b)
|
|
|
|
if len(matches) != 2 {
|
|
|
|
return 0, fmt.Errorf("failed to match regexp in output: %q", string(b))
|
|
|
|
}
|
|
|
|
m, err := strconv.ParseUint(string(matches[1]), 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert to bytes.
|
|
|
|
return m * 1024, err
|
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
// Looks for sysfs cpu path containing given CPU property, e.g. core_id or physical_package_id
|
|
|
|
// and returns number of unique values of given property, exemplary usage: getting number of CPU physical cores
|
|
|
|
func getUniqueCPUPropertyCount(cpuBusPath string, propertyName string) int {
|
|
|
|
pathPattern := cpuBusPath + "cpu*[0-9]"
|
|
|
|
sysCPUPaths, err := filepath.Glob(pathPattern)
|
2019-04-07 17:07:55 +00:00
|
|
|
if err != nil {
|
2020-08-10 17:43:49 +00:00
|
|
|
klog.Errorf("Cannot find files matching pattern (pathPattern: %s), number of unique %s set to 0", pathPattern, propertyName)
|
|
|
|
return 0
|
2019-04-07 17:07:55 +00:00
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
uniques := make(map[string]bool)
|
|
|
|
for _, sysCPUPath := range sysCPUPaths {
|
|
|
|
onlinePath := filepath.Join(sysCPUPath, "online")
|
|
|
|
onlineVal, err := ioutil.ReadFile(onlinePath)
|
2019-04-07 17:07:55 +00:00
|
|
|
if err != nil {
|
2020-08-10 17:43:49 +00:00
|
|
|
klog.Warningf("Cannot determine CPU %s online state, skipping", sysCPUPath)
|
2019-04-07 17:07:55 +00:00
|
|
|
continue
|
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
onlineVal = bytes.TrimSpace(onlineVal)
|
|
|
|
if len(onlineVal) == 0 || onlineVal[0] != 49 {
|
|
|
|
klog.Warningf("CPU %s is offline, skipping", sysCPUPath)
|
2019-01-12 04:58:27 +00:00
|
|
|
continue
|
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
propertyPath := filepath.Join(sysCPUPath, sysFsCPUTopology, propertyName)
|
|
|
|
propertyVal, err := ioutil.ReadFile(propertyPath)
|
2019-01-12 04:58:27 +00:00
|
|
|
if err != nil {
|
2020-08-10 17:43:49 +00:00
|
|
|
klog.Errorf("Cannot open %s, number of unique %s set to 0", propertyPath, propertyName)
|
|
|
|
return 0
|
2019-01-12 04:58:27 +00:00
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
uniques[string(propertyVal)] = true
|
2019-01-12 04:58:27 +00:00
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
return len(uniques)
|
2019-01-12 04:58:27 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
// getUniqueMatchesCount returns number of unique matches in given argument using provided regular expression
|
|
|
|
func getUniqueMatchesCount(s string, r *regexp.Regexp) int {
|
|
|
|
matches := r.FindAllString(s, -1)
|
|
|
|
uniques := make(map[string]bool)
|
|
|
|
for _, match := range matches {
|
|
|
|
uniques[match] = true
|
2019-01-12 04:58:27 +00:00
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
return len(uniques)
|
2019-01-12 04:58:27 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
func getMachineArch() string {
|
2019-01-12 04:58:27 +00:00
|
|
|
uname := unix.Utsname{}
|
|
|
|
err := unix.Uname(&uname)
|
|
|
|
if err != nil {
|
2020-08-10 17:43:49 +00:00
|
|
|
klog.Errorf("Cannot get machine architecture, err: %v", err)
|
|
|
|
return ""
|
2019-01-12 04:58:27 +00:00
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
return string(uname.Machine[:])
|
2019-01-12 04:58:27 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
// arm32 changes
|
2019-01-12 04:58:27 +00:00
|
|
|
func isArm32() bool {
|
2020-08-10 17:43:49 +00:00
|
|
|
return strings.Contains(machineArch, "arm")
|
2019-01-12 04:58:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// aarch64 changes
|
|
|
|
func isAArch64() bool {
|
2020-08-10 17:43:49 +00:00
|
|
|
return strings.Contains(machineArch, "aarch64")
|
2019-01-12 04:58:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// s390/s390x changes
|
|
|
|
func isSystemZ() bool {
|
2020-08-10 17:43:49 +00:00
|
|
|
return strings.Contains(machineArch, "390")
|
|
|
|
}
|
|
|
|
|
|
|
|
// riscv64 changes
|
|
|
|
func isRiscv64() bool {
|
|
|
|
return strings.Contains(machineArch, "riscv64")
|
|
|
|
}
|
|
|
|
|
|
|
|
// mips64 changes
|
|
|
|
func isMips64() bool {
|
|
|
|
return strings.Contains(machineArch, "mips64")
|
2019-01-12 04:58:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// s390/s390x changes
|
|
|
|
func getNumCores() int {
|
|
|
|
maxProcs := runtime.GOMAXPROCS(0)
|
|
|
|
numCPU := runtime.NumCPU()
|
|
|
|
|
|
|
|
if maxProcs < numCPU {
|
|
|
|
return maxProcs
|
|
|
|
}
|
|
|
|
|
|
|
|
return numCPU
|
|
|
|
}
|