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