summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorNataliia Solomko <natalirs1985@gmail.com>2025-08-19 11:47:47 +0300
committerNataliia Solomko <natalirs1985@gmail.com>2025-08-21 16:48:05 +0300
commitae336bdf8a57e28c0bc544ee6b7d57ab048123df (patch)
tree918bb66c1194373c6caab5adcc6443656ab15a78 /python
parentd104ac2c0c5e94316f3d5825df73b93c639ec0cc (diff)
downloadvyos-1x-ae336bdf8a57e28c0bc544ee6b7d57ab048123df.tar.gz
vyos-1x-ae336bdf8a57e28c0bc544ee6b7d57ab048123df.zip
T7670: VPP: Rely on all types of memory to verify memory resources
VPP can use 2M hugepages and 1G hugepages at the same time
Diffstat (limited to 'python')
-rw-r--r--python/vyos/vpp/config_resource_checks/memory.py108
-rw-r--r--python/vyos/vpp/config_verify.py66
-rw-r--r--python/vyos/vpp/utils.py11
3 files changed, 130 insertions, 55 deletions
diff --git a/python/vyos/vpp/config_resource_checks/memory.py b/python/vyos/vpp/config_resource_checks/memory.py
index 66b9fd9a3..9391e21e1 100644
--- a/python/vyos/vpp/config_resource_checks/memory.py
+++ b/python/vyos/vpp/config_resource_checks/memory.py
@@ -16,7 +16,9 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+import os
import re
+import psutil
from vyos.utils.process import cmd
from vyos.vpp.utils import (
@@ -26,29 +28,55 @@ from vyos.vpp.utils import (
from vyos.vpp.config_resource_checks.resource_defaults import default_resource_map
+def classify_page_size(page_size_bytes: int) -> str:
+ """
+ Returns one of: '4K', '2M', '1G' based on page size.
+ """
+ if page_size_bytes == 1 << 30:
+ return '1G'
+ if page_size_bytes == 2 << 20:
+ return '2M'
+ return '4K'
+
+
def get_hugepages_info() -> dict:
"""
- Returns the information about HugePages for default hugepage size
- retrieved from /proc/meminfo
+ Returns the information about HugePages
+ retrieved from /sys/kernel/mm/hugepages
"""
+ base_path = '/sys/kernel/mm/hugepages'
info = {}
- with open('/proc/meminfo', 'r') as meminfo:
- for line in meminfo:
- if line.startswith('Huge'):
- key, value, *_ = line.strip().split()
- info[key.rstrip(':')] = int(value)
+
+ for entry in os.listdir(base_path):
+ page_size_kb = entry[10:]
+ page_size = human_page_memory_to_bytes(page_size_kb)
+ key = classify_page_size(page_size)
+ info[key] = {}
+
+ with open(os.path.join(base_path, entry, 'nr_hugepages')) as f:
+ count = int(f.read().strip())
+ info[key]['pages'] = count
+ info[key]['memory'] = page_size * count
+
return info
-def get_total_hugepages_memory() -> int:
+def get_available_memory() -> dict:
+ memory = {size: info.get('memory') for size, info in get_hugepages_info().items()}
+ memory['4K'] = psutil.virtual_memory().available
+
+ return memory
+
+
+def get_vpp_used_memory() -> int:
"""
- Returns the total amount of hugepage memory (in bytes)
+ Returns memory currently used by VPP in bytes (RSS value)
"""
- info = get_hugepages_info()
- hugepages_total = info.get('HugePages_Total')
- hugepage_size = info.get('Hugepagesize') * 1024
-
- return hugepage_size * hugepages_total
+ try:
+ out = cmd('ps -o rss= -p $(pidof vpp)')
+ except OSError:
+ out = 0
+ return int(out) << 10
def get_numa_count():
@@ -61,6 +89,11 @@ def get_numa_count():
return int(m.group(1)) if m else 0
+def buffer_page_size(settings: dict) -> int:
+ page_size = settings.get('buffers', {}).get('page_size', 'default')
+ return human_page_memory_to_bytes(page_size)
+
+
def buffer_size(settings: dict) -> int:
numa_count = get_numa_count()
buffers_per_numa = int(
@@ -110,7 +143,7 @@ def statseg_size(settings: dict) -> int:
def statseg_page_size(settings: dict) -> int:
- page_size = settings.get('statseg', {}).get('page_size', 'default')
+ page_size = settings.get('statseg', {}).get('page_size')
return human_page_memory_to_bytes(page_size)
@@ -118,28 +151,39 @@ def total_statseg_size(_statseg_size: int, _statseg_page: int) -> int:
return (_statseg_size + _statseg_page - 1) & ~(_statseg_page - 1)
-def total_memory_required(settings: dict) -> int:
- mem_required = 0
+def total_memory_required(settings: dict) -> dict:
+ memory = {'2M': 0, '1G': 0, '4K': 0}
mem_stats = {
- 'memory_buffers': buffer_size(settings),
- 'netlink_buffer_size': int(
- settings.get('lcp', {}).get(
- 'rx_buffer_size', default_resource_map.get('netlink_rx_buffer_size')
- )
+ 'memory_buffers': (buffer_size(settings), buffer_page_size(settings)),
+ 'netlink_buffer_size': (
+ int(
+ settings.get('lcp', {})
+ .get('netlink', {})
+ .get(
+ 'rx_buffer_size', default_resource_map.get('netlink_rx_buffer_size')
+ )
+ ),
+ 0,
),
- 'heap_size': total_heap_size(
- heap_size=memory_main_heap(settings),
- heap_page_size=main_heap_page_size(settings),
+ 'heap_size': (
+ total_heap_size(
+ heap_size=memory_main_heap(settings),
+ heap_page_size=main_heap_page_size(settings),
+ ),
+ main_heap_page_size(settings),
),
- 'statseg_size': total_statseg_size(
- _statseg_size=statseg_size(settings),
- _statseg_page=statseg_page_size(settings),
+ 'statseg_size': (
+ total_statseg_size(
+ _statseg_size=statseg_size(settings),
+ _statseg_page=statseg_page_size(settings),
+ ),
+ statseg_page_size(settings),
),
- 'ipv6_heap_size': ipv6_heap_size(settings),
+ 'ipv6_heap_size': (ipv6_heap_size(settings), 0),
}
- for stat in mem_stats:
- mem_required += mem_stats[stat]
+ for memory_size, page_size in mem_stats.values():
+ memory[classify_page_size(page_size)] += memory_size
- return mem_required
+ return memory
diff --git a/python/vyos/vpp/config_verify.py b/python/vyos/vpp/config_verify.py
index c6f43e340..cf0b923bb 100644
--- a/python/vyos/vpp/config_verify.py
+++ b/python/vyos/vpp/config_verify.py
@@ -211,9 +211,9 @@ def verify_vpp_minimum_memory():
)
-def verify_vpp_memory(config: dict):
- main_heap_size = mem_checks.memory_main_heap(config['settings'])
- main_heap_page_size = mem_checks.main_heap_page_size(config['settings'])
+def verify_vpp_main_heap_size(settings: dict):
+ main_heap_size = mem_checks.memory_main_heap(settings)
+ main_heap_page_size = mem_checks.main_heap_page_size(settings)
if main_heap_size < 1 << 30:
raise ConfigError('The main heap size must be greater than or equal to 1G')
@@ -225,28 +225,54 @@ def verify_vpp_memory(config: dict):
f'The main heap size must be greater than or equal to page-size ({readable_heap_page})'
)
- available_memory = mem_checks.get_total_hugepages_memory()
- memory_required = mem_checks.total_memory_required(config['settings'])
- if main_heap_size > available_memory:
- available_memory_in_mb = bytes_to_human_memory(available_memory, 'M')
- raise ConfigError(
- f'"memory main-heap-size" must not be greater than hugepages memory. Reduce to {available_memory_in_mb} or less'
+def verify_vpp_memory(config: dict):
+ memory_required = mem_checks.total_memory_required(config['settings'])
+ memory_available = mem_checks.get_available_memory()
+
+ # Check if there is a config currently active
+ # If yes, calculate how much memory it consumes (only for 4k pages)
+ # and exclude it from required memory
+ if config.get('effective'):
+ memory_effective = mem_checks.total_memory_required(
+ config['effective']['settings']
)
- memory_required = round(memory_required / 1024**3, 1)
- available_memory = round(available_memory / 1024**3, 1)
-
- # Allow 10% error margin
- allowed_margin = memory_required * 0.1
+ # If we want to reduce memory configs then we don't need
+ # to check 4K memory type
+ if memory_effective['4K'] >= memory_required['4K']:
+ del memory_required['4K']
+ else:
+ # Get memory currently used by VPP and add it to available memory
+ memory_used = mem_checks.get_vpp_used_memory()
+ memory_available['4K'] += memory_used
+
+ memory_required_gb = {k: round(v / 1024**3, 1) for k, v in memory_required.items()}
+ memory_available_gb = {
+ k: round(v / 1024**3, 1) for k, v in memory_available.items()
+ }
+
+ errors = {}
+ for page_size, req_gb in memory_required_gb.items():
+ avail_gb = memory_available_gb.get(page_size, 0)
+
+ if req_gb > avail_gb:
+ label = 'System' if page_size == '4K' else f'{page_size} HugePages'
+ errors[page_size] = (
+ f'{label} memory: available {avail_gb} GB, '
+ f'required {memory_required_gb[page_size]} GB'
+ )
- # Compare HugePage memory with required memory for VPP
- if memory_required > available_memory + allowed_margin:
+ if errors:
raise ConfigError(
- f'Not enough free hugepage memory to start VPP: '
- f'available: {available_memory} GB, required: {memory_required} GB. '
- 'Please add kernel memory options for HugePages '
- '"set system option kernel memory hugepage-size ..." and reboot'
+ 'Not enough free memory to start VPP! '.ljust(72)
+ + '. '.join([line.ljust(72) for line in errors.values()])
+ + (
+ 'To add HugePages memory please use command '.ljust(72)
+ + '"set system option kernel memory hugepage-size ..." and reboot!'
+ if any(k in errors for k in ('2M', '1G'))
+ else ''
+ )
)
diff --git a/python/vyos/vpp/utils.py b/python/vyos/vpp/utils.py
index 8b3a8c038..4e42232cd 100644
--- a/python/vyos/vpp/utils.py
+++ b/python/vyos/vpp/utils.py
@@ -23,7 +23,7 @@ from pathlib import Path
from struct import pack
-mem_shift = {'K': 10, 'k': 10, 'M': 20, 'm': 20, 'G': 30, 'g': 30}
+mem_shift = {'K': 10, 'KB': 10, 'M': 20, 'MB': 20, 'G': 30, 'GB': 30}
def iftunnel_transform(iface: str) -> str:
@@ -302,15 +302,19 @@ def get_hugepage_sizes() -> list[int]:
def human_memory_to_bytes(value: str) -> int:
"""
- Convert a human-readable vpp memory format (K, M, G) to a byte value.
+ Convert a human-readable vpp memory format (K, M, G, xB) to a byte value.
:param value: The string memory size in vpp human-readable format.
:return: A int representing the value.
"""
+ value = value.strip().upper()
try:
return int(value)
except ValueError:
- return int(value[:-1]) << mem_shift[value[-1]]
+ for unit in sorted(mem_shift.keys(), key=len, reverse=True):
+ if value.endswith(unit):
+ num = value[: -len(unit)]
+ return int(num) << mem_shift[unit]
def bytes_to_human_memory(value: int, unit: str) -> str | None:
@@ -321,6 +325,7 @@ def bytes_to_human_memory(value: int, unit: str) -> str | None:
:param unit: The unit to convert to ('K', 'M', 'G').
:return: A string representing the value in the specified unit, or None if zero.
"""
+ unit = unit.upper()
val = value >> mem_shift[unit]
return f'{val}{unit}' if val else None