[PATCH 25/36] tuna: Add --cpuset option to move command

John Kacur <[email protected]> Fri, 10 Jul 2026 10:15:03 -0400
Newsgroups org.kernel.vger.linux-rt-users
Message-ID <[email protected]>
Add cpuset integration to the tuna move command, allowing users to move
threads and IRQs into cpusets (cgroup v2) instead of just setting CPU
affinity. This brings cpuset support to tuna's existing rich entity
selection mechanisms (IRQ patterns, thread patterns, sockets, nohz_full).

Changes:
- Add --cpuset option to move command parser (mutually exclusive with
  -c/--cpus, -S/--sockets, -N/--nohz_full)
- Implement cpuset-based move logic for threads and IRQ handler threads
- Verify cpuset exists before attempting to move processes
- Honor process blocklist to protect critical system processes
- Provide success/failure summary output
- Update move command help text to reflect new cpuset capability

Example usage:
  tuna move --cpuset tuna0 -t 1234,5678
  tuna move --cpuset tuna0 -q eth0*
  tuna move --cpuset tuna0 -t systemd*

The --cpuset option cannot be used with the spread command (argparse
enforces mutual exclusion, defensive check remains in code).

Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: John Kacur <[email protected]>
---
 tuna-cmd.py | 70 ++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 64 insertions(+), 6 deletions(-)

diff --git a/tuna-cmd.py b/tuna-cmd.py
index 557a07891183..b50691fa2d05 100755
--- a/tuna-cmd.py
+++ b/tuna-cmd.py
@@ -159,8 +159,8 @@ def gen_parser():
                                     help="Move all allowed threads and IRQs away from CPU-LIST")
     include = subparser.add_parser('include', description="Allow all threads to run on CPU-LIST",
                                      help="Allow all threads to run on CPU-LIST")
-    move = subparser.add_parser('move', description="Move selected entities to CPU-LIST",
-                                    help="Move selected entities to CPU-LIST")
+    move = subparser.add_parser('move', description="Move selected threads/IRQs to specified CPUs, sockets, or cpusets",
+                                    help="Move selected threads/IRQs to specified CPUs, sockets, or cpusets")
     spread = subparser.add_parser('spread', description="Spread selected entities to CPU-LIST",
                                     help="Spread selected entities over CPU-LIST")
     priority = subparser.add_parser('priority', description="Set thread scheduler tunables: POLICY and RTPRIO",
@@ -192,6 +192,7 @@ def gen_parser():
     move_group.add_argument('-c', '--cpus', **MODS['cpus'])
     move_group.add_argument('-S', '--sockets', **MODS['sockets'])
     move_group.add_argument('-N', '--nohz_full', **MODS['nohz_full'])
+    move_group.add_argument('--cpuset', type=str, metavar='CPUSET-NAME', help='Move threads/IRQs into cpuset (cgroup v2) instead of setting CPU affinity')
     move.add_argument('-t', '--threads', **MODS['threads'])
     move.add_argument('-q', '--irqs', **MODS['irqs'])
     move.add_argument('-U', '--no_uthreads', **MODS['no_uthreads'])
@@ -1662,11 +1663,68 @@ def main():
         if not (args.thread_list or args.irq_list):
             parser.error(f"tuna: {args.command} requires a thread/irq list!\n")
 
-        if args.thread_list:
-            tuna.move_threads_to_cpu(args.cpu_list, args.thread_list, args.uthreads, args.kthreads, spread=spread)
+        # Check if using cpuset mode
+        if hasattr(args, 'cpuset') and args.cpuset:
+            # Cpuset mode: move entities to a cpuset instead of setting affinity
+            if spread:
+                print("Error: --cpuset cannot be used with spread command", file=sys.stderr)
+                sys.exit(2)
+
+            # 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)
+
+            # Create cpuset object
+            try:
+                cs = cpuset.Cpuset(args.cpuset, existing_ok=True)
+            except (OSError, ValueError) as e:
+                print(f"Error accessing cpuset '{args.cpuset}': {e}", file=sys.stderr)
+                sys.exit(1)
+
+            success_count = 0
+            fail_count = 0
+
+            # Move threads to cpuset
+            if args.thread_list:
+                for pid in args.thread_list:
+                    if cs.write_pid(pid):
+                        success_count += 1
+                    else:
+                        fail_count += 1
+
+            # Move IRQ threads to cpuset
+            if args.irq_list:
+                if not ps:
+                    ps = procfs.pidstats()
+                irqs = procfs.interrupts()
+
+                for irq in args.irq_list:
+                    # Find IRQ thread for this IRQ number
+                    irq_re = tuna.threaded_irq_re(irq)
+                    irq_threads = ps.find_by_regex(irq_re)
+
+                    for irq_pid in irq_threads:
+                        if cs.write_pid(irq_pid):
+                            success_count += 1
+                        else:
+                            fail_count += 1
+
+            # Print summary
+            print(f"Moved {success_count} task(s) to cpuset '{args.cpuset}'", end='')
+            if fail_count > 0:
+                print(f" ({fail_count} failed)")
+            else:
+                print()
+
+        else:
+            # Original affinity mode
+            if args.thread_list:
+                tuna.move_threads_to_cpu(args.cpu_list, args.thread_list, args.uthreads, args.kthreads, spread=spread)
 
-        if args.irq_list:
-            tuna.move_irqs_to_cpu(args.cpu_list, args.irq_list, spread=spread)
+            if args.irq_list:
+                tuna.move_irqs_to_cpu(args.cpu_list, args.irq_list, spread=spread)
 
     elif args.command in ['s', 'save']:
         save(args.cpu_list, args.thread_list, args.filename)
-- 
2.54.0