| #!/usr/bin/env drgn |
| # |
| # Copyright (C) 2023 Tejun Heo <tj@kernel.org> |
| # Copyright (C) 2023 Meta Platforms, Inc. and affiliates. |
| |
| desc = """ |
| This is a drgn script to show the current workqueue configuration. For more |
| info on drgn, visit https://github.com/osandov/drgn. |
| |
| Affinity Scopes |
| =============== |
| |
| Shows the CPUs that can be used for unbound workqueues and how they will be |
| grouped by each available affinity type. For each type: |
| |
| nr_pods number of CPU pods in the affinity type |
| pod_cpus CPUs in each pod |
| pod_node NUMA node for memory allocation for each pod |
| cpu_pod pod that each CPU is associated to |
| |
| Worker Pools |
| ============ |
| |
| Lists all worker pools indexed by their ID. For each pool: |
| |
| ref number of pool_workqueue's associated with this pool |
| nice nice value of the worker threads in the pool |
| idle number of idle workers |
| workers number of all workers |
| cpu CPU the pool is associated with (per-cpu pool) |
| cpus CPUs the workers in the pool can run on (unbound pool) |
| |
| Workqueue CPU -> pool |
| ===================== |
| |
| Lists all workqueues along with their type and worker pool association. For |
| each workqueue: |
| |
| NAME TYPE[,FLAGS] POOL_ID... |
| |
| NAME name of the workqueue |
| TYPE percpu, unbound or ordered |
| FLAGS S: strict affinity scope |
| POOL_ID worker pool ID associated with each possible CPU |
| """ |
| |
| import sys |
| |
| import drgn |
| from drgn.helpers.linux.list import list_for_each_entry,list_empty |
| from drgn.helpers.linux.percpu import per_cpu_ptr |
| from drgn.helpers.linux.cpumask import for_each_cpu,for_each_possible_cpu |
| from drgn.helpers.linux.idr import idr_for_each |
| |
| import argparse |
| parser = argparse.ArgumentParser(description=desc, |
| formatter_class=argparse.RawTextHelpFormatter) |
| args = parser.parse_args() |
| |
| def err(s): |
| print(s, file=sys.stderr, flush=True) |
| sys.exit(1) |
| |
| def cpumask_str(cpumask): |
| output = "" |
| base = 0 |
| v = 0 |
| for cpu in for_each_cpu(cpumask[0]): |
| while cpu - base >= 32: |
| output += f'{hex(v)} ' |
| base += 32 |
| v = 0 |
| v |= 1 << (cpu - base) |
| if v > 0: |
| output += f'{v:08x}' |
| return output.strip() |
| |
| worker_pool_idr = prog['worker_pool_idr'] |
| workqueues = prog['workqueues'] |
| wq_unbound_cpumask = prog['wq_unbound_cpumask'] |
| wq_pod_types = prog['wq_pod_types'] |
| wq_affn_dfl = prog['wq_affn_dfl'] |
| wq_affn_names = prog['wq_affn_names'] |
| |
| WQ_UNBOUND = prog['WQ_UNBOUND'] |
| WQ_ORDERED = prog['__WQ_ORDERED'] |
| WQ_MEM_RECLAIM = prog['WQ_MEM_RECLAIM'] |
| |
| WQ_AFFN_CPU = prog['WQ_AFFN_CPU'] |
| WQ_AFFN_SMT = prog['WQ_AFFN_SMT'] |
| WQ_AFFN_CACHE = prog['WQ_AFFN_CACHE'] |
| WQ_AFFN_NUMA = prog['WQ_AFFN_NUMA'] |
| WQ_AFFN_SYSTEM = prog['WQ_AFFN_SYSTEM'] |
| |
| print('Affinity Scopes') |
| print('===============') |
| |
| print(f'wq_unbound_cpumask={cpumask_str(wq_unbound_cpumask)}') |
| |
| def print_pod_type(pt): |
| print(f' nr_pods {pt.nr_pods.value_()}') |
| |
| print(' pod_cpus', end='') |
| for pod in range(pt.nr_pods): |
| print(f' [{pod}]={cpumask_str(pt.pod_cpus[pod])}', end='') |
| print('') |
| |
| print(' pod_node', end='') |
| for pod in range(pt.nr_pods): |
| print(f' [{pod}]={pt.pod_node[pod].value_()}', end='') |
| print('') |
| |
| print(f' cpu_pod ', end='') |
| for cpu in for_each_possible_cpu(prog): |
| print(f' [{cpu}]={pt.cpu_pod[cpu].value_()}', end='') |
| print('') |
| |
| for affn in [WQ_AFFN_CPU, WQ_AFFN_SMT, WQ_AFFN_CACHE, WQ_AFFN_NUMA, WQ_AFFN_SYSTEM]: |
| print('') |
| print(f'{wq_affn_names[affn].string_().decode().upper()}{" (default)" if affn == wq_affn_dfl else ""}') |
| print_pod_type(wq_pod_types[affn]) |
| |
| print('') |
| print('Worker Pools') |
| print('============') |
| |
| max_pool_id_len = 0 |
| max_ref_len = 0 |
| for pi, pool in idr_for_each(worker_pool_idr): |
| pool = drgn.Object(prog, 'struct worker_pool', address=pool) |
| max_pool_id_len = max(max_pool_id_len, len(f'{pi}')) |
| max_ref_len = max(max_ref_len, len(f'{pool.refcnt.value_()}')) |
| |
| for pi, pool in idr_for_each(worker_pool_idr): |
| pool = drgn.Object(prog, 'struct worker_pool', address=pool) |
| print(f'pool[{pi:0{max_pool_id_len}}] ref={pool.refcnt.value_():{max_ref_len}} nice={pool.attrs.nice.value_():3} ', end='') |
| print(f'idle/workers={pool.nr_idle.value_():3}/{pool.nr_workers.value_():3} ', end='') |
| if pool.cpu >= 0: |
| print(f'cpu={pool.cpu.value_():3}', end='') |
| else: |
| print(f'cpus={cpumask_str(pool.attrs.cpumask)}', end='') |
| print(f' pod_cpus={cpumask_str(pool.attrs.__pod_cpumask)}', end='') |
| if pool.attrs.affn_strict: |
| print(' strict', end='') |
| print('') |
| |
| print('') |
| print('Workqueue CPU -> pool') |
| print('=====================') |
| |
| print('[ workqueue \ type CPU', end='') |
| for cpu in for_each_possible_cpu(prog): |
| print(f' {cpu:{max_pool_id_len}}', end='') |
| print(' dfl]') |
| |
| for wq in list_for_each_entry('struct workqueue_struct', workqueues.address_of_(), 'list'): |
| print(f'{wq.name.string_().decode()[-24:]:24}', end='') |
| if wq.flags & WQ_UNBOUND: |
| if wq.flags & WQ_ORDERED: |
| print(' ordered ', end='') |
| else: |
| print(' unbound', end='') |
| if wq.unbound_attrs.affn_strict: |
| print(',S ', end='') |
| else: |
| print(' ', end='') |
| else: |
| print(' percpu ', end='') |
| |
| for cpu in for_each_possible_cpu(prog): |
| pool_id = per_cpu_ptr(wq.cpu_pwq, cpu)[0].pool.id.value_() |
| field_len = max(len(str(cpu)), max_pool_id_len) |
| print(f' {pool_id:{field_len}}', end='') |
| |
| if wq.flags & WQ_UNBOUND: |
| print(f' {wq.dfl_pwq.pool.id.value_():{max_pool_id_len}}', end='') |
| print('') |