diff options
| author | khramshinr <khramshinr@gmail.com> | 2025-02-20 14:08:54 +0700 |
|---|---|---|
| committer | khramshinr <khramshinr@gmail.com> | 2025-02-21 17:24:12 +0700 |
| commit | 4abb970491dfc83234b817808d18ae6bfeeeffe8 (patch) | |
| tree | deb6f8f74428592ab59e7755767edbaa40394cec | |
| parent | 752b400aaf5520c29726dadfc94127a06e0ad5a9 (diff) | |
| download | vyos-1x-4abb970491dfc83234b817808d18ae6bfeeeffe8.tar.gz vyos-1x-4abb970491dfc83234b817808d18ae6bfeeeffe8.zip | |
T7073: Verify VPP buffers page size
T7077: Verify VPP memory default-hugepage-size
T7079: Verify VPP memory main-heap-page-size
T7080: Verify VPP statseg page-size
get available hugepage sizes
align memory main-heap-size by page size
validate host_resources max_map_count
| -rw-r--r-- | interface-definitions/include/unformat_log2_page_size.xml.i | 16 | ||||
| -rw-r--r-- | interface-definitions/vpp.xml.in | 4 | ||||
| -rw-r--r-- | python/vyos/vpp/utils.py | 94 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_vpp.py | 76 | ||||
| -rw-r--r-- | src/completion/list_mem_page_size.py | 69 | ||||
| -rwxr-xr-x | src/conf_mode/vpp.py | 47 |
6 files changed, 269 insertions, 37 deletions
diff --git a/interface-definitions/include/unformat_log2_page_size.xml.i b/interface-definitions/include/unformat_log2_page_size.xml.i index 240fd5bb8..ce6f70dc8 100644 --- a/interface-definitions/include/unformat_log2_page_size.xml.i +++ b/interface-definitions/include/unformat_log2_page_size.xml.i @@ -1,6 +1,7 @@ <!-- include start from unformat_log2_page_size.xml.i --> <completionHelp> <list>default default-hugepage</list> + <script>sudo ${vyos_completion_dir}/list_mem_page_size.py</script> </completionHelp> <valueHelp> <format>default</format> @@ -10,20 +11,7 @@ <format>default-hugepage</format> <description>Default huge-page</description> </valueHelp> -<valueHelp> - <format><number>K</format> - <description>Kilobyte</description> -</valueHelp> -<valueHelp> - <format><number>M</format> - <description>Megabyte</description> -</valueHelp> -<valueHelp> - <format><number>G</format> - <description>Gigabyte</description> -</valueHelp> <constraint> - <validator name="numeric" argument="--range 0-4294967295"/> - <regex>(default|default-hugepage|\d+K|\d+M|\d+G)</regex> + <regex>(default|default-hugepage|4K|8K|1024K|64K|256K|2048K|4096K|16384K|262144K|1048576K|16777216K|1M|2M|4M|16M|256M|1024M|16384M|1G|16G)</regex> </constraint> <!-- include end --> diff --git a/interface-definitions/vpp.xml.in b/interface-definitions/vpp.xml.in index 1f1cb44cc..e0784fc43 100644 --- a/interface-definitions/vpp.xml.in +++ b/interface-definitions/vpp.xml.in @@ -774,7 +774,9 @@ <leafNode name="default-hugepage-size"> <properties> <help>Default hugepage size</help> - #include <include/unformat_log2_page_size.xml.i> + <completionHelp> + <script>sudo ${vyos_completion_dir}/list_mem_page_size.py --hugepage_only True </script> + </completionHelp> </properties> </leafNode> </children> diff --git a/python/vyos/vpp/utils.py b/python/vyos/vpp/utils.py index 08d69bba2..681d25578 100644 --- a/python/vyos/vpp/utils.py +++ b/python/vyos/vpp/utils.py @@ -16,12 +16,16 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import ctypes +import os import socket from fcntl import ioctl from pathlib import Path from struct import pack +mem_shift = {'K': 10, 'k': 10, 'M': 20, 'm': 20, 'G': 30, 'g': 30} + + def iftunnel_transform(iface: str) -> str: """Transform interface name from `xxxNN` to `xxx_tunnelNN` @@ -211,6 +215,96 @@ def cli_ifaces_lcp_kernel_list( return lcp_kernel_ifaces +def get_default_hugepage_size() -> int: + """ + Retrieve the system's default huge page size. + :return: The default huge page size in bytes. + """ + page_size = None + try: + # default huge page size + memfd = os.memfd_create('tmp', os.MFD_HUGETLB) + st = os.fstat(memfd) + page_size = st.st_blksize + os.close(memfd) + except OSError: + pass + + return page_size + + +def get_default_page_size() -> int: + """ + Retrieve the system's default page size. + :return: The default page size in bytes. + """ + return os.sysconf('SC_PAGESIZE') + + +def get_hugepage_sizes() -> list[int]: + """ + Retrieve all available huge page sizes from the system. + :return: A list of huge page sizes in bytes. + """ + huge_sizes = [] + path = '/sys/kernel/mm/hugepages/' + try: + entries = os.listdir(path) + for entry in entries: + if entry.startswith('hugepages-'): + try: + size_kb = int(entry.replace('hugepages-', '').replace('kB', '')) + huge_sizes.append(size_kb << 10) # Convert KB to bytes + except ValueError: + pass + except FileNotFoundError: + pass + + return huge_sizes + + +def human_memory_to_bytes(value: str) -> int: + """ + Convert a human-readable vpp memory format (K, M, G) to a byte value. + + :param value: The string memory size in vpp human-readable format. + :return: A int representing the value. + """ + try: + return int(value) + except ValueError: + return int(value[:-1]) << mem_shift[value[-1]] + + +def bytes_to_human_memory(value: int, unit: str) -> str | None: + """ + Convert a byte value to a human-readable format (K, M, G). + + :param value: The size in bytes. + :param unit: The unit to convert to ('K', 'M', 'G'). + :return: A string representing the value in the specified unit, or None if zero. + """ + val = value >> mem_shift[unit] + return f'{val}{unit}' if val else None + + +def human_page_memory_to_bytes(value: str) -> int: + """ + Convert a human-readable vpp page size format to a byte value. + + :param value: The string memory size in vpp human-readable format. + :return: A int representing the value. + """ + default = { + 'default': get_default_page_size, + 'default-hugepage': get_default_hugepage_size, + } + try: + return default[value]() + except KeyError: + return human_memory_to_bytes(value) + + class EthtoolGDrvinfo: """Return interface details like `ethtol -i` does""" diff --git a/smoketest/scripts/cli/test_vpp.py b/smoketest/scripts/cli/test_vpp.py index 86b00da44..9f28c970f 100755 --- a/smoketest/scripts/cli/test_vpp.py +++ b/smoketest/scripts/cli/test_vpp.py @@ -19,7 +19,9 @@ import os import re +import sys import unittest +from collections import defaultdict from json import loads @@ -30,6 +32,9 @@ from vyos.utils.process import process_named_running from vyos.utils.file import read_file from vyos.utils.process import rc_cmd +sys.path.append(os.getenv('vyos_completion_dir')) +from list_mem_page_size import list_mem_page_size + PROCESS_NAME = 'vpp_main' VPP_CONF = '/run/vpp/vpp.conf' base_path = ['vpp'] @@ -37,6 +42,38 @@ driver = 'dpdk' interface = 'eth1' +def get_vpp_config(): + config = defaultdict(dict) + current_section = None + + with open(VPP_CONF, 'r') as f: + for line in f: + line = line.strip() + + if not line or line.startswith('#'): # Ignore empty lines and comments + continue + + section_match = re.match(r'([a-zA-Z0-9_-]+)\s*{', line) + if section_match: + current_section = section_match.group(1) + config[current_section] = {} + continue + + if line == '}': # End of section + current_section = None + continue + + key_value_match = re.match(r'([a-zA-Z0-9_-]+)\s+(.+)', line) + if key_value_match: + key, value = key_value_match.groups() + if current_section: + config[current_section][key] = value + else: + config[key] = value + + return config + + def get_address(interface): rc, data = rc_cmd(f'ip --json address show dev {interface}') if rc == 0: @@ -60,12 +97,13 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['settings', 'unix', 'poll-sleep-usec', '10']) def tearDown(self): - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - - # delete test config - self.cli_delete(base_path) - self.cli_commit() + try: + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + finally: + # Ensure these cleanup operations always run + self.cli_delete(base_path) + self.cli_commit() self.assertFalse(os.path.exists(VPP_CONF)) self.assertFalse(process_named_running(PROCESS_NAME)) @@ -1064,6 +1102,32 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): for config_entry in config_entries: self.assertIn(config_entry, config) + def test_13_mem_page_size(self): + sizes = ['default', 'default-hugepage'] + list_mem_page_size() + for size in sizes: + self.cli_set(base_path + ['settings', 'buffers', 'page-size', size]) + self.cli_set(base_path + ['settings', 'statseg', 'page-size', size]) + self.cli_set( + base_path + ['settings', 'memory', 'main-heap-page-size', size] + ) + self.cli_commit() + + conf = get_vpp_config() + self.assertEqual(conf['buffers']['page-size'], size) + self.assertEqual(conf['statseg']['page-size'], size) + self.assertEqual(conf['memory']['main-heap-page-size'], size) + + def test_14_mem_default_hugepage(self): + sizes = list_mem_page_size(hugepage_only=True) + for size in sizes: + self.cli_set( + base_path + ['settings', 'memory', 'default-hugepage-size', size] + ) + self.cli_commit() + + conf = get_vpp_config() + self.assertEqual(conf['memory']['default-hugepage-size'], size) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/completion/list_mem_page_size.py b/src/completion/list_mem_page_size.py new file mode 100644 index 000000000..1ed81a15f --- /dev/null +++ b/src/completion/list_mem_page_size.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2025 VyOS Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. +import argparse +from vyos.vpp.utils import ( + get_hugepage_sizes, + get_default_hugepage_size, + get_default_page_size, + bytes_to_human_memory, +) + + +def get_default_page_sizes() -> list[int]: + """ + Retrieve the system's default page sizes, including huge pages. + :return: A list of page sizes in bytes. + """ + page_sizes = [] + # default system page size + page_size = get_default_page_size() + if page_size: + page_sizes.append(page_size) + + # default huge page size + page_size = get_default_hugepage_size() + if page_size: + page_sizes.append(page_size) + + return page_sizes + + +def list_mem_page_size(hugepage_only=None) -> list[str]: + result = [] + page_sizes = get_hugepage_sizes() + + if not hugepage_only: + page_sizes += get_default_page_sizes() + + page_sizes = set(page_sizes) + for unit in ['K', 'M', 'G']: + for size in page_sizes: + if val := bytes_to_human_memory(size, unit): + result.append(val) + + return result + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument( + '--hugepage_only', type=str, help='List only available hugepage sizes.' + ) + args = parser.parse_args() + + result = list_mem_page_size(args.hugepage_only) + print(' '.join(result)) diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py index ec6873e0d..aea0c1754 100755 --- a/src/conf_mode/vpp.py +++ b/src/conf_mode/vpp.py @@ -39,7 +39,11 @@ from vyos.vpp import control_host from vyos.vpp.config_deps import deps_xconnect_dict from vyos.vpp.config_verify import verify_dev_driver from vyos.vpp.config_filter import iface_filter_eth -from vyos.vpp.utils import EthtoolGDrvinfo +from vyos.vpp.utils import ( + EthtoolGDrvinfo, + human_page_memory_to_bytes, + human_memory_to_bytes, +) from vyos.vpp.configdb import JSONStorage airbag.enable() @@ -252,18 +256,6 @@ def get_config(config=None): return config -def convert_to_int(val): - rates = { - 'K': 1024, - 'M': 1024**2, - 'G': 1024**3, - } - try: - return int(val) - except ValueError: - return int(val[:-1]) * rates[val[-1]] - - def verify_memory(settings): memory_available: int = virtual_memory().available cpus: int = get_core_count() @@ -283,12 +275,24 @@ def verify_memory(settings): ) memory_required += netlink_buffer_size - memory_main_heap = convert_to_int( + memory_main_heap = human_memory_to_bytes( settings.get('memory', {}).get('main_heap_size', '1G') ) - memory_required += memory_main_heap - statseg_size = convert_to_int(settings.get('statseg', {}).get('size', '96M')) + memory_main_heap_page_size = settings.get('memory', {}).get( + 'main_heap_page_size', 0 + ) + if memory_main_heap_page_size: + memory_main_heap_page_size = human_page_memory_to_bytes( + memory_main_heap_page_size + ) + memory_required += (memory_main_heap + memory_main_heap_page_size - 1) & ~( + memory_main_heap_page_size - 1 + ) + else: + memory_required += memory_main_heap + + statseg_size = human_memory_to_bytes(settings.get('statseg', {}).get('size', '96M')) memory_required += statseg_size if memory_available < memory_required: @@ -452,6 +456,17 @@ def verify(config): raise ConfigError('"cpu corelist-workers" is not correct') verify_memory(config['settings']) + if 'host_resources' in config['settings']: + if ( + 'nr_hugepages' in config['settings']['host_resources'] + and 'max_map_count' in config['settings']['host_resources'] + ): + if int(config['settings']['host_resources']['max_map_count']) < 2 * int( + config['settings']['host_resources']['nr_hugepages'] + ): + raise ConfigError( + 'The max_map_count must be greater than or equal to (2 * nr_hugepages)' + ) # Check if deleted interfaces are not xconnect memebrs for iface_config in config.get('removed_ifaces', []): |
