diff options
| author | Nataliia Solomko <natalirs1985@gmail.com> | 2025-08-19 11:47:47 +0300 |
|---|---|---|
| committer | Nataliia Solomko <natalirs1985@gmail.com> | 2025-08-21 16:48:05 +0300 |
| commit | ae336bdf8a57e28c0bc544ee6b7d57ab048123df (patch) | |
| tree | 918bb66c1194373c6caab5adcc6443656ab15a78 /python | |
| parent | d104ac2c0c5e94316f3d5825df73b93c639ec0cc (diff) | |
| download | vyos-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.py | 108 | ||||
| -rw-r--r-- | python/vyos/vpp/config_verify.py | 66 | ||||
| -rw-r--r-- | python/vyos/vpp/utils.py | 11 |
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 |
