Commit | Line | Data |
---|---|---|
6954ff18 TH |
1 | #!/usr/bin/env drgn |
2 | # | |
3 | # Copyright (C) 2019 Tejun Heo <tj@kernel.org> | |
4 | # Copyright (C) 2019 Facebook | |
5 | ||
6 | desc = """ | |
7 | This is a drgn script to monitor the blk-iocost cgroup controller. | |
8 | See the comment at the top of block/blk-iocost.c for more details. | |
9 | For drgn, visit https://github.com/osandov/drgn. | |
10 | """ | |
11 | ||
12 | import sys | |
13 | import re | |
14 | import time | |
15 | import json | |
b06f2d35 | 16 | import math |
6954ff18 TH |
17 | |
18 | import drgn | |
19 | from drgn import container_of | |
20 | from drgn.helpers.linux.list import list_for_each_entry,list_empty | |
21 | from drgn.helpers.linux.radixtree import radix_tree_for_each,radix_tree_lookup | |
22 | ||
23 | import argparse | |
24 | parser = argparse.ArgumentParser(description=desc, | |
25 | formatter_class=argparse.RawTextHelpFormatter) | |
26 | parser.add_argument('devname', metavar='DEV', | |
27 | help='Target block device name (e.g. sda)') | |
28 | parser.add_argument('--cgroup', action='append', metavar='REGEX', | |
29 | help='Regex for target cgroups, ') | |
30 | parser.add_argument('--interval', '-i', metavar='SECONDS', type=float, default=1, | |
f4fe3ea6 TH |
31 | help='Monitoring interval in seconds (0 exits immediately ' |
32 | 'after checking requirements)') | |
6954ff18 TH |
33 | parser.add_argument('--json', action='store_true', |
34 | help='Output in json') | |
35 | args = parser.parse_args() | |
36 | ||
37 | def err(s): | |
38 | print(s, file=sys.stderr, flush=True) | |
39 | sys.exit(1) | |
40 | ||
41 | try: | |
42 | blkcg_root = prog['blkcg_root'] | |
43 | plid = prog['blkcg_policy_iocost'].plid.value_() | |
44 | except: | |
45 | err('The kernel does not have iocost enabled') | |
46 | ||
47 | IOC_RUNNING = prog['IOC_RUNNING'].value_() | |
a7863b34 | 48 | WEIGHT_ONE = prog['WEIGHT_ONE'].value_() |
6954ff18 TH |
49 | VTIME_PER_SEC = prog['VTIME_PER_SEC'].value_() |
50 | VTIME_PER_USEC = prog['VTIME_PER_USEC'].value_() | |
51 | AUTOP_SSD_FAST = prog['AUTOP_SSD_FAST'].value_() | |
52 | AUTOP_SSD_DFL = prog['AUTOP_SSD_DFL'].value_() | |
53 | AUTOP_SSD_QD1 = prog['AUTOP_SSD_QD1'].value_() | |
54 | AUTOP_HDD = prog['AUTOP_HDD'].value_() | |
55 | ||
56 | autop_names = { | |
57 | AUTOP_SSD_FAST: 'ssd_fast', | |
58 | AUTOP_SSD_DFL: 'ssd_dfl', | |
59 | AUTOP_SSD_QD1: 'ssd_qd1', | |
60 | AUTOP_HDD: 'hdd', | |
61 | } | |
62 | ||
63 | class BlkgIterator: | |
64 | def blkcg_name(blkcg): | |
65 | return blkcg.css.cgroup.kn.name.string_().decode('utf-8') | |
66 | ||
67 | def walk(self, blkcg, q_id, parent_path): | |
68 | if not self.include_dying and \ | |
69 | not (blkcg.css.flags.value_() & prog['CSS_ONLINE'].value_()): | |
70 | return | |
71 | ||
72 | name = BlkgIterator.blkcg_name(blkcg) | |
73 | path = parent_path + '/' + name if parent_path else name | |
74 | blkg = drgn.Object(prog, 'struct blkcg_gq', | |
9ea37e24 | 75 | address=radix_tree_lookup(blkcg.blkg_tree.address_of_(), q_id)) |
6954ff18 TH |
76 | if not blkg.address_: |
77 | return | |
78 | ||
79 | self.blkgs.append((path if path else '/', blkg)) | |
80 | ||
81 | for c in list_for_each_entry('struct blkcg', | |
82 | blkcg.css.children.address_of_(), 'css.sibling'): | |
83 | self.walk(c, q_id, path) | |
84 | ||
85 | def __init__(self, root_blkcg, q_id, include_dying=False): | |
86 | self.include_dying = include_dying | |
87 | self.blkgs = [] | |
88 | self.walk(root_blkcg, q_id, '') | |
89 | ||
90 | def __iter__(self): | |
91 | return iter(self.blkgs) | |
92 | ||
93 | class IocStat: | |
94 | def __init__(self, ioc): | |
95 | global autop_names | |
96 | ||
97 | self.enabled = ioc.enabled.value_() | |
98 | self.running = ioc.running.value_() == IOC_RUNNING | |
b06f2d35 | 99 | self.period_ms = ioc.period_us.value_() / 1_000 |
6954ff18 TH |
100 | self.period_at = ioc.period_at.value_() / 1_000_000 |
101 | self.vperiod_at = ioc.period_at_vtime.value_() / VTIME_PER_SEC | |
a7863b34 | 102 | self.vrate_pct = ioc.vtime_base_rate.value_() * 100 / VTIME_PER_USEC |
6954ff18 TH |
103 | self.busy_level = ioc.busy_level.value_() |
104 | self.autop_idx = ioc.autop_idx.value_() | |
105 | self.user_cost_model = ioc.user_cost_model.value_() | |
106 | self.user_qos_params = ioc.user_qos_params.value_() | |
107 | ||
108 | if self.autop_idx in autop_names: | |
109 | self.autop_name = autop_names[self.autop_idx] | |
110 | else: | |
111 | self.autop_name = '?' | |
112 | ||
113 | def dict(self, now): | |
114 | return { 'device' : devname, | |
21f3cfea TH |
115 | 'timestamp' : now, |
116 | 'enabled' : self.enabled, | |
117 | 'running' : self.running, | |
118 | 'period_ms' : self.period_ms, | |
119 | 'period_at' : self.period_at, | |
120 | 'period_vtime_at' : self.vperiod_at, | |
121 | 'busy_level' : self.busy_level, | |
122 | 'vrate_pct' : self.vrate_pct, } | |
6954ff18 TH |
123 | |
124 | def table_preamble_str(self): | |
125 | state = ('RUN' if self.running else 'IDLE') if self.enabled else 'OFF' | |
126 | output = f'{devname} {state:4} ' \ | |
127 | f'per={self.period_ms}ms ' \ | |
128 | f'cur_per={self.period_at:.3f}:v{self.vperiod_at:.3f} ' \ | |
129 | f'busy={self.busy_level:+3} ' \ | |
130 | f'vrate={self.vrate_pct:6.2f}% ' \ | |
131 | f'params={self.autop_name}' | |
132 | if self.user_cost_model or self.user_qos_params: | |
133 | output += f'({"C" if self.user_cost_model else ""}{"Q" if self.user_qos_params else ""})' | |
134 | return output | |
135 | ||
136 | def table_header_str(self): | |
137 | return f'{"":25} active {"weight":>9} {"hweight%":>13} {"inflt%":>6} ' \ | |
a7863b34 | 138 | f'{"debt":>7} {"delay":>7} {"usage%"}' |
6954ff18 TH |
139 | |
140 | class IocgStat: | |
141 | def __init__(self, iocg): | |
142 | ioc = iocg.ioc | |
143 | blkg = iocg.pd.blkg | |
144 | ||
145 | self.is_active = not list_empty(iocg.active_list.address_of_()) | |
a7863b34 TH |
146 | self.weight = iocg.weight.value_() / WEIGHT_ONE |
147 | self.active = iocg.active.value_() / WEIGHT_ONE | |
148 | self.inuse = iocg.inuse.value_() / WEIGHT_ONE | |
149 | self.hwa_pct = iocg.hweight_active.value_() * 100 / WEIGHT_ONE | |
150 | self.hwi_pct = iocg.hweight_inuse.value_() * 100 / WEIGHT_ONE | |
b06f2d35 | 151 | self.address = iocg.value_() |
6954ff18 TH |
152 | |
153 | vdone = iocg.done_vtime.counter.value_() | |
154 | vtime = iocg.vtime.counter.value_() | |
155 | vrate = ioc.vtime_rate.counter.value_() | |
156 | period_vtime = ioc.period_us.value_() * vrate | |
157 | if period_vtime: | |
158 | self.inflight_pct = (vtime - vdone) * 100 / period_vtime | |
159 | else: | |
160 | self.inflight_pct = 0 | |
161 | ||
a7863b34 TH |
162 | self.usage = (100 * iocg.usage_delta_us.value_() / |
163 | ioc.period_us.value_()) if self.active else 0 | |
164 | self.debt_ms = iocg.abs_vdebt.value_() / VTIME_PER_USEC / 1000 | |
165 | if blkg.use_delay.counter.value_() != 0: | |
166 | self.delay_ms = blkg.delay_nsec.counter.value_() / 1_000_000 | |
167 | else: | |
168 | self.delay_ms = 0 | |
6954ff18 TH |
169 | |
170 | def dict(self, now, path): | |
171 | out = { 'cgroup' : path, | |
21f3cfea TH |
172 | 'timestamp' : now, |
173 | 'is_active' : self.is_active, | |
174 | 'weight' : self.weight, | |
175 | 'weight_active' : self.active, | |
176 | 'weight_inuse' : self.inuse, | |
177 | 'hweight_active_pct' : self.hwa_pct, | |
178 | 'hweight_inuse_pct' : self.hwi_pct, | |
179 | 'inflight_pct' : self.inflight_pct, | |
180 | 'debt_ms' : self.debt_ms, | |
21f3cfea TH |
181 | 'delay_ms' : self.delay_ms, |
182 | 'usage_pct' : self.usage, | |
183 | 'address' : self.address } | |
6954ff18 TH |
184 | return out |
185 | ||
186 | def table_row_str(self, path): | |
187 | out = f'{path[-28:]:28} ' \ | |
188 | f'{"*" if self.is_active else " "} ' \ | |
a7863b34 | 189 | f'{round(self.inuse):5}/{round(self.active):5} ' \ |
6954ff18 TH |
190 | f'{self.hwi_pct:6.2f}/{self.hwa_pct:6.2f} ' \ |
191 | f'{self.inflight_pct:6.2f} ' \ | |
a7863b34 TH |
192 | f'{self.debt_ms:7.2f} ' \ |
193 | f'{self.delay_ms:7.2f} '\ | |
194 | f'{min(self.usage, 999):6.2f}' | |
6954ff18 TH |
195 | out = out.rstrip(':') |
196 | return out | |
197 | ||
198 | # handle args | |
199 | table_fmt = not args.json | |
200 | interval = args.interval | |
201 | devname = args.devname | |
202 | ||
203 | if args.json: | |
204 | table_fmt = False | |
205 | ||
206 | re_str = None | |
207 | if args.cgroup: | |
208 | for r in args.cgroup: | |
209 | if re_str is None: | |
210 | re_str = r | |
211 | else: | |
212 | re_str += '|' + r | |
213 | ||
214 | filter_re = re.compile(re_str) if re_str else None | |
215 | ||
216 | # Locate the roots | |
217 | q_id = None | |
218 | root_iocg = None | |
219 | ioc = None | |
220 | ||
9ea37e24 | 221 | for i, ptr in radix_tree_for_each(blkcg_root.blkg_tree.address_of_()): |
6954ff18 TH |
222 | blkg = drgn.Object(prog, 'struct blkcg_gq', address=ptr) |
223 | try: | |
224 | if devname == blkg.q.kobj.parent.name.string_().decode('utf-8'): | |
225 | q_id = blkg.q.id.value_() | |
226 | if blkg.pd[plid]: | |
227 | root_iocg = container_of(blkg.pd[plid], 'struct ioc_gq', 'pd') | |
228 | ioc = root_iocg.ioc | |
229 | break | |
230 | except: | |
231 | pass | |
232 | ||
233 | if ioc is None: | |
234 | err(f'Could not find ioc for {devname}'); | |
235 | ||
f4fe3ea6 TH |
236 | if interval == 0: |
237 | sys.exit(0) | |
238 | ||
6954ff18 TH |
239 | # Keep printing |
240 | while True: | |
241 | now = time.time() | |
242 | iocstat = IocStat(ioc) | |
243 | output = '' | |
244 | ||
245 | if table_fmt: | |
246 | output += '\n' + iocstat.table_preamble_str() | |
247 | output += '\n' + iocstat.table_header_str() | |
248 | else: | |
249 | output += json.dumps(iocstat.dict(now)) | |
250 | ||
251 | for path, blkg in BlkgIterator(blkcg_root, q_id): | |
252 | if filter_re and not filter_re.match(path): | |
253 | continue | |
254 | if not blkg.pd[plid]: | |
255 | continue | |
256 | ||
257 | iocg = container_of(blkg.pd[plid], 'struct ioc_gq', 'pd') | |
258 | iocg_stat = IocgStat(iocg) | |
259 | ||
260 | if not filter_re and not iocg_stat.is_active: | |
261 | continue | |
262 | ||
263 | if table_fmt: | |
264 | output += '\n' + iocg_stat.table_row_str(path) | |
265 | else: | |
266 | output += '\n' + json.dumps(iocg_stat.dict(now, path)) | |
267 | ||
268 | print(output) | |
269 | sys.stdout.flush() | |
270 | time.sleep(interval) |