| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 |
|
| 6 | desc = """
|
| 7 | This is a drgn script to show the current workqueue configuration. For more
|
| 8 | info on drgn, visit https://github.com/osandov/drgn.
|
| 9 |
|
| 10 | Affinity Scopes
|
| 11 | ===============
|
| 12 |
|
| 13 | Shows the CPUs that can be used for unbound workqueues and how they will be
|
| 14 | grouped by each available affinity type. For each type:
|
| 15 |
|
| 16 | nr_pods number of CPU pods in the affinity type
|
| 17 | pod_cpus CPUs in each pod
|
| 18 | pod_node NUMA node for memory allocation for each pod
|
| 19 | cpu_pod pod that each CPU is associated to
|
| 20 |
|
| 21 | Worker Pools
|
| 22 | ============
|
| 23 |
|
| 24 | Lists all worker pools indexed by their ID. For each pool:
|
| 25 |
|
| 26 | ref number of pool_workqueue's associated with this pool
|
| 27 | nice nice value of the worker threads in the pool
|
| 28 | idle number of idle workers
|
| 29 | workers number of all workers
|
| 30 | cpu CPU the pool is associated with (per-cpu pool)
|
| 31 | cpus CPUs the workers in the pool can run on (unbound pool)
|
| 32 |
|
| 33 | Workqueue CPU -> pool
|
| 34 | =====================
|
| 35 |
|
| 36 | Lists all workqueues along with their type and worker pool association. For
|
| 37 | each workqueue:
|
| 38 |
|
| 39 | NAME TYPE[,FLAGS] POOL_ID...
|
| 40 |
|
| 41 | NAME name of the workqueue
|
| 42 | TYPE percpu, unbound or ordered
|
| 43 | FLAGS S: strict affinity scope
|
| 44 | POOL_ID worker pool ID associated with each possible CPU
|
| 45 | """
|
| 46 |
|
| 47 | import sys
|
| 48 |
|
| 49 | import argparse
|
| 50 | parser = argparse.ArgumentParser(description=desc,
|
| 51 | formatter_class=argparse.RawTextHelpFormatter)
|
| 52 | args = parser.parse_args()
|
| 53 |
|
| 54 | import drgn
|
| 55 | from drgn.helpers.linux.list import list_for_each_entry,list_empty
|
| 56 | from drgn.helpers.linux.percpu import per_cpu_ptr
|
| 57 | from drgn.helpers.linux.cpumask import for_each_cpu,for_each_possible_cpu
|
| 58 | from drgn.helpers.linux.nodemask import for_each_node
|
| 59 | from drgn.helpers.linux.idr import idr_for_each
|
| 60 |
|
| 61 | def err(s):
|
| 62 | print(s, file=sys.stderr, flush=True)
|
| 63 | sys.exit(1)
|
| 64 |
|
| 65 | def cpumask_str(cpumask):
|
| 66 | output = ""
|
| 67 | base = 0
|
| 68 | v = 0
|
| 69 | for cpu in for_each_cpu(cpumask[0]):
|
| 70 | while cpu - base >= 32:
|
| 71 | output += f'{hex(v)} '
|
| 72 | base += 32
|
| 73 | v = 0
|
| 74 | v |= 1 << (cpu - base)
|
| 75 | if v > 0:
|
| 76 | output += f'{v:08x}'
|
| 77 | return output.strip()
|
| 78 |
|
| 79 | wq_type_len = 9
|
| 80 |
|
| 81 | def wq_attrs(wq):
|
| 82 | try:
|
| 83 | return wq.attrs
|
| 84 | except AttributeError:
|
| 85 | return wq.unbound_attrs
|
| 86 |
|
| 87 | def wq_type_str(wq):
|
| 88 | if wq.flags & WQ_BH:
|
| 89 | return f'{"bh":{wq_type_len}}'
|
| 90 | elif wq.flags & WQ_UNBOUND:
|
| 91 | if wq.flags & WQ_ORDERED:
|
| 92 | return f'{"ordered":{wq_type_len}}'
|
| 93 | else:
|
| 94 | if wq_attrs(wq).affn_strict:
|
| 95 | return f'{"unbound,S":{wq_type_len}}'
|
| 96 | else:
|
| 97 | return f'{"unbound":{wq_type_len}}'
|
| 98 | else:
|
| 99 | return f'{"percpu":{wq_type_len}}'
|
| 100 |
|
| 101 | worker_pool_idr = prog['worker_pool_idr']
|
| 102 | workqueues = prog['workqueues']
|
| 103 | wq_unbound_cpumask = prog['wq_unbound_cpumask']
|
| 104 | wq_pod_types = prog['wq_pod_types']
|
| 105 | wq_affn_dfl = prog['wq_affn_dfl']
|
| 106 | wq_affn_names = prog['wq_affn_names']
|
| 107 |
|
| 108 | WQ_BH = prog['WQ_BH']
|
| 109 | WQ_UNBOUND = prog['WQ_UNBOUND']
|
| 110 | WQ_ORDERED = prog['__WQ_ORDERED']
|
| 111 | WQ_MEM_RECLAIM = prog['WQ_MEM_RECLAIM']
|
| 112 |
|
| 113 | WQ_AFFN_CPU = prog['WQ_AFFN_CPU']
|
| 114 | WQ_AFFN_SMT = prog['WQ_AFFN_SMT']
|
| 115 | WQ_AFFN_CACHE = prog['WQ_AFFN_CACHE']
|
| 116 | WQ_AFFN_CACHE_SHARD = prog['WQ_AFFN_CACHE_SHARD']
|
| 117 | WQ_AFFN_NUMA = prog['WQ_AFFN_NUMA']
|
| 118 | WQ_AFFN_SYSTEM = prog['WQ_AFFN_SYSTEM']
|
| 119 |
|
| 120 | POOL_BH = prog['POOL_BH']
|
| 121 |
|
| 122 | WQ_NAME_LEN = prog['WQ_NAME_LEN'].value_()
|
| 123 | cpumask_str_len = len(cpumask_str(wq_unbound_cpumask))
|
| 124 |
|
| 125 | print('Affinity Scopes')
|
| 126 | print('===============')
|
| 127 |
|
| 128 | print(f'wq_unbound_cpumask={cpumask_str(wq_unbound_cpumask)}')
|
| 129 |
|
| 130 | def print_pod_type(pt):
|
| 131 | print(f' nr_pods {pt.nr_pods.value_()}')
|
| 132 |
|
| 133 | print(' pod_cpus', end='')
|
| 134 | for pod in range(pt.nr_pods):
|
| 135 | print(f' [{pod}]={cpumask_str(pt.pod_cpus[pod])}', end='')
|
| 136 | print('')
|
| 137 |
|
| 138 | print(' pod_node', end='')
|
| 139 | for pod in range(pt.nr_pods):
|
| 140 | print(f' [{pod}]={pt.pod_node[pod].value_()}', end='')
|
| 141 | print('')
|
| 142 |
|
| 143 | print(f' cpu_pod ', end='')
|
| 144 | for cpu in for_each_possible_cpu(prog):
|
| 145 | print(f' [{cpu}]={pt.cpu_pod[cpu].value_()}', end='')
|
| 146 | print('')
|
| 147 |
|
| 148 | for affn in [WQ_AFFN_CPU, WQ_AFFN_SMT, WQ_AFFN_CACHE, WQ_AFFN_CACHE_SHARD, WQ_AFFN_NUMA, WQ_AFFN_SYSTEM]:
|
| 149 | print('')
|
| 150 | print(f'{wq_affn_names[affn].string_().decode().upper()}{" (default)" if affn == wq_affn_dfl else ""}')
|
| 151 | print_pod_type(wq_pod_types[affn])
|
| 152 |
|
| 153 | print('')
|
| 154 | print('Worker Pools')
|
| 155 | print('============')
|
| 156 |
|
| 157 | max_pool_id_len = 0
|
| 158 | max_ref_len = 0
|
| 159 | for pi, pool in idr_for_each(worker_pool_idr):
|
| 160 | pool = drgn.Object(prog, 'struct worker_pool', address=pool)
|
| 161 | max_pool_id_len = max(max_pool_id_len, len(f'{pi}'))
|
| 162 | max_ref_len = max(max_ref_len, len(f'{pool.refcnt.value_()}'))
|
| 163 |
|
| 164 | for pi, pool in idr_for_each(worker_pool_idr):
|
| 165 | pool = drgn.Object(prog, 'struct worker_pool', address=pool)
|
| 166 | 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='')
|
| 167 | print(f'idle/workers={pool.nr_idle.value_():3}/{pool.nr_workers.value_():3} ', end='')
|
| 168 | if pool.cpu >= 0:
|
| 169 | print(f'cpu={pool.cpu.value_():3}', end='')
|
| 170 | if pool.flags & POOL_BH:
|
| 171 | print(' bh', end='')
|
| 172 | else:
|
| 173 | print(f'cpus={cpumask_str(pool.attrs.cpumask)}', end='')
|
| 174 | print(f' pod_cpus={cpumask_str(pool.attrs.__pod_cpumask)}', end='')
|
| 175 | if pool.attrs.affn_strict:
|
| 176 | print(' strict', end='')
|
| 177 | print('')
|
| 178 |
|
| 179 | print('')
|
| 180 | print('Workqueue CPU -> pool')
|
| 181 | print('=====================')
|
| 182 |
|
| 183 | print(f'[{"workqueue":^{WQ_NAME_LEN-2}}\\ {"type CPU":{wq_type_len}}', end='')
|
| 184 | for cpu in for_each_possible_cpu(prog):
|
| 185 | print(f' {cpu:{max_pool_id_len}}', end='')
|
| 186 | print(' dfl]')
|
| 187 |
|
| 188 | for wq in list_for_each_entry('struct workqueue_struct', workqueues.address_of_(), 'list'):
|
| 189 | print(f'{wq.name.string_().decode():{WQ_NAME_LEN}} {wq_type_str(wq):10}', end='')
|
| 190 |
|
| 191 | for cpu in for_each_possible_cpu(prog):
|
| 192 | pool_id = per_cpu_ptr(wq.cpu_pwq, cpu)[0].pool.id.value_()
|
| 193 | field_len = max(len(str(cpu)), max_pool_id_len)
|
| 194 | print(f' {pool_id:{field_len}}', end='')
|
| 195 |
|
| 196 | if wq.flags & WQ_UNBOUND:
|
| 197 | print(f' {wq.dfl_pwq.pool.id.value_():{max_pool_id_len}}', end='')
|
| 198 | print('')
|
| 199 |
|
| 200 | print('')
|
| 201 | print('Workqueue -> rescuer')
|
| 202 | print('====================')
|
| 203 |
|
| 204 | ucpus_len = max(cpumask_str_len, len("unbound_cpus"))
|
| 205 | rcpus_len = max(cpumask_str_len, len("rescuer_cpus"))
|
| 206 |
|
| 207 | print(f'[{"workqueue":^{WQ_NAME_LEN-2}}\\ {"unbound_cpus":{ucpus_len}} pid {"rescuer_cpus":{rcpus_len}} ]')
|
| 208 |
|
| 209 | for wq in list_for_each_entry('struct workqueue_struct', workqueues.address_of_(), 'list'):
|
| 210 | if not (wq.flags & WQ_MEM_RECLAIM):
|
| 211 | continue
|
| 212 |
|
| 213 | print(f'{wq.name.string_().decode():{WQ_NAME_LEN}}', end='')
|
| 214 | if wq.flags & WQ_UNBOUND:
|
| 215 | print(f' {cpumask_str(wq_attrs(wq).cpumask):{ucpus_len}}', end='')
|
| 216 | else:
|
| 217 | print(f' {"":{ucpus_len}}', end='')
|
| 218 |
|
| 219 | print(f' {wq.rescuer.task.pid.value_():6}', end='')
|
| 220 | print(f' {cpumask_str(wq.rescuer.task.cpus_ptr):{rcpus_len}}', end='')
|
| 221 | print('')
|
| 222 |
|
| 223 | print('')
|
| 224 | print('Unbound workqueue -> node_nr/max_active')
|
| 225 | print('=======================================')
|
| 226 |
|
| 227 | if 'node_to_cpumask_map' in prog:
|
| 228 | __cpu_online_mask = prog['__cpu_online_mask']
|
| 229 | node_to_cpumask_map = prog['node_to_cpumask_map']
|
| 230 | nr_node_ids = prog['nr_node_ids'].value_()
|
| 231 |
|
| 232 | print(f'online_cpus={cpumask_str(__cpu_online_mask.address_of_())}')
|
| 233 | for node in for_each_node():
|
| 234 | print(f'NODE[{node:02}]={cpumask_str(node_to_cpumask_map[node])}')
|
| 235 | print('')
|
| 236 |
|
| 237 | print(f'[{"workqueue":^{WQ_NAME_LEN-1}} {"min":>4} {"max":>4}', end='')
|
| 238 | for node in for_each_node():
|
| 239 | print(f' {"NODE " + str(node):>9}', end='')
|
| 240 | print(f' {"dfl":>9} ]')
|
| 241 | print('')
|
| 242 |
|
| 243 | for wq in list_for_each_entry('struct workqueue_struct', workqueues.address_of_(), 'list'):
|
| 244 | if not (wq.flags & WQ_UNBOUND):
|
| 245 | continue
|
| 246 |
|
| 247 | print(f'{wq.name.string_().decode():{WQ_NAME_LEN}} ', end='')
|
| 248 | print(f'{wq.min_active.value_():4} {wq.max_active.value_():4}', end='')
|
| 249 | for node in for_each_node():
|
| 250 | nna = wq.node_nr_active[node]
|
| 251 | print(f' {f"{nna.nr.counter.value_()}/{nna.max.value_()}":>9}', end='')
|
| 252 | nna = wq.node_nr_active[nr_node_ids]
|
| 253 | print(f' {f"{nna.nr.counter.value_()}/{nna.max.value_()}":>9}')
|
| 254 | else:
|
| 255 | printf(f'node_to_cpumask_map not present, is NUMA enabled?')
|