Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Kemeng Shi and committed by
Andrew Morton
881f1bb5 4b5bbc39

+172
+172
tools/writeback/wb_monitor.py
··· 1 + #!/usr/bin/env drgn 2 + # 3 + # Copyright (C) 2024 Kemeng Shi <shikemeng@huaweicloud.com> 4 + # Copyright (C) 2024 Huawei Inc 5 + 6 + desc = """ 7 + This is a drgn script based on wq_monitor.py to monitor writeback info on 8 + backing dev. For more info on drgn, visit https://github.com/osandov/drgn. 9 + 10 + writeback(kB) Amount of dirty pages are currently being written back to 11 + disk. 12 + 13 + reclaimable(kB) Amount of pages are currently reclaimable. 14 + 15 + dirtied(kB) Amount of pages have been dirtied. 16 + 17 + wrttien(kB) Amount of dirty pages have been written back to disk. 18 + 19 + avg_wb(kBps) Smoothly estimated write bandwidth of writing dirty pages 20 + back to disk. 21 + """ 22 + 23 + import signal 24 + import re 25 + import time 26 + import json 27 + 28 + import drgn 29 + from drgn.helpers.linux.list import list_for_each_entry 30 + 31 + import argparse 32 + parser = argparse.ArgumentParser(description=desc, 33 + formatter_class=argparse.RawTextHelpFormatter) 34 + parser.add_argument('bdi', metavar='REGEX', nargs='*', 35 + help='Target backing device name patterns (all if empty)') 36 + parser.add_argument('-i', '--interval', metavar='SECS', type=float, default=1, 37 + help='Monitoring interval (0 to print once and exit)') 38 + parser.add_argument('-j', '--json', action='store_true', 39 + help='Output in json') 40 + parser.add_argument('-c', '--cgroup', action='store_true', 41 + help='show writeback of bdi in cgroup') 42 + args = parser.parse_args() 43 + 44 + bdi_list = prog['bdi_list'] 45 + 46 + WB_RECLAIMABLE = prog['WB_RECLAIMABLE'] 47 + WB_WRITEBACK = prog['WB_WRITEBACK'] 48 + WB_DIRTIED = prog['WB_DIRTIED'] 49 + WB_WRITTEN = prog['WB_WRITTEN'] 50 + NR_WB_STAT_ITEMS = prog['NR_WB_STAT_ITEMS'] 51 + 52 + PAGE_SHIFT = prog['PAGE_SHIFT'] 53 + 54 + def K(x): 55 + return x << (PAGE_SHIFT - 10) 56 + 57 + class Stats: 58 + def dict(self, now): 59 + return { 'timestamp' : now, 60 + 'name' : self.name, 61 + 'writeback' : self.stats[WB_WRITEBACK], 62 + 'reclaimable' : self.stats[WB_RECLAIMABLE], 63 + 'dirtied' : self.stats[WB_DIRTIED], 64 + 'written' : self.stats[WB_WRITTEN], 65 + 'avg_wb' : self.avg_bw, } 66 + 67 + def table_header_str(): 68 + return f'{"":>16} {"writeback":>10} {"reclaimable":>12} ' \ 69 + f'{"dirtied":>9} {"written":>9} {"avg_bw":>9}' 70 + 71 + def table_row_str(self): 72 + out = f'{self.name[-16:]:16} ' \ 73 + f'{self.stats[WB_WRITEBACK]:10} ' \ 74 + f'{self.stats[WB_RECLAIMABLE]:12} ' \ 75 + f'{self.stats[WB_DIRTIED]:9} ' \ 76 + f'{self.stats[WB_WRITTEN]:9} ' \ 77 + f'{self.avg_bw:9} ' 78 + return out 79 + 80 + def show_header(): 81 + if Stats.table_fmt: 82 + print() 83 + print(Stats.table_header_str()) 84 + 85 + def show_stats(self): 86 + if Stats.table_fmt: 87 + print(self.table_row_str()) 88 + else: 89 + print(self.dict(Stats.now)) 90 + 91 + class WbStats(Stats): 92 + def __init__(self, wb): 93 + bdi_name = wb.bdi.dev_name.string_().decode() 94 + # avoid to use bdi.wb.memcg_css which is only defined when 95 + # CONFIG_CGROUP_WRITEBACK is enabled 96 + if wb == wb.bdi.wb.address_of_(): 97 + ino = "1" 98 + else: 99 + ino = str(wb.memcg_css.cgroup.kn.id.value_()) 100 + self.name = bdi_name + '_' + ino 101 + 102 + self.stats = [0] * NR_WB_STAT_ITEMS 103 + for i in range(NR_WB_STAT_ITEMS): 104 + if wb.stat[i].count >= 0: 105 + self.stats[i] = int(K(wb.stat[i].count)) 106 + else: 107 + self.stats[i] = 0 108 + 109 + self.avg_bw = int(K(wb.avg_write_bandwidth)) 110 + 111 + class BdiStats(Stats): 112 + def __init__(self, bdi): 113 + self.name = bdi.dev_name.string_().decode() 114 + self.stats = [0] * NR_WB_STAT_ITEMS 115 + self.avg_bw = 0 116 + 117 + def collectStats(self, wb_stats): 118 + for i in range(NR_WB_STAT_ITEMS): 119 + self.stats[i] += wb_stats.stats[i] 120 + 121 + self.avg_bw += wb_stats.avg_bw 122 + 123 + exit_req = False 124 + 125 + def sigint_handler(signr, frame): 126 + global exit_req 127 + exit_req = True 128 + 129 + def main(): 130 + # handle args 131 + Stats.table_fmt = not args.json 132 + interval = args.interval 133 + cgroup = args.cgroup 134 + 135 + re_str = None 136 + if args.bdi: 137 + for r in args.bdi: 138 + if re_str is None: 139 + re_str = r 140 + else: 141 + re_str += '|' + r 142 + 143 + filter_re = re.compile(re_str) if re_str else None 144 + 145 + # monitoring loop 146 + signal.signal(signal.SIGINT, sigint_handler) 147 + 148 + while not exit_req: 149 + Stats.now = time.time() 150 + 151 + Stats.show_header() 152 + for bdi in list_for_each_entry('struct backing_dev_info', bdi_list.address_of_(), 'bdi_list'): 153 + bdi_stats = BdiStats(bdi) 154 + if filter_re and not filter_re.search(bdi_stats.name): 155 + continue 156 + 157 + for wb in list_for_each_entry('struct bdi_writeback', bdi.wb_list.address_of_(), 'bdi_node'): 158 + wb_stats = WbStats(wb) 159 + bdi_stats.collectStats(wb_stats) 160 + if cgroup: 161 + wb_stats.show_stats() 162 + 163 + bdi_stats.show_stats() 164 + if cgroup and Stats.table_fmt: 165 + print() 166 + 167 + if interval == 0: 168 + break 169 + time.sleep(interval) 170 + 171 + if __name__ == "__main__": 172 + main()