mirror of
https://github.com/k3s-io/k3s.git
synced 2024-06-07 19:41:36 +00:00
462 lines
12 KiB
Go
462 lines
12 KiB
Go
// 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 (
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
// 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"
|
|
|
|
"k8s.io/klog"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
var (
|
|
cpuRegExp = regexp.MustCompile(`^processor\s*:\s*([0-9]+)$`)
|
|
coreRegExp = regexp.MustCompile(`^core id\s*:\s*([0-9]+)$`)
|
|
nodeRegExp = regexp.MustCompile(`^physical id\s*:\s*([0-9]+)$`)
|
|
nodeBusRegExp = regexp.MustCompile(`^node([0-9]+)$`)
|
|
// Power systems have a different format so cater for both
|
|
cpuClockSpeedMHz = regexp.MustCompile(`(?:cpu MHz|clock)\s*:\s*([0-9]+\.[0-9]+)(?:MHz)?`)
|
|
memoryCapacityRegexp = regexp.MustCompile(`MemTotal:\s*([0-9]+) kB`)
|
|
swapCapacityRegexp = regexp.MustCompile(`SwapTotal:\s*([0-9]+) kB`)
|
|
)
|
|
|
|
const maxFreqFile = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"
|
|
const cpuBusPath = "/sys/bus/cpu/devices/"
|
|
const nodePath = "/sys/devices/system/node"
|
|
|
|
// GetClockSpeed returns the CPU clock speed, given a []byte formatted as the /proc/cpuinfo file.
|
|
func GetClockSpeed(procInfo []byte) (uint64, error) {
|
|
// s390/s390x, aarch64 and arm32 changes
|
|
if isSystemZ() || isAArch64() || isArm32() {
|
|
return 0, nil
|
|
}
|
|
|
|
// 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
|
|
}
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
/* Look for sysfs cpu path containing core_id */
|
|
/* Such as: sys/bus/cpu/devices/cpu0/topology/core_id */
|
|
func getCoreIdFromCpuBus(cpuBusPath string, threadId int) (int, error) {
|
|
path := filepath.Join(cpuBusPath, fmt.Sprintf("cpu%d/topology", threadId))
|
|
file := filepath.Join(path, "core_id")
|
|
|
|
num, err := ioutil.ReadFile(file)
|
|
if err != nil {
|
|
return threadId, err
|
|
}
|
|
|
|
coreId, err := strconv.ParseInt(string(bytes.TrimSpace(num)), 10, 32)
|
|
if err != nil {
|
|
return threadId, err
|
|
}
|
|
|
|
if coreId < 0 {
|
|
// report threadId if found coreId < 0
|
|
coreId = int64(threadId)
|
|
}
|
|
|
|
return int(coreId), nil
|
|
}
|
|
|
|
/* Look for sysfs cpu path containing node id */
|
|
/* Such as: /sys/bus/cpu/devices/cpu0/node%d */
|
|
func getNodeIdFromCpuBus(cpuBusPath string, threadId int) (int, error) {
|
|
path := filepath.Join(cpuBusPath, fmt.Sprintf("cpu%d", threadId))
|
|
|
|
files, err := ioutil.ReadDir(path)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
nodeId := 0
|
|
for _, file := range files {
|
|
filename := file.Name()
|
|
|
|
isNode, error := regexp.MatchString("^node([0-9]+)$", filename)
|
|
if error != nil {
|
|
continue
|
|
}
|
|
if !isNode {
|
|
continue
|
|
}
|
|
|
|
ok, val, _ := extractValue(filename, nodeBusRegExp)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if ok {
|
|
if val < 0 {
|
|
continue
|
|
}
|
|
nodeId = val
|
|
}
|
|
}
|
|
|
|
return nodeId, nil
|
|
}
|
|
|
|
// GetHugePagesInfo returns information about pre-allocated huge pages
|
|
// hugepagesDirectory should be top directory of hugepages
|
|
// Such as: /sys/kernel/mm/hugepages/
|
|
func GetHugePagesInfo(hugepagesDirectory string) ([]info.HugePagesInfo, error) {
|
|
var hugePagesInfo []info.HugePagesInfo
|
|
files, err := ioutil.ReadDir(hugepagesDirectory)
|
|
if err != nil {
|
|
// treat as non-fatal since kernels and machine can be
|
|
// configured to disable hugepage support
|
|
return hugePagesInfo, nil
|
|
}
|
|
for _, st := range files {
|
|
nameArray := strings.Split(st.Name(), "-")
|
|
pageSizeArray := strings.Split(nameArray[1], "kB")
|
|
pageSize, err := strconv.ParseUint(string(pageSizeArray[0]), 10, 64)
|
|
if err != nil {
|
|
return hugePagesInfo, err
|
|
}
|
|
|
|
numFile := hugepagesDirectory + st.Name() + "/nr_hugepages"
|
|
val, err := ioutil.ReadFile(numFile)
|
|
if err != nil {
|
|
return hugePagesInfo, err
|
|
}
|
|
var numPages uint64
|
|
// we use sscanf as the file as a new-line that trips up ParseUint
|
|
// it returns the number of tokens successfully parsed, so if
|
|
// n != 1, it means we were unable to parse a number from the file
|
|
n, err := fmt.Sscanf(string(val), "%d", &numPages)
|
|
if err != nil || n != 1 {
|
|
return hugePagesInfo, fmt.Errorf("could not parse file %v contents %q", numFile, string(val))
|
|
}
|
|
|
|
hugePagesInfo = append(hugePagesInfo, info.HugePagesInfo{
|
|
NumPages: numPages,
|
|
PageSize: pageSize,
|
|
})
|
|
}
|
|
return hugePagesInfo, nil
|
|
}
|
|
|
|
func GetTopology(sysFs sysfs.SysFs, cpuinfo string) ([]info.Node, int, error) {
|
|
nodes := []info.Node{}
|
|
|
|
// s390/s390x changes
|
|
if true == isSystemZ() {
|
|
return nodes, getNumCores(), nil
|
|
}
|
|
|
|
numCores := 0
|
|
lastThread := -1
|
|
lastCore := -1
|
|
lastNode := -1
|
|
for _, line := range strings.Split(cpuinfo, "\n") {
|
|
if line == "" {
|
|
continue
|
|
}
|
|
ok, val, err := extractValue(line, cpuRegExp)
|
|
if err != nil {
|
|
return nil, -1, fmt.Errorf("could not parse cpu info from %q: %v", line, err)
|
|
}
|
|
if ok {
|
|
thread := val
|
|
numCores++
|
|
if lastThread != -1 {
|
|
// New cpu section. Save last one.
|
|
nodeIdx, err := addNode(&nodes, lastNode)
|
|
if err != nil {
|
|
return nil, -1, fmt.Errorf("failed to add node %d: %v", lastNode, err)
|
|
}
|
|
nodes[nodeIdx].AddThread(lastThread, lastCore)
|
|
lastCore = -1
|
|
lastNode = -1
|
|
}
|
|
lastThread = thread
|
|
|
|
/* On Arm platform, no 'core id' and 'physical id' in '/proc/cpuinfo'. */
|
|
/* So we search sysfs cpu path directly. */
|
|
/* This method can also be used on other platforms, such as x86, ppc64le... */
|
|
/* /sys/bus/cpu/devices/cpu%d contains the information of 'core_id' & 'node_id'. */
|
|
/* Such as: /sys/bus/cpu/devices/cpu0/topology/core_id */
|
|
/* Such as: /sys/bus/cpu/devices/cpu0/node0 */
|
|
if isAArch64() {
|
|
val, err = getCoreIdFromCpuBus(cpuBusPath, lastThread)
|
|
if err != nil {
|
|
// Report thread id if no NUMA
|
|
val = lastThread
|
|
}
|
|
lastCore = val
|
|
|
|
val, err = getNodeIdFromCpuBus(cpuBusPath, lastThread)
|
|
if err != nil {
|
|
// Report node 0 if no NUMA
|
|
val = 0
|
|
}
|
|
lastNode = val
|
|
}
|
|
continue
|
|
}
|
|
|
|
if isAArch64() {
|
|
/* On Arm platform, no 'core id' and 'physical id' in '/proc/cpuinfo'. */
|
|
continue
|
|
}
|
|
|
|
ok, val, err = extractValue(line, coreRegExp)
|
|
if err != nil {
|
|
return nil, -1, fmt.Errorf("could not parse core info from %q: %v", line, err)
|
|
}
|
|
if ok {
|
|
lastCore = val
|
|
continue
|
|
}
|
|
|
|
ok, val, err = extractValue(line, nodeRegExp)
|
|
if err != nil {
|
|
return nil, -1, fmt.Errorf("could not parse node info from %q: %v", line, err)
|
|
}
|
|
if ok {
|
|
lastNode = val
|
|
continue
|
|
}
|
|
}
|
|
|
|
nodeIdx, err := addNode(&nodes, lastNode)
|
|
if err != nil {
|
|
return nil, -1, fmt.Errorf("failed to add node %d: %v", lastNode, err)
|
|
}
|
|
nodes[nodeIdx].AddThread(lastThread, lastCore)
|
|
if numCores < 1 {
|
|
return nil, numCores, fmt.Errorf("could not detect any cores")
|
|
}
|
|
for idx, node := range nodes {
|
|
caches, err := sysinfo.GetCacheInfo(sysFs, node.Cores[0].Threads[0])
|
|
if err != nil {
|
|
klog.Errorf("failed to get cache information for node %d: %v", node.Id, err)
|
|
continue
|
|
}
|
|
numThreadsPerCore := len(node.Cores[0].Threads)
|
|
numThreadsPerNode := len(node.Cores) * numThreadsPerCore
|
|
for _, cache := range caches {
|
|
c := info.Cache{
|
|
Size: cache.Size,
|
|
Level: cache.Level,
|
|
Type: cache.Type,
|
|
}
|
|
if cache.Cpus == numThreadsPerNode && cache.Level > 2 {
|
|
// Add a node-level cache.
|
|
nodes[idx].AddNodeCache(c)
|
|
} else if cache.Cpus == numThreadsPerCore {
|
|
// Add to each core.
|
|
nodes[idx].AddPerCoreCache(c)
|
|
}
|
|
// Ignore unknown caches.
|
|
}
|
|
}
|
|
return nodes, numCores, nil
|
|
}
|
|
|
|
func extractValue(s string, r *regexp.Regexp) (bool, int, error) {
|
|
matches := r.FindSubmatch([]byte(s))
|
|
if len(matches) == 2 {
|
|
val, err := strconv.ParseInt(string(matches[1]), 10, 32)
|
|
if err != nil {
|
|
return false, -1, err
|
|
}
|
|
return true, int(val), nil
|
|
}
|
|
return false, -1, nil
|
|
}
|
|
|
|
func findNode(nodes []info.Node, id int) (bool, int) {
|
|
for i, n := range nodes {
|
|
if n.Id == id {
|
|
return true, i
|
|
}
|
|
}
|
|
return false, -1
|
|
}
|
|
|
|
func addNode(nodes *[]info.Node, id int) (int, error) {
|
|
var idx int
|
|
if id == -1 {
|
|
// Some VMs don't fill topology data. Export single package.
|
|
id = 0
|
|
}
|
|
|
|
ok, idx := findNode(*nodes, id)
|
|
if !ok {
|
|
// New node
|
|
node := info.Node{Id: id}
|
|
// Add per-node memory information.
|
|
meminfo := fmt.Sprintf("/sys/devices/system/node/node%d/meminfo", id)
|
|
out, err := ioutil.ReadFile(meminfo)
|
|
// Ignore if per-node info is not available.
|
|
if err == nil {
|
|
m, err := parseCapacity(out, memoryCapacityRegexp)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
node.Memory = uint64(m)
|
|
}
|
|
// Look for per-node hugepages info using node id
|
|
// Such as: /sys/devices/system/node/node%d/hugepages
|
|
hugepagesDirectory := fmt.Sprintf("%s/node%d/hugepages/", nodePath, id)
|
|
hugePagesInfo, err := GetHugePagesInfo(hugepagesDirectory)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
node.HugePages = hugePagesInfo
|
|
|
|
*nodes = append(*nodes, node)
|
|
idx = len(*nodes) - 1
|
|
}
|
|
return idx, nil
|
|
}
|
|
|
|
// s390/s390x changes
|
|
func getMachineArch() (string, error) {
|
|
uname := unix.Utsname{}
|
|
err := unix.Uname(&uname)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(uname.Machine[:]), nil
|
|
}
|
|
|
|
// arm32 chanes
|
|
func isArm32() bool {
|
|
arch, err := getMachineArch()
|
|
if err == nil {
|
|
return strings.Contains(arch, "arm")
|
|
}
|
|
return false
|
|
}
|
|
|
|
// aarch64 changes
|
|
func isAArch64() bool {
|
|
arch, err := getMachineArch()
|
|
if err == nil {
|
|
return strings.Contains(arch, "aarch64")
|
|
}
|
|
return false
|
|
}
|
|
|
|
// s390/s390x changes
|
|
func isSystemZ() bool {
|
|
arch, err := getMachineArch()
|
|
if err == nil {
|
|
return strings.Contains(arch, "390")
|
|
}
|
|
return false
|
|
}
|
|
|
|
// s390/s390x changes
|
|
func getNumCores() int {
|
|
maxProcs := runtime.GOMAXPROCS(0)
|
|
numCPU := runtime.NumCPU()
|
|
|
|
if maxProcs < numCPU {
|
|
return maxProcs
|
|
}
|
|
|
|
return numCPU
|
|
}
|