script: add iostat.py to check bdev iostat
this tool likes sysstat/iostat added to qos test Signed-off-by: jiaqizho <jiaqi.zhou@intel.com> Change-Id: I7ed6538095d34fa8c5835f28b25d618f1b3d81b7 Reviewed-on: https://review.gerrithub.io/c/spdk/spdk/+/476537 Community-CI: Broadcom SPDK FC-NVMe CI <spdk-ci.pdl@broadcom.com> Community-CI: SPDK CI Jenkins <sys_sgci@intel.com> Tested-by: SPDK CI Jenkins <sys_sgci@intel.com> Reviewed-by: Seth Howell <seth.howell@intel.com> Reviewed-by: Ben Walker <benjamin.walker@intel.com> Reviewed-by: Jim Harris <james.r.harris@intel.com> Reviewed-by: Shuhei Matsumoto <shuhei.matsumoto.xt@hitachi.com>
This commit is contained in:
parent
253fb41b65
commit
f1687a1bd2
356
scripts/iostat.py
Executable file
356
scripts/iostat.py
Executable file
@ -0,0 +1,356 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import argparse
|
||||
import time
|
||||
import rpc
|
||||
|
||||
|
||||
SPDK_CPU_STAT = "/proc/stat"
|
||||
SPDK_UPTIME = "/proc/uptime"
|
||||
|
||||
SPDK_CPU_STAT_HEAD = ['cpu_stat:', 'user_stat', 'nice_stat',
|
||||
'system_stat', 'iowait_stat', 'steal_stat', 'idle_stat']
|
||||
SPDK_BDEV_KB_STAT_HEAD = ['Device', 'tps', 'KB_read/s',
|
||||
'KB_wrtn/s', 'KB_dscd/s', 'KB_read', 'KB_wrtn', 'KB_dscd']
|
||||
SPDK_BDEV_MB_STAT_HEAD = ['Device', 'tps', 'MB_read/s',
|
||||
'MB_wrtn/s', 'MB_dscd/s', 'MB_read', 'MB_wrtn', 'MB_dscd']
|
||||
|
||||
SPDK_MAX_SECTORS = 0xffffffff
|
||||
|
||||
|
||||
class BdevStat:
|
||||
|
||||
def __init__(self, dictionary):
|
||||
if dictionary is None:
|
||||
return
|
||||
for k, value in dictionary.items():
|
||||
if k == 'name':
|
||||
self.bdev_name = value
|
||||
elif k == 'bytes_read':
|
||||
self.rd_sectors = value >> 9
|
||||
elif k == 'bytes_written':
|
||||
self.wr_sectors = value >> 9
|
||||
elif k == 'bytes_unmapped':
|
||||
self.dc_sectors = value >> 9
|
||||
elif k == 'num_read_ops':
|
||||
self.rd_ios = value
|
||||
elif k == 'num_write_ops':
|
||||
self.wr_ios = value
|
||||
elif k == 'num_unmap_ops':
|
||||
self.dc_ios = value
|
||||
elif k == 'read_latency_ticks':
|
||||
self.rd_ticks = value
|
||||
elif k == 'write_latency_ticks':
|
||||
self.wr_ticks = value
|
||||
elif k == 'unmap_latency_ticks':
|
||||
self.dc_ticks = value
|
||||
elif k == 'queue_depth':
|
||||
self.ios_pgr = value
|
||||
elif k == 'io_time':
|
||||
self.tot_ticks = value
|
||||
elif k == 'weighted_io_time':
|
||||
self.rq_ticks = value
|
||||
|
||||
self.rd_merges = 0
|
||||
self.wr_merges = 0
|
||||
self.dc_merges = 0
|
||||
self.upt = 0.0
|
||||
|
||||
def __getattr__(self, name):
|
||||
return 0
|
||||
|
||||
|
||||
def uptime():
|
||||
with open(SPDK_UPTIME, 'r') as f:
|
||||
return float(f.readline().split()[0])
|
||||
|
||||
|
||||
def _stat_format(data, header, leave_first=False):
|
||||
list_size = len(data)
|
||||
header_len = len(header)
|
||||
|
||||
if list_size == 0:
|
||||
raise AssertionError
|
||||
list_len = len(data[0])
|
||||
|
||||
for ll in data:
|
||||
if len(ll) != list_len:
|
||||
raise AssertionError
|
||||
for i, r in enumerate(ll):
|
||||
ll[i] = str(r)
|
||||
|
||||
if (leave_first and list_len + 1 != header_len) or \
|
||||
(not leave_first and list_len != header_len):
|
||||
raise AssertionError
|
||||
|
||||
item_sizes = [0 for i in range(header_len)]
|
||||
|
||||
for i in range(0, list_len):
|
||||
if leave_first and i == 0:
|
||||
item_sizes[i] = len(header[i + 1])
|
||||
|
||||
data_len = 0
|
||||
for x in data:
|
||||
data_len = max(data_len, len(x[i]))
|
||||
index = i + 1 if leave_first else i
|
||||
item_sizes[index] = max(len(header[index]), data_len)
|
||||
|
||||
_format = ' '.join('%%-%ss' % item_sizes[i] for i in range(0, header_len))
|
||||
print(_format % tuple(header))
|
||||
if leave_first:
|
||||
print('\n'.join(_format % ('', *tuple(ll)) for ll in data))
|
||||
else:
|
||||
print('\n'.join(_format % tuple(ll) for ll in data))
|
||||
|
||||
print()
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def read_cpu_stat(last_cpu_info, cpu_info):
|
||||
jiffies = 0
|
||||
for i in range(0, 7):
|
||||
jiffies += cpu_info[i] - \
|
||||
(last_cpu_info[i] if last_cpu_info else 0)
|
||||
|
||||
if last_cpu_info:
|
||||
info_stat = [
|
||||
"{:.2%}".format((cpu_info[0] - last_cpu_info[0]) / jiffies),
|
||||
"{:.2%}".format((cpu_info[1] - last_cpu_info[1]) / jiffies),
|
||||
"{:.2%}".format(((cpu_info[2] + cpu_info[5] + cpu_info[6]) -
|
||||
(last_cpu_info[2] + last_cpu_info[5] + last_cpu_info[6])) / jiffies),
|
||||
"{:.2%}".format((cpu_info[4] - last_cpu_info[4]) / jiffies),
|
||||
"{:.2%}".format((cpu_info[7] - last_cpu_info[7]) / jiffies),
|
||||
"{:.2%}".format((cpu_info[3] - last_cpu_info[3]) / jiffies),
|
||||
]
|
||||
else:
|
||||
info_stat = [
|
||||
"{:.2%}".format(cpu_info[0] / jiffies),
|
||||
"{:.2%}".format(cpu_info[1] / jiffies),
|
||||
"{:.2%}".format((cpu_info[2] + cpu_info[5]
|
||||
+ cpu_info[6]) / jiffies),
|
||||
"{:.2%}".format(cpu_info[4] / jiffies),
|
||||
"{:.2%}".format(cpu_info[7] / jiffies),
|
||||
"{:.2%}".format(cpu_info[3] / jiffies),
|
||||
]
|
||||
|
||||
_stat_format([info_stat], SPDK_CPU_STAT_HEAD, True)
|
||||
|
||||
|
||||
def check_positive(value):
|
||||
v = int(value)
|
||||
if v <= 0:
|
||||
raise argparse.ArgumentTypeError("%s should be positive int value" % v)
|
||||
return v
|
||||
|
||||
|
||||
def get_cpu_stat():
|
||||
with open(SPDK_CPU_STAT, "r") as cpu_file:
|
||||
cpu_dump_info = []
|
||||
line = cpu_file.readline()
|
||||
while line:
|
||||
line = line.strip()
|
||||
if "cpu " in line:
|
||||
cpu_dump_info = [int(data) for data in line[5:].split(' ')]
|
||||
break
|
||||
|
||||
line = cpu_file.readline()
|
||||
return cpu_dump_info
|
||||
|
||||
|
||||
def read_bdev_stat(last_stat, stat, mb, use_upt):
|
||||
if use_upt:
|
||||
upt_cur = uptime()
|
||||
else:
|
||||
upt_cur = stat['ticks']
|
||||
upt_rate = stat['tick_rate']
|
||||
|
||||
info_stats = []
|
||||
unit = 2048 if mb else 2
|
||||
|
||||
bdev_stats = []
|
||||
if last_stat:
|
||||
for bdev in stat['bdevs']:
|
||||
_stat = BdevStat(bdev)
|
||||
_stat.upt = upt_cur
|
||||
bdev_stats.append(_stat)
|
||||
_last_stat = None
|
||||
for last_bdev in last_stat:
|
||||
if (_stat.bdev_name == last_bdev.bdev_name):
|
||||
_last_stat = last_bdev
|
||||
break
|
||||
|
||||
# get the interval time
|
||||
if use_upt:
|
||||
upt = _stat.upt - _last_stat.upt
|
||||
else:
|
||||
upt = (_stat.upt - _last_stat.upt) / upt_rate
|
||||
|
||||
rd_sec = _stat.rd_sectors - _last_stat.rd_sectors
|
||||
if (_stat.rd_sectors < _last_stat.rd_sectors) and (_last_stat.rd_sectors <= SPDK_MAX_SECTORS):
|
||||
rd_sec &= SPDK_MAX_SECTORS
|
||||
|
||||
wr_sec = _stat.wr_sectors - _last_stat.wr_sectors
|
||||
if (_stat.wr_sectors < _last_stat.wr_sectors) and (_last_stat.wr_sectors <= SPDK_MAX_SECTORS):
|
||||
wr_sec &= SPDK_MAX_SECTORS
|
||||
|
||||
dc_sec = _stat.dc_sectors - _last_stat.dc_sectors
|
||||
if (_stat.dc_sectors < _last_stat.dc_sectors) and (_last_stat.dc_sectors <= SPDK_MAX_SECTORS):
|
||||
dc_sec &= SPDK_MAX_SECTORS
|
||||
|
||||
tps = ((_stat.rd_ios + _stat.dc_ios + _stat.wr_ios) -
|
||||
(_last_stat.rd_ios + _last_stat.dc_ios + _last_stat.wr_ios)) / upt
|
||||
|
||||
info_stat = [
|
||||
_stat.bdev_name,
|
||||
"{:.2f}".format(tps),
|
||||
"{:.2f}".format(
|
||||
(_stat.rd_sectors - _last_stat.rd_sectors) / upt / unit),
|
||||
"{:.2f}".format(
|
||||
(_stat.wr_sectors - _last_stat.wr_sectors) / upt / unit),
|
||||
"{:.2f}".format(
|
||||
(_stat.dc_sectors - _last_stat.dc_sectors) / upt / unit),
|
||||
"{:.2f}".format(rd_sec / unit),
|
||||
"{:.2f}".format(wr_sec / unit),
|
||||
"{:.2f}".format(dc_sec / unit),
|
||||
]
|
||||
info_stats.append(info_stat)
|
||||
else:
|
||||
for bdev in stat['bdevs']:
|
||||
_stat = BdevStat(bdev)
|
||||
_stat.upt = upt_cur
|
||||
bdev_stats.append(_stat)
|
||||
|
||||
if use_upt:
|
||||
upt = _stat.upt
|
||||
else:
|
||||
upt = _stat.upt / upt_rate
|
||||
|
||||
tps = (_stat.rd_ios + _stat.dc_ios + _stat.wr_ios) / upt
|
||||
info_stat = [
|
||||
_stat.bdev_name,
|
||||
"{:.2f}".format(tps),
|
||||
"{:.2f}".format(_stat.rd_sectors / upt / unit),
|
||||
"{:.2f}".format(_stat.wr_sectors / upt / unit),
|
||||
"{:.2f}".format(_stat.dc_sectors / upt / unit),
|
||||
"{:.2f}".format(_stat.rd_sectors / unit),
|
||||
"{:.2f}".format(_stat.wr_sectors / unit),
|
||||
"{:.2f}".format(_stat.dc_sectors / unit),
|
||||
]
|
||||
info_stats.append(info_stat)
|
||||
|
||||
_stat_format(
|
||||
info_stats, SPDK_BDEV_MB_STAT_HEAD if mb else SPDK_BDEV_KB_STAT_HEAD)
|
||||
return bdev_stats
|
||||
|
||||
|
||||
def get_bdev_stat(client, name):
|
||||
return rpc.bdev.bdev_get_iostat(client, name=name)
|
||||
|
||||
|
||||
def io_stat_display(args, cpu_info, stat):
|
||||
if args.cpu_stat and not args.bdev_stat:
|
||||
_cpu_info = get_cpu_stat()
|
||||
read_cpu_stat(cpu_info, _cpu_info)
|
||||
return _cpu_info, None
|
||||
|
||||
if args.bdev_stat and not args.cpu_stat:
|
||||
_stat = get_bdev_stat(args.client, args.name)
|
||||
bdev_stats = read_bdev_stat(
|
||||
stat, _stat, args.mb_display, args.use_uptime)
|
||||
return None, bdev_stats
|
||||
|
||||
_cpu_info = get_cpu_stat()
|
||||
read_cpu_stat(cpu_info, _cpu_info)
|
||||
|
||||
_stat = get_bdev_stat(args.client, args.name)
|
||||
bdev_stats = read_bdev_stat(stat, _stat, args.mb_display, args.use_uptime)
|
||||
return _cpu_info, bdev_stats
|
||||
|
||||
|
||||
def io_stat_display_loop(args):
|
||||
interval = args.interval
|
||||
time_in_second = args.time_in_second
|
||||
args.client = rpc.client.JSONRPCClient(
|
||||
args.server_addr, args.port, args.timeout, log_level=getattr(logging, args.verbose.upper()))
|
||||
|
||||
last_cpu_stat = None
|
||||
bdev_stats = None
|
||||
|
||||
cur = 0
|
||||
while True:
|
||||
last_cpu_stat, bdev_stats = io_stat_display(
|
||||
args, last_cpu_stat, bdev_stats)
|
||||
|
||||
time.sleep(interval)
|
||||
cur += interval
|
||||
if cur >= time_in_second:
|
||||
break
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description='SPDK iostats command line interface')
|
||||
|
||||
parser.add_argument('-c', '--cpu-status', dest='cpu_stat',
|
||||
action='store_true', help="Only display cpu status",
|
||||
required=False, default=False)
|
||||
|
||||
parser.add_argument('-d', '--bdev-status', dest='bdev_stat',
|
||||
action='store_true', help="Only display Blockdev io stats",
|
||||
required=False, default=False)
|
||||
|
||||
parser.add_argument('-k', '--kb-display', dest='kb_display',
|
||||
action='store_true', help="Display drive stats in KiB",
|
||||
required=False, default=False)
|
||||
|
||||
parser.add_argument('-m', '--mb-display', dest='mb_display',
|
||||
action='store_true', help="Display drive stats in MiB",
|
||||
required=False, default=False)
|
||||
|
||||
parser.add_argument('-u', '--use-uptime', dest='use_uptime',
|
||||
action='store_true', help='Use uptime or spdk ticks(default) as \
|
||||
the interval variable to calculate iostat changes.',
|
||||
required=False, default=False)
|
||||
|
||||
parser.add_argument('-i', '--interval', dest='interval',
|
||||
type=check_positive, help='Time interval (in seconds) on which \
|
||||
to poll I/O stats. Used in conjunction with -t',
|
||||
required=False, default=0)
|
||||
|
||||
parser.add_argument('-t', '--time', dest='time_in_second',
|
||||
type=check_positive, help='The number of second to display stats \
|
||||
before returning. Used in conjunction with -i',
|
||||
required=False, default=0)
|
||||
|
||||
parser.add_argument('-s', "--server", dest='server_addr',
|
||||
help='RPC domain socket path or IP address',
|
||||
default='/var/tmp/spdk.sock')
|
||||
|
||||
parser.add_argument('-p', "--port", dest='port',
|
||||
help='RPC port number (if server_addr is IP address)',
|
||||
default=4420, type=int)
|
||||
|
||||
parser.add_argument('-b', '--name', dest='name',
|
||||
help="Name of the Blockdev. Example: Nvme0n1", required=False)
|
||||
|
||||
parser.add_argument('-o', '--timeout', dest='timeout',
|
||||
help='Timeout as a floating point number expressed in seconds \
|
||||
waiting for response. Default: 60.0',
|
||||
default=60.0, type=float)
|
||||
|
||||
parser.add_argument('-v', dest='verbose', action='store_const', const="INFO",
|
||||
help='Set verbose mode to INFO', default="ERROR")
|
||||
|
||||
args = parser.parse_args()
|
||||
if ((args.interval == 0 and args.time_in_second != 0) or
|
||||
(args.interval != 0 and args.time_in_second == 0)):
|
||||
raise argparse.ArgumentTypeError(
|
||||
"interval and time_in_second should be greater than 0 at the same time")
|
||||
|
||||
if args.kb_display and args.mb_display:
|
||||
parser.print_help()
|
||||
exit()
|
||||
|
||||
io_stat_display_loop(args)
|
@ -165,36 +165,18 @@ function fio_test_suite() {
|
||||
function get_io_result() {
|
||||
local limit_type=$1
|
||||
local qos_dev=$2
|
||||
|
||||
io_result=$($rpc_py bdev_get_iostat -b $qos_dev)
|
||||
local iostat_result
|
||||
iostat_result=$($rootdir/scripts/iostat.py -d -i 1 -t $QOS_RUN_TIME | grep $qos_dev | tail -1)
|
||||
if [ $limit_type = IOPS ]; then
|
||||
io_result_before=$(echo $io_result | jq -r '.bdevs[0].num_read_ops')
|
||||
else
|
||||
io_result_before=$(echo $io_result | jq -r '.bdevs[0].bytes_read')
|
||||
iostat_result=$(awk '{print $2}' <<< $iostat_result)
|
||||
elif [ $limit_type = BANDWIDTH ]; then
|
||||
iostat_result=$(awk '{print $6}' <<< $iostat_result)
|
||||
fi
|
||||
tick_rate=$(echo $io_result | jq -r '.tick_rate')
|
||||
ticks_before=$(echo $io_result | jq -r '.ticks')
|
||||
|
||||
sleep $QOS_RUN_TIME
|
||||
|
||||
io_result=$($rpc_py bdev_get_iostat -b $qos_dev)
|
||||
if [ $limit_type = IOPS ]; then
|
||||
io_result_after=$(echo $io_result | jq -r '.bdevs[0].num_read_ops')
|
||||
else
|
||||
io_result_after=$(echo $io_result | jq -r '.bdevs[0].bytes_read')
|
||||
fi
|
||||
ticks_after=$(echo $io_result | jq -r '.ticks')
|
||||
|
||||
if [ $limit_type = IOPS ]; then
|
||||
io_result_diff=$((io_result_after-io_result_before))
|
||||
else
|
||||
# To avoid potential overflow as throughput is in byte
|
||||
# Return throughput in kilobyte
|
||||
io_result_diff=$(((io_result_after-io_result_before)/1024))
|
||||
fi
|
||||
echo $(((io_result_diff*tick_rate)/(ticks_after-ticks_before)))
|
||||
echo ${iostat_result/.*}
|
||||
}
|
||||
|
||||
|
||||
function run_qos_test() {
|
||||
local qos_limit=$1
|
||||
local qos_result=0
|
||||
|
Loading…
Reference in New Issue
Block a user