xref: /openbmc/linux/tools/cgroup/iocost_monitor.py (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
16954ff18STejun Heo#!/usr/bin/env drgn
26954ff18STejun Heo#
36954ff18STejun Heo# Copyright (C) 2019 Tejun Heo <tj@kernel.org>
46954ff18STejun Heo# Copyright (C) 2019 Facebook
56954ff18STejun Heo
66954ff18STejun Heodesc = """
76954ff18STejun HeoThis is a drgn script to monitor the blk-iocost cgroup controller.
86954ff18STejun HeoSee the comment at the top of block/blk-iocost.c for more details.
96954ff18STejun HeoFor drgn, visit https://github.com/osandov/drgn.
106954ff18STejun Heo"""
116954ff18STejun Heo
126954ff18STejun Heoimport sys
136954ff18STejun Heoimport re
146954ff18STejun Heoimport time
156954ff18STejun Heoimport json
16b06f2d35STejun Heoimport math
176954ff18STejun Heo
186954ff18STejun Heoimport drgn
196954ff18STejun Heofrom drgn import container_of
206954ff18STejun Heofrom drgn.helpers.linux.list import list_for_each_entry,list_empty
216954ff18STejun Heofrom drgn.helpers.linux.radixtree import radix_tree_for_each,radix_tree_lookup
226954ff18STejun Heo
236954ff18STejun Heoimport argparse
246954ff18STejun Heoparser = argparse.ArgumentParser(description=desc,
256954ff18STejun Heo                                 formatter_class=argparse.RawTextHelpFormatter)
266954ff18STejun Heoparser.add_argument('devname', metavar='DEV',
276954ff18STejun Heo                    help='Target block device name (e.g. sda)')
286954ff18STejun Heoparser.add_argument('--cgroup', action='append', metavar='REGEX',
296954ff18STejun Heo                    help='Regex for target cgroups, ')
306954ff18STejun Heoparser.add_argument('--interval', '-i', metavar='SECONDS', type=float, default=1,
31f4fe3ea6STejun Heo                    help='Monitoring interval in seconds (0 exits immediately '
32f4fe3ea6STejun Heo                    'after checking requirements)')
336954ff18STejun Heoparser.add_argument('--json', action='store_true',
346954ff18STejun Heo                    help='Output in json')
356954ff18STejun Heoargs = parser.parse_args()
366954ff18STejun Heo
376954ff18STejun Heodef err(s):
386954ff18STejun Heo    print(s, file=sys.stderr, flush=True)
396954ff18STejun Heo    sys.exit(1)
406954ff18STejun Heo
416954ff18STejun Heotry:
426954ff18STejun Heo    blkcg_root = prog['blkcg_root']
436954ff18STejun Heo    plid = prog['blkcg_policy_iocost'].plid.value_()
446954ff18STejun Heoexcept:
456954ff18STejun Heo    err('The kernel does not have iocost enabled')
466954ff18STejun Heo
476954ff18STejun HeoIOC_RUNNING     = prog['IOC_RUNNING'].value_()
48a7863b34STejun HeoWEIGHT_ONE      = prog['WEIGHT_ONE'].value_()
496954ff18STejun HeoVTIME_PER_SEC   = prog['VTIME_PER_SEC'].value_()
506954ff18STejun HeoVTIME_PER_USEC  = prog['VTIME_PER_USEC'].value_()
516954ff18STejun HeoAUTOP_SSD_FAST  = prog['AUTOP_SSD_FAST'].value_()
526954ff18STejun HeoAUTOP_SSD_DFL   = prog['AUTOP_SSD_DFL'].value_()
536954ff18STejun HeoAUTOP_SSD_QD1   = prog['AUTOP_SSD_QD1'].value_()
546954ff18STejun HeoAUTOP_HDD       = prog['AUTOP_HDD'].value_()
556954ff18STejun Heo
566954ff18STejun Heoautop_names = {
576954ff18STejun Heo    AUTOP_SSD_FAST:        'ssd_fast',
586954ff18STejun Heo    AUTOP_SSD_DFL:         'ssd_dfl',
596954ff18STejun Heo    AUTOP_SSD_QD1:         'ssd_qd1',
606954ff18STejun Heo    AUTOP_HDD:             'hdd',
616954ff18STejun Heo}
626954ff18STejun Heo
636954ff18STejun Heoclass BlkgIterator:
64b74440d8SElijah Conners    def __init__(self, root_blkcg, q_id, include_dying=False):
65b74440d8SElijah Conners        self.include_dying = include_dying
66b74440d8SElijah Conners        self.blkgs = []
67b74440d8SElijah Conners        self.walk(root_blkcg, q_id, '')
68b74440d8SElijah Conners
696954ff18STejun Heo    def blkcg_name(blkcg):
706954ff18STejun Heo        return blkcg.css.cgroup.kn.name.string_().decode('utf-8')
716954ff18STejun Heo
726954ff18STejun Heo    def walk(self, blkcg, q_id, parent_path):
736954ff18STejun Heo        if not self.include_dying and \
746954ff18STejun Heo           not (blkcg.css.flags.value_() & prog['CSS_ONLINE'].value_()):
756954ff18STejun Heo            return
766954ff18STejun Heo
776954ff18STejun Heo        name = BlkgIterator.blkcg_name(blkcg)
786954ff18STejun Heo        path = parent_path + '/' + name if parent_path else name
796954ff18STejun Heo        blkg = drgn.Object(prog, 'struct blkcg_gq',
809ea37e24STejun Heo                           address=radix_tree_lookup(blkcg.blkg_tree.address_of_(), q_id))
816954ff18STejun Heo        if not blkg.address_:
826954ff18STejun Heo            return
836954ff18STejun Heo
846954ff18STejun Heo        self.blkgs.append((path if path else '/', blkg))
856954ff18STejun Heo
866954ff18STejun Heo        for c in list_for_each_entry('struct blkcg',
876954ff18STejun Heo                                     blkcg.css.children.address_of_(), 'css.sibling'):
886954ff18STejun Heo            self.walk(c, q_id, path)
896954ff18STejun Heo
906954ff18STejun Heo    def __iter__(self):
916954ff18STejun Heo        return iter(self.blkgs)
926954ff18STejun Heo
936954ff18STejun Heoclass IocStat:
946954ff18STejun Heo    def __init__(self, ioc):
956954ff18STejun Heo        global autop_names
966954ff18STejun Heo
976954ff18STejun Heo        self.enabled = ioc.enabled.value_()
986954ff18STejun Heo        self.running = ioc.running.value_() == IOC_RUNNING
99b06f2d35STejun Heo        self.period_ms = ioc.period_us.value_() / 1_000
1006954ff18STejun Heo        self.period_at = ioc.period_at.value_() / 1_000_000
1016954ff18STejun Heo        self.vperiod_at = ioc.period_at_vtime.value_() / VTIME_PER_SEC
102a7863b34STejun Heo        self.vrate_pct = ioc.vtime_base_rate.value_() * 100 / VTIME_PER_USEC
1038e93c1acSChengming Zhou        self.ivrate_pct = ioc.vtime_rate.counter.value_() * 100 / VTIME_PER_USEC
1046954ff18STejun Heo        self.busy_level = ioc.busy_level.value_()
1056954ff18STejun Heo        self.autop_idx = ioc.autop_idx.value_()
1066954ff18STejun Heo        self.user_cost_model = ioc.user_cost_model.value_()
1076954ff18STejun Heo        self.user_qos_params = ioc.user_qos_params.value_()
1086954ff18STejun Heo
1096954ff18STejun Heo        if self.autop_idx in autop_names:
1106954ff18STejun Heo            self.autop_name = autop_names[self.autop_idx]
1116954ff18STejun Heo        else:
1126954ff18STejun Heo            self.autop_name = '?'
1136954ff18STejun Heo
1146954ff18STejun Heo    def dict(self, now):
1156954ff18STejun Heo        return { 'device'               : devname,
11621f3cfeaSTejun Heo                 'timestamp'            : now,
11721f3cfeaSTejun Heo                 'enabled'              : self.enabled,
11821f3cfeaSTejun Heo                 'running'              : self.running,
11921f3cfeaSTejun Heo                 'period_ms'            : self.period_ms,
12021f3cfeaSTejun Heo                 'period_at'            : self.period_at,
12121f3cfeaSTejun Heo                 'period_vtime_at'      : self.vperiod_at,
12221f3cfeaSTejun Heo                 'busy_level'           : self.busy_level,
1238e93c1acSChengming Zhou                 'vrate_pct'            : self.vrate_pct,
1248e93c1acSChengming Zhou                 'ivrate_pct'           : self.ivrate_pct,
1258e93c1acSChengming Zhou                }
1266954ff18STejun Heo
1276954ff18STejun Heo    def table_preamble_str(self):
1286954ff18STejun Heo        state = ('RUN' if self.running else 'IDLE') if self.enabled else 'OFF'
1296954ff18STejun Heo        output = f'{devname} {state:4} ' \
1306954ff18STejun Heo                 f'per={self.period_ms}ms ' \
1316954ff18STejun Heo                 f'cur_per={self.period_at:.3f}:v{self.vperiod_at:.3f} ' \
1326954ff18STejun Heo                 f'busy={self.busy_level:+3} ' \
1338e93c1acSChengming Zhou                 f'vrate={self.vrate_pct:6.2f}%:{self.ivrate_pct:6.2f}% ' \
1346954ff18STejun Heo                 f'params={self.autop_name}'
1356954ff18STejun Heo        if self.user_cost_model or self.user_qos_params:
1366954ff18STejun Heo            output += f'({"C" if self.user_cost_model else ""}{"Q" if self.user_qos_params else ""})'
1376954ff18STejun Heo        return output
1386954ff18STejun Heo
1396954ff18STejun Heo    def table_header_str(self):
1406954ff18STejun Heo        return f'{"":25} active {"weight":>9} {"hweight%":>13} {"inflt%":>6} ' \
141*68392b00SChengming Zhou               f'{"usage%":>6} {"wait":>7} {"debt":>7} {"delay":>7}'
1426954ff18STejun Heo
1436954ff18STejun Heoclass IocgStat:
1446954ff18STejun Heo    def __init__(self, iocg):
1456954ff18STejun Heo        ioc = iocg.ioc
1466954ff18STejun Heo        blkg = iocg.pd.blkg
1476954ff18STejun Heo
1486954ff18STejun Heo        self.is_active = not list_empty(iocg.active_list.address_of_())
149a7863b34STejun Heo        self.weight = iocg.weight.value_() / WEIGHT_ONE
150a7863b34STejun Heo        self.active = iocg.active.value_() / WEIGHT_ONE
151a7863b34STejun Heo        self.inuse = iocg.inuse.value_() / WEIGHT_ONE
152a7863b34STejun Heo        self.hwa_pct = iocg.hweight_active.value_() * 100 / WEIGHT_ONE
153a7863b34STejun Heo        self.hwi_pct = iocg.hweight_inuse.value_() * 100 / WEIGHT_ONE
154b06f2d35STejun Heo        self.address = iocg.value_()
1556954ff18STejun Heo
1566954ff18STejun Heo        vdone = iocg.done_vtime.counter.value_()
1576954ff18STejun Heo        vtime = iocg.vtime.counter.value_()
1586954ff18STejun Heo        vrate = ioc.vtime_rate.counter.value_()
1596954ff18STejun Heo        period_vtime = ioc.period_us.value_() * vrate
1606954ff18STejun Heo        if period_vtime:
1616954ff18STejun Heo            self.inflight_pct = (vtime - vdone) * 100 / period_vtime
1626954ff18STejun Heo        else:
1636954ff18STejun Heo            self.inflight_pct = 0
1646954ff18STejun Heo
165a7863b34STejun Heo        self.usage = (100 * iocg.usage_delta_us.value_() /
166a7863b34STejun Heo                      ioc.period_us.value_()) if self.active else 0
167*68392b00SChengming Zhou        self.wait_ms = (iocg.stat.wait_us.value_() -
168*68392b00SChengming Zhou                        iocg.last_stat.wait_us.value_()) / 1000
1690b80f986STejun Heo        self.debt_ms = iocg.abs_vdebt.value_() / VTIME_PER_USEC / 1000
170a7863b34STejun Heo        if blkg.use_delay.counter.value_() != 0:
171b06f2d35STejun Heo            self.delay_ms = blkg.delay_nsec.counter.value_() / 1_000_000
172a7863b34STejun Heo        else:
173a7863b34STejun Heo            self.delay_ms = 0
1746954ff18STejun Heo
1756954ff18STejun Heo    def dict(self, now, path):
1766954ff18STejun Heo        out = { 'cgroup'                : path,
17721f3cfeaSTejun Heo                'timestamp'             : now,
17821f3cfeaSTejun Heo                'is_active'             : self.is_active,
17921f3cfeaSTejun Heo                'weight'                : self.weight,
18021f3cfeaSTejun Heo                'weight_active'         : self.active,
18121f3cfeaSTejun Heo                'weight_inuse'          : self.inuse,
18221f3cfeaSTejun Heo                'hweight_active_pct'    : self.hwa_pct,
18321f3cfeaSTejun Heo                'hweight_inuse_pct'     : self.hwi_pct,
18421f3cfeaSTejun Heo                'inflight_pct'          : self.inflight_pct,
185*68392b00SChengming Zhou                'usage_pct'             : self.usage,
186*68392b00SChengming Zhou                'wait_ms'               : self.wait_ms,
18721f3cfeaSTejun Heo                'debt_ms'               : self.debt_ms,
18821f3cfeaSTejun Heo                'delay_ms'              : self.delay_ms,
18921f3cfeaSTejun Heo                'address'               : self.address }
1906954ff18STejun Heo        return out
1916954ff18STejun Heo
1926954ff18STejun Heo    def table_row_str(self, path):
1936954ff18STejun Heo        out = f'{path[-28:]:28} ' \
1946954ff18STejun Heo              f'{"*" if self.is_active else " "} ' \
195a7863b34STejun Heo              f'{round(self.inuse):5}/{round(self.active):5} ' \
1966954ff18STejun Heo              f'{self.hwi_pct:6.2f}/{self.hwa_pct:6.2f} ' \
1976954ff18STejun Heo              f'{self.inflight_pct:6.2f} ' \
198*68392b00SChengming Zhou              f'{min(self.usage, 999):6.2f} ' \
199*68392b00SChengming Zhou              f'{self.wait_ms:7.2f} ' \
200a7863b34STejun Heo              f'{self.debt_ms:7.2f} ' \
201*68392b00SChengming Zhou              f'{self.delay_ms:7.2f}'
2026954ff18STejun Heo        out = out.rstrip(':')
2036954ff18STejun Heo        return out
2046954ff18STejun Heo
2056954ff18STejun Heo# handle args
2066954ff18STejun Heotable_fmt = not args.json
2076954ff18STejun Heointerval = args.interval
2086954ff18STejun Heodevname = args.devname
2096954ff18STejun Heo
2106954ff18STejun Heoif args.json:
2116954ff18STejun Heo    table_fmt = False
2126954ff18STejun Heo
2136954ff18STejun Heore_str = None
2146954ff18STejun Heoif args.cgroup:
2156954ff18STejun Heo    for r in args.cgroup:
2166954ff18STejun Heo        if re_str is None:
2176954ff18STejun Heo            re_str = r
2186954ff18STejun Heo        else:
2196954ff18STejun Heo            re_str += '|' + r
2206954ff18STejun Heo
2216954ff18STejun Heofilter_re = re.compile(re_str) if re_str else None
2226954ff18STejun Heo
2236954ff18STejun Heo# Locate the roots
2246954ff18STejun Heoq_id = None
2256954ff18STejun Heoroot_iocg = None
2266954ff18STejun Heoioc = None
2276954ff18STejun Heo
2289ea37e24STejun Heofor i, ptr in radix_tree_for_each(blkcg_root.blkg_tree.address_of_()):
2296954ff18STejun Heo    blkg = drgn.Object(prog, 'struct blkcg_gq', address=ptr)
2306954ff18STejun Heo    try:
2312eae9c49SChengming Zhou        if devname == blkg.q.mq_kobj.parent.name.string_().decode('utf-8'):
2326954ff18STejun Heo            q_id = blkg.q.id.value_()
2336954ff18STejun Heo            if blkg.pd[plid]:
2346954ff18STejun Heo                root_iocg = container_of(blkg.pd[plid], 'struct ioc_gq', 'pd')
2356954ff18STejun Heo                ioc = root_iocg.ioc
2366954ff18STejun Heo            break
2376954ff18STejun Heo    except:
2386954ff18STejun Heo        pass
2396954ff18STejun Heo
2406954ff18STejun Heoif ioc is None:
2416954ff18STejun Heo    err(f'Could not find ioc for {devname}');
2426954ff18STejun Heo
243f4fe3ea6STejun Heoif interval == 0:
244f4fe3ea6STejun Heo    sys.exit(0)
245f4fe3ea6STejun Heo
2466954ff18STejun Heo# Keep printing
2476954ff18STejun Heowhile True:
2486954ff18STejun Heo    now = time.time()
2496954ff18STejun Heo    iocstat = IocStat(ioc)
2506954ff18STejun Heo    output = ''
2516954ff18STejun Heo
2526954ff18STejun Heo    if table_fmt:
2536954ff18STejun Heo        output += '\n' + iocstat.table_preamble_str()
2546954ff18STejun Heo        output += '\n' + iocstat.table_header_str()
2556954ff18STejun Heo    else:
2566954ff18STejun Heo        output += json.dumps(iocstat.dict(now))
2576954ff18STejun Heo
2586954ff18STejun Heo    for path, blkg in BlkgIterator(blkcg_root, q_id):
2596954ff18STejun Heo        if filter_re and not filter_re.match(path):
2606954ff18STejun Heo            continue
2616954ff18STejun Heo        if not blkg.pd[plid]:
2626954ff18STejun Heo            continue
2636954ff18STejun Heo
2646954ff18STejun Heo        iocg = container_of(blkg.pd[plid], 'struct ioc_gq', 'pd')
2656954ff18STejun Heo        iocg_stat = IocgStat(iocg)
2666954ff18STejun Heo
2676954ff18STejun Heo        if not filter_re and not iocg_stat.is_active:
2686954ff18STejun Heo            continue
2696954ff18STejun Heo
2706954ff18STejun Heo        if table_fmt:
2716954ff18STejun Heo            output += '\n' + iocg_stat.table_row_str(path)
2726954ff18STejun Heo        else:
2736954ff18STejun Heo            output += '\n' + json.dumps(iocg_stat.dict(now, path))
2746954ff18STejun Heo
2756954ff18STejun Heo    print(output)
2766954ff18STejun Heo    sys.stdout.flush()
2776954ff18STejun Heo    time.sleep(interval)
278