[PATCH 22/36] tuna: Add cpuset modify command
John Kacur <[email protected]> Fri, 10 Jul 2026 10:15:00 -0400
| Newsgroups | org.kernel.vger.linux-rt-users |
|---|---|
| Message-ID | <[email protected]> |
Add ability to modify existing cpusets without destroying them: - Add/remove CPUs: --add-cpus and --remove-cpus options - Change memory nodes: -m/--memory-nodes option - Toggle partition type: --isolated/--no-isolated options - Multiple modifications can be combined in a single command - Shows before/after summary of changes New Cpuset class methods: - get_cpus(): Get current CPU assignment - get_memnode(): Get current memory node assignment - get_partition_type(): Get current partition type - modify_cpus(add_cpus, remove_cpus): Atomically modify CPU assignment (CPUs are added before removal to avoid zero-CPU scenarios) Example usage: tuna cpuset modify tuna0 --add-cpus 8-11 tuna cpuset modify tuna0 --remove-cpus 0-1 --add-cpus 4-7 tuna cpuset modify tuna0 --memory-nodes 0-1 --isolated Assisted-by: Claude:claude-sonnet-4-5 Signed-off-by: John Kacur <[email protected]> --- tuna-cmd.py | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++ tuna/cpuset.py | 66 ++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) diff --git a/tuna-cmd.py b/tuna-cmd.py index 62553bf51869..557a07891183 100755 --- a/tuna-cmd.py +++ b/tuna-cmd.py @@ -311,6 +311,19 @@ def gen_parser(): 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') + # cpuset modify + cpuset_modify = cpuset_subparser.add_parser('modify', description='Modify an existing cpuset', help='Modify cpuset configuration') + cpuset_modify.add_argument('name', type=str, metavar='NAME', help='Cpuset name to modify') + cpuset_modify.add_argument('--add-cpus', dest='add_cpu_list', type=str, metavar='CPU-LIST', + help='CPUs to add to the cpuset (e.g., 4-7 or 4,5,6,7)') + cpuset_modify.add_argument('--remove-cpus', dest='remove_cpu_list', type=str, metavar='CPU-LIST', + help='CPUs to remove from the cpuset (e.g., 0-1 or 0,1)') + cpuset_modify.add_argument('-m', '--memory-nodes', type=str, metavar='MEM-NODES', dest='memory_nodes', + help='Set memory nodes (e.g., "0" or "0-1")') + partition_group = cpuset_modify.add_mutually_exclusive_group() + partition_group.add_argument('--isolated', action='store_true', help='Set CPU partition to isolated') + partition_group.add_argument('--no-isolated', action='store_true', help='Set CPU partition to member (not isolated)') + return parser @@ -1410,6 +1423,79 @@ def cpuset_status(pattern, show_empty, recursive): print(" These critical system processes should not be in custom cpusets!", file=sys.stderr) +def cpuset_modify(name, add_cpus, remove_cpus, memory_nodes, isolated, no_isolated): + """Handler for 'tuna cpuset modify' command. + + Args: + name: Cpuset name to modify + add_cpus: List of CPU numbers to add (or None) + remove_cpus: List of CPU numbers to remove (or None) + memory_nodes: Memory nodes string to set (or None to keep current) + isolated: If True, set partition to isolated + no_isolated: If True, set partition to member + """ + # Check cgroup v2 support + ci = cpuset.CpusetsInit() + if not ci.supported: + print("Error: cgroup v2 cpusets not supported on this system", file=sys.stderr) + sys.exit(1) + + # Validate name + cpuset_path = os.path.join('/sys/fs/cgroup', name) + if not os.path.exists(cpuset_path): + print(f"Error: cpuset '{name}' does not exist", file=sys.stderr) + sys.exit(1) + + # At least one modification must be specified + if not any([add_cpus, remove_cpus, memory_nodes is not None, isolated, no_isolated]): + print("Error: at least one modification option must be specified", file=sys.stderr) + print(" --add-cpus, --remove-cpus, --memory-nodes, --isolated, or --no-isolated", file=sys.stderr) + sys.exit(1) + + # Can't specify both --isolated and --no-isolated + if isolated and no_isolated: + print("Error: --isolated and --no-isolated are mutually exclusive", file=sys.stderr) + sys.exit(1) + + try: + # Create Cpuset object for existing cpuset + cs = cpuset.Cpuset(name, existing_ok=True) + + changes = [] + + # Modify CPUs if requested + if add_cpus or remove_cpus: + old_cpus = cs.get_cpus() + cs.modify_cpus(add_cpus=add_cpus, remove_cpus=remove_cpus) + new_cpus = cs.get_cpus() + changes.append(f"CPUs: {old_cpus or '(none)'} → {new_cpus or '(none)'}") + + # Modify memory nodes if requested + if memory_nodes is not None: + old_mems = cs.get_memnode() + cs.write_memnode(memory_nodes) + changes.append(f"Memory nodes: {old_mems or '(none)'} → {memory_nodes}") + + # Modify partition type if requested + if isolated: + old_partition = cs.get_partition_type() + cs.set_cpu_exclusive() + changes.append(f"Partition: {old_partition} → isolated") + elif no_isolated: + old_partition = cs.get_partition_type() + cs.unset_cpu_exclusive() + changes.append(f"Partition: {old_partition} → member") + + # Print summary of changes + print(f"Modified cpuset '{name}':") + for change in changes: + print(f" {change}") + + except (OSError, ValueError) as e: + print(f"Error modifying cpuset: {e}", file=sys.stderr) + sys.exit(1) + + def main(): i18n_init() parser = gen_parser() @@ -1639,6 +1725,18 @@ def main(): elif args.cpuset_command == 'status': cpuset_status(args.pattern, args.show_empty, args.recursive) + elif args.cpuset_command == 'modify': + # Parse CPU lists if provided + add_cpus = None + remove_cpus = None + if args.add_cpu_list: + add_cpus = tuna.cpustring_to_list(args.add_cpu_list) + if args.remove_cpu_list: + remove_cpus = tuna.cpustring_to_list(args.remove_cpu_list) + + cpuset_modify(args.name, add_cpus, remove_cpus, args.memory_nodes, + args.isolated, args.no_isolated) + if __name__ == '__main__': main() diff --git a/tuna/cpuset.py b/tuna/cpuset.py index 79f86d8cfe9f..3f9b0534a397 100644 --- a/tuna/cpuset.py +++ b/tuna/cpuset.py @@ -264,6 +264,72 @@ class Cpuset: with open(path, 'r', encoding='utf-8') as f: return [line.strip() for line in f if line.strip()] + def get_cpus(self): + """ + Get the current CPU assignment as a string + Returns string like "0-3" or "0,2,4-7" or empty string if none assigned + """ + path = os.path.join(self._cpuset_path, "cpuset.cpus") + with open(path, 'r', encoding='utf-8') as f: + return f.read().strip() + + def get_memnode(self): + """ + Get the current memory node assignment as a string + Returns string like "0" or "0-1" or empty string if none assigned + """ + path = os.path.join(self._cpuset_path, "cpuset.mems") + with open(path, 'r', encoding='utf-8') as f: + return f.read().strip() + + def get_partition_type(self): + """ + Get the current partition type + Returns 'isolated', 'member', or 'root' depending on cpuset.cpus.partition + Returns None if partition file doesn't exist + """ + path = os.path.join(self._cpuset_path, "cpuset.cpus.partition") + if not os.path.exists(path): + return None + with open(path, 'r', encoding='utf-8') as f: + return f.read().strip() + + def modify_cpus(self, add_cpus=None, remove_cpus=None): + """ + Modify CPU assignment by adding and/or removing CPUs + + Args: + add_cpus: List of CPU numbers to add (e.g., [4, 5, 6, 7]) + remove_cpus: List of CPU numbers to remove (e.g., [0, 1]) + + Note: CPUs are added before removal. This is important when replacing CPUs + to avoid having zero CPUs assigned (which may cause errors). + """ + from tuna import tuna + + # Get current CPUs + current_cpus_str = self.get_cpus() + if current_cpus_str: + current_cpus = set(tuna.cpustring_to_list(current_cpus_str)) + else: + current_cpus = set() + + # Add new CPUs + if add_cpus: + current_cpus.update(add_cpus) + + # Remove CPUs + if remove_cpus: + current_cpus.difference_update(remove_cpus) + + # Convert back to string and assign + if current_cpus: + new_cpus_str = tuna.collapse_cpulist(sorted(current_cpus)) + self.assign_cpus(new_cpus_str) + else: + # If no CPUs left, assign empty string + self.assign_cpus("") + class CpusetsInit(Cpuset): """ -- 2.54.0