[PATCH 29/36] tuna: Document high-level workflow vs manual primitives for cpusets

John Kacur <[email protected]> Fri, 10 Jul 2026 10:15:07 -0400
Newsgroups org.kernel.vger.linux-rt-users
Message-ID <[email protected]>
Add new section to tuna/cpuset_README.md explaining the difference
between high-level workflow commands (like 'tuna isolate --cpuset')
and manual primitives (like 'tuna cpuset create').

The documentation includes:
- High-level workflow example showing automatic cpuset setup
- Manual equivalent using primitive commands (create, migrate)
- Guidance on when to use each approach
- Design philosophy: primitives as Unix-style building blocks
  vs workflows that combine them for common tasks

This helps users understand:
- What 'tuna isolate --cpuset' does automatically (creates both
  isolated and housekeeping cpusets, migrates processes, sets
  NUMA nodes, configures partitions)
- How to replicate the same setup manually for custom workflows
- When to choose convenience vs fine-grained control

Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: John Kacur <[email protected]>
---
 tuna/cpuset_README.md | 597 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 597 insertions(+)
 create mode 100644 tuna/cpuset_README.md

diff --git a/tuna/cpuset_README.md b/tuna/cpuset_README.md
new file mode 100644
index 000000000000..7ce75dc4eb84
--- /dev/null
+++ b/tuna/cpuset_README.md
@@ -0,0 +1,597 @@
+# tuna.cpuset - CPU Set Management for Tuna
+
+## Overview
+
+The `tuna.cpuset` module provides Python interfaces for managing CPU sets (cpusets) using Linux cgroup v2. Cpusets allow you to isolate CPUs by assigning processes to specific CPU groups, complementing the kernel's `isolcpus` boot parameter with userspace-level CPU isolation.
+
+This module was originally prototyped in tuna, implemented in rteval, and has been adapted back to tuna with tuna-specific enhancements.
+
+## Key Features
+
+### Core Functionality (rteval-compatible)
+- **Cpuset class**: Create and manipulate individual cpusets
+- **CpusetsInit class**: Detect cgroup v2 support and represent the root cgroup
+- **TaskMigrate class**: Migrate processes between cpusets
+- **Context manager support**: Automatic cleanup with `auto_destroy=True`
+- **NUMA awareness**: Multi-NUMA node support
+- **CPU partitioning**: Isolated vs member partition types
+
+### Tuna-Specific Enhancements
+- **Persistent cpusets** (default): Cpusets remain after program exits, unlike rteval's ephemeral approach
+- **Discovery functions**: List and find existing cpusets (including nested hierarchies)
+- **Cleanup utilities**: Destroy cpusets by pattern matching
+- **Flexible destruction**: Can destroy any cpuset, not just self-created ones
+- **Nested cpuset support**: Handles systemd, container, and user-created hierarchies
+
+## Requirements
+
+- **Linux kernel**: cgroup v2 support (kernel 4.5+, recommended 5.0+)
+- **Root permissions**: Required for creating/destroying cgroups
+- **Python**: 3.6+ (uses f-strings, type hints)
+
+### Checking cgroup v2 Support
+
+```python
+from tuna import cpuset
+
+ci = cpuset.CpusetsInit()
+if ci.supported:
+    print(f"cgroup v2 is supported, {ci.numa_nodes} NUMA nodes")
+else:
+    print("cgroup v2 is not supported on this system")
+```
+
+## Installation
+
+The module is part of tuna and doesn't require separate installation. Just ensure tuna is in your Python path:
+
+```python
+from tuna import cpuset
+```
+
+## Basic Usage
+
+### Example 1: Creating a Persistent Cpuset (Tuna Style)
+
+```python
+from tuna import cpuset
+
+# Create a persistent cpuset (default behavior)
+cs = cpuset.Cpuset('tuna_isolated')
+
+# Configure it (must set memory before CPUs in cgroup v2)
+cs.write_memnode('0')      # NUMA node 0
+cs.assign_cpus('0-3')      # CPUs 0-3
+
+# Optionally set as isolated partition
+cs.set_cpu_exclusive()
+
+# The cpuset persists after the program exits
+# Other programs can use it later
+```
+
+### Example 2: Using Context Manager for Automatic Cleanup (rteval Style)
+
+```python
+from tuna import cpuset
+
+# Create with auto_destroy=True for automatic cleanup
+with cpuset.Cpuset('temp_cpuset', auto_destroy=True) as cs:
+    cs.write_memnode('0')
+    cs.assign_cpus('4-7')
+
+    # Do work with the cpuset...
+
+# Cpuset is automatically destroyed when exiting the with block
+```
+
+### Example 3: Moving Processes to a Cpuset
+
+```python
+from tuna import cpuset
+import os
+
+# Create and configure cpuset
+cs = cpuset.Cpuset('workload_cpus')
+cs.write_memnode('0')
+cs.assign_cpus('8-15')
+
+# Move current process
+cs.write_pid(os.getpid())
+
+# Move another process
+cs.write_pid(12345)
+```
+
+### Example 4: Migrating All Tasks from Root to Cpuset
+
+```python
+from tuna import cpuset
+
+# Get root cgroup
+root = cpuset.CpusetsInit()
+
+# Create isolated cpuset
+isolated = cpuset.Cpuset('isolated_cpus')
+isolated.write_memnode('0')
+isolated.assign_cpus('0-7')
+
+# Migrate most tasks to isolated cpuset
+tm = cpuset.TaskMigrate(root, isolated)
+migrated, failed = tm.migrate()
+print(f"Migrated {migrated} tasks, {failed} failed")
+```
+
+### Example 5: Discovering Existing Cpusets
+
+```python
+from tuna import cpuset
+
+# List all cpusets
+all_cpusets = cpuset.list_cpusets()
+print(f"Found cpusets: {all_cpusets}")
+
+# List only tuna-created cpusets
+tuna_cpusets = cpuset.list_cpusets(pattern='tuna_*')
+print(f"Tuna cpusets: {tuna_cpusets}")
+
+# List with recursive search (finds nested cpusets)
+nested = cpuset.list_cpusets(pattern='systemd*', recursive=True)
+print(f"Systemd cpusets: {nested}")
+```
+
+### Example 6: Cleaning Up Cpusets
+
+```python
+from tuna import cpuset
+
+# Destroy a single cpuset
+cpuset.destroy_cpuset('tuna_isolated', force=False)
+
+# Destroy with force (migrates tasks to root first)
+cpuset.destroy_cpuset('busy_cpuset', force=True)
+
+# Bulk cleanup with pattern matching
+cpuset.cleanup_cpusets('tuna_*', force=True)
+
+# Cleanup nested hierarchies (destroys children before parents)
+cpuset.cleanup_cpusets('systemd/user*', force=True, recursive=True)
+```
+
+## API Reference
+
+### Classes
+
+#### `Cpuset(name, auto_destroy=False)`
+
+Core class for managing a single cpuset.
+
+**Parameters:**
+- `name` (str): Name of the cpuset (required)
+- `auto_destroy` (bool): If True, destroy on context manager exit (default: False)
+
+**Methods:**
+- `assign_cpus(cpu_str)`: Assign CPUs (e.g., "0-3,5,7")
+- `write_memnode(memnode)`: Set NUMA memory node (e.g., "0")
+- `write_pid(pid)`: Move a process to this cpuset (returns True/False)
+- `get_tasks()`: Get list of PIDs in this cpuset
+- `set_cpu_exclusive()`: Set as isolated partition
+- `unset_cpu_exclusive()`: Set as member partition
+- `destroy()`: Remove this cpuset (must be empty)
+
+**Properties:**
+- `cpuset_path`: Full path to cpuset (e.g., "/sys/fs/cgroup/tuna_0")
+- `cpuset_name`: Name of cpuset
+- `auto_destroy`: Whether to auto-destroy on context exit
+
+#### `CpusetsInit()`
+
+Represents the root cgroup and provides system information.
+
+**Properties:**
+- `supported`: True if cgroup v2 is available
+- `numa_nodes`: Number of NUMA nodes in the system
+- `cpuset_path`: Path to root cgroup ("/sys/fs/cgroup")
+
+**Methods:**
+- Inherits all methods from `Cpuset` class
+- Use to migrate tasks to/from root cgroup
+
+#### `TaskMigrate(cpuset_from, cpuset_to)`
+
+Helper class for bulk task migration.
+
+**Methods:**
+- `migrate()`: Migrate all tasks, returns (migrated_count, failed_count)
+
+**Attributes:**
+- `migrated`: Number of successfully migrated tasks
+- `failed`: Number of tasks that couldn't be migrated
+
+### Functions
+
+#### `list_cpusets(pattern=None, recursive=True)`
+
+List cpusets under `/sys/fs/cgroup`.
+
+**Parameters:**
+- `pattern` (str): Glob pattern to filter (e.g., "tuna_*")
+- `recursive` (bool): If True, search nested cpusets
+
+**Returns:** List of cpuset paths relative to /sys/fs/cgroup
+
+**Example:**
+```python
+# All cpusets
+all_cpusets = cpuset.list_cpusets()
+
+# Only tuna cpusets
+tuna_cpusets = cpuset.list_cpusets(pattern='tuna_*')
+
+# Top-level only
+top_level = cpuset.list_cpusets(recursive=False)
+```
+
+#### `destroy_cpuset(name_or_path, force=False)`
+
+Destroy a single cpuset.
+
+**Parameters:**
+- `name_or_path` (str): Cpuset name or full path
+- `force` (bool): If True, migrate tasks to root first
+
+**Raises:**
+- `OSError`: If cpuset doesn't exist or can't be removed
+- `ValueError`: If trying to destroy root cgroup
+
+**Example:**
+```python
+# Destroy empty cpuset
+cpuset.destroy_cpuset('tuna_isolated')
+
+# Destroy with tasks (migrates to root first)
+cpuset.destroy_cpuset('busy_cpuset', force=True)
+```
+
+#### `cleanup_cpusets(pattern, force=True, recursive=True)`
+
+Bulk cleanup of multiple cpusets.
+
+**Parameters:**
+- `pattern` (str): Glob pattern (e.g., "tuna_*")
+- `force` (bool): If True, migrate tasks before destroying
+- `recursive` (bool): If True, find and clean nested cpusets
+
+**Example:**
+```python
+# Clean all tuna cpusets
+cpuset.cleanup_cpusets('tuna_*', force=True)
+
+# Clean nested hierarchies (children destroyed before parents)
+cpuset.cleanup_cpusets('user/*', force=True, recursive=True)
+```
+
+## Testing
+
+### Running Tests
+
+The test suite uses Python's unittest framework:
+
+```bash
+# Run basic tests (no root required)
+python3 -m unittest tests.test_cpuset
+
+# Run all tests (requires root)
+sudo python3 -m unittest tests.test_cpuset -v
+
+# Run specific test class
+sudo python3 -m unittest tests.test_cpuset.TestCpusetCreation -v
+
+# Run basic functionality test script
+sudo python3 test_cpuset_basic.py
+```
+
+### Test Coverage
+
+- **TestCpusetsInit**: Initialization and cgroup v2 detection (no root needed)
+- **TestCpusetCreation**: Creating and destroying cpusets (requires root)
+- **TestCpusetConfiguration**: CPU/memory assignment (requires root)
+- **TestContextManager**: auto_destroy behavior (requires root)
+- **TestTaskMigration**: Process migration (requires root)
+- **TestDiscoveryFunctions**: list_cpusets() functionality (requires root)
+- **TestCleanupFunctions**: destroy_cpuset() and cleanup_cpusets() (requires root)
+
+## Key Differences from rteval
+
+| Feature | rteval | tuna |
+|---------|--------|------|
+| Default behavior | Ephemeral (auto-destroyed) | Persistent |
+| auto_destroy default | True (via context manager) | False |
+| Cleanup after exit | Automatic | Manual (or opt-in auto_destroy) |
+| Discovery functions | No | Yes (list_cpusets) |
+| Arbitrary destroy | No | Yes (destroy_cpuset, cleanup_cpusets) |
+| Nested cpuset support | Basic | Enhanced (recursive search) |
+| Primary use case | Testing (temporary isolation) | Environment setup (persistent) |
+
+## Architecture
+
+### Persistent vs Ephemeral Cpusets
+
+**rteval approach** (ephemeral):
+- Creates cpusets at start of measurement run
+- Uses them during testing
+- Destroys them automatically when run finishes
+- Cpusets don't persist after rteval exits
+
+**tuna approach** (persistent):
+- Creates cpusets that persist after tuna exits
+- Other programs use these cpusets after tuna has finished
+- Provides utilities to list and manage existing cpusets
+- Can destroy any cpuset (not just ones tuna created)
+
+### Context Manager Behavior
+
+```python
+# rteval style: auto-cleanup
+with cpuset.Cpuset('temp', auto_destroy=True) as cs:
+    # Use cpuset
+    pass
+# Cpuset is destroyed here
+
+# tuna style: persistent (default)
+with cpuset.Cpuset('persistent') as cs:
+    # Use cpuset
+    pass
+# Cpuset still exists here
+```
+
+## Nested Cpusets
+
+The module supports nested cpuset hierarchies created by systemd, containers, or users:
+
+```python
+# Create parent
+parent = cpuset.Cpuset('parent')
+parent.write_memnode('0')
+
+# Create child manually
+child_path = os.path.join(parent.cpuset_path, 'child')
+os.mkdir(child_path)
+
+# Discovery finds both
+all_cpusets = cpuset.list_cpusets(recursive=True)
+# Returns: ['parent', 'parent/child']
+
+# Cleanup handles nesting (children destroyed first)
+cpuset.cleanup_cpusets('parent*', recursive=True)
+```
+
+## Common Use Cases
+
+### 1. Isolate CPUs for Real-Time Workload
+
+```python
+from tuna import cpuset
+
+# Create isolated cpuset for RT tasks
+rt_cpuset = cpuset.Cpuset('realtime_cpus')
+rt_cpuset.write_memnode('0')
+rt_cpuset.assign_cpus('0-3')
+rt_cpuset.set_cpu_exclusive()
+
+# Move RT processes to isolated CPUs
+for pid in [1234, 5678]:
+    rt_cpuset.write_pid(pid)
+```
+
+### 2. Move Housekeeping Tasks Away from Critical CPUs
+
+```python
+from tuna import cpuset
+
+# Create cpuset for housekeeping
+housekeeping = cpuset.Cpuset('housekeeping')
+housekeeping.write_memnode('0')
+housekeeping.assign_cpus('4-7')
+
+# Migrate most tasks from root to housekeeping
+root = cpuset.CpusetsInit()
+tm = cpuset.TaskMigrate(root, housekeeping)
+migrated, failed = tm.migrate()
+```
+
+### 3. Setup and Teardown for Testing
+
+```python
+from tuna import cpuset
+
+# Setup: Create test environment
+def setup_test_cpusets():
+    # Housekeeping CPUs
+    hk = cpuset.Cpuset('test_housekeeping')
+    hk.write_memnode('0')
+    hk.assign_cpus('4-7')
+
+    # Isolated CPUs
+    iso = cpuset.Cpuset('test_isolated')
+    iso.write_memnode('0')
+    iso.assign_cpus('0-3')
+    iso.set_cpu_exclusive()
+
+    return hk, iso
+
+# Teardown: Clean up
+def teardown_test_cpusets():
+    cpuset.cleanup_cpusets('test_*', force=True)
+```
+
+### 4. Discover and Report Existing Cpusets
+
+```python
+from tuna import cpuset
+
+def report_cpusets():
+    """Generate a report of all active cpusets"""
+    all_cpusets = cpuset.list_cpusets(recursive=True)
+
+    for cs_name in all_cpusets:
+        cs_path = f"/sys/fs/cgroup/{cs_name}"
+
+        # Read configuration
+        with open(f"{cs_path}/cpuset.cpus") as f:
+            cpus = f.read().strip()
+        with open(f"{cs_path}/cgroup.procs") as f:
+            tasks = f.read().strip().split('\n')
+
+        print(f"{cs_name}:")
+        print(f"  CPUs: {cpus}")
+        print(f"  Tasks: {len(tasks)}")
+```
+
+### 5. High-Level Workflow vs Manual Primitives
+
+The `tuna isolate --cpuset` command provides a high-level workflow that automatically creates both isolated and housekeeping cpusets, migrates processes, and sets up kernel-enforced CPU isolation. For advanced users or custom setups, you can replicate this manually using the primitive cpuset commands.
+
+#### High-Level Workflow (Recommended)
+
+```bash
+# Automatic: Creates both cpusets, sets partition=isolated, migrates processes
+$ tuna isolate -c 0-1 --cpuset --cpuset-housekeeping 2-3
+
+# What happens automatically:
+# 1. Creates tuna_isolated cpuset with CPUs 0-1 (partition=isolated, empty)
+# 2. Creates tuna_housekeeping cpuset with CPUs 2-3 (partition=isolated)
+# 3. Auto-detects NUMA nodes for both cpusets
+# 4. Migrates all processes from root to tuna_housekeeping
+# 5. Leaves tuna_isolated empty and ready for RT workload
+```
+
+#### Manual Equivalent Using Primitives
+
+```bash
+# Step 1: Create isolated cpuset (partition=isolated, empty)
+$ tuna cpuset create -c 0-1 -n tuna_isolated --isolated
+
+# Step 2: Create housekeeping cpuset (partition=isolated)
+$ tuna cpuset create -c 2-3 -n tuna_housekeeping --isolated
+
+# Step 3: Migrate all processes from root to housekeeping
+# (This requires reading PIDs from root and moving them one by one)
+$ for pid in $(cat /sys/fs/cgroup/cgroup.procs); do
+    echo $pid > /sys/fs/cgroup/tuna_housekeeping/cgroup.procs 2>/dev/null || true
+done
+
+# Or use Python with the cpuset module:
+# >>> from tuna import cpuset
+# >>> root = cpuset.CpusetsInit()
+# >>> hk = cpuset.Cpuset('tuna_housekeeping', existing_ok=True)
+# >>> tm = cpuset.TaskMigrate(root, hk)
+# >>> migrated, failed = tm.migrate()
+```
+
+#### When to Use Each Approach
+
+**Use the high-level workflow** (`tuna isolate --cpuset`):
+- When you want complete CPU isolation setup in one command
+- For standard RT workload scenarios (isolated CPUs + housekeeping CPUs)
+- When you want automatic NUMA-aware configuration
+- For quick setup with best practices built-in
+
+**Use manual primitives** (`tuna cpuset create`, `modify`, `move`):
+- When you need fine-grained control over each cpuset
+- For non-standard configurations (e.g., multiple isolated cpusets)
+- When you want to create cpusets without immediate process migration
+- For scripting custom workflows or integration with other tools
+- When you need to modify existing cpusets incrementally
+
+#### Design Philosophy
+
+The primitive commands (`tuna cpuset create`, `modify`, `move`, etc.) are **building blocks** that follow the Unix philosophy: do one thing well. They give you complete control.
+
+The workflow commands (`tuna isolate --cpuset`, `tuna move --cpuset`) combine multiple primitives to accomplish common high-level tasks with sensible defaults.
+
+Both approaches are valid and coexist peacefully - choose based on your use case and level of control needed.
+
+## Troubleshooting
+
+### "Permission denied" errors
+
+Most operations require root permissions. Run with `sudo`:
+
+```bash
+sudo python3 your_script.py
+```
+
+### "cgroup v2 not supported"
+
+Check if cgroup v2 is enabled:
+
+```bash
+# Check mount
+mount | grep cgroup2
+
+# Should show:
+# cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
+
+# Enable cgroup v2 (if needed)
+# Add to kernel boot parameters: systemd.unified_cgroup_hierarchy=1
+```
+
+### "Invalid argument" when assigning CPUs
+
+In cgroup v2, you must set memory nodes before CPUs:
+
+```python
+# Correct order
+cs.write_memnode('0')    # First
+cs.assign_cpus('0-3')    # Then
+
+# Wrong order (will fail)
+cs.assign_cpus('0-3')    # Error!
+cs.write_memnode('0')
+```
+
+### Can't destroy cpuset: "Device or resource busy"
+
+The cpuset still has processes. Use `force=True` to migrate them first:
+
+```python
+cpuset.destroy_cpuset('busy_cpuset', force=True)
+```
+
+### Can't destroy parent cpuset: "Directory not empty"
+
+Destroy children before parents:
+
+```python
+# Manual approach
+os.rmdir('/sys/fs/cgroup/parent/child')
+os.rmdir('/sys/fs/cgroup/parent')
+
+# Or use recursive cleanup (handles ordering automatically)
+cpuset.cleanup_cpusets('parent*', recursive=True)
+```
+
+## Contributing
+
+When contributing to this module, please:
+
+1. Maintain compatibility with rteval's core API
+2. Keep tuna-specific enhancements clearly documented
+3. Add tests for new functionality
+4. Ensure tests can run both with and without root (use `@unittest.skipUnless`)
+5. Update this documentation
+
+## References
+
+- [Linux cgroup v2 documentation](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html)
+- [CPU management in cgroup v2](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html#cpu)
+- [rteval cpuset implementation](https://git.kernel.org/pub/scm/utils/rteval/rteval.git/)
+
+## License
+
+SPDX-License-Identifier: GPL-2.0-only
+
+Copyright 2026 John Kacur <[email protected]>
+
+Originally prototyped in tuna, implemented in rteval, and adapted back to tuna with enhancements.
-- 
2.54.0