xref: /spdk/scripts/iostat.py (revision 0ed85362c8132a2d1927757fbcade66b6660d26a)
1#!/usr/bin/env python3
2
3import logging
4import sys
5import argparse
6import time
7import rpc
8
9
10SPDK_CPU_STAT = "/proc/stat"
11SPDK_UPTIME = "/proc/uptime"
12
13SPDK_CPU_STAT_HEAD = ['cpu_stat:', 'user_stat', 'nice_stat',
14                      'system_stat', 'iowait_stat', 'steal_stat', 'idle_stat']
15SPDK_BDEV_KB_STAT_HEAD = ['Device', 'tps', 'KB_read/s',
16                          'KB_wrtn/s', 'KB_dscd/s', 'KB_read', 'KB_wrtn', 'KB_dscd']
17SPDK_BDEV_MB_STAT_HEAD = ['Device', 'tps', 'MB_read/s',
18                          'MB_wrtn/s', 'MB_dscd/s', 'MB_read', 'MB_wrtn', 'MB_dscd']
19
20SPDK_MAX_SECTORS = 0xffffffff
21
22
23class BdevStat:
24
25    def __init__(self, dictionary):
26        if dictionary is None:
27            return
28        for k, value in dictionary.items():
29            if k == 'name':
30                self.bdev_name = value
31            elif k == 'bytes_read':
32                self.rd_sectors = value >> 9
33            elif k == 'bytes_written':
34                self.wr_sectors = value >> 9
35            elif k == 'bytes_unmapped':
36                self.dc_sectors = value >> 9
37            elif k == 'num_read_ops':
38                self.rd_ios = value
39            elif k == 'num_write_ops':
40                self.wr_ios = value
41            elif k == 'num_unmap_ops':
42                self.dc_ios = value
43            elif k == 'read_latency_ticks':
44                self.rd_ticks = value
45            elif k == 'write_latency_ticks':
46                self.wr_ticks = value
47            elif k == 'unmap_latency_ticks':
48                self.dc_ticks = value
49            elif k == 'queue_depth':
50                self.ios_pgr = value
51            elif k == 'io_time':
52                self.tot_ticks = value
53            elif k == 'weighted_io_time':
54                self.rq_ticks = value
55
56        self.rd_merges = 0
57        self.wr_merges = 0
58        self.dc_merges = 0
59        self.upt = 0.0
60
61    def __getattr__(self, name):
62        return 0
63
64
65def uptime():
66    with open(SPDK_UPTIME, 'r') as f:
67        return float(f.readline().split()[0])
68
69
70def _stat_format(data, header, leave_first=False):
71    list_size = len(data)
72    header_len = len(header)
73
74    if list_size == 0:
75        raise AssertionError
76    list_len = len(data[0])
77
78    for ll in data:
79        if len(ll) != list_len:
80            raise AssertionError
81        for i, r in enumerate(ll):
82            ll[i] = str(r)
83
84    if (leave_first and list_len + 1 != header_len) or \
85            (not leave_first and list_len != header_len):
86        raise AssertionError
87
88    item_sizes = [0 for i in range(header_len)]
89
90    for i in range(0, list_len):
91        if leave_first and i == 0:
92            item_sizes[i] = len(header[i + 1])
93
94        data_len = 0
95        for x in data:
96            data_len = max(data_len, len(x[i]))
97        index = i + 1 if leave_first else i
98        item_sizes[index] = max(len(header[index]), data_len)
99
100    _format = '  '.join('%%-%ss' % item_sizes[i] for i in range(0, header_len))
101    print(_format % tuple(header))
102    if leave_first:
103        print('\n'.join(_format % ('', *tuple(ll)) for ll in data))
104    else:
105        print('\n'.join(_format % tuple(ll) for ll in data))
106
107    print()
108    sys.stdout.flush()
109
110
111def read_cpu_stat(last_cpu_info, cpu_info):
112    jiffies = 0
113    for i in range(0, 7):
114        jiffies += cpu_info[i] - \
115            (last_cpu_info[i] if last_cpu_info else 0)
116
117    if last_cpu_info:
118        info_stat = [
119            "{:.2%}".format((cpu_info[0] - last_cpu_info[0]) / jiffies),
120            "{:.2%}".format((cpu_info[1] - last_cpu_info[1]) / jiffies),
121            "{:.2%}".format(((cpu_info[2] + cpu_info[5] + cpu_info[6]) -
122                             (last_cpu_info[2] + last_cpu_info[5] + last_cpu_info[6])) / jiffies),
123            "{:.2%}".format((cpu_info[4] - last_cpu_info[4]) / jiffies),
124            "{:.2%}".format((cpu_info[7] - last_cpu_info[7]) / jiffies),
125            "{:.2%}".format((cpu_info[3] - last_cpu_info[3]) / jiffies),
126        ]
127    else:
128        info_stat = [
129            "{:.2%}".format(cpu_info[0] / jiffies),
130            "{:.2%}".format(cpu_info[1] / jiffies),
131            "{:.2%}".format((cpu_info[2] + cpu_info[5]
132                             + cpu_info[6]) / jiffies),
133            "{:.2%}".format(cpu_info[4] / jiffies),
134            "{:.2%}".format(cpu_info[7] / jiffies),
135            "{:.2%}".format(cpu_info[3] / jiffies),
136        ]
137
138    _stat_format([info_stat], SPDK_CPU_STAT_HEAD, True)
139
140
141def check_positive(value):
142    v = int(value)
143    if v <= 0:
144        raise argparse.ArgumentTypeError("%s should be positive int value" % v)
145    return v
146
147
148def get_cpu_stat():
149    with open(SPDK_CPU_STAT, "r") as cpu_file:
150        cpu_dump_info = []
151        line = cpu_file.readline()
152        while line:
153            line = line.strip()
154            if "cpu " in line:
155                cpu_dump_info = [int(data) for data in line[5:].split(' ')]
156                break
157
158            line = cpu_file.readline()
159    return cpu_dump_info
160
161
162def read_bdev_stat(last_stat, stat, mb, use_upt):
163    if use_upt:
164        upt_cur = uptime()
165    else:
166        upt_cur = stat['ticks']
167        upt_rate = stat['tick_rate']
168
169    info_stats = []
170    unit = 2048 if mb else 2
171
172    bdev_stats = []
173    if last_stat:
174        for bdev in stat['bdevs']:
175            _stat = BdevStat(bdev)
176            _stat.upt = upt_cur
177            bdev_stats.append(_stat)
178            _last_stat = None
179            for last_bdev in last_stat:
180                if (_stat.bdev_name == last_bdev.bdev_name):
181                    _last_stat = last_bdev
182                    break
183
184            # get the interval time
185            if use_upt:
186                upt = _stat.upt - _last_stat.upt
187            else:
188                upt = (_stat.upt - _last_stat.upt) / upt_rate
189
190            rd_sec = _stat.rd_sectors - _last_stat.rd_sectors
191            if (_stat.rd_sectors < _last_stat.rd_sectors) and (_last_stat.rd_sectors <= SPDK_MAX_SECTORS):
192                rd_sec &= SPDK_MAX_SECTORS
193
194            wr_sec = _stat.wr_sectors - _last_stat.wr_sectors
195            if (_stat.wr_sectors < _last_stat.wr_sectors) and (_last_stat.wr_sectors <= SPDK_MAX_SECTORS):
196                wr_sec &= SPDK_MAX_SECTORS
197
198            dc_sec = _stat.dc_sectors - _last_stat.dc_sectors
199            if (_stat.dc_sectors < _last_stat.dc_sectors) and (_last_stat.dc_sectors <= SPDK_MAX_SECTORS):
200                dc_sec &= SPDK_MAX_SECTORS
201
202            tps = ((_stat.rd_ios + _stat.dc_ios + _stat.wr_ios) -
203                   (_last_stat.rd_ios + _last_stat.dc_ios + _last_stat.wr_ios)) / upt
204
205            info_stat = [
206                _stat.bdev_name,
207                "{:.2f}".format(tps),
208                "{:.2f}".format(
209                    (_stat.rd_sectors - _last_stat.rd_sectors) / upt / unit),
210                "{:.2f}".format(
211                    (_stat.wr_sectors - _last_stat.wr_sectors) / upt / unit),
212                "{:.2f}".format(
213                    (_stat.dc_sectors - _last_stat.dc_sectors) / upt / unit),
214                "{:.2f}".format(rd_sec / unit),
215                "{:.2f}".format(wr_sec / unit),
216                "{:.2f}".format(dc_sec / unit),
217            ]
218            info_stats.append(info_stat)
219    else:
220        for bdev in stat['bdevs']:
221            _stat = BdevStat(bdev)
222            _stat.upt = upt_cur
223            bdev_stats.append(_stat)
224
225            if use_upt:
226                upt = _stat.upt
227            else:
228                upt = _stat.upt / upt_rate
229
230            tps = (_stat.rd_ios + _stat.dc_ios + _stat.wr_ios) / upt
231            info_stat = [
232                _stat.bdev_name,
233                "{:.2f}".format(tps),
234                "{:.2f}".format(_stat.rd_sectors / upt / unit),
235                "{:.2f}".format(_stat.wr_sectors / upt / unit),
236                "{:.2f}".format(_stat.dc_sectors / upt / unit),
237                "{:.2f}".format(_stat.rd_sectors / unit),
238                "{:.2f}".format(_stat.wr_sectors / unit),
239                "{:.2f}".format(_stat.dc_sectors / unit),
240            ]
241            info_stats.append(info_stat)
242
243    _stat_format(
244        info_stats, SPDK_BDEV_MB_STAT_HEAD if mb else SPDK_BDEV_KB_STAT_HEAD)
245    return bdev_stats
246
247
248def get_bdev_stat(client, name):
249    return rpc.bdev.bdev_get_iostat(client, name=name)
250
251
252def io_stat_display(args, cpu_info, stat):
253    if args.cpu_stat and not args.bdev_stat:
254        _cpu_info = get_cpu_stat()
255        read_cpu_stat(cpu_info, _cpu_info)
256        return _cpu_info, None
257
258    if args.bdev_stat and not args.cpu_stat:
259        _stat = get_bdev_stat(args.client, args.name)
260        bdev_stats = read_bdev_stat(
261            stat, _stat, args.mb_display, args.use_uptime)
262        return None, bdev_stats
263
264    _cpu_info = get_cpu_stat()
265    read_cpu_stat(cpu_info, _cpu_info)
266
267    _stat = get_bdev_stat(args.client, args.name)
268    bdev_stats = read_bdev_stat(stat, _stat, args.mb_display, args.use_uptime)
269    return _cpu_info, bdev_stats
270
271
272def io_stat_display_loop(args):
273    interval = args.interval
274    time_in_second = args.time_in_second
275    args.client = rpc.client.JSONRPCClient(
276        args.server_addr, args.port, args.timeout, log_level=getattr(logging, args.verbose.upper()))
277
278    last_cpu_stat = None
279    bdev_stats = None
280
281    cur = 0
282    while True:
283        last_cpu_stat, bdev_stats = io_stat_display(
284            args, last_cpu_stat, bdev_stats)
285
286        time.sleep(interval)
287        cur += interval
288        if cur >= time_in_second:
289            break
290
291
292if __name__ == "__main__":
293    parser = argparse.ArgumentParser(
294        description='SPDK iostats command line interface')
295
296    parser.add_argument('-c', '--cpu-status', dest='cpu_stat',
297                        action='store_true', help="Only display cpu status",
298                        required=False, default=False)
299
300    parser.add_argument('-d', '--bdev-status', dest='bdev_stat',
301                        action='store_true', help="Only display Blockdev io stats",
302                        required=False, default=False)
303
304    parser.add_argument('-k', '--kb-display', dest='kb_display',
305                        action='store_true', help="Display drive stats in KiB",
306                        required=False, default=False)
307
308    parser.add_argument('-m', '--mb-display', dest='mb_display',
309                        action='store_true', help="Display drive stats in MiB",
310                        required=False, default=False)
311
312    parser.add_argument('-u', '--use-uptime', dest='use_uptime',
313                        action='store_true', help='Use uptime or spdk ticks(default) as \
314                        the interval variable to calculate iostat changes.',
315                        required=False, default=False)
316
317    parser.add_argument('-i', '--interval', dest='interval',
318                        type=check_positive, help='Time interval (in seconds) on which \
319                        to poll I/O stats. Used in conjunction with -t',
320                        required=False, default=0)
321
322    parser.add_argument('-t', '--time', dest='time_in_second',
323                        type=check_positive, help='The number of second to display stats \
324                        before returning. Used in conjunction with -i',
325                        required=False, default=0)
326
327    parser.add_argument('-s', "--server", dest='server_addr',
328                        help='RPC domain socket path or IP address',
329                        default='/var/tmp/spdk.sock')
330
331    parser.add_argument('-p', "--port", dest='port',
332                        help='RPC port number (if server_addr is IP address)',
333                        default=4420, type=int)
334
335    parser.add_argument('-b', '--name', dest='name',
336                        help="Name of the Blockdev. Example: Nvme0n1", required=False)
337
338    parser.add_argument('-o', '--timeout', dest='timeout',
339                        help='Timeout as a floating point number expressed in seconds \
340                        waiting for response. Default: 60.0',
341                        default=60.0, type=float)
342
343    parser.add_argument('-v', dest='verbose', action='store_const', const="INFO",
344                        help='Set verbose mode to INFO', default="ERROR")
345
346    args = parser.parse_args()
347    if ((args.interval == 0 and args.time_in_second != 0) or
348            (args.interval != 0 and args.time_in_second == 0)):
349        raise argparse.ArgumentTypeError(
350            "interval and time_in_second should be greater than 0 at the same time")
351
352    if args.kb_display and args.mb_display:
353        parser.print_help()
354        exit()
355
356    io_stat_display_loop(args)
357