writeback: add wb_monitor.py script to monitor writeback info on bdi
Add wb_monitor.py script to monitor writeback information on backing dev which makes it easier and more convenient to observe writeback behaviors of running system. The wb_monitor.py script is written based on wq_monitor.py. Following domain hierarchy is tested: global domain (320G) / \ cgroup domain1(10G) cgroup domain2(10G) | | bdi wb1 wb2 The wb_monitor.py script output is as following: ./wb_monitor.py 252:16 -c writeback reclaimable dirtied written avg_bw 252:16_1 0 0 0 0 102400 252:16_4284 672 820064 9230368 8410304 685612 252:16_4325 896 819840 10491264 9671648 652348 252:16 1568 1639904 19721632 18081952 1440360 writeback reclaimable dirtied written avg_bw 252:16_1 0 0 0 0 102400 252:16_4284 672 820064 9230368 8410304 685612 252:16_4325 896 819840 10491264 9671648 652348 252:16 1568 1639904 19721632 18081952 1440360 ... Link: https://lkml.kernel.org/r/20240423034643.141219-5-shikemeng@huaweicloud.com Signed-off-by: Kemeng Shi <shikemeng@huaweicloud.com> Suggested-by: Tejun Heo <tj@kernel.org> Cc: Brian Foster <bfoster@redhat.com> Cc: David Howells <dhowells@redhat.com> Cc: David Sterba <dsterba@suse.com> Cc: Jan Kara <jack@suse.cz> Cc: Mateusz Guzik <mjguzik@gmail.com> Cc: Matthew Wilcox (Oracle) <willy@infradead.org> Cc: SeongJae Park <sj@kernel.org> Cc: Stephen Rothwell <sfr@canb.auug.org.au> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
This commit is contained in:
parent
4b5bbc39d7
commit
881f1bb5e2
|
@ -0,0 +1,172 @@
|
||||||
|
#!/usr/bin/env drgn
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024 Kemeng Shi <shikemeng@huaweicloud.com>
|
||||||
|
# Copyright (C) 2024 Huawei Inc
|
||||||
|
|
||||||
|
desc = """
|
||||||
|
This is a drgn script based on wq_monitor.py to monitor writeback info on
|
||||||
|
backing dev. For more info on drgn, visit https://github.com/osandov/drgn.
|
||||||
|
|
||||||
|
writeback(kB) Amount of dirty pages are currently being written back to
|
||||||
|
disk.
|
||||||
|
|
||||||
|
reclaimable(kB) Amount of pages are currently reclaimable.
|
||||||
|
|
||||||
|
dirtied(kB) Amount of pages have been dirtied.
|
||||||
|
|
||||||
|
wrttien(kB) Amount of dirty pages have been written back to disk.
|
||||||
|
|
||||||
|
avg_wb(kBps) Smoothly estimated write bandwidth of writing dirty pages
|
||||||
|
back to disk.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import signal
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
|
import drgn
|
||||||
|
from drgn.helpers.linux.list import list_for_each_entry
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
parser = argparse.ArgumentParser(description=desc,
|
||||||
|
formatter_class=argparse.RawTextHelpFormatter)
|
||||||
|
parser.add_argument('bdi', metavar='REGEX', nargs='*',
|
||||||
|
help='Target backing device name patterns (all if empty)')
|
||||||
|
parser.add_argument('-i', '--interval', metavar='SECS', type=float, default=1,
|
||||||
|
help='Monitoring interval (0 to print once and exit)')
|
||||||
|
parser.add_argument('-j', '--json', action='store_true',
|
||||||
|
help='Output in json')
|
||||||
|
parser.add_argument('-c', '--cgroup', action='store_true',
|
||||||
|
help='show writeback of bdi in cgroup')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
bdi_list = prog['bdi_list']
|
||||||
|
|
||||||
|
WB_RECLAIMABLE = prog['WB_RECLAIMABLE']
|
||||||
|
WB_WRITEBACK = prog['WB_WRITEBACK']
|
||||||
|
WB_DIRTIED = prog['WB_DIRTIED']
|
||||||
|
WB_WRITTEN = prog['WB_WRITTEN']
|
||||||
|
NR_WB_STAT_ITEMS = prog['NR_WB_STAT_ITEMS']
|
||||||
|
|
||||||
|
PAGE_SHIFT = prog['PAGE_SHIFT']
|
||||||
|
|
||||||
|
def K(x):
|
||||||
|
return x << (PAGE_SHIFT - 10)
|
||||||
|
|
||||||
|
class Stats:
|
||||||
|
def dict(self, now):
|
||||||
|
return { 'timestamp' : now,
|
||||||
|
'name' : self.name,
|
||||||
|
'writeback' : self.stats[WB_WRITEBACK],
|
||||||
|
'reclaimable' : self.stats[WB_RECLAIMABLE],
|
||||||
|
'dirtied' : self.stats[WB_DIRTIED],
|
||||||
|
'written' : self.stats[WB_WRITTEN],
|
||||||
|
'avg_wb' : self.avg_bw, }
|
||||||
|
|
||||||
|
def table_header_str():
|
||||||
|
return f'{"":>16} {"writeback":>10} {"reclaimable":>12} ' \
|
||||||
|
f'{"dirtied":>9} {"written":>9} {"avg_bw":>9}'
|
||||||
|
|
||||||
|
def table_row_str(self):
|
||||||
|
out = f'{self.name[-16:]:16} ' \
|
||||||
|
f'{self.stats[WB_WRITEBACK]:10} ' \
|
||||||
|
f'{self.stats[WB_RECLAIMABLE]:12} ' \
|
||||||
|
f'{self.stats[WB_DIRTIED]:9} ' \
|
||||||
|
f'{self.stats[WB_WRITTEN]:9} ' \
|
||||||
|
f'{self.avg_bw:9} '
|
||||||
|
return out
|
||||||
|
|
||||||
|
def show_header():
|
||||||
|
if Stats.table_fmt:
|
||||||
|
print()
|
||||||
|
print(Stats.table_header_str())
|
||||||
|
|
||||||
|
def show_stats(self):
|
||||||
|
if Stats.table_fmt:
|
||||||
|
print(self.table_row_str())
|
||||||
|
else:
|
||||||
|
print(self.dict(Stats.now))
|
||||||
|
|
||||||
|
class WbStats(Stats):
|
||||||
|
def __init__(self, wb):
|
||||||
|
bdi_name = wb.bdi.dev_name.string_().decode()
|
||||||
|
# avoid to use bdi.wb.memcg_css which is only defined when
|
||||||
|
# CONFIG_CGROUP_WRITEBACK is enabled
|
||||||
|
if wb == wb.bdi.wb.address_of_():
|
||||||
|
ino = "1"
|
||||||
|
else:
|
||||||
|
ino = str(wb.memcg_css.cgroup.kn.id.value_())
|
||||||
|
self.name = bdi_name + '_' + ino
|
||||||
|
|
||||||
|
self.stats = [0] * NR_WB_STAT_ITEMS
|
||||||
|
for i in range(NR_WB_STAT_ITEMS):
|
||||||
|
if wb.stat[i].count >= 0:
|
||||||
|
self.stats[i] = int(K(wb.stat[i].count))
|
||||||
|
else:
|
||||||
|
self.stats[i] = 0
|
||||||
|
|
||||||
|
self.avg_bw = int(K(wb.avg_write_bandwidth))
|
||||||
|
|
||||||
|
class BdiStats(Stats):
|
||||||
|
def __init__(self, bdi):
|
||||||
|
self.name = bdi.dev_name.string_().decode()
|
||||||
|
self.stats = [0] * NR_WB_STAT_ITEMS
|
||||||
|
self.avg_bw = 0
|
||||||
|
|
||||||
|
def collectStats(self, wb_stats):
|
||||||
|
for i in range(NR_WB_STAT_ITEMS):
|
||||||
|
self.stats[i] += wb_stats.stats[i]
|
||||||
|
|
||||||
|
self.avg_bw += wb_stats.avg_bw
|
||||||
|
|
||||||
|
exit_req = False
|
||||||
|
|
||||||
|
def sigint_handler(signr, frame):
|
||||||
|
global exit_req
|
||||||
|
exit_req = True
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# handle args
|
||||||
|
Stats.table_fmt = not args.json
|
||||||
|
interval = args.interval
|
||||||
|
cgroup = args.cgroup
|
||||||
|
|
||||||
|
re_str = None
|
||||||
|
if args.bdi:
|
||||||
|
for r in args.bdi:
|
||||||
|
if re_str is None:
|
||||||
|
re_str = r
|
||||||
|
else:
|
||||||
|
re_str += '|' + r
|
||||||
|
|
||||||
|
filter_re = re.compile(re_str) if re_str else None
|
||||||
|
|
||||||
|
# monitoring loop
|
||||||
|
signal.signal(signal.SIGINT, sigint_handler)
|
||||||
|
|
||||||
|
while not exit_req:
|
||||||
|
Stats.now = time.time()
|
||||||
|
|
||||||
|
Stats.show_header()
|
||||||
|
for bdi in list_for_each_entry('struct backing_dev_info', bdi_list.address_of_(), 'bdi_list'):
|
||||||
|
bdi_stats = BdiStats(bdi)
|
||||||
|
if filter_re and not filter_re.search(bdi_stats.name):
|
||||||
|
continue
|
||||||
|
|
||||||
|
for wb in list_for_each_entry('struct bdi_writeback', bdi.wb_list.address_of_(), 'bdi_node'):
|
||||||
|
wb_stats = WbStats(wb)
|
||||||
|
bdi_stats.collectStats(wb_stats)
|
||||||
|
if cgroup:
|
||||||
|
wb_stats.show_stats()
|
||||||
|
|
||||||
|
bdi_stats.show_stats()
|
||||||
|
if cgroup and Stats.table_fmt:
|
||||||
|
print()
|
||||||
|
|
||||||
|
if interval == 0:
|
||||||
|
break
|
||||||
|
time.sleep(interval)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
Loading…
Reference in New Issue