wb_monitor.py 5.3 KB

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