Skip to content

Commit 7f7dc37

Browse files
committed
workqueue: Add tools/workqueue/wq_dump.py which prints out workqueue configuration
Lack of visibility has always been a pain point for workqueues. While the recently added wq_monitor.py improved the situation, it's still difficult to understand what worker pools are active in the system, how workqueues map to them and why. The lack of visibility into how workqueues are configured is going to become more noticeable as workqueue improves locality awareness and provides more mechanisms to customize locality related behaviors. Now that the basic framework for more flexible locality support is in place, this is a good time to improve the situation. This patch adds tools/workqueues/wq_dump.py which prints out the topology configuration, worker pools and how workqueues are mapped to pools. Read the command's help message for more details. Signed-off-by: Tejun Heo <[email protected]>
1 parent 84193c0 commit 7f7dc37

File tree

2 files changed

+225
-0
lines changed

2 files changed

+225
-0
lines changed

Documentation/core-api/workqueue.rst

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,65 @@ Guidelines
347347
level of locality in wq operations and work item execution.
348348

349349

350+
Examining Configuration
351+
=======================
352+
353+
Use tools/workqueue/wq_dump.py to examine unbound CPU affinity
354+
configuration, worker pools and how workqueues map to the pools: ::
355+
356+
$ tools/workqueue/wq_dump.py
357+
Affinity Scopes
358+
===============
359+
wq_unbound_cpumask=0000000f
360+
361+
NUMA
362+
nr_pods 2
363+
pod_cpus [0]=00000003 [1]=0000000c
364+
pod_node [0]=0 [1]=1
365+
cpu_pod [0]=0 [1]=0 [2]=1 [3]=1
366+
367+
SYSTEM
368+
nr_pods 1
369+
pod_cpus [0]=0000000f
370+
pod_node [0]=-1
371+
cpu_pod [0]=0 [1]=0 [2]=0 [3]=0
372+
373+
Worker Pools
374+
============
375+
pool[00] ref= 1 nice= 0 idle/workers= 4/ 4 cpu= 0
376+
pool[01] ref= 1 nice=-20 idle/workers= 2/ 2 cpu= 0
377+
pool[02] ref= 1 nice= 0 idle/workers= 4/ 4 cpu= 1
378+
pool[03] ref= 1 nice=-20 idle/workers= 2/ 2 cpu= 1
379+
pool[04] ref= 1 nice= 0 idle/workers= 4/ 4 cpu= 2
380+
pool[05] ref= 1 nice=-20 idle/workers= 2/ 2 cpu= 2
381+
pool[06] ref= 1 nice= 0 idle/workers= 3/ 3 cpu= 3
382+
pool[07] ref= 1 nice=-20 idle/workers= 2/ 2 cpu= 3
383+
pool[08] ref=42 nice= 0 idle/workers= 6/ 6 cpus=0000000f
384+
pool[09] ref=28 nice= 0 idle/workers= 3/ 3 cpus=00000003
385+
pool[10] ref=28 nice= 0 idle/workers= 17/ 17 cpus=0000000c
386+
pool[11] ref= 1 nice=-20 idle/workers= 1/ 1 cpus=0000000f
387+
pool[12] ref= 2 nice=-20 idle/workers= 1/ 1 cpus=00000003
388+
pool[13] ref= 2 nice=-20 idle/workers= 1/ 1 cpus=0000000c
389+
390+
Workqueue CPU -> pool
391+
=====================
392+
[ workqueue \ CPU 0 1 2 3 dfl]
393+
events percpu 0 2 4 6
394+
events_highpri percpu 1 3 5 7
395+
events_long percpu 0 2 4 6
396+
events_unbound unbound 9 9 10 10 8
397+
events_freezable percpu 0 2 4 6
398+
events_power_efficient percpu 0 2 4 6
399+
events_freezable_power_ percpu 0 2 4 6
400+
rcu_gp percpu 0 2 4 6
401+
rcu_par_gp percpu 0 2 4 6
402+
slub_flushwq percpu 0 2 4 6
403+
netns ordered 8 8 8 8 8
404+
...
405+
406+
See the command's help message for more info.
407+
408+
350409
Monitoring
351410
==========
352411

tools/workqueue/wq_dump.py

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#!/usr/bin/env drgn
2+
#
3+
# Copyright (C) 2023 Tejun Heo <[email protected]>
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+
39+
NAME TYPE POOL_ID...
40+
41+
NAME name of the workqueue
42+
TYPE percpu, unbound or ordered
43+
POOL_ID worker pool ID associated with each possible CPU
44+
"""
45+
46+
import sys
47+
48+
import drgn
49+
from drgn.helpers.linux.list import list_for_each_entry,list_empty
50+
from drgn.helpers.linux.percpu import per_cpu_ptr
51+
from drgn.helpers.linux.cpumask import for_each_cpu,for_each_possible_cpu
52+
from drgn.helpers.linux.idr import idr_for_each
53+
54+
import argparse
55+
parser = argparse.ArgumentParser(description=desc,
56+
formatter_class=argparse.RawTextHelpFormatter)
57+
args = parser.parse_args()
58+
59+
def err(s):
60+
print(s, file=sys.stderr, flush=True)
61+
sys.exit(1)
62+
63+
def cpumask_str(cpumask):
64+
output = ""
65+
base = 0
66+
v = 0
67+
for cpu in for_each_cpu(cpumask[0]):
68+
while cpu - base >= 32:
69+
output += f'{hex(v)} '
70+
base += 32
71+
v = 0
72+
v |= 1 << (cpu - base)
73+
if v > 0:
74+
output += f'{v:08x}'
75+
return output.strip()
76+
77+
worker_pool_idr = prog['worker_pool_idr']
78+
workqueues = prog['workqueues']
79+
wq_unbound_cpumask = prog['wq_unbound_cpumask']
80+
wq_pod_types = prog['wq_pod_types']
81+
82+
WQ_UNBOUND = prog['WQ_UNBOUND']
83+
WQ_ORDERED = prog['__WQ_ORDERED']
84+
WQ_MEM_RECLAIM = prog['WQ_MEM_RECLAIM']
85+
86+
WQ_AFFN_NUMA = prog['WQ_AFFN_NUMA']
87+
WQ_AFFN_SYSTEM = prog['WQ_AFFN_SYSTEM']
88+
89+
print('Affinity Scopes')
90+
print('===============')
91+
92+
print(f'wq_unbound_cpumask={cpumask_str(wq_unbound_cpumask)}')
93+
94+
def print_pod_type(pt):
95+
print(f' nr_pods {pt.nr_pods.value_()}')
96+
97+
print(' pod_cpus', end='')
98+
for pod in range(pt.nr_pods):
99+
print(f' [{pod}]={cpumask_str(pt.pod_cpus[pod])}', end='')
100+
print('')
101+
102+
print(' pod_node', end='')
103+
for pod in range(pt.nr_pods):
104+
print(f' [{pod}]={pt.pod_node[pod].value_()}', end='')
105+
print('')
106+
107+
print(f' cpu_pod ', end='')
108+
for cpu in for_each_possible_cpu(prog):
109+
print(f' [{cpu}]={pt.cpu_pod[cpu].value_()}', end='')
110+
print('')
111+
112+
print('')
113+
print('NUMA')
114+
print_pod_type(wq_pod_types[WQ_AFFN_NUMA])
115+
print('')
116+
print('SYSTEM')
117+
print_pod_type(wq_pod_types[WQ_AFFN_SYSTEM])
118+
119+
print('')
120+
print('Worker Pools')
121+
print('============')
122+
123+
max_pool_id_len = 0
124+
max_ref_len = 0
125+
for pi, pool in idr_for_each(worker_pool_idr):
126+
pool = drgn.Object(prog, 'struct worker_pool', address=pool)
127+
max_pool_id_len = max(max_pool_id_len, len(f'{pi}'))
128+
max_ref_len = max(max_ref_len, len(f'{pool.refcnt.value_()}'))
129+
130+
for pi, pool in idr_for_each(worker_pool_idr):
131+
pool = drgn.Object(prog, 'struct worker_pool', address=pool)
132+
print(f'pool[{pi:0{max_pool_id_len}}] ref={pool.refcnt.value_():{max_ref_len}} nice={pool.attrs.nice.value_():3} ', end='')
133+
print(f'idle/workers={pool.nr_idle.value_():3}/{pool.nr_workers.value_():3} ', end='')
134+
if pool.cpu >= 0:
135+
print(f'cpu={pool.cpu.value_():3}', end='')
136+
else:
137+
print(f'cpus={cpumask_str(pool.attrs.cpumask)}', end='')
138+
print('')
139+
140+
print('')
141+
print('Workqueue CPU -> pool')
142+
print('=====================')
143+
144+
print('[ workqueue \ CPU ', end='')
145+
for cpu in for_each_possible_cpu(prog):
146+
print(f' {cpu:{max_pool_id_len}}', end='')
147+
print(' dfl]')
148+
149+
for wq in list_for_each_entry('struct workqueue_struct', workqueues.address_of_(), 'list'):
150+
print(f'{wq.name.string_().decode()[-24:]:24}', end='')
151+
if wq.flags & WQ_UNBOUND:
152+
if wq.flags & WQ_ORDERED:
153+
print(' ordered', end='')
154+
else:
155+
print(' unbound', end='')
156+
else:
157+
print(' percpu ', end='')
158+
159+
for cpu in for_each_possible_cpu(prog):
160+
pool_id = per_cpu_ptr(wq.cpu_pwq, cpu)[0].pool.id.value_()
161+
field_len = max(len(str(cpu)), max_pool_id_len)
162+
print(f' {pool_id:{field_len}}', end='')
163+
164+
if wq.flags & WQ_UNBOUND:
165+
print(f' {wq.dfl_pwq.pool.id.value_():{max_pool_id_len}}', end='')
166+
print('')

0 commit comments

Comments
 (0)