[PATCH 18/36] tuna: Add cpuset show and status commands
John Kacur <[email protected]> Fri, 10 Jul 2026 10:14:56 -0400
| Newsgroups | org.kernel.vger.linux-rt-users |
|---|---|
| Message-ID | <[email protected]> |
Add two new cpuset subcommands to provide detailed information about cpusets and system-wide overview: 1. 'tuna cpuset show <name>' - Display detailed information about a specific cpuset including CPUs, memory nodes, partition type, task count, and optional detailed task list with --show-tasks flag. 2. 'tuna cpuset status' - Display system-wide overview of all cpusets with summary statistics (total cpusets, CPUs assigned, tasks count, isolated cpusets) and a table view of all cpusets. Supports pattern filtering with --pattern and --skip-empty options. Both commands share infrastructure through new helper functions: - get_cpuset_info() - Gather detailed info about a single cpuset - get_all_cpusets_info() - Gather info about multiple cpusets These commands complement the existing create/list/destroy/move commands and provide better visibility into cpuset configuration and usage. Assisted-by: Claude:claude-sonnet-4-5 Signed-off-by: John Kacur <[email protected]> --- tuna-cmd.py | 269 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) diff --git a/tuna-cmd.py b/tuna-cmd.py index 5c21d54bc2bf..b560ec805b7a 100755 --- a/tuna-cmd.py +++ b/tuna-cmd.py @@ -300,6 +300,17 @@ def gen_parser(): cpuset_move.add_argument('-t', '--threads', **MODS['threads']) cpuset_move.add_argument('-p', '--pids', dest='pid_list', type=str, metavar='PID-LIST', help='Comma-separated list of PIDs to move') + # cpuset show + cpuset_show = cpuset_subparser.add_parser('show', description='Show detailed information about a cpuset', help='Show cpuset details') + cpuset_show.add_argument('name', type=str, metavar='NAME', help='Cpuset name to show') + cpuset_show.add_argument('--show-tasks', action='store_true', help='Show detailed list of tasks/processes') + + # 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('--no-recursive', dest='recursive', action='store_false', default=True, help='Only show top-level cpusets') + return parser @@ -831,6 +842,101 @@ def get_next_tuna_cpuset_name(): return f'tuna{i}' +def get_cpuset_info(cpuset_name): + """Get detailed information about a cpuset. + + Args: + cpuset_name: Name of the cpuset (e.g., 'tuna0') + + Returns: + dict: Cpuset information with keys: + - name: cpuset name + - path: full path to cpuset + - cpus: CPU list string (e.g., "0-3") + - mems: Memory nodes string (e.g., "0") + - tasks: List of PIDs + - task_count: Number of tasks + - partition: Partition type (isolated/member/root/n/a) + - blocklisted_pids: List of (pid, comm) tuples for blocklisted processes + - error: Error message if failed to read info + + """ + cs_path = os.path.join('/sys/fs/cgroup', cpuset_name) + info = { + 'name': cpuset_name, + 'path': cs_path, + 'cpus': '', + 'mems': '', + 'tasks': [], + 'task_count': 0, + 'partition': 'n/a', + 'blocklisted_pids': [], + 'error': None + } + + try: + # Read CPUs + with open(os.path.join(cs_path, 'cpuset.cpus'), 'r', encoding='utf-8') as f: + info['cpus'] = f.read().strip() + + # Read memory nodes + with open(os.path.join(cs_path, 'cpuset.mems'), 'r', encoding='utf-8') as f: + info['mems'] = f.read().strip() + + # Read tasks + with open(os.path.join(cs_path, 'cgroup.procs'), 'r', encoding='utf-8') as f: + info['tasks'] = [line.strip() for line in f if line.strip()] + info['task_count'] = len(info['tasks']) + + # Read partition type (if available) + partition_file = os.path.join(cs_path, 'cpuset.cpus.partition') + if os.path.exists(partition_file): + with open(partition_file, 'r', encoding='utf-8') as f: + info['partition'] = f.read().strip() + + # Check for blocklisted processes (skip system cgroups) + if not cpuset_name.endswith(('.mount', '.scope', '.slice')): + for pid_str in info['tasks']: + try: + pid = int(pid_str) + if cpuset.Cpuset._is_process_blocklisted(pid): + comm = cpuset.Cpuset._get_process_name(pid) + info['blocklisted_pids'].append((pid, comm)) + except ValueError: + pass + + except (OSError, IOError) as e: + info['error'] = str(e) + + return info + + +def get_all_cpusets_info(pattern=None, recursive=True, skip_empty=False): + """Get information about all cpusets matching criteria. + + Args: + pattern: Glob pattern to filter cpusets (None for all) + recursive: If True, search nested cpusets + skip_empty: If True, skip cpusets with no CPUs assigned + + Returns: + list: List of cpuset info dictionaries (from get_cpuset_info) + """ + cpusets = cpuset.list_cpusets(pattern=pattern, recursive=recursive) + infos = [] + + for cs_name in cpusets: + info = get_cpuset_info(cs_name) + + # Skip empty cpusets if requested + if skip_empty and not info['cpus']: + continue + + infos.append(info) + + return infos + + def cpuset_create(cpu_list, name, isolated, memory_nodes=None): """Handler for 'tuna cpuset create' command. @@ -1147,6 +1253,163 @@ def cpuset_move(name, thread_list, pid_list): sys.exit(1) +def cpuset_show(name, show_tasks): + """Handler for 'tuna cpuset show' command. + + Args: + name: Cpuset name to show details for + show_tasks: If True, show detailed task list + """ + # 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) + + # Check if cpuset exists + 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) + + # Get cpuset info + info = get_cpuset_info(name) + + if info['error']: + print(f"Error reading cpuset info: {info['error']}", file=sys.stderr) + sys.exit(1) + + # Print detailed information + print(f"Cpuset: {name}") + print(f"Path: {info['path']}") + print(f"CPUs: {info['cpus'] or '(none)'}") + print(f"Memory Nodes: {info['mems'] or '(none)'}") + print(f"Partition: {info['partition']}") + print(f"Task Count: {info['task_count']}") + + # Show task details if requested + if show_tasks and info['tasks']: + print(f"\nTasks ({len(info['tasks'])} total):") + print(f"{'PID':<10} {'COMMAND':<30} {'STATUS':<10}") + print("-" * 52) + + for pid_str in info['tasks']: + try: + pid = int(pid_str) + comm = cpuset.Cpuset._get_process_name(pid) + is_blocked = cpuset.Cpuset._is_process_blocklisted(pid) + status = "BLOCKED" if is_blocked else "ok" + print(f"{pid:<10} {comm:<30} {status:<10}") + except (ValueError, OSError): + print(f"{pid_str:<10} {'(error)':<30} {'error':<10}") + + # Show blocklist warnings + if info['blocklisted_pids']: + print() + print("⚠️ WARNINGS:") + for pid, comm in info['blocklisted_pids']: + print(f" - Blocklisted process PID {pid} ({comm})", file=sys.stderr) + print(" These critical system processes should not be in custom cpusets!", file=sys.stderr) + + +def cpuset_status(pattern, skip_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 + recursive: If True, search nested cpusets + """ + # 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) + + # Get all cpusets info + infos = get_all_cpusets_info(pattern=pattern, recursive=recursive, skip_empty=skip_empty) + + if not infos: + if pattern: + print(f"No cpusets found matching pattern '{pattern}'") + else: + print("No cpusets found") + return + + # Calculate system-wide statistics + total_cpus_assigned = set() + total_tasks = 0 + isolated_cpusets = [] + cpusets_with_tasks = [] + + for info in infos: + if info['cpus']: + # Parse CPU list to get individual CPUs + try: + cpu_list = tuna.expand_cpulist(info['cpus']) + total_cpus_assigned.update(cpu_list) + except (ValueError, IndexError): + pass + + total_tasks += info['task_count'] + + if info['partition'] == 'isolated': + isolated_cpusets.append(info['name']) + + if info['task_count'] > 0: + cpusets_with_tasks.append(info['name']) + + # Print system overview + print("=" * 70) + print("SYSTEM CPUSET OVERVIEW") + print("=" * 70) + print(f"Total cpusets: {len(infos)}") + print(f"CPUs assigned: {len(total_cpus_assigned)}") + print(f"Total tasks: {total_tasks}") + print(f"Isolated cpusets: {len(isolated_cpusets)}") + print() + + # Print cpuset summary table + print(f"{'NAME':<25} {'CPUS':<20} {'MEMS':<8} {'TASKS':<7} {'PARTITION':<10}") + print("-" * 75) + + for info in infos: + cpus_display = info['cpus'] or "(none)" + mems_display = info['mems'] or "(none)" + + # Truncate long names with ellipsis + name_display = info['name'] + if len(name_display) > 24: + name_display = name_display[:21] + "..." + + # Truncate long CPU lists + if len(cpus_display) > 19: + cpus_display = cpus_display[:16] + "..." + + print(f"{name_display:<25} {cpus_display:<20} {mems_display:<8} {info['task_count']:<7} {info['partition']:<10}") + + # Show isolated cpusets if any + if isolated_cpusets: + print() + print(f"Isolated cpusets ({len(isolated_cpusets)}):") + for name in isolated_cpusets: + print(f" - {name}") + + # Check for blocklisted processes across all cpusets + warnings = [] + for info in infos: + if info['blocklisted_pids']: + for pid, comm in info['blocklisted_pids']: + warnings.append((info['name'], pid, comm)) + + if warnings: + print() + print("⚠️ WARNINGS - Blocklisted processes found:") + for cpuset_name, pid, comm in warnings: + print(f" - {cpuset_name}: PID {pid} ({comm})", file=sys.stderr) + print(" These critical system processes should not be in custom cpusets!", file=sys.stderr) + + def main(): i18n_init() parser = gen_parser() @@ -1370,6 +1633,12 @@ def main(): elif args.cpuset_command == 'move': cpuset_move(args.name, args.thread_list, args.pid_list) + elif args.cpuset_command == 'show': + cpuset_show(args.name, args.show_tasks) + + elif args.cpuset_command == 'status': + cpuset_status(args.pattern, args.skip_empty, args.recursive) + if __name__ == '__main__': main() -- 2.54.0