Repository: kubernetes-sigs/node-feature-discovery
Pull request #593 (2021-09-12 13:30:30)

resourcemonitor: aggregate and provide the memory and hugepages information
With the moving of the memory manager to beta, we have an option to expose relevant memory and hugepages information under the `NodeResourceTopology` CR.

k8s-ci-robot: Hi @cynepco3hahue. Thanks for your PR.

I'm waiting for a [kubernetes-sigs](https://github.com/orgs/kubernetes-sigs/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fnode-feature-discovery).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/node-feature-discovery/pull/593#" title="Author self-approved">cynepco3hahue</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **marquiz** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @marquiz` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fnode-feature-discovery).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/node-feature-discovery/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["marquiz"]} -->
cynepco3hahue: The PR depends on https://github.com/kubernetes-sigs/node-feature-discovery/issues/333 and probably will require the rebase once the parent PR will be merged.
k8s-ci-robot: @cynepco3hahue: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-node-feature-discovery-verify | d56e7e9097a4abf6db11da8ed449d66178b44e4a | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_node-feature-discovery/593/pull-node-feature-discovery-verify/1455926667057827840) | true | `/test pull-node-feature-discovery-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=node-feature-discovery&pr=593). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Acynepco3hahue). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/node-feature-discovery/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @cynepco3hahue: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-node-feature-discovery-verify | d56e7e9097a4abf6db11da8ed449d66178b44e4a | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_node-feature-discovery/593/pull-node-feature-discovery-verify/1455926667057827840) | true | `/test pull-node-feature-discovery-verify`
pull-node-feature-discovery-build-image | d56e7e9097a4abf6db11da8ed449d66178b44e4a | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_node-feature-discovery/593/pull-node-feature-discovery-build-image/1455926667154296832) | true | `/test pull-node-feature-discovery-build-image`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=node-feature-discovery&pr=593). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Acynepco3hahue). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/node-feature-discovery/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
cynepco3hahue: @marquiz Thanks for the code update, I applied all changes and fixed unittests, so now it should be ok.

Patch:
diff --git a/pkg/resourcemonitor/noderesourcesaggregator.go b/pkg/resourcemonitor/noderesourcesaggregator.go
index e14e4f304..edb5d0703 100644
--- a/pkg/resourcemonitor/noderesourcesaggregator.go
+++ b/pkg/resourcemonitor/noderesourcesaggregator.go
@@ -19,6 +19,8 @@ package resourcemonitor
 import (
 	"context"
 	"fmt"
+	"strconv"
+	"strings"
 	"time"
 
 	"github.com/jaypipes/ghw"
@@ -27,6 +29,8 @@ import (
 	"k8s.io/apimachinery/pkg/api/resource"
 	"k8s.io/klog/v2"
 	podresourcesapi "k8s.io/kubelet/pkg/apis/podresources/v1"
+
+	"sigs.k8s.io/node-feature-discovery/pkg/utils"
 	"sigs.k8s.io/node-feature-discovery/source"
 )
 
@@ -38,9 +42,10 @@ const (
 type nodeResources struct {
 	perNUMAAllocatable map[int]map[v1.ResourceName]int64
 	// mapping: resourceName -> resourceID -> nodeID
-	resourceID2NUMAID    map[string]map[string]int
-	topo                 *ghw.TopologyInfo
-	reservedCPUIDPerNUMA map[int][]string
+	resourceID2NUMAID              map[string]map[string]int
+	topo                           *ghw.TopologyInfo
+	reservedCPUIDPerNUMA           map[int][]string
+	memoryResourcesCapacityPerNUMA utils.NumaMemoryResources
 }
 
 type resourceData struct {
@@ -59,6 +64,11 @@ func NewResourcesAggregator(podResourceClient podresourcesapi.PodResourcesLister
 		return nil, err
 	}
 
+	memoryResourcesCapacityPerNUMA, err := getMemoryResourcesCapacity()
+	if err != nil {
+		return nil, err
+	}
+
 	ctx, cancel := context.WithTimeout(context.Background(), defaultPodResourcesTimeout)
 	defer cancel()
 
@@ -68,17 +78,18 @@ func NewResourcesAggregator(podResourceClient podresourcesapi.PodResourcesLister
 		return nil, fmt.Errorf("can't receive response: %v.Get(_) = _, %w", podResourceClient, err)
 	}
 
-	return NewResourcesAggregatorFromData(topo, resp), nil
+	return NewResourcesAggregatorFromData(topo, resp, memoryResourcesCapacityPerNUMA), nil
 }
 
 // NewResourcesAggregatorFromData is used to aggregate resource information based on the received data from underlying hardware and podresource API
-func NewResourcesAggregatorFromData(topo *ghw.TopologyInfo, resp *podresourcesapi.AllocatableResourcesResponse) ResourcesAggregator {
+func NewResourcesAggregatorFromData(topo *ghw.TopologyInfo, resp *podresourcesapi.AllocatableResourcesResponse, memoryResourceCapacity utils.NumaMemoryResources) ResourcesAggregator {
 	allDevs := getContainerDevicesFromAllocatableResources(resp, topo)
 	return &nodeResources{
-		topo:                 topo,
-		resourceID2NUMAID:    makeResourceMap(len(topo.Nodes), allDevs),
-		perNUMAAllocatable:   makeNodeAllocatable(allDevs),
-		reservedCPUIDPerNUMA: makeReservedCPUMap(topo.Nodes, allDevs),
+		topo:                           topo,
+		resourceID2NUMAID:              makeResourceMap(len(topo.Nodes), allDevs),
+		perNUMAAllocatable:             makeNodeAllocatable(allDevs, resp.GetMemory()),
+		reservedCPUIDPerNUMA:           makeReservedCPUMap(topo.Nodes, allDevs),
+		memoryResourcesCapacityPerNUMA: memoryResourceCapacity,
 	}
 }
 
@@ -89,18 +100,34 @@ func (noderesourceData *nodeResources) Aggregate(podResData []PodResources) topo
 		nodeRes, ok := noderesourceData.perNUMAAllocatable[nodeID]
 		if ok {
 			perNuma[nodeID] = make(map[v1.ResourceName]*resourceData)
-			for resName, resCap := range nodeRes {
-				if resName == "cpu" {
+			for resName, allocatable := range nodeRes {
+				switch {
+				case resName == "cpu":
 					perNuma[nodeID][resName] = &resourceData{
-						allocatable: resCap,
-						available:   resCap,
-						capacity:    resCap + int64(len(noderesourceData.reservedCPUIDPerNUMA[nodeID])),
+						allocatable: allocatable,
+						available:   allocatable,
+						capacity:    allocatable + int64(len(noderesourceData.reservedCPUIDPerNUMA[nodeID])),
+					}
+				case resName == v1.ResourceMemory, strings.HasPrefix(string(resName), v1.ResourceHugePagesPrefix):
+					var capacity int64
+					if _, ok := noderesourceData.memoryResourcesCapacityPerNUMA[nodeID]; !ok {
+						capacity = allocatable
+					} else if _, ok := noderesourceData.memoryResourcesCapacityPerNUMA[nodeID][resName]; !ok {
+						capacity = allocatable
+					} else {
+						capacity = noderesourceData.memoryResourcesCapacityPerNUMA[nodeID][resName]
 					}
-				} else {
+
 					perNuma[nodeID][resName] = &resourceData{
-						allocatable: resCap,
-						available:   resCap,
-						capacity:    resCap,
+						allocatable: allocatable,
+						available:   allocatable,
+						capacity:    capacity,
+					}
+				default:
+					perNuma[nodeID][resName] = &resourceData{
+						allocatable: allocatable,
+						available:   allocatable,
+						capacity:    allocatable,
 					}
 				}
 			}
@@ -119,6 +146,11 @@ func (noderesourceData *nodeResources) Aggregate(podResData []PodResources) topo
 	for _, podRes := range podResData {
 		for _, contRes := range podRes.Containers {
 			for _, res := range contRes.Resources {
+				if res.Name == v1.ResourceMemory || strings.HasPrefix(string(res.Name), v1.ResourceHugePagesPrefix) {
+					noderesourceData.updateMemoryAvailable(perNuma, res)
+					continue
+				}
+
 				noderesourceData.updateAvailable(perNuma, res)
 			}
 		}
@@ -136,7 +168,7 @@ func (noderesourceData *nodeResources) Aggregate(podResData []PodResources) topo
 		if err != nil {
 			klog.Infof("cannot find costs for NUMA node %d: %v", nodeID, err)
 		} else {
-			zone.Costs = topologyv1alpha1.CostList(costs)
+			zone.Costs = costs
 		}
 
 		for name, resData := range resList {
@@ -219,7 +251,7 @@ func makeZoneName(nodeID int) string {
 // makeNodeAllocatable computes the node allocatable as mapping (NUMA node ID) -> Resource -> Allocatable (amount, int).
 // The computation is done assuming all the resources to represent the allocatable for are represented on a slice
 // of ContainerDevices. No special treatment is done for CPU IDs. See getContainerDevicesFromAllocatableResources.
-func makeNodeAllocatable(devices []*podresourcesapi.ContainerDevices) map[int]map[v1.ResourceName]int64 {
+func makeNodeAllocatable(devices []*podresourcesapi.ContainerDevices, memoryBlocks []*podresourcesapi.ContainerMemory) map[int]map[v1.ResourceName]int64 {
 	perNUMAAllocatable := make(map[int]map[v1.ResourceName]int64)
 	// initialize with the capacities
 	for _, device := range devices {
@@ -234,6 +266,31 @@ func makeNodeAllocatable(devices []*podresourcesapi.ContainerDevices) map[int]ma
 			perNUMAAllocatable[nodeID] = nodeRes
 		}
 	}
+
+	for _, block := range memoryBlocks {
+		memoryType := v1.ResourceName(block.GetMemoryType())
+
+		blockTopology := block.GetTopology()
+		if blockTopology == nil {
+			continue
+		}
+
+		for _, node := range blockTopology.GetNodes() {
+			nodeID := int(node.GetID())
+			if _, ok := perNUMAAllocatable[nodeID]; !ok {
+				perNUMAAllocatable[nodeID] = make(map[v1.ResourceName]int64)
+			}
+
+			if _, ok := perNUMAAllocatable[nodeID][memoryType]; !ok {
+				perNUMAAllocatable[nodeID][memoryType] = 0
+			}
+
+			// I do not like the idea to cast from uint64 to int64, but until the memory size does not go over
+			// 8589934592Gi, it should be ok
+			perNUMAAllocatable[nodeID][memoryType] += int64(block.GetSize_())
+		}
+	}
+
 	return perNUMAAllocatable
 }
 
@@ -330,3 +387,86 @@ func getCPUs(devices []*podresourcesapi.ContainerDevices) map[string]int {
 	}
 	return cpuMap
 }
+
+// updateMemoryAvailable computes the actual amount of the available memory.
+// This function assumes the available resources are initialized to be equal to the capacity.
+func (noderesourceData *nodeResources) updateMemoryAvailable(numaData map[int]map[v1.ResourceName]*resourceData, ri ResourceInfo) {
+	if len(ri.NumaNodeIds) == 0 {
+		klog.Warningf("no NUMA nodes information is available for device %q", ri.Name)
+		return
+	}
+
+	if len(ri.Data) != 1 {
+		klog.Warningf("no size information is available for the device %q", ri.Name)
+		return
+	}
+
+	requestedSize, err := strconv.ParseInt(ri.Data[0], 10, 64)
+	if err != nil {
+		klog.Errorf("failed to parse resource requested size: %w", err)
+		return
+	}
+
+	for _, numaNodeID := range ri.NumaNodeIds {
+		if requestedSize == 0 {
+			return
+		}
+
+		if _, ok := numaData[numaNodeID]; !ok {
+			klog.Warningf("failed to find NUMA node ID %d under the node topology", numaNodeID)
+			continue
+		}
+
+		if _, ok := numaData[numaNodeID][ri.Name]; !ok {
+			klog.Warningf("failed to find resource %q under the node topology", ri.Name)
+			return
+		}
+
+		if numaData[numaNodeID][ri.Name].available == 0 {
+			klog.V(4).Infof("no available memory on the node %d for the resource %q", numaNodeID, ri.Name)
+			continue
+		}
+
+		// For the container pinned only to one NUMA node the calculation is pretty straight forward, the code will
+		// just reduce the specified NUMA node free size
+		// For the container pinned to multiple NUMA nodes, the code will reduce the free size of NUMA nodes
+		// in ascending order. For example, for a container pinned to NUMA node 0 and NUMA node 1,
+		// it will first reduce the memory of the NUMA node 0 to zero, and after the remaining
+		// amount of memory from the NUMA node 1.
+		// This behavior is tightly coupled with the Kubernetes memory manager logic.
+		if requestedSize >= numaData[numaNodeID][ri.Name].available {
+			requestedSize -= numaData[numaNodeID][ri.Name].available
+			numaData[numaNodeID][ri.Name].available = 0
+		} else {
+			numaData[numaNodeID][ri.Name].available -= requestedSize
+			requestedSize = 0
+		}
+	}
+
+	if requestedSize > 0 {
+		klog.Warningf("the resource %q requested size was not fully satisfied by NUMA nodes", ri.Name)
+	}
+}
+
+func getMemoryResourcesCapacity() (utils.NumaMemoryResources, error) {
+	memoryResources, err := utils.GetNumaMemoryResources()
+	if err != nil {
+		return nil, err
+	}
+
+	capacity := make(utils.NumaMemoryResources)
+	for numaID, resources := range memoryResources {
+		if _, ok := capacity[numaID]; !ok {
+			capacity[numaID] = map[v1.ResourceName]int64{}
+		}
+
+		for resourceName, value := range resources {
+			if _, ok := capacity[numaID][resourceName]; !ok {
+				capacity[numaID][resourceName] = 0
+			}
+			capacity[numaID][resourceName] += value
+		}
+	}
+
+	return capacity, nil
+}
diff --git a/pkg/resourcemonitor/podresourcesscanner.go b/pkg/resourcemonitor/podresourcesscanner.go
index 29b8d2b50..4bc68cecb 100644
--- a/pkg/resourcemonitor/podresourcesscanner.go
+++ b/pkg/resourcemonitor/podresourcesscanner.go
@@ -162,9 +162,24 @@ func (resMon *PodResourcesScanner) Scan() ([]PodResources, error) {
 			}
 
 			for _, device := range container.GetDevices() {
+				numaNodesIDs := getNumaNodeIds(device.GetTopology())
 				contRes.Resources = append(contRes.Resources, ResourceInfo{
-					Name: v1.ResourceName(device.ResourceName),
-					Data: device.DeviceIds,
+					Name:        v1.ResourceName(device.ResourceName),
+					Data:        device.DeviceIds,
+					NumaNodeIds: numaNodesIDs,
+				})
+			}
+
+			for _, block := range container.GetMemory() {
+				if block.GetSize_() == 0 {
+					continue
+				}
+
+				topology := getNumaNodeIds(block.GetTopology())
+				contRes.Resources = append(contRes.Resources, ResourceInfo{
+					Name:        v1.ResourceName(block.MemoryType),
+					Data:        []string{fmt.Sprintf("%d", block.GetSize_())},
+					NumaNodeIds: topology,
 				})
 			}
 
@@ -194,3 +209,18 @@ func hasDevice(podResource *podresourcesapi.PodResources) bool {
 	klog.Infof("pod:%s doesn't have devices", podResource.GetName())
 	return false
 }
+
+func getNumaNodeIds(topologyInfo *podresourcesapi.TopologyInfo) []int {
+	if topologyInfo == nil {
+		return nil
+	}
+
+	var topology []int
+	for _, node := range topologyInfo.Nodes {
+		if node != nil {
+			topology = append(topology, int(node.ID))
+		}
+	}
+
+	return topology
+}
diff --git a/pkg/resourcemonitor/types.go b/pkg/resourcemonitor/types.go
index 438964332..e42209d44 100644
--- a/pkg/resourcemonitor/types.go
+++ b/pkg/resourcemonitor/types.go
@@ -34,8 +34,9 @@ type Args struct {
 
 // ResourceInfo stores information of resources and their corresponding IDs obtained from PodResource API
 type ResourceInfo struct {
-	Name corev1.ResourceName
-	Data []string
+	Name        corev1.ResourceName
+	Data        []string
+	NumaNodeIds []int
 }
 
 // ContainerResources contains information about the node resources assigned to a container
diff --git a/pkg/utils/memory_resources.go b/pkg/utils/memory_resources.go
new file mode 100644
index 000000000..705dd8fb9
--- /dev/null
+++ b/pkg/utils/memory_resources.go
@@ -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)
+}


Test patch:
diff --git a/pkg/resourcemonitor/noderesourcesaggregator_test.go b/pkg/resourcemonitor/noderesourcesaggregator_test.go
index b6b7c5e12..a5ddeca89 100644
--- a/pkg/resourcemonitor/noderesourcesaggregator_test.go
+++ b/pkg/resourcemonitor/noderesourcesaggregator_test.go
@@ -22,14 +22,17 @@ import (
 	"sort"
 	"testing"
 
+	"github.com/google/go-cmp/cmp"
 	"github.com/jaypipes/ghw"
-
-	cmp "github.com/google/go-cmp/cmp"
 	. "github.com/smartystreets/goconvey/convey"
+
+	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
+	v1 "k8s.io/kubelet/pkg/apis/podresources/v1"
 
 	topologyv1alpha1 "github.com/k8stopologyawareschedwg/noderesourcetopology-api/pkg/apis/topology/v1alpha1"
-	v1 "k8s.io/kubelet/pkg/apis/podresources/v1"
+
+	"sigs.k8s.io/node-feature-discovery/pkg/utils"
 )
 
 func TestResourcesAggregator(t *testing.T) {
@@ -45,78 +48,78 @@ func TestResourcesAggregator(t *testing.T) {
 	Convey("When I aggregate the node resources fake data and no pod allocation", t, func() {
 		availRes := &v1.AllocatableResourcesResponse{
 			Devices: []*v1.ContainerDevices{
-				&v1.ContainerDevices{
+				{
 					ResourceName: "fake.io/net",
 					DeviceIds:    []string{"netAAA-0"},
 					Topology: &v1.TopologyInfo{
 						Nodes: []*v1.NUMANode{
-							&v1.NUMANode{
+							{
 								ID: 0,
 							},
 						},
 					},
 				},
-				&v1.ContainerDevices{
+				{
 					ResourceName: "fake.io/net",
 					DeviceIds:    []string{"netAAA-1"},
 					Topology: &v1.TopologyInfo{
 						Nodes: []*v1.NUMANode{
-							&v1.NUMANode{
+							{
 								ID: 0,
 							},
 						},
 					},
 				},
-				&v1.ContainerDevices{
+				{
 					ResourceName: "fake.io/net",
 					DeviceIds:    []string{"netAAA-2"},
 					Topology: &v1.TopologyInfo{
 						Nodes: []*v1.NUMANode{
-							&v1.NUMANode{
+							{
 								ID: 0,
 							},
 						},
 					},
 				},
-				&v1.ContainerDevices{
+				{
 					ResourceName: "fake.io/net",
 					DeviceIds:    []string{"netAAA-3"},
 					Topology: &v1.TopologyInfo{
 						Nodes: []*v1.NUMANode{
-							&v1.NUMANode{
+							{
 								ID: 0,
 							},
 						},
 					},
 				},
-				&v1.ContainerDevices{
+				{
 					ResourceName: "fake.io/net",
 					DeviceIds:    []string{"netBBB-0"},
 					Topology: &v1.TopologyInfo{
 						Nodes: []*v1.NUMANode{
-							&v1.NUMANode{
+							{
 								ID: 1,
 							},
 						},
 					},
 				},
-				&v1.ContainerDevices{
+				{
 					ResourceName: "fake.io/net",
 					DeviceIds:    []string{"netBBB-1"},
 					Topology: &v1.TopologyInfo{
 						Nodes: []*v1.NUMANode{
-							&v1.NUMANode{
+							{
 								ID: 1,
 							},
 						},
 					},
 				},
-				&v1.ContainerDevices{
+				{
 					ResourceName: "fake.io/gpu",
 					DeviceIds:    []string{"gpuAAA"},
 					Topology: &v1.TopologyInfo{
 						Nodes: []*v1.NUMANode{
-							&v1.NUMANode{
+							{
 								ID: 1,
 							},
 						},
@@ -129,9 +132,53 @@ func TestResourcesAggregator(t *testing.T) {
 				2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
 				12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
 			},
+			Memory: []*v1.ContainerMemory{
+				{
+					MemoryType: "memory",
+					Size_:      1024,
+					Topology: &v1.TopologyInfo{
+						Nodes: []*v1.NUMANode{
+							{
+								ID: 0,
+							},
+						},
+					},
+				},
+				{
+					MemoryType: "memory",
+					Size_:      1024,
+					Topology: &v1.TopologyInfo{
+						Nodes: []*v1.NUMANode{
+							{
+								ID: 1,
+							},
+						},
+					},
+				},
+				{
+					MemoryType: "hugepages-2Mi",
+					Size_:      1024,
+					Topology: &v1.TopologyInfo{
+						Nodes: []*v1.NUMANode{
+							{
+								ID: 1,
+							},
+						},
+					},
+				},
+			},
 		}
 
-		resAggr = NewResourcesAggregatorFromData(&fakeTopo, availRes)
+		memoryResourcesCapacity := utils.NumaMemoryResources{
+			0: map[corev1.ResourceName]int64{
+				corev1.ResourceMemory: 2048,
+			},
+			1: map[corev1.ResourceName]int64{
+				corev1.ResourceMemory:                2048,
+				corev1.ResourceName("hugepages-2Mi"): 2048,
+			},
+		}
+		resAggr = NewResourcesAggregatorFromData(&fakeTopo, availRes, memoryResourcesCapacity)
 
 		Convey("When aggregating resources", func() {
 			expected := topologyv1alpha1.ZoneList{
@@ -151,15 +198,21 @@ func TestResourcesAggregator(t *testing.T) {
 					Resources: topologyv1alpha1.ResourceInfoList{
 						topologyv1alpha1.ResourceInfo{
 							Name:        "cpu",
-							Available:   resource.MustParse("11"),
-							Allocatable: resource.MustParse("11"),
-							Capacity:    resource.MustParse("12"),
+							Available:   *resource.NewQuantity(11, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(11, resource.DecimalSI),
+							Capacity:    *resource.NewQuantity(12, resource.DecimalSI),
 						},
 						topologyv1alpha1.ResourceInfo{
 							Name:        "fake.io/net",
-							Available:   resource.MustParse("4"),
-							Allocatable: resource.MustParse("4"),
-							Capacity:    resource.MustParse("4"),
+							Available:   *resource.NewQuantity(4, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(4, resource.DecimalSI),
+							Capacity:    *resource.NewQuantity(4, resource.DecimalSI),
+						},
+						topologyv1alpha1.ResourceInfo{
+							Name:        "memory",
+							Available:   *resource.NewQuantity(1024, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(1024, resource.DecimalSI),
+							Capacity:    *resource.NewQuantity(2048, resource.DecimalSI),
 						},
 					},
 				},
@@ -179,21 +232,33 @@ func TestResourcesAggregator(t *testing.T) {
 					Resources: topologyv1alpha1.ResourceInfoList{
 						topologyv1alpha1.ResourceInfo{
 							Name:        "cpu",
-							Available:   resource.MustParse("11"),
-							Allocatable: resource.MustParse("11"),
-							Capacity:    resource.MustParse("12"),
+							Available:   *resource.NewQuantity(11, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(11, resource.DecimalSI),
+							Capacity:    *resource.NewQuantity(12, resource.DecimalSI),
 						},
 						topologyv1alpha1.ResourceInfo{
 							Name:        "fake.io/gpu",
-							Available:   resource.MustParse("1"),
-							Allocatable: resource.MustParse("1"),
-							Capacity:    resource.MustParse("1"),
+							Available:   *resource.NewQuantity(1, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(1, resource.DecimalSI),
+							Capacity:    *resource.NewQuantity(1, resource.DecimalSI),
 						},
 						topologyv1alpha1.ResourceInfo{
 							Name:        "fake.io/net",
-							Available:   resource.MustParse("4"),
-							Allocatable: resource.MustParse("4"),
-							Capacity:    resource.MustParse("4"),
+							Available:   *resource.NewQuantity(2, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(2, resource.DecimalSI),
+							Capacity:    *resource.NewQuantity(2, resource.DecimalSI),
+						},
+						topologyv1alpha1.ResourceInfo{
+							Name:        "hugepages-2Mi",
+							Available:   *resource.NewQuantity(1024, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(1024, resource.DecimalSI),
+							Capacity:    *resource.NewQuantity(2048, resource.DecimalSI),
+						},
+						topologyv1alpha1.ResourceInfo{
+							Name:        "memory",
+							Available:   *resource.NewQuantity(1024, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(1024, resource.DecimalSI),
+							Capacity:    *resource.NewQuantity(2048, resource.DecimalSI),
 						},
 					},
 				},
@@ -213,44 +278,45 @@ func TestResourcesAggregator(t *testing.T) {
 					return resource.Resources[x].Name < resource.Resources[y].Name
 				})
 			}
-			log.Printf("result=%v", res)
-			log.Printf("expected=%v", expected)
+
+			log.Printf("result=%+v", res)
+			log.Printf("expected=%+v", expected)
 			log.Printf("diff=%s", cmp.Diff(res, expected))
-			So(cmp.Equal(res, expected), ShouldBeFalse)
+			So(cmp.Equal(res, expected), ShouldBeTrue)
 		})
 	})
 
 	Convey("When I aggregate the node resources fake data and some pod allocation", t, func() {
 		availRes := &v1.AllocatableResourcesResponse{
 			Devices: []*v1.ContainerDevices{
-				&v1.ContainerDevices{
+				{
 					ResourceName: "fake.io/net",
 					DeviceIds:    []string{"netAAA"},
 					Topology: &v1.TopologyInfo{
 						Nodes: []*v1.NUMANode{
-							&v1.NUMANode{
+							{
 								ID: 0,
 							},
 						},
 					},
 				},
-				&v1.ContainerDevices{
+				{
 					ResourceName: "fake.io/net",
 					DeviceIds:    []string{"netBBB"},
 					Topology: &v1.TopologyInfo{
 						Nodes: []*v1.NUMANode{
-							&v1.NUMANode{
+							{
 								ID: 1,
 							},
 						},
 					},
 				},
-				&v1.ContainerDevices{
+				{
 					ResourceName: "fake.io/gpu",
 					DeviceIds:    []string{"gpuAAA"},
 					Topology: &v1.TopologyInfo{
 						Nodes: []*v1.NUMANode{
-							&v1.NUMANode{
+							{
 								ID: 1,
 							},
 						},
@@ -263,27 +329,82 @@ func TestResourcesAggregator(t *testing.T) {
 				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
 				12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
 			},
+			Memory: []*v1.ContainerMemory{
+				{
+					MemoryType: "memory",
+					Size_:      1024,
+					Topology: &v1.TopologyInfo{
+						Nodes: []*v1.NUMANode{
+							{
+								ID: 0,
+							},
+						},
+					},
+				},
+				{
+					MemoryType: "memory",
+					Size_:      1024,
+					Topology: &v1.TopologyInfo{
+						Nodes: []*v1.NUMANode{
+							{
+								ID: 1,
+							},
+						},
+					},
+				},
+				{
+					MemoryType: "hugepages-2Mi",
+					Size_:      1024,
+					Topology: &v1.TopologyInfo{
+						Nodes: []*v1.NUMANode{
+							{
+								ID: 1,
+							},
+						},
+					},
+				},
+			},
+		}
+
+		memoryResourcesCapacity := utils.NumaMemoryResources{
+			0: map[corev1.ResourceName]int64{
+				corev1.ResourceMemory: 2048,
+			},
+			1: map[corev1.ResourceName]int64{
+				corev1.ResourceMemory:                2048,
+				corev1.ResourceName("hugepages-2Mi"): 2048,
+			},
 		}
 
-		resAggr = NewResourcesAggregatorFromData(&fakeTopo, availRes)
+		resAggr = NewResourcesAggregatorFromData(&fakeTopo, availRes, memoryResourcesCapacity)
 
 		Convey("When aggregating resources", func() {
 			podRes := []PodResources{
-				PodResources{
+				{
 					Name:      "test-pod-0",
 					Namespace: "default",
 					Containers: []ContainerResources{
-						ContainerResources{
+						{
 							Name: "test-cnt-0",
 							Resources: []ResourceInfo{
-								ResourceInfo{
+								{
 									Name: "cpu",
 									Data: []string{"5", "7"},
 								},
-								ResourceInfo{
+								{
 									Name: "fake.io/net",
 									Data: []string{"netBBB"},
 								},
+								{
+									Name:        "memory",
+									Data:        []string{"512"},
+									NumaNodeIds: []int{1},
+								},
+								{
+									Name:        "hugepages-2Mi",
+									Data:        []string{"512"},
+									NumaNodeIds: []int{1},
+								},
 							},
 						},
 					},
@@ -307,15 +428,21 @@ func TestResourcesAggregator(t *testing.T) {
 					Resources: topologyv1alpha1.ResourceInfoList{
 						topologyv1alpha1.ResourceInfo{
 							Name:        "cpu",
-							Available:   resource.MustParse("11"),
-							Allocatable: resource.MustParse("11"),
-							Capacity:    resource.MustParse("12"),
+							Available:   *resource.NewQuantity(11, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(11, resource.DecimalSI),
+							Capacity:    *resource.NewQuantity(12, resource.DecimalSI),
 						},
 						topologyv1alpha1.ResourceInfo{
 							Name:        "fake.io/net",
-							Available:   resource.MustParse("1"),
-							Allocatable: resource.MustParse("1"),
-							Capacity:    resource.MustParse("1"),
+							Available:   *resource.NewQuantity(1, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(1, resource.DecimalSI),
+							Capacity:    *resource.NewQuantity(1, resource.DecimalSI),
+						},
+						topologyv1alpha1.ResourceInfo{
+							Name:        "memory",
+							Available:   *resource.NewQuantity(1024, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(1024, resource.DecimalSI),
+							Capacity:    *resource.NewQuantity(2048, resource.DecimalSI),
 						},
 					},
 				},
@@ -336,20 +463,32 @@ func TestResourcesAggregator(t *testing.T) {
 						topologyv1alpha1.ResourceInfo{
 							Name:        "cpu",
 							Available:   resource.MustParse("10"),
-							Allocatable: resource.MustParse("12"),
-							Capacity:    resource.MustParse("12"),
+							Allocatable: *resource.NewQuantity(12, resource.DecimalSI),
+							Capacity:    *resource.NewQuantity(12, resource.DecimalSI),
 						},
 						topologyv1alpha1.ResourceInfo{
 							Name:        "fake.io/gpu",
-							Available:   resource.MustParse("1"),
-							Allocatable: resource.MustParse("1"),
-							Capacity:    resource.MustParse("1"),
+							Available:   *resource.NewQuantity(1, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(1, resource.DecimalSI),
+							Capacity:    *resource.NewQuantity(1, resource.DecimalSI),
 						},
 						topologyv1alpha1.ResourceInfo{
 							Name:        "fake.io/net",
-							Available:   resource.MustParse("0"),
-							Allocatable: resource.MustParse("1"),
-							Capacity:    resource.MustParse("1"),
+							Available:   *resource.NewQuantity(0, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(1, resource.DecimalSI),
+							Capacity:    *resource.NewQuantity(1, resource.DecimalSI),
+						},
+						topologyv1alpha1.ResourceInfo{
+							Name:        "hugepages-2Mi",
+							Available:   *resource.NewQuantity(512, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(1024, resource.DecimalSI),
+							Capacity:    *resource.NewQuantity(2048, resource.DecimalSI),
+						},
+						topologyv1alpha1.ResourceInfo{
+							Name:        "memory",
+							Available:   *resource.NewQuantity(512, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(1024, resource.DecimalSI),
+							Capacity:    *resource.NewQuantity(2048, resource.DecimalSI),
 						},
 					},
 				},
@@ -369,8 +508,8 @@ func TestResourcesAggregator(t *testing.T) {
 					return resource.Resources[x].Name < resource.Resources[y].Name
 				})
 			}
-			log.Printf("result=%v", res)
-			log.Printf("expected=%v", expected)
+			log.Printf("result=%+v", res)
+			log.Printf("expected=%+v", expected)
 			log.Printf("diff=%s", cmp.Diff(res, expected))
 			So(cmp.Equal(res, expected), ShouldBeTrue)
 		})
@@ -379,7 +518,7 @@ func TestResourcesAggregator(t *testing.T) {
 }
 
 // ghwc topology -f json
-var testTopology string = `{
+var testTopology = `{
     "nodes": [
       {
         "id": 0,
diff --git a/pkg/resourcemonitor/podresourcesscanner_test.go b/pkg/resourcemonitor/podresourcesscanner_test.go
index a2b1bd08e..c8b927364 100644
--- a/pkg/resourcemonitor/podresourcesscanner_test.go
+++ b/pkg/resourcemonitor/podresourcesscanner_test.go
@@ -25,16 +25,14 @@ import (
 	. "github.com/smartystreets/goconvey/convey"
 	"github.com/stretchr/testify/mock"
 
+	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	k8sclient "k8s.io/client-go/kubernetes"
 	v1 "k8s.io/kubelet/pkg/apis/podresources/v1"
 
 	"sigs.k8s.io/node-feature-discovery/pkg/apihelper"
 	"sigs.k8s.io/node-feature-discovery/pkg/podres"
-
-	corev1 "k8s.io/api/core/v1"
-
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	k8sclient "k8s.io/client-go/kubernetes"
 )
 
 func TestPodScanner(t *testing.T) {
@@ -79,24 +77,44 @@ func TestPodScanner(t *testing.T) {
 		Convey("When I successfully get valid response", func() {
 			resp := &v1.ListPodResourcesResponse{
 				PodResources: []*v1.PodResources{
-					&v1.PodResources{
+					{
 						Name:      "test-pod-0",
 						Namespace: "default",
 						Containers: []*v1.ContainerResources{
-							&v1.ContainerResources{
+							{
 								Name: "test-cnt-0",
 								Devices: []*v1.ContainerDevices{
-									&v1.ContainerDevices{
+									{
 										ResourceName: "fake.io/resource",
 										DeviceIds:    []string{"devA"},
 										Topology: &v1.TopologyInfo{
 											Nodes: []*v1.NUMANode{
-												&v1.NUMANode{ID: 0},
+												{ID: 0},
 											},
 										},
 									},
 								},
 								CpuIds: []int64{0, 1},
+								Memory: []*v1.ContainerMemory{
+									{
+										MemoryType: "hugepages-2Mi",
+										Size_:      512,
+										Topology: &v1.TopologyInfo{
+											Nodes: []*v1.NUMANode{
+												{ID: 1},
+											},
+										},
+									},
+									{
+										MemoryType: "memory",
+										Size_:      512,
+										Topology: &v1.TopologyInfo{
+											Nodes: []*v1.NUMANode{
+												{ID: 0},
+											},
+										},
+									},
+								},
 							},
 						},
 					},
@@ -120,12 +138,14 @@ func TestPodScanner(t *testing.T) {
 								Requests: corev1.ResourceList{
 									corev1.ResourceCPU:                      *resource.NewQuantity(2, resource.DecimalSI),
 									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
-									corev1.ResourceMemory:                   *resource.NewQuantity(100, resource.DecimalSI),
+									corev1.ResourceMemory:                   *resource.NewQuantity(512, resource.DecimalSI),
+									"hugepages-2Mi":                         *resource.NewQuantity(512, resource.DecimalSI),
 								},
 								Limits: corev1.ResourceList{
 									corev1.ResourceCPU:                      *resource.NewQuantity(2, resource.DecimalSI),
 									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
-									corev1.ResourceMemory:                   *resource.NewQuantity(100, resource.DecimalSI),
+									corev1.ResourceMemory:                   *resource.NewQuantity(512, resource.DecimalSI),
+									"hugepages-2Mi":                         *resource.NewQuantity(512, resource.DecimalSI),
 								},
 							},
 						},
@@ -143,20 +163,31 @@ func TestPodScanner(t *testing.T) {
 				So(len(res), ShouldBeGreaterThan, 0)
 
 				expected := []PodResources{
-					PodResources{
+					{
 						Name:      "test-pod-0",
 						Namespace: "default",
 						Containers: []ContainerResources{
-							ContainerResources{
+							{
 								Name: "test-cnt-0",
 								Resources: []ResourceInfo{
-									ResourceInfo{
+									{
 										Name: "cpu",
 										Data: []string{"0", "1"},
 									},
-									ResourceInfo{
-										Name: "fake.io/resource",
-										Data: []string{"devA"},
+									{
+										Name:        "fake.io/resource",
+										Data:        []string{"devA"},
+										NumaNodeIds: []int{0},
+									},
+									{
+										Name:        "hugepages-2Mi",
+										Data:        []string{"512"},
+										NumaNodeIds: []int{1},
+									},
+									{
+										Name:        "memory",
+										Data:        []string{"512"},
+										NumaNodeIds: []int{0},
 									},
 								},
 							},
@@ -177,14 +208,14 @@ func TestPodScanner(t *testing.T) {
 		Convey("When I successfully get valid response without topology", func() {
 			resp := &v1.ListPodResourcesResponse{
 				PodResources: []*v1.PodResources{
-					&v1.PodResources{
+					{
 						Name:      "test-pod-0",
 						Namespace: "default",
 						Containers: []*v1.ContainerResources{
-							&v1.ContainerResources{
+							{
 								Name: "test-cnt-0",
 								Devices: []*v1.ContainerDevices{
-									&v1.ContainerDevices{
+									{
 										ResourceName: "fake.io/resource",
 										DeviceIds:    []string{"devA"},
 									},
@@ -238,18 +269,18 @@ func TestPodScanner(t *testing.T) {
 				So(len(res), ShouldBeGreaterThan, 0)
 
 				expected := []PodResources{
-					PodResources{
+					{
 						Name:      "test-pod-0",
 						Namespace: "default",
 						Containers: []ContainerResources{
-							ContainerResources{
+							{
 								Name: "test-cnt-0",
 								Resources: []ResourceInfo{
-									ResourceInfo{
+									{
 										Name: "cpu",
 										Data: []string{"0", "1"},
 									},
-									ResourceInfo{
+									{
 										Name: "fake.io/resource",
 										Data: []string{"devA"},
 									},
@@ -266,11 +297,11 @@ func TestPodScanner(t *testing.T) {
 		Convey("When I successfully get valid response without devices", func() {
 			resp := &v1.ListPodResourcesResponse{
 				PodResources: []*v1.PodResources{
-					&v1.PodResources{
+					{
 						Name:      "test-pod-0",
 						Namespace: "default",
 						Containers: []*v1.ContainerResources{
-							&v1.ContainerResources{
+							{
 								Name:   "test-cnt-0",
 								CpuIds: []int64{0, 1},
 							},
@@ -317,14 +348,14 @@ func TestPodScanner(t *testing.T) {
 				So(len(res), ShouldBeGreaterThan, 0)
 
 				expected := []PodResources{
-					PodResources{
+					{
 						Name:      "test-pod-0",
 						Namespace: "default",
 						Containers: []ContainerResources{
-							ContainerResources{
+							{
 								Name: "test-cnt-0",
 								Resources: []ResourceInfo{
-									ResourceInfo{
+									{
 										Name: "cpu",
 										Data: []string{"0", "1"},
 									},
@@ -341,14 +372,14 @@ func TestPodScanner(t *testing.T) {
 		Convey("When I successfully get valid response without cpus", func() {
 			resp := &v1.ListPodResourcesResponse{
 				PodResources: []*v1.PodResources{
-					&v1.PodResources{
+					{
 						Name:      "test-pod-0",
 						Namespace: "default",
 						Containers: []*v1.ContainerResources{
-							&v1.ContainerResources{
+							{
 								Name: "test-cnt-0",
 								Devices: []*v1.ContainerDevices{
-									&v1.ContainerDevices{
+									{
 										ResourceName: "fake.io/resource",
 										DeviceIds:    []string{"devA"},
 									},
@@ -399,14 +430,14 @@ func TestPodScanner(t *testing.T) {
 				So(len(res), ShouldBeGreaterThan, 0)
 
 				expected := []PodResources{
-					PodResources{
+					{
 						Name:      "test-pod-0",
 						Namespace: "default",
 						Containers: []ContainerResources{
-							ContainerResources{
+							{
 								Name: "test-cnt-0",
 								Resources: []ResourceInfo{
-									ResourceInfo{
+									{
 										Name: "fake.io/resource",
 										Data: []string{"devA"},
 									},
@@ -423,14 +454,14 @@ func TestPodScanner(t *testing.T) {
 		Convey("When I successfully get valid response for (non-guaranteed) pods with devices without cpus", func() {
 			resp := &v1.ListPodResourcesResponse{
 				PodResources: []*v1.PodResources{
-					&v1.PodResources{
+					{
 						Name:      "test-pod-0",
 						Namespace: "default",
 						Containers: []*v1.ContainerResources{
-							&v1.ContainerResources{
+							{
 								Name: "test-cnt-0",
 								Devices: []*v1.ContainerDevices{
-									&v1.ContainerDevices{
+									{
 										ResourceName: "fake.io/resource",
 										DeviceIds:    []string{"devA"},
 									},
@@ -478,14 +509,14 @@ func TestPodScanner(t *testing.T) {
 			})
 
 			expected := []PodResources{
-				PodResources{
+				{
 					Name:      "test-pod-0",
 					Namespace: "default",
 					Containers: []ContainerResources{
-						ContainerResources{
+						{
 							Name: "test-cnt-0",
 							Resources: []ResourceInfo{
-								ResourceInfo{
+								{
 									Name: "fake.io/resource",
 									Data: []string{"devA"},
 								},
@@ -501,15 +532,15 @@ func TestPodScanner(t *testing.T) {
 		Convey("When I successfully get valid response for (non-guaranteed) pods with devices with cpus", func() {
 			resp := &v1.ListPodResourcesResponse{
 				PodResources: []*v1.PodResources{
-					&v1.PodResources{
+					{
 						Name:      "test-pod-0",
 						Namespace: "default",
 						Containers: []*v1.ContainerResources{
-							&v1.ContainerResources{
+							{
 								Name:   "test-cnt-0",
 								CpuIds: []int64{0, 1},
 								Devices: []*v1.ContainerDevices{
-									&v1.ContainerDevices{
+									{
 										ResourceName: "fake.io/resource",
 										DeviceIds:    []string{"devA"},
 									},
@@ -562,14 +593,14 @@ func TestPodScanner(t *testing.T) {
 			})
 
 			expected := []PodResources{
-				PodResources{
+				{
 					Name:      "test-pod-0",
 					Namespace: "default",
 					Containers: []ContainerResources{
-						ContainerResources{
+						{
 							Name: "test-cnt-0",
 							Resources: []ResourceInfo{
-								ResourceInfo{
+								{
 									Name: "fake.io/resource",
 									Data: []string{"devA"},
 								},
@@ -620,19 +651,19 @@ func TestPodScanner(t *testing.T) {
 		Convey("When I successfully get valid response", func() {
 			resp := &v1.ListPodResourcesResponse{
 				PodResources: []*v1.PodResources{
-					&v1.PodResources{
+					{
 						Name:      "test-pod-0",
 						Namespace: "default",
 						Containers: []*v1.ContainerResources{
-							&v1.ContainerResources{
+							{
 								Name: "test-cnt-0",
 								Devices: []*v1.ContainerDevices{
-									&v1.ContainerDevices{
+									{
 										ResourceName: "fake.io/resource",
 										DeviceIds:    []string{"devA"},
 										Topology: &v1.TopologyInfo{
 											Nodes: []*v1.NUMANode{
-												&v1.NUMANode{ID: 0},
+												{ID: 0},
 											},
 										},
 									},
@@ -688,14 +719,14 @@ func TestPodScanner(t *testing.T) {
 		Convey("When I successfully get valid response when pod is in the monitoring namespace", func() {
 			resp := &v1.ListPodResourcesResponse{
 				PodResources: []*v1.PodResources{
-					&v1.PodResources{
+					{
 						Name:      "test-pod-0",
 						Namespace: "pod-res-test",
 						Containers: []*v1.ContainerResources{
-							&v1.ContainerResources{
+							{
 								Name: "test-cnt-0",
 								Devices: []*v1.ContainerDevices{
-									&v1.ContainerDevices{
+									{
 										ResourceName: "fake.io/resource",
 										DeviceIds:    []string{"devA"},
 									},
@@ -750,18 +781,18 @@ func TestPodScanner(t *testing.T) {
 				So(len(res), ShouldBeGreaterThan, 0)
 
 				expected := []PodResources{
-					PodResources{
+					{
 						Name:      "test-pod-0",
 						Namespace: "pod-res-test",
 						Containers: []ContainerResources{
-							ContainerResources{
+							{
 								Name: "test-cnt-0",
 								Resources: []ResourceInfo{
-									ResourceInfo{
+									{
 										Name: "cpu",
 										Data: []string{"0", "1"},
 									},
-									ResourceInfo{
+									{
 										Name: "fake.io/resource",
 										Data: []string{"devA"},
 									},
@@ -778,11 +809,11 @@ func TestPodScanner(t *testing.T) {
 		Convey("When I successfully get valid empty response for a pod not in the monitoring namespace without devices", func() {
 			resp := &v1.ListPodResourcesResponse{
 				PodResources: []*v1.PodResources{
-					&v1.PodResources{
+					{
 						Name:      "test-pod-0",
 						Namespace: "default",
 						Containers: []*v1.ContainerResources{
-							&v1.ContainerResources{
+							{
 								Name:   "test-cnt-0",
 								CpuIds: []int64{0, 1},
 							},
@@ -835,14 +866,14 @@ func TestPodScanner(t *testing.T) {
 		Convey("When I successfully get an empty valid response for a pod without cpus when pod is not in the monitoring namespace", func() {
 			resp := &v1.ListPodResourcesResponse{
 				PodResources: []*v1.PodResources{
-					&v1.PodResources{
+					{
 						Name:      "test-pod-0",
 						Namespace: "default",
 						Containers: []*v1.ContainerResources{
-							&v1.ContainerResources{
+							{
 								Name: "test-cnt-0",
 								Devices: []*v1.ContainerDevices{
-									&v1.ContainerDevices{
+									{
 										ResourceName: "fake.io/resource",
 										DeviceIds:    []string{"devA"},
 									},
@@ -899,14 +930,14 @@ func TestPodScanner(t *testing.T) {
 		Convey("When I successfully get valid response for (non-guaranteed) pods with devices without cpus", func() {
 			resp := &v1.ListPodResourcesResponse{
 				PodResources: []*v1.PodResources{
-					&v1.PodResources{
+					{
 						Name:      "test-pod-0",
 						Namespace: "pod-res-test",
 						Containers: []*v1.ContainerResources{
-							&v1.ContainerResources{
+							{
 								Name: "test-cnt-0",
 								Devices: []*v1.ContainerDevices{
-									&v1.ContainerDevices{
+									{
 										ResourceName: "fake.io/resource",
 										DeviceIds:    []string{"devA"},
 									},
@@ -954,14 +985,14 @@ func TestPodScanner(t *testing.T) {
 			})
 
 			expected := []PodResources{
-				PodResources{
+				{
 					Name:      "test-pod-0",
 					Namespace: "pod-res-test",
 					Containers: []ContainerResources{
-						ContainerResources{
+						{
 							Name: "test-cnt-0",
 							Resources: []ResourceInfo{
-								ResourceInfo{
+								{
 									Name: "fake.io/resource",
 									Data: []string{"devA"},
 								},
@@ -977,15 +1008,15 @@ func TestPodScanner(t *testing.T) {
 		Convey("When I successfully get valid response for (non-guaranteed) pods with devices with cpus", func() {
 			resp := &v1.ListPodResourcesResponse{
 				PodResources: []*v1.PodResources{
-					&v1.PodResources{
+					{
 						Name:      "test-pod-0",
 						Namespace: "pod-res-test",
 						Containers: []*v1.ContainerResources{
-							&v1.ContainerResources{
+							{
 								Name:   "test-cnt-0",
 								CpuIds: []int64{0, 1},
 								Devices: []*v1.ContainerDevices{
-									&v1.ContainerDevices{
+									{
 										ResourceName: "fake.io/resource",
 										DeviceIds:    []string{"devA"},
 									},
@@ -1038,14 +1069,14 @@ func TestPodScanner(t *testing.T) {
 			})
 
 			expected := []PodResources{
-				PodResources{
+				{
 					Name:      "test-pod-0",
 					Namespace: "pod-res-test",
 					Containers: []ContainerResources{
-						ContainerResources{
+						{
 							Name: "test-cnt-0",
 							Resources: []ResourceInfo{
-								ResourceInfo{
+								{
 									Name: "fake.io/resource",
 									Data: []string{"devA"},
 								},
diff --git a/pkg/utils/memory_resources_test.go b/pkg/utils/memory_resources_test.go
new file mode 100644
index 000000000..9f1f4b063
--- /dev/null
+++ b/pkg/utils/memory_resources_test.go
@@ -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)
+}