[PATCH 21/36] tuna: Change cpuset commands to skip empty by default

John Kacur <[email protected]> Fri, 10 Jul 2026 10:14:59 -0400
Newsgroups org.kernel.vger.linux-rt-users
Message-ID <[email protected]>
Change the default behavior for cpuset list, destroy, and status
commands to skip empty cpusets (cpusets with no CPUs assigned).

Previously, these commands required --skip-empty to filter out empty
cpusets. This was counterintuitive since empty cpusets are rarely
useful in practice.

New flags:
- cpuset list/status: Use --show-empty to include empty cpusets
- cpuset destroy: Use --include-empty to destroy empty cpusets

The default behavior (without flags) now skips empty cpusets for all
three commands, providing a cleaner and more useful user experience.

Tests updated to reflect the new flag names and default behavior.

Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: John Kacur <[email protected]>
---
 tests/test_cpuset_cli.py | 22 +++++++++++-----------
 tuna-cmd.py              | 36 ++++++++++++++++++------------------
 2 files changed, 29 insertions(+), 29 deletions(-)

diff --git a/tests/test_cpuset_cli.py b/tests/test_cpuset_cli.py
index 51c9b5d193c0..f8bf723d5c72 100644
--- a/tests/test_cpuset_cli.py
+++ b/tests/test_cpuset_cli.py
@@ -248,12 +248,12 @@ class TestCpusetListCLI(unittest.TestCase):
         self.assertIn('test_list_2', cpusets)
         self.assertIn('test_list_empty', cpusets)
 
-    def test_list_skip_empty(self):
-        """Test cpuset list with --skip-empty"""
+    def test_list_default_skips_empty(self):
+        """Test cpuset list default behavior (skips empty cpusets)"""
         # Get all cpusets matching pattern
         all_cpusets = cpuset.list_cpusets(pattern='test_list_*', recursive=False)
 
-        # Filter out empty ones manually (simulating --skip-empty)
+        # Filter out empty ones manually (simulating default behavior)
         non_empty = []
         for cs_name in all_cpusets:
             cs_path = os.path.join('/sys/fs/cgroup', cs_name)
@@ -300,7 +300,7 @@ class TestCpusetDestroyCLI(unittest.TestCase):
 
         # Destroy it (without force, no tasks)
         cpuset_destroy(name='test_destroy_single', pattern=None, force=False,
-                       skip_empty=False, recursive=True)
+                       include_empty=True, recursive=True)
 
         # Should be gone
         self.assertFalse(os.path.exists(path))
@@ -317,15 +317,15 @@ class TestCpusetDestroyCLI(unittest.TestCase):
 
         # Destroy by pattern
         cpuset_destroy(name=None, pattern='test_destroy_bulk_*', force=True,
-                       skip_empty=False, recursive=False)
+                       include_empty=True, recursive=False)
 
         # Should be gone
         self.assertFalse(os.path.exists(cs1.cpuset_path))
         self.assertFalse(os.path.exists(cs2.cpuset_path))
         self.created_cpusets = []
 
-    def test_destroy_with_skip_empty(self):
-        """Test destroying with --skip-empty (should skip empty cpusets)"""
+    def test_destroy_default_skips_empty(self):
+        """Test destroying with default behavior (skips empty cpusets)"""
         # Create cpuset with CPUs
         cs1 = cpuset.Cpuset('test_destroy_nonempty')
         cs1.write_memnode('0')
@@ -337,9 +337,9 @@ class TestCpusetDestroyCLI(unittest.TestCase):
         cs2.write_memnode('0')
         self.created_cpusets.append('test_destroy_empty')
 
-        # Destroy with skip_empty
+        # Destroy with default (include_empty=False, which skips empty)
         cpuset_destroy(name=None, pattern='test_destroy_*', force=True,
-                       skip_empty=True, recursive=False)
+                       include_empty=False, recursive=False)
 
         # Non-empty should be destroyed
         self.assertFalse(os.path.exists(cs1.cpuset_path))
@@ -617,8 +617,8 @@ class TestCpusetStatusCLI(unittest.TestCase):
         except:
             pass
 
-    def test_status_skip_empty(self):
-        """Test status with skip_empty option"""
+    def test_status_empty_filtering(self):
+        """Test status filtering of empty cpusets"""
         # Create cpuset with CPUs
         cs1 = cpuset.Cpuset('test_status_filled')
         cs1.write_memnode('0')
diff --git a/tuna-cmd.py b/tuna-cmd.py
index b560ec805b7a..62553bf51869 100755
--- a/tuna-cmd.py
+++ b/tuna-cmd.py
@@ -283,7 +283,7 @@ def gen_parser():
     cpuset_list = cpuset_subparser.add_parser('list', description='List cpusets', help='List cpusets')
     cpuset_list.add_argument('-p', '--pattern', type=str, metavar='PATTERN', help='Filter by glob pattern (e.g., tuna*)')
     cpuset_list.add_argument('-v', '--verbose', action='store_true', help='Show detailed information (CPUs, tasks, partition type)')
-    cpuset_list.add_argument('--skip-empty', action='store_true', help='Skip cpusets with no CPUs assigned')
+    cpuset_list.add_argument('--show-empty', action='store_true', help='Include cpusets with no CPUs assigned (default: skip empty)')
     cpuset_list.add_argument('--no-recursive', dest='recursive', action='store_false', default=True, help='Only search top-level cpusets')
 
     # cpuset destroy
@@ -291,7 +291,7 @@ def gen_parser():
     cpuset_destroy.add_argument('name', nargs='?', type=str, metavar='NAME', help='Cpuset name to destroy (use this OR --pattern)')
     cpuset_destroy.add_argument('-p', '--pattern', type=str, metavar='PATTERN', help='Glob pattern to destroy multiple cpusets (e.g., tuna*)')
     cpuset_destroy.add_argument('-f', '--force', action='store_true', help='Migrate tasks to root cgroup before destroying')
-    cpuset_destroy.add_argument('--skip-empty', action='store_true', help='When using --pattern, skip cpusets with no CPUs assigned')
+    cpuset_destroy.add_argument('--include-empty', action='store_true', help='When using --pattern, include cpusets with no CPUs assigned (default: skip empty)')
     cpuset_destroy.add_argument('--no-recursive', dest='recursive', action='store_false', default=True, help='Only destroy top-level cpusets when using --pattern')
 
     # cpuset move
@@ -308,7 +308,7 @@ def gen_parser():
     # cpuset status
     cpuset_status = cpuset_subparser.add_parser('status', description='Show system-wide cpuset overview', help='Show cpuset status')
     cpuset_status.add_argument('-p', '--pattern', type=str, metavar='PATTERN', help='Filter by glob pattern (e.g., tuna*)')
-    cpuset_status.add_argument('--skip-empty', action='store_true', help='Skip cpusets with no CPUs assigned')
+    cpuset_status.add_argument('--show-empty', action='store_true', help='Include cpusets with no CPUs assigned (default: skip empty)')
     cpuset_status.add_argument('--no-recursive', dest='recursive', action='store_false', default=True, help='Only show top-level cpusets')
 
     return parser
@@ -983,13 +983,13 @@ def cpuset_create(cpu_list, name, isolated, memory_nodes=None):
         sys.exit(1)
 
 
-def cpuset_list(pattern, verbose, skip_empty, recursive):
+def cpuset_list(pattern, verbose, show_empty, recursive):
     """Handler for 'tuna cpuset list' command.
 
     Args:
         pattern: Glob pattern to filter cpusets (None for all)
         verbose: If True, show detailed info (CPUs, memory nodes, tasks, partition)
-        skip_empty: If True, skip cpusets with no CPUs assigned
+        show_empty: If True, show cpusets with no CPUs assigned (default: skip empty)
         recursive: If True, search nested cpusets
     """
     # Check cgroup v2 support
@@ -1001,8 +1001,8 @@ def cpuset_list(pattern, verbose, skip_empty, recursive):
     # List cpusets
     cpusets = cpuset.list_cpusets(pattern=pattern, recursive=recursive)
 
-    # Filter out empty cpusets if requested
-    if skip_empty:
+    # Filter out empty cpusets unless --show-empty was specified
+    if not show_empty:
         filtered_cpusets = []
         for cs_name in cpusets:
             cs_path = os.path.join('/sys/fs/cgroup', cs_name)
@@ -1096,14 +1096,14 @@ def cpuset_list(pattern, verbose, skip_empty, recursive):
         print("This may indicate a problem with blocklist protection.", file=sys.stderr)
 
 
-def cpuset_destroy(name, pattern, force, skip_empty, recursive):
+def cpuset_destroy(name, pattern, force, include_empty, recursive):
     """Handler for 'tuna cpuset destroy' command.
 
     Args:
         name: Cpuset name to destroy (single cpuset, mutually exclusive with pattern)
         pattern: Glob pattern for cpusets to destroy (mutually exclusive with name)
         force: If True, migrate tasks to root before destroying
-        skip_empty: If True, skip cpusets with no CPUs assigned (only used with pattern)
+        include_empty: If True, include cpusets with no CPUs assigned (default: skip empty, only used with pattern)
         recursive: If True, find and clean nested cpusets (only used with pattern)
     """
     # Check cgroup v2 support
@@ -1122,12 +1122,12 @@ def cpuset_destroy(name, pattern, force, skip_empty, recursive):
 
     try:
         if name:
-            # Single cpuset destroy (skip_empty not applicable)
+            # Single cpuset destroy (include_empty not applicable)
             cpuset.destroy_cpuset(name, force=force)
             print(f"Destroyed cpuset '{name}'")
         else:
             # Pattern-based cleanup
-            if skip_empty:
+            if not include_empty:
                 # Filter out empty cpusets before destroying
                 all_cpusets = cpuset.list_cpusets(pattern=pattern, recursive=recursive)
                 cpusets_to_destroy = []
@@ -1312,12 +1312,12 @@ def cpuset_show(name, show_tasks):
         print("  These critical system processes should not be in custom cpusets!", file=sys.stderr)
 
 
-def cpuset_status(pattern, skip_empty, recursive):
+def cpuset_status(pattern, show_empty, recursive):
     """Handler for 'tuna cpuset status' command.
 
     Args:
         pattern: Glob pattern to filter cpusets (None for all)
-        skip_empty: If True, skip cpusets with no CPUs assigned
+        show_empty: If True, show cpusets with no CPUs assigned (default: skip empty)
         recursive: If True, search nested cpusets
     """
     # Check cgroup v2 support
@@ -1326,8 +1326,8 @@ def cpuset_status(pattern, skip_empty, recursive):
         print("Error: cgroup v2 cpusets not supported on this system", file=sys.stderr)
         sys.exit(1)
 
-    # Get all cpusets info
-    infos = get_all_cpusets_info(pattern=pattern, recursive=recursive, skip_empty=skip_empty)
+    # Get all cpusets info (skip_empty is inverse of show_empty)
+    infos = get_all_cpusets_info(pattern=pattern, recursive=recursive, skip_empty=not show_empty)
 
     if not infos:
         if pattern:
@@ -1625,10 +1625,10 @@ def main():
             cpuset_create(args.cpu_list, args.name, args.isolated, args.memory_nodes)
 
         elif args.cpuset_command == 'list':
-            cpuset_list(args.pattern, args.verbose, args.skip_empty, args.recursive)
+            cpuset_list(args.pattern, args.verbose, args.show_empty, args.recursive)
 
         elif args.cpuset_command == 'destroy':
-            cpuset_destroy(args.name, args.pattern, args.force, args.skip_empty, args.recursive)
+            cpuset_destroy(args.name, args.pattern, args.force, args.include_empty, args.recursive)
 
         elif args.cpuset_command == 'move':
             cpuset_move(args.name, args.thread_list, args.pid_list)
@@ -1637,7 +1637,7 @@ def main():
             cpuset_show(args.name, args.show_tasks)
 
         elif args.cpuset_command == 'status':
-            cpuset_status(args.pattern, args.skip_empty, args.recursive)
+            cpuset_status(args.pattern, args.show_empty, args.recursive)
 
 
 if __name__ == '__main__':
-- 
2.54.0