mirror of
https://github.com/kubernetes-sigs/node-feature-discovery.git
synced 2025-03-05 08:17:04 +00:00
utils: add methods to fetch NUMA nodes hugepages and memory capacity
The methods are used during calculation of reserved memory for system workloads. The calcualation is `resourceCapacity - resourceAllocatable`. Signed-off-by: Artyom Lukianov <alukiano@redhat.com>
This commit is contained in:
parent
347b16daea
commit
a93b660f7c
2 changed files with 314 additions and 0 deletions
147
pkg/utils/memory_resources.go
Normal file
147
pkg/utils/memory_resources.go
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/klog/v2"
|
||||
resourcehelper "k8s.io/kubernetes/pkg/apis/core/helper"
|
||||
|
||||
"sigs.k8s.io/node-feature-discovery/source"
|
||||
)
|
||||
|
||||
var (
|
||||
sysBusNodeBasepath = source.SysfsDir.Path("bus/node/devices")
|
||||
)
|
||||
|
||||
// NumaMemoryResources contains information of the memory resources per NUMA
|
||||
// nodes of the system.
|
||||
type NumaMemoryResources map[int]MemoryResourceInfo
|
||||
|
||||
// MemoryResourceInfo holds information of memory resources per resource type.
|
||||
type MemoryResourceInfo map[v1.ResourceName]int64
|
||||
|
||||
// GetNumaMemoryResources returns total amount of memory and hugepages under NUMA nodes
|
||||
func GetNumaMemoryResources() (NumaMemoryResources, error) {
|
||||
nodes, err := ioutil.ReadDir(sysBusNodeBasepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
memoryResources := make(NumaMemoryResources, len(nodes))
|
||||
for _, n := range nodes {
|
||||
numaNode := n.Name()
|
||||
nodeID, err := strconv.Atoi(numaNode[4:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse NUMA node ID of %q", numaNode)
|
||||
}
|
||||
|
||||
info := make(MemoryResourceInfo)
|
||||
|
||||
// Get total memory
|
||||
nodeTotalMemory, err := readTotalMemoryFromMeminfo(filepath.Join(sysBusNodeBasepath, numaNode, "meminfo"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info[v1.ResourceMemory] = nodeTotalMemory
|
||||
|
||||
// Get hugepages
|
||||
hugepageBytes, err := getHugepagesBytes(filepath.Join(sysBusNodeBasepath, numaNode, "hugepages"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for n, s := range hugepageBytes {
|
||||
info[n] = s
|
||||
}
|
||||
|
||||
memoryResources[nodeID] = info
|
||||
}
|
||||
|
||||
return memoryResources, nil
|
||||
}
|
||||
|
||||
func getHugepagesBytes(path string) (MemoryResourceInfo, error) {
|
||||
entries, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hugepagesBytes := make(MemoryResourceInfo)
|
||||
for _, entry := range entries {
|
||||
split := strings.SplitN(entry.Name(), "-", 2)
|
||||
if len(split) != 2 || split[0] != "hugepages" {
|
||||
klog.Warningf("malformed hugepages entry %q", entry.Name())
|
||||
continue
|
||||
}
|
||||
|
||||
// Use Ki instead of kB
|
||||
q, err := resource.ParseQuantity(strings.Replace(split[1], "kB", "Ki", 1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(filepath.Join(path, entry.Name(), "nr_hugepages"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nr, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
size, _ := q.AsInt64()
|
||||
name := v1.ResourceName(resourcehelper.HugePageResourceName(q))
|
||||
hugepagesBytes[name] = nr * size
|
||||
}
|
||||
|
||||
return hugepagesBytes, nil
|
||||
}
|
||||
|
||||
func readTotalMemoryFromMeminfo(path string) (int64, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
split := strings.SplitN(line, ":", 2)
|
||||
if len(split) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(split[0], "MemTotal") {
|
||||
memValue := strings.Trim(split[1], "\t\n kB")
|
||||
convertedValue, err := strconv.ParseInt(memValue, 10, 64)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("failed to convert value: %v", memValue)
|
||||
}
|
||||
|
||||
// return information in bytes
|
||||
return 1024 * convertedValue, nil
|
||||
}
|
||||
}
|
||||
|
||||
return -1, fmt.Errorf("failed to find MemTotal field under the file %q", path)
|
||||
}
|
167
pkg/utils/memory_resources_test.go
Normal file
167
pkg/utils/memory_resources_test.go
Normal file
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
HugepageSize2Mi = 2048
|
||||
HugepageSize1Gi = 1048576
|
||||
)
|
||||
|
||||
const testMeminfo = `Node 0 MemTotal: 32718644 kB
|
||||
Node 0 MemFree: 2915988 kB
|
||||
Node 0 MemUsed: 29802656 kB
|
||||
Node 0 Active: 19631832 kB
|
||||
Node 0 Inactive: 8089096 kB
|
||||
Node 0 Active(anon): 10104396 kB
|
||||
Node 0 Inactive(anon): 511432 kB
|
||||
Node 0 Active(file): 9527436 kB
|
||||
Node 0 Inactive(file): 7577664 kB
|
||||
Node 0 Unevictable: 637864 kB
|
||||
Node 0 Mlocked: 0 kB
|
||||
Node 0 Dirty: 1140 kB
|
||||
Node 0 Writeback: 0 kB
|
||||
Node 0 FilePages: 18206092 kB
|
||||
Node 0 Mapped: 2000244 kB
|
||||
Node 0 AnonPages: 10152780 kB
|
||||
Node 0 Shmem: 1249348 kB
|
||||
Node 0 KernelStack: 37440 kB
|
||||
Node 0 PageTables: 110460 kB
|
||||
Node 0 NFS_Unstable: 0 kB
|
||||
Node 0 Bounce: 0 kB
|
||||
Node 0 WritebackTmp: 0 kB
|
||||
Node 0 KReclaimable: 843624 kB
|
||||
Node 0 Slab: 1198060 kB
|
||||
Node 0 SReclaimable: 843624 kB
|
||||
Node 0 SUnreclaim: 354436 kB
|
||||
Node 0 AnonHugePages: 26624 kB
|
||||
Node 0 ShmemHugePages: 0 kB
|
||||
Node 0 ShmemPmdMapped: 0 kB
|
||||
Node 0 FileHugePages: 0 kB
|
||||
Node 0 FilePmdMapped: 0 kB
|
||||
Node 0 HugePages_Total: 0
|
||||
Node 0 HugePages_Free: 0
|
||||
Node 0 HugePages_Surp: 0`
|
||||
|
||||
func TestGetMemoryResourceCounters(t *testing.T) {
|
||||
rootDir, err := os.MkdirTemp("", "fakehp")
|
||||
if err != nil {
|
||||
t.Errorf("failed to create temporary directory: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(rootDir) // clean up
|
||||
|
||||
sysBusNodeBasepath = rootDir
|
||||
|
||||
// set mock hugepages
|
||||
if err := makeHugepagesTree(rootDir, 2); err != nil {
|
||||
t.Errorf("failed to setup the fake tree on %q: %v", rootDir, err)
|
||||
}
|
||||
if err := setHPCount(rootDir, 0, HugepageSize2Mi, 6); err != nil {
|
||||
t.Errorf("failed to setup hugepages on node %d the fake tree on %q: %v", 0, rootDir, err)
|
||||
}
|
||||
if err := setHPCount(rootDir, 1, HugepageSize2Mi, 8); err != nil {
|
||||
t.Errorf("failed to setup hugepages on node %d the fake tree on %q: %v", 0, rootDir, err)
|
||||
}
|
||||
|
||||
// set mock memory
|
||||
if err := makeMemoryTree(rootDir, 2); err != nil {
|
||||
t.Errorf("failed to setup the fake tree on %q: %v", rootDir, err)
|
||||
}
|
||||
|
||||
memoryCounters, err := GetNumaMemoryResources()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if memoryCounters[0]["hugepages-2Mi"] != 12582912 {
|
||||
t.Errorf("found unexpected amount of 2Mi hugepages under the NUMA node 0: %d", memoryCounters[0]["hugepages-2Mi"])
|
||||
}
|
||||
if memoryCounters[1]["hugepages-2Mi"] != 16777216 {
|
||||
t.Errorf("found unexpected amount of 2Mi hugepages under the NUMA node 1: %d", memoryCounters[1]["hugepages-2Mi"])
|
||||
}
|
||||
|
||||
if memoryCounters[0]["hugepages-1Gi"] != 0 {
|
||||
t.Errorf("found unexpected 1Gi hugepages for node 0: %v", memoryCounters[0]["hugepages-1Gi"])
|
||||
}
|
||||
if memoryCounters[1]["hugepages-1Gi"] != 0 {
|
||||
t.Errorf("found unexpected 1Gi hugepages for node 1: %v", memoryCounters[0]["hugepages-1Gi"])
|
||||
}
|
||||
|
||||
if memoryCounters[0]["memory"] != 32718644*1024 {
|
||||
t.Errorf("found unexpected amount of memory under the NUMA node 0: %d", memoryCounters[0][v1.ResourceMemory])
|
||||
}
|
||||
|
||||
if memoryCounters[1]["memory"] != 32718644*1024 {
|
||||
t.Errorf("found unexpected amount of memory under the NUMA node 1: %d", memoryCounters[0][v1.ResourceMemory])
|
||||
}
|
||||
}
|
||||
|
||||
func makeMemoryTree(root string, numNodes int) error {
|
||||
for idx := 0; idx < numNodes; idx++ {
|
||||
path := filepath.Join(
|
||||
root,
|
||||
fmt.Sprintf("node%d", idx),
|
||||
)
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
meminfoFile := filepath.Join(path, "meminfo")
|
||||
if err := os.WriteFile(meminfoFile, []byte(testMeminfo), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeHugepagesTree(root string, numNodes int) error {
|
||||
for idx := 0; idx < numNodes; idx++ {
|
||||
for _, size := range []int{HugepageSize2Mi, HugepageSize1Gi} {
|
||||
path := filepath.Join(
|
||||
root,
|
||||
fmt.Sprintf("node%d", idx),
|
||||
"hugepages",
|
||||
fmt.Sprintf("hugepages-%dkB", size),
|
||||
)
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setHPCount(root, idx, size, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setHPCount(root string, nodeID, pageSize, numPages int) error {
|
||||
path := filepath.Join(
|
||||
root,
|
||||
fmt.Sprintf("node%d", nodeID),
|
||||
"hugepages",
|
||||
fmt.Sprintf("hugepages-%dkB", pageSize),
|
||||
"nr_hugepages",
|
||||
)
|
||||
return os.WriteFile(path, []byte(fmt.Sprintf("%d", numPages)), 0644)
|
||||
}
|
Loading…
Add table
Reference in a new issue