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