| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 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 | |
| 95 | |
| 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 | |
| 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 | |
| 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()
|