[PATCH 6/6] rteval: Add --housekeeping option to reserve isolated CPUs

John Kacur
2026-04-17T19:51:39Z

Add a new --housekeeping option that allows users to specify isolated CPUs
that should be reserved for system housekeeping tasks and not used by
rteval's measurement or load modules.

Key features:
- Validates that housekeeping CPUs are in the isolated CPU list (isolcpus)
- Detects conflicts with explicitly specified --measurement-cpulist or
--loads-cpulist options and exits with a clear error message
- Filters housekeeping CPUs from both measurement and load CPU lists
- Correctly excludes housekeeping CPUs from inverted CPU lists when only
one of measurement/loads is specified

Example usage:
rteval --housekeeping 0-3 --measurement-run-on-isolcpus
Reserves isolcpus 0-3 for system tasks, runs measurements on
remaining isolated CPUs (4+) plus non-isolated CPUs

Implementation:
- systopology.py: Add validate_housekeeping_cpus() function to validate
that housekeeping CPUs are in isolcpus
- rteval-cmd: Add --housekeeping argument, conflict checking, filtering,
and fix inversion logic to exclude housekeeping CPUs

This feature is purely additive and does not change existing behavior
when --housekeeping is not specified.

Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: John Kacur <jkacur@redhat.com>
---
rteval-cmd | 46 ++++++++++++++++++++++++++++++++++++++++++-
rteval/systopology.py | 24 ++++++++++++++++++++++
2 files changed, 69 insertions(+), 1 deletion(-)

diff --git a/rteval-cmd b/rteval-cmd
index 0ad90cf29ec4..a903b537c891 100755
--- a/rteval-cmd
+++ b/rteval-cmd
@@ -31,7 +31,7 @@ from rteval.modules.loads import LoadModules
from rteval.modules.measurement import MeasurementModules
from rteval import cpupower
from rteval.version import RTEVAL_VERSION
-from rteval.systopology import SysTopology, parse_cpulist_from_config
+from rteval.systopology import SysTopology, parse_cpulist_from_config, validate_housekeeping_cpus
from rteval.modules.loads.kcompile import ModuleParameters
from rteval.cpulist_utils import CpuList, is_relative, collapse_cpulist

@@ -113,6 +113,9 @@ def parse_options(cfg, parser, cmdargs):
parser.add_argument("-i", "--installdir", dest="rteval___installdir",
type=str, default=rtevcfg.installdir, metavar="DIRECTORY",
help=f"place to locate installed templates (default: {rtevcfg.installdir})")
+ parser.add_argument("--housekeeping", dest="rteval___housekeeping",
+ type=str, default="", metavar="CPULIST",
+ help="isolated CPUs reserved for system tasks (not used by rteval)")
parser.add_argument("-s", "--sysreport", dest="rteval___sysreport",
action="store_true", default=rtevcfg.sysreport,
help=f'run sysreport to collect system data (default: {rtevcfg.sysreport})')
@@ -370,6 +373,13 @@ if __name__ == '__main__':

ldcfg = config.GetSection('loads')
msrcfg = config.GetSection('measurement')
+
+ # Validate and process housekeeping CPUs
+ housekeeping_cpus = []
+ if rtevcfg.housekeeping:
+ housekeeping_cpus = validate_housekeeping_cpus(rtevcfg.housekeeping)
+ logger.log(Log.DEBUG, f"housekeeping cpulist: {collapse_cpulist(housekeeping_cpus)}")
+
# Remember if cpulists were explicitly set by the user before running
# parse_cpulist_from_config, which generates default value for them
msrcfg_cpulist_present = msrcfg.cpulist != ""
@@ -382,17 +392,51 @@ if __name__ == '__main__':
msrcfg.cpulist = collapse_cpulist(cpulist)
cpulist = parse_cpulist_from_config(ldcfg.cpulist)
ldcfg.cpulist = collapse_cpulist(cpulist)
+
+ # Check for conflicts between housekeeping and measurement/load cpulists
+ if housekeeping_cpus:
+ msrcfg_cpus = CpuList(msrcfg.cpulist).cpus if msrcfg.cpulist else []
+ ldcfg_cpus = CpuList(ldcfg.cpulist).cpus if ldcfg.cpulist else []
+
+ # Check measurement conflicts
+ if msrcfg_cpulist_present:
+ conflicts = [cpu for cpu in housekeeping_cpus if cpu in msrcfg_cpus]
+ if conflicts:
+ raise RuntimeError(
+ f"Housekeeping CPUs {collapse_cpulist(conflicts)} conflict with "
+ f"--measurement-cpulist {msrcfg.cpulist}"
+ )
+
+ # Check load conflicts
+ if ldcfg_cpulist_present:
+ conflicts = [cpu for cpu in housekeeping_cpus if cpu in ldcfg_cpus]
+ if conflicts:
+ raise RuntimeError(
+ f"Housekeeping CPUs {collapse_cpulist(conflicts)} conflict with "
+ f"--loads-cpulist {ldcfg.cpulist}"
+ )
+
+ # Filter out housekeeping CPUs from measurement and load lists
+ msrcfg_cpus = [cpu for cpu in msrcfg_cpus if cpu not in housekeeping_cpus]
+ ldcfg_cpus = [cpu for cpu in ldcfg_cpus if cpu not in housekeeping_cpus]
+ msrcfg.cpulist = collapse_cpulist(msrcfg_cpus) if msrcfg_cpus else ""
+ ldcfg.cpulist = collapse_cpulist(ldcfg_cpus) if ldcfg_cpus else ""
+
# if we only specified one set of cpus (loads or measurement)
# default the other to the inverse of the specified list
if not ldcfg_cpulist_present and msrcfg_cpulist_present:
tmplist = CpuList(msrcfg.cpulist).cpus
tmplist = SysTopology().invert_cpulist(tmplist)
tmplist = CpuList(tmplist).online().cpus
+ # Exclude housekeeping CPUs from the inverted list
+ tmplist = [cpu for cpu in tmplist if cpu not in housekeeping_cpus]
ldcfg.cpulist = collapse_cpulist(tmplist)
if not msrcfg_cpulist_present and ldcfg_cpulist_present:
tmplist = CpuList(ldcfg.cpulist).cpus
tmplist = SysTopology().invert_cpulist(tmplist)
tmplist = CpuList(tmplist).online().cpus
+ # Exclude housekeeping CPUs from the inverted list
+ tmplist = [cpu for cpu in tmplist if cpu not in housekeeping_cpus]
msrcfg.cpulist = collapse_cpulist(tmplist)

if ldcfg_cpulist_present:
diff --git a/rteval/systopology.py b/rteval/systopology.py
index 7305fc278995..5f2bc291f608 100644
--- a/rteval/systopology.py
+++ b/rteval/systopology.py
@@ -239,6 +239,30 @@ class SysTopology:
return [c for c in self.online_cpus() if c in cpulist]

+def validate_housekeeping_cpus(housekeeping_cpulist):
+ """
+ Validates that housekeeping CPUs are in isolated CPU list
+ :param housekeeping_cpulist: CPU list string for housekeeping CPUs
+ :return: Sorted list of validated housekeeping CPUs as integers
+ :raises: RuntimeError if housekeeping CPUs are not in isolcpus
+ """
+ if not housekeeping_cpulist:
+ return []
+
+ housekeeping = CpuList(housekeeping_cpulist).online().cpus
+ isolcpus = SysTopology().isolated_cpus()
+
+ # Check if all housekeeping CPUs are in isolcpus
+ not_isolated = [cpu for cpu in housekeeping if cpu not in isolcpus]
+ if not_isolated:
+ isolcpus_str = collapse_cpulist(isolcpus) if isolcpus else "none"
+ raise RuntimeError(
+ f"Housekeeping CPUs {collapse_cpulist(not_isolated)} are not in isolated CPUs [{isolcpus_str}]"
+ )
+
+ return sorted(housekeeping)
+
+
def parse_cpulist_from_config(cpulist, run_on_isolcpus=False):
"""
Generates a cpulist based on --*-cpulist argument given by user
--
2.53.0