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