Tejun Heo | 6954ff1 | 2019-08-28 15:05:59 -0700 | [diff] [blame] | 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 |
Tejun Heo | b06f2d3 | 2019-09-04 12:45:55 -0700 | [diff] [blame] | 16 | import math |
Tejun Heo | 6954ff1 | 2019-08-28 15:05:59 -0700 | [diff] [blame] | 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, |
Tejun Heo | f4fe3ea | 2020-04-13 12:27:57 -0400 | [diff] [blame] | 31 | help='Monitoring interval in seconds (0 exits immediately ' |
| 32 | 'after checking requirements)') |
Tejun Heo | 6954ff1 | 2019-08-28 15:05:59 -0700 | [diff] [blame] | 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_() |
Tejun Heo | a7863b3 | 2020-09-01 14:52:57 -0400 | [diff] [blame] | 48 | WEIGHT_ONE = prog['WEIGHT_ONE'].value_() |
Tejun Heo | 6954ff1 | 2019-08-28 15:05:59 -0700 | [diff] [blame] | 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', |
Tejun Heo | 9ea37e2 | 2020-01-17 11:54:35 -0800 | [diff] [blame] | 75 | address=radix_tree_lookup(blkcg.blkg_tree.address_of_(), q_id)) |
Tejun Heo | 6954ff1 | 2019-08-28 15:05:59 -0700 | [diff] [blame] | 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 |
Tejun Heo | b06f2d3 | 2019-09-04 12:45:55 -0700 | [diff] [blame] | 99 | self.period_ms = ioc.period_us.value_() / 1_000 |
Tejun Heo | 6954ff1 | 2019-08-28 15:05:59 -0700 | [diff] [blame] | 100 | self.period_at = ioc.period_at.value_() / 1_000_000 |
| 101 | self.vperiod_at = ioc.period_at_vtime.value_() / VTIME_PER_SEC |
Tejun Heo | a7863b3 | 2020-09-01 14:52:57 -0400 | [diff] [blame] | 102 | self.vrate_pct = ioc.vtime_base_rate.value_() * 100 / VTIME_PER_USEC |
Tejun Heo | 6954ff1 | 2019-08-28 15:05:59 -0700 | [diff] [blame] | 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, |
Tejun Heo | 21f3cfe | 2020-04-13 12:27:58 -0400 | [diff] [blame] | 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, } |
Tejun Heo | 6954ff1 | 2019-08-28 15:05:59 -0700 | [diff] [blame] | 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} ' \ |
Tejun Heo | a7863b3 | 2020-09-01 14:52:57 -0400 | [diff] [blame] | 138 | f'{"debt":>7} {"delay":>7} {"usage%"}' |
Tejun Heo | 6954ff1 | 2019-08-28 15:05:59 -0700 | [diff] [blame] | 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_()) |
Tejun Heo | a7863b3 | 2020-09-01 14:52:57 -0400 | [diff] [blame] | 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 |
Tejun Heo | b06f2d3 | 2019-09-04 12:45:55 -0700 | [diff] [blame] | 151 | self.address = iocg.value_() |
Tejun Heo | 6954ff1 | 2019-08-28 15:05:59 -0700 | [diff] [blame] | 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 | |
Tejun Heo | a7863b3 | 2020-09-01 14:52:57 -0400 | [diff] [blame] | 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 |
Tejun Heo | 6954ff1 | 2019-08-28 15:05:59 -0700 | [diff] [blame] | 169 | |
| 170 | def dict(self, now, path): |
| 171 | out = { 'cgroup' : path, |
Tejun Heo | 21f3cfe | 2020-04-13 12:27:58 -0400 | [diff] [blame] | 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, |
Tejun Heo | 21f3cfe | 2020-04-13 12:27:58 -0400 | [diff] [blame] | 181 | 'delay_ms' : self.delay_ms, |
| 182 | 'usage_pct' : self.usage, |
| 183 | 'address' : self.address } |
Tejun Heo | 6954ff1 | 2019-08-28 15:05:59 -0700 | [diff] [blame] | 184 | return out |
| 185 | |
| 186 | def table_row_str(self, path): |
| 187 | out = f'{path[-28:]:28} ' \ |
| 188 | f'{"*" if self.is_active else " "} ' \ |
Tejun Heo | a7863b3 | 2020-09-01 14:52:57 -0400 | [diff] [blame] | 189 | f'{round(self.inuse):5}/{round(self.active):5} ' \ |
Tejun Heo | 6954ff1 | 2019-08-28 15:05:59 -0700 | [diff] [blame] | 190 | f'{self.hwi_pct:6.2f}/{self.hwa_pct:6.2f} ' \ |
| 191 | f'{self.inflight_pct:6.2f} ' \ |
Tejun Heo | a7863b3 | 2020-09-01 14:52:57 -0400 | [diff] [blame] | 192 | f'{self.debt_ms:7.2f} ' \ |
| 193 | f'{self.delay_ms:7.2f} '\ |
| 194 | f'{min(self.usage, 999):6.2f}' |
Tejun Heo | 6954ff1 | 2019-08-28 15:05:59 -0700 | [diff] [blame] | 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 | |
Tejun Heo | 9ea37e2 | 2020-01-17 11:54:35 -0800 | [diff] [blame] | 221 | for i, ptr in radix_tree_for_each(blkcg_root.blkg_tree.address_of_()): |
Tejun Heo | 6954ff1 | 2019-08-28 15:05:59 -0700 | [diff] [blame] | 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 | |
Tejun Heo | f4fe3ea | 2020-04-13 12:27:57 -0400 | [diff] [blame] | 236 | if interval == 0: |
| 237 | sys.exit(0) |
| 238 | |
Tejun Heo | 6954ff1 | 2019-08-28 15:05:59 -0700 | [diff] [blame] | 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) |