xref: /linux/tools/workqueue/wq_dump.py (revision 3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d)
1#!/usr/bin/env drgn
2#
3# Copyright (C) 2023 Tejun Heo <tj@kernel.org>
4# Copyright (C) 2023 Meta Platforms, Inc. and affiliates.
5
6desc = """
7This is a drgn script to show the current workqueue configuration. For more
8info on drgn, visit https://github.com/osandov/drgn.
9
10Affinity Scopes
11===============
12
13Shows the CPUs that can be used for unbound workqueues and how they will be
14grouped 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
21Worker Pools
22============
23
24Lists 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
33Workqueue CPU -> pool
34=====================
35
36Lists all workqueues along with their type and worker pool association. For
37each 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
47import sys
48
49import argparse
50parser = argparse.ArgumentParser(description=desc,
51                                 formatter_class=argparse.RawTextHelpFormatter)
52args = parser.parse_args()
53
54import drgn
55from drgn.helpers.linux.list import list_for_each_entry,list_empty
56from drgn.helpers.linux.percpu import per_cpu_ptr
57from drgn.helpers.linux.cpumask import for_each_cpu,for_each_possible_cpu
58from drgn.helpers.linux.nodemask import for_each_node
59from drgn.helpers.linux.idr import idr_for_each
60
61def err(s):
62    print(s, file=sys.stderr, flush=True)
63    sys.exit(1)
64
65def 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
79wq_type_len = 9
80
81def wq_attrs(wq):
82    try:
83        return wq.attrs
84    except AttributeError:
85        return wq.unbound_attrs
86
87def 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
101worker_pool_idr         = prog['worker_pool_idr']
102workqueues              = prog['workqueues']
103wq_unbound_cpumask      = prog['wq_unbound_cpumask']
104wq_pod_types            = prog['wq_pod_types']
105wq_affn_dfl             = prog['wq_affn_dfl']
106wq_affn_names           = prog['wq_affn_names']
107
108WQ_BH                   = prog['WQ_BH']
109WQ_UNBOUND              = prog['WQ_UNBOUND']
110WQ_ORDERED              = prog['__WQ_ORDERED']
111WQ_MEM_RECLAIM          = prog['WQ_MEM_RECLAIM']
112
113WQ_AFFN_CPU             = prog['WQ_AFFN_CPU']
114WQ_AFFN_SMT             = prog['WQ_AFFN_SMT']
115WQ_AFFN_CACHE           = prog['WQ_AFFN_CACHE']
116WQ_AFFN_CACHE_SHARD     = prog['WQ_AFFN_CACHE_SHARD']
117WQ_AFFN_NUMA            = prog['WQ_AFFN_NUMA']
118WQ_AFFN_SYSTEM          = prog['WQ_AFFN_SYSTEM']
119
120POOL_BH                 = prog['POOL_BH']
121
122WQ_NAME_LEN             = prog['WQ_NAME_LEN'].value_()
123cpumask_str_len         = len(cpumask_str(wq_unbound_cpumask))
124
125print('Affinity Scopes')
126print('===============')
127
128print(f'wq_unbound_cpumask={cpumask_str(wq_unbound_cpumask)}')
129
130def 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
148for 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
153print('')
154print('Worker Pools')
155print('============')
156
157max_pool_id_len = 0
158max_ref_len = 0
159for 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
164for 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
179print('')
180print('Workqueue CPU -> pool')
181print('=====================')
182
183print(f'[{"workqueue":^{WQ_NAME_LEN-2}}\\ {"type   CPU":{wq_type_len}}', end='')
184for cpu in for_each_possible_cpu(prog):
185    print(f' {cpu:{max_pool_id_len}}', end='')
186print(' dfl]')
187
188for 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
200print('')
201print('Workqueue -> rescuer')
202print('====================')
203
204ucpus_len = max(cpumask_str_len, len("unbound_cpus"))
205rcpus_len = max(cpumask_str_len, len("rescuer_cpus"))
206
207print(f'[{"workqueue":^{WQ_NAME_LEN-2}}\\ {"unbound_cpus":{ucpus_len}}    pid {"rescuer_cpus":{rcpus_len}} ]')
208
209for 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
223print('')
224print('Unbound workqueue -> node_nr/max_active')
225print('=======================================')
226
227if '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}')
254else:
255    printf(f'node_to_cpumask_map not present, is NUMA enabled?')
256