[PATCH 34/36] tuna: Add --cpuset option to run command
John Kacur <[email protected]> Fri, 10 Jul 2026 10:15:12 -0400
| Newsgroups | org.kernel.vger.linux-rt-users |
|---|---|
| Message-ID | <[email protected]> |
Add support for running commands directly in cpusets using the --cpuset option. This allows launching processes with both CPU isolation (via cpusets) and scheduling priority settings. The implementation: - Adds --cpuset CPUSET-NAME option to run command parser - Makes --cpuset mutually exclusive with CPU affinity options (-c, -S, -N) - Validates cpuset exists before launching the process - Extends run_command() to accept cpuset_name parameter - Implements parent-child coordination: child sets priority/affinity and execs command, parent migrates child to cpuset - Handles race condition where fast commands finish before migration by checking process existence with signal 0 - Adds signal import for SIGTERM handling Example usage: tuna run --cpuset my_rt 'taskset -pc $$ && sleep 5' tuna run --cpuset my_rt -p FIFO:50 'stress-ng --cpu 1' Assisted-by: Claude:claude-sonnet-4-5 Signed-off-by: John Kacur <[email protected]> --- tuna-cmd.py | 14 ++++++++++++-- tuna/tuna.py | 28 +++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/tuna-cmd.py b/tuna-cmd.py index 4703ea41f8d4..25257c465532 100755 --- a/tuna-cmd.py +++ b/tuna-cmd.py @@ -221,6 +221,7 @@ def gen_parser(): run_group.add_argument('-c', '--cpus', **MODS['cpus']) run_group.add_argument('-S', '--sockets', **MODS['sockets']) run_group.add_argument('-N', '--nohz_full', **MODS['nohz_full']) + run_group.add_argument('--cpuset', type=str, metavar='CPUSET-NAME', help='Run process in specified cpuset (cgroup v2) instead of setting CPU affinity') run.add_argument('-p', '--priority', **MODS['priority']) run.add_argument('-b', '--background', **MODS['background']) @@ -1659,7 +1660,7 @@ def main(): print(f"[ERROR] Invalid RT priority: {rtprio}. RT priorities must be 1-99", file=sys.stderr) sys.exit(2) # If only priority specified (no policy), assume RT and validate - elif policy is None and rtprio != 0: + elif policy is None and rtprio is not None and rtprio != 0: if rtprio < 1 or rtprio > 99: print(f"[ERROR] Invalid RT priority: {rtprio}. RT priorities must be 1-99", file=sys.stderr) sys.exit(2) @@ -1738,8 +1739,17 @@ def main(): sys.exit(1) elif args.command in ['run', 'r']: + # Check if using cpuset mode + cpuset_name = None + if hasattr(args, 'cpuset') and args.cpuset: + # Verify cpuset exists + cpuset_path = os.path.join('/sys/fs/cgroup', args.cpuset) + if not os.path.exists(cpuset_path): + print(f"Error: cpuset '{args.cpuset}' does not exist", file=sys.stderr) + sys.exit(1) + cpuset_name = args.cpuset - tuna.run_command(args.run_command, args.priority[0], args.priority[1], args.cpu_list, args.background) + tuna.run_command(args.run_command, args.priority[0], args.priority[1], args.cpu_list, args.background, cpuset_name) elif args.command in ['priority', 'p']: diff --git a/tuna/tuna.py b/tuna/tuna.py index 5e5f5578fbe4..fd08b5efe1ae 100755 --- a/tuna/tuna.py +++ b/tuna/tuna.py @@ -6,6 +6,7 @@ import copy import errno import os import re +import signal import sys import shlex import fnmatch @@ -708,7 +709,7 @@ def get_kthread_sched_tunings(proc=None): return kthreads -def run_command(cmd, policy, rtprio, cpu_list, background): +def run_command(cmd, policy, rtprio, cpu_list, background, cpuset_name=None): newpid = os.fork() if newpid == 0: cmd_list = shlex.split(cmd) @@ -732,6 +733,31 @@ def run_command(cmd, policy, rtprio, cpu_list, background): print(f"tuna: {err}") sys.exit(2) else: + # If cpuset mode, move the child process to the cpuset + if cpuset_name: + from tuna import cpuset + try: + cs = cpuset.Cpuset(cpuset_name, existing_ok=True) + if not cs.write_pid(newpid): + # Check if process still exists - if it exited quickly, that's okay + try: + os.kill(newpid, 0) # Signal 0 just checks if process exists + # Process still exists but couldn't be moved to cpuset - this is an error + print(f"tuna: failed to move process {newpid} to cpuset '{cpuset_name}'", file=sys.stderr) + os.kill(newpid, signal.SIGTERM) + sys.exit(1) + except OSError: + # Process already exited - that's okay, it just ran quickly + pass + except (OSError, ValueError) as e: + print(f"tuna: error accessing cpuset '{cpuset_name}': {e}", file=sys.stderr) + # Try to kill the child process if it still exists + try: + os.kill(newpid, signal.SIGTERM) + except OSError: + pass + sys.exit(1) + if not background: os.waitpid(newpid, 0) -- 2.54.0