public inbox for linux-rt-users@vger.kernel.org
 help / color / mirror / Atom feed
From: John Kacur <jkacur@redhat.com>
To: linux-rt-users@vger.kernel.org
Cc: Clark Williams <williams@redhat.com>, Tomas Glozar <tglozar@redhat.com>
Subject: [PATCH 6/6] rteval: Add --housekeeping option to reserve isolated CPUs
Date: Fri, 17 Apr 2026 15:51:13 -0400	[thread overview]
Message-ID: <20260417195113.177799-7-jkacur@redhat.com> (raw)
In-Reply-To: <20260417195113.177799-1-jkacur@redhat.com>

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


  parent reply	other threads:[~2026-04-17 19:51 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-17 19:51 [PATCH 0/6] rteval: Improve CPU management infrastructure and add housekeeping option John Kacur
2026-04-17 19:51 ` [PATCH 1/6] rteval: Add cpuset module for cgroup v2 management John Kacur
2026-04-17 19:51 ` [PATCH 2/6] rteval: Fix xmlout unit test XSL file path John Kacur
2026-04-17 19:51 ` [PATCH 3/6] rteval: Add CpuList class to cpulist_utils module John Kacur
2026-04-17 19:51 ` [PATCH 4/6] rteval: Migrate call sites to use CpuList class where beneficial John Kacur
2026-04-17 19:51 ` [PATCH 5/6] rteval: Add unit tests for CpuList class John Kacur
2026-04-17 19:51 ` John Kacur [this message]
2026-04-20  9:33   ` [PATCH 6/6] rteval: Add --housekeeping option to reserve isolated CPUs Tomas Glozar
2026-04-20 21:42     ` John Kacur

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260417195113.177799-7-jkacur@redhat.com \
    --to=jkacur@redhat.com \
    --cc=linux-rt-users@vger.kernel.org \
    --cc=tglozar@redhat.com \
    --cc=williams@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox