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