summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkhramshinr <khramshinr@gmail.com>2025-02-20 14:08:54 +0700
committerkhramshinr <khramshinr@gmail.com>2025-02-21 17:24:12 +0700
commit4abb970491dfc83234b817808d18ae6bfeeeffe8 (patch)
treedeb6f8f74428592ab59e7755767edbaa40394cec
parent752b400aaf5520c29726dadfc94127a06e0ad5a9 (diff)
downloadvyos-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.i16
-rw-r--r--interface-definitions/vpp.xml.in4
-rw-r--r--python/vyos/vpp/utils.py94
-rwxr-xr-xsmoketest/scripts/cli/test_vpp.py76
-rw-r--r--src/completion/list_mem_page_size.py69
-rwxr-xr-xsrc/conf_mode/vpp.py47
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>&lt;number&gt;K</format>
- <description>Kilobyte</description>
-</valueHelp>
-<valueHelp>
- <format>&lt;number&gt;M</format>
- <description>Megabyte</description>
-</valueHelp>
-<valueHelp>
- <format>&lt;number&gt;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', []):