Commit | Line | Data |
---|---|---|
7f7dc377 TH |
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 | ||
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 | ||
8639eceb | 39 | NAME TYPE[,FLAGS] POOL_ID... |
7f7dc377 TH |
40 | |
41 | NAME name of the workqueue | |
42 | TYPE percpu, unbound or ordered | |
8639eceb | 43 | FLAGS S: strict affinity scope |
7f7dc377 TH |
44 | POOL_ID worker pool ID associated with each possible CPU |
45 | """ | |
46 | ||
47 | import sys | |
48 | ||
49 | import drgn | |
50 | from drgn.helpers.linux.list import list_for_each_entry,list_empty | |
51 | from drgn.helpers.linux.percpu import per_cpu_ptr | |
52 | from drgn.helpers.linux.cpumask import for_each_cpu,for_each_possible_cpu | |
07daa99b | 53 | from drgn.helpers.linux.nodemask import for_each_node |
7f7dc377 TH |
54 | from drgn.helpers.linux.idr import idr_for_each |
55 | ||
56 | import argparse | |
57 | parser = argparse.ArgumentParser(description=desc, | |
58 | formatter_class=argparse.RawTextHelpFormatter) | |
59 | args = parser.parse_args() | |
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 | ||
a6b48c83 TH |
79 | wq_type_len = 9 |
80 | ||
81 | def wq_type_str(wq): | |
4cb1ef64 TH |
82 | if wq.flags & WQ_BH: |
83 | return f'{"bh":{wq_type_len}}' | |
84 | elif wq.flags & WQ_UNBOUND: | |
a6b48c83 TH |
85 | if wq.flags & WQ_ORDERED: |
86 | return f'{"ordered":{wq_type_len}}' | |
87 | else: | |
88 | if wq.unbound_attrs.affn_strict: | |
89 | return f'{"unbound,S":{wq_type_len}}' | |
90 | else: | |
91 | return f'{"unbound":{wq_type_len}}' | |
92 | else: | |
93 | return f'{"percpu":{wq_type_len}}' | |
94 | ||
7f7dc377 TH |
95 | worker_pool_idr = prog['worker_pool_idr'] |
96 | workqueues = prog['workqueues'] | |
97 | wq_unbound_cpumask = prog['wq_unbound_cpumask'] | |
98 | wq_pod_types = prog['wq_pod_types'] | |
63c5484e TH |
99 | wq_affn_dfl = prog['wq_affn_dfl'] |
100 | wq_affn_names = prog['wq_affn_names'] | |
7f7dc377 | 101 | |
4cb1ef64 | 102 | WQ_BH = prog['WQ_BH'] |
7f7dc377 TH |
103 | WQ_UNBOUND = prog['WQ_UNBOUND'] |
104 | WQ_ORDERED = prog['__WQ_ORDERED'] | |
105 | WQ_MEM_RECLAIM = prog['WQ_MEM_RECLAIM'] | |
106 | ||
63c5484e TH |
107 | WQ_AFFN_CPU = prog['WQ_AFFN_CPU'] |
108 | WQ_AFFN_SMT = prog['WQ_AFFN_SMT'] | |
109 | WQ_AFFN_CACHE = prog['WQ_AFFN_CACHE'] | |
7f7dc377 TH |
110 | WQ_AFFN_NUMA = prog['WQ_AFFN_NUMA'] |
111 | WQ_AFFN_SYSTEM = prog['WQ_AFFN_SYSTEM'] | |
112 | ||
4cb1ef64 TH |
113 | POOL_BH = prog['POOL_BH'] |
114 | ||
a6b48c83 | 115 | WQ_NAME_LEN = prog['WQ_NAME_LEN'].value_() |
a6b48c83 TH |
116 | cpumask_str_len = len(cpumask_str(wq_unbound_cpumask)) |
117 | ||
7f7dc377 TH |
118 | print('Affinity Scopes') |
119 | print('===============') | |
120 | ||
121 | print(f'wq_unbound_cpumask={cpumask_str(wq_unbound_cpumask)}') | |
122 | ||
123 | def print_pod_type(pt): | |
124 | print(f' nr_pods {pt.nr_pods.value_()}') | |
125 | ||
126 | print(' pod_cpus', end='') | |
127 | for pod in range(pt.nr_pods): | |
128 | print(f' [{pod}]={cpumask_str(pt.pod_cpus[pod])}', end='') | |
129 | print('') | |
130 | ||
131 | print(' pod_node', end='') | |
132 | for pod in range(pt.nr_pods): | |
133 | print(f' [{pod}]={pt.pod_node[pod].value_()}', end='') | |
134 | print('') | |
135 | ||
136 | print(f' cpu_pod ', end='') | |
137 | for cpu in for_each_possible_cpu(prog): | |
138 | print(f' [{cpu}]={pt.cpu_pod[cpu].value_()}', end='') | |
139 | print('') | |
140 | ||
63c5484e TH |
141 | for affn in [WQ_AFFN_CPU, WQ_AFFN_SMT, WQ_AFFN_CACHE, WQ_AFFN_NUMA, WQ_AFFN_SYSTEM]: |
142 | print('') | |
143 | print(f'{wq_affn_names[affn].string_().decode().upper()}{" (default)" if affn == wq_affn_dfl else ""}') | |
144 | print_pod_type(wq_pod_types[affn]) | |
7f7dc377 TH |
145 | |
146 | print('') | |
147 | print('Worker Pools') | |
148 | print('============') | |
149 | ||
150 | max_pool_id_len = 0 | |
151 | max_ref_len = 0 | |
152 | for pi, pool in idr_for_each(worker_pool_idr): | |
153 | pool = drgn.Object(prog, 'struct worker_pool', address=pool) | |
154 | max_pool_id_len = max(max_pool_id_len, len(f'{pi}')) | |
155 | max_ref_len = max(max_ref_len, len(f'{pool.refcnt.value_()}')) | |
156 | ||
157 | for pi, pool in idr_for_each(worker_pool_idr): | |
158 | pool = drgn.Object(prog, 'struct worker_pool', address=pool) | |
4cb1ef64 | 159 | 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='') |
7f7dc377 TH |
160 | print(f'idle/workers={pool.nr_idle.value_():3}/{pool.nr_workers.value_():3} ', end='') |
161 | if pool.cpu >= 0: | |
162 | print(f'cpu={pool.cpu.value_():3}', end='') | |
4cb1ef64 TH |
163 | if pool.flags & POOL_BH: |
164 | print(' bh', end='') | |
7f7dc377 TH |
165 | else: |
166 | print(f'cpus={cpumask_str(pool.attrs.cpumask)}', end='') | |
8639eceb TH |
167 | print(f' pod_cpus={cpumask_str(pool.attrs.__pod_cpumask)}', end='') |
168 | if pool.attrs.affn_strict: | |
169 | print(' strict', end='') | |
7f7dc377 TH |
170 | print('') |
171 | ||
172 | print('') | |
173 | print('Workqueue CPU -> pool') | |
174 | print('=====================') | |
175 | ||
a6b48c83 | 176 | print(f'[{"workqueue":^{WQ_NAME_LEN-2}}\\ {"type CPU":{wq_type_len}}', end='') |
7f7dc377 TH |
177 | for cpu in for_each_possible_cpu(prog): |
178 | print(f' {cpu:{max_pool_id_len}}', end='') | |
179 | print(' dfl]') | |
180 | ||
181 | for wq in list_for_each_entry('struct workqueue_struct', workqueues.address_of_(), 'list'): | |
a6b48c83 | 182 | print(f'{wq.name.string_().decode():{WQ_NAME_LEN}} {wq_type_str(wq):10}', end='') |
7f7dc377 TH |
183 | |
184 | for cpu in for_each_possible_cpu(prog): | |
185 | pool_id = per_cpu_ptr(wq.cpu_pwq, cpu)[0].pool.id.value_() | |
186 | field_len = max(len(str(cpu)), max_pool_id_len) | |
187 | print(f' {pool_id:{field_len}}', end='') | |
188 | ||
189 | if wq.flags & WQ_UNBOUND: | |
190 | print(f' {wq.dfl_pwq.pool.id.value_():{max_pool_id_len}}', end='') | |
191 | print('') | |
ab5e5b99 JL |
192 | |
193 | print('') | |
194 | print('Workqueue -> rescuer') | |
a6b48c83 TH |
195 | print('====================') |
196 | ||
197 | ucpus_len = max(cpumask_str_len, len("unbound_cpus")) | |
198 | rcpus_len = max(cpumask_str_len, len("rescuer_cpus")) | |
199 | ||
200 | print(f'[{"workqueue":^{WQ_NAME_LEN-2}}\\ {"unbound_cpus":{ucpus_len}} pid {"rescuer_cpus":{rcpus_len}} ]') | |
ab5e5b99 JL |
201 | |
202 | for wq in list_for_each_entry('struct workqueue_struct', workqueues.address_of_(), 'list'): | |
a6b48c83 TH |
203 | if not (wq.flags & WQ_MEM_RECLAIM): |
204 | continue | |
205 | ||
206 | print(f'{wq.name.string_().decode():{WQ_NAME_LEN}}', end='') | |
207 | if wq.unbound_attrs.value_() != 0: | |
208 | print(f' {cpumask_str(wq.unbound_attrs.cpumask):{ucpus_len}}', end='') | |
ab5e5b99 | 209 | else: |
a6b48c83 | 210 | print(f' {"":{ucpus_len}}', end='') |
ab5e5b99 | 211 | |
a6b48c83 TH |
212 | print(f' {wq.rescuer.task.pid.value_():6}', end='') |
213 | print(f' {cpumask_str(wq.rescuer.task.cpus_ptr):{rcpus_len}}', end='') | |
ab5e5b99 | 214 | print('') |
07daa99b TH |
215 | |
216 | print('') | |
217 | print('Unbound workqueue -> node_nr/max_active') | |
218 | print('=======================================') | |
219 | ||
220 | if 'node_to_cpumask_map' in prog: | |
221 | __cpu_online_mask = prog['__cpu_online_mask'] | |
222 | node_to_cpumask_map = prog['node_to_cpumask_map'] | |
223 | nr_node_ids = prog['nr_node_ids'].value_() | |
224 | ||
225 | print(f'online_cpus={cpumask_str(__cpu_online_mask.address_of_())}') | |
226 | for node in for_each_node(): | |
227 | print(f'NODE[{node:02}]={cpumask_str(node_to_cpumask_map[node])}') | |
228 | print('') | |
229 | ||
230 | print(f'[{"workqueue":^{WQ_NAME_LEN-2}}\\ min max', end='') | |
231 | first = True | |
232 | for node in for_each_node(): | |
233 | if first: | |
234 | print(f' NODE {node}', end='') | |
235 | first = False | |
236 | else: | |
237 | print(f' {node:7}', end='') | |
238 | print(f' {"dfl":>7} ]') | |
239 | print('') | |
240 | ||
241 | for wq in list_for_each_entry('struct workqueue_struct', workqueues.address_of_(), 'list'): | |
242 | if not (wq.flags & WQ_UNBOUND): | |
243 | continue | |
244 | ||
245 | print(f'{wq.name.string_().decode():{WQ_NAME_LEN}} ', end='') | |
246 | print(f'{wq.min_active.value_():3} {wq.max_active.value_():3}', end='') | |
247 | for node in for_each_node(): | |
248 | nna = wq.node_nr_active[node] | |
249 | print(f' {nna.nr.counter.value_():3}/{nna.max.value_():3}', end='') | |
250 | nna = wq.node_nr_active[nr_node_ids] | |
251 | print(f' {nna.nr.counter.value_():3}/{nna.max.value_():3}') | |
252 | else: | |
253 | printf(f'node_to_cpumask_map not present, is NUMA enabled?') |