summaryrefslogtreecommitdiffstats
path: root/tools/writeback/wb_monitor.py
blob: 5e3591f1f9a9dffb797dfd249b6919b39c0da327 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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()