| #!/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.nodemask import for_each_node |
| 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() |
| |
| wq_type_len = 9 |
| |
| def wq_type_str(wq): |
| if wq.flags & WQ_BH: |
| return f'{"bh":{wq_type_len}}' |
| elif wq.flags & WQ_UNBOUND: |
| if wq.flags & WQ_ORDERED: |
| return f'{"ordered":{wq_type_len}}' |
| else: |
| if wq.unbound_attrs.affn_strict: |
| return f'{"unbound,S":{wq_type_len}}' |
| else: |
| return f'{"unbound":{wq_type_len}}' |
| else: |
| return f'{"percpu":{wq_type_len}}' |
| |
| 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_BH = prog['WQ_BH'] |
| 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'] |
| |
| POOL_BH = prog['POOL_BH'] |
| |
| WQ_NAME_LEN = prog['WQ_NAME_LEN'].value_() |
| cpumask_str_len = len(cpumask_str(wq_unbound_cpumask)) |
| |
| 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}}] flags=0x{pool.flags.value_():02x} 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='') |
| if pool.flags & POOL_BH: |
| print(' bh', 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(f'[{"workqueue":^{WQ_NAME_LEN-2}}\\ {"type CPU":{wq_type_len}}', 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():{WQ_NAME_LEN}} {wq_type_str(wq):10}', 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('') |
| |
| print('') |
| print('Workqueue -> rescuer') |
| print('====================') |
| |
| ucpus_len = max(cpumask_str_len, len("unbound_cpus")) |
| rcpus_len = max(cpumask_str_len, len("rescuer_cpus")) |
| |
| print(f'[{"workqueue":^{WQ_NAME_LEN-2}}\\ {"unbound_cpus":{ucpus_len}} pid {"rescuer_cpus":{rcpus_len}} ]') |
| |
| for wq in list_for_each_entry('struct workqueue_struct', workqueues.address_of_(), 'list'): |
| if not (wq.flags & WQ_MEM_RECLAIM): |
| continue |
| |
| print(f'{wq.name.string_().decode():{WQ_NAME_LEN}}', end='') |
| if wq.unbound_attrs.value_() != 0: |
| print(f' {cpumask_str(wq.unbound_attrs.cpumask):{ucpus_len}}', end='') |
| else: |
| print(f' {"":{ucpus_len}}', end='') |
| |
| print(f' {wq.rescuer.task.pid.value_():6}', end='') |
| print(f' {cpumask_str(wq.rescuer.task.cpus_ptr):{rcpus_len}}', end='') |
| print('') |
| |
| print('') |
| print('Unbound workqueue -> node_nr/max_active') |
| print('=======================================') |
| |
| if 'node_to_cpumask_map' in prog: |
| __cpu_online_mask = prog['__cpu_online_mask'] |
| node_to_cpumask_map = prog['node_to_cpumask_map'] |
| nr_node_ids = prog['nr_node_ids'].value_() |
| |
| print(f'online_cpus={cpumask_str(__cpu_online_mask.address_of_())}') |
| for node in for_each_node(): |
| print(f'NODE[{node:02}]={cpumask_str(node_to_cpumask_map[node])}') |
| print('') |
| |
| print(f'[{"workqueue":^{WQ_NAME_LEN-2}}\\ min max', end='') |
| first = True |
| for node in for_each_node(): |
| if first: |
| print(f' NODE {node}', end='') |
| first = False |
| else: |
| print(f' {node:7}', end='') |
| print(f' {"dfl":>7} ]') |
| print('') |
| |
| for wq in list_for_each_entry('struct workqueue_struct', workqueues.address_of_(), 'list'): |
| if not (wq.flags & WQ_UNBOUND): |
| continue |
| |
| print(f'{wq.name.string_().decode():{WQ_NAME_LEN}} ', end='') |
| print(f'{wq.min_active.value_():3} {wq.max_active.value_():3}', end='') |
| for node in for_each_node(): |
| nna = wq.node_nr_active[node] |
| print(f' {nna.nr.counter.value_():3}/{nna.max.value_():3}', end='') |
| nna = wq.node_nr_active[nr_node_ids] |
| print(f' {nna.nr.counter.value_():3}/{nna.max.value_():3}') |
| else: |
| printf(f'node_to_cpumask_map not present, is NUMA enabled?') |