blob: 75c429e454cf6c5fff4518674a0be891263b0f2c [file] [log] [blame]
#!/usr/bin/env python3
"""
# strided.py
#
# Test zonemode=strided. This uses the null ioengine when no file is
# specified. If a file is specified, use it for randdom read testing.
# Some of the zoneranges in the tests are 16MiB. So when using a file
# a minimum size of 64MiB is recommended.
#
# USAGE
# python strided.py fio-executable [-f file/device]
#
# EXAMPLES
# python t/strided.py ./fio
# python t/strided.py ./fio -f /dev/sda
# dd if=/dev/zero of=temp bs=1M count=64
# python t/strided.py ./fio -f temp
#
# ===TEST MATRIX===
#
# --zonemode=strided, zoneskip unset
# w/ randommap and LFSR
# zonesize=zonerange all blocks in zonerange touched
# zonesize>zonerange all blocks touched and roll-over back into zone
# zonesize<zonerange all blocks inside zone
#
# w/o randommap all blocks inside zone
"""
import os
import sys
import time
import argparse
from pathlib import Path
from fiotestlib import FioJobCmdTest, run_fio_tests
class StridedTest(FioJobCmdTest):
"""Test zonemode=strided."""
def setup(self, parameters):
fio_args = [
"--name=strided",
"--zonemode=strided",
"--log_offset=1",
"--randrepeat=0",
"--rw=randread",
f"--write_iops_log={self.filenames['iopslog']}",
f"--output={self.filenames['output']}",
f"--zonerange={self.fio_opts['zonerange']}",
f"--zonesize={self.fio_opts['zonesize']}",
f"--bs={self.fio_opts['bs']}",
]
for opt in ['norandommap', 'random_generator', 'offset']:
if opt in self.fio_opts:
option = f"--{opt}={self.fio_opts[opt]}"
fio_args.append(option)
if 'filename' in self.fio_opts:
for opt in ['filename', 'filesize']:
option = f"--{opt}={self.fio_opts[opt]}"
fio_args.append(option)
else:
fio_args.append('--ioengine=null')
for opt in ['size', 'io_size', 'filesize']:
option = f"--{opt}={self.fio_opts[opt]}"
fio_args.append(option)
super().setup(fio_args)
def check_result(self):
super().check_result()
if not self.passed:
return
zonestart = 0 if 'offset' not in self.fio_opts else self.fio_opts['offset']
iospersize = self.fio_opts['zonesize'] / self.fio_opts['bs']
iosperrange = self.fio_opts['zonerange'] / self.fio_opts['bs']
iosperzone = 0
lines = self.iops_log_lines.split('\n')
zoneset = set()
for line in lines:
if len(line) == 0:
continue
if iosperzone == iospersize:
# time to move to a new zone
iosperzone = 0
zoneset = set()
zonestart += self.fio_opts['zonerange']
if zonestart >= self.fio_opts['filesize']:
zonestart = 0 if 'offset' not in self.fio_opts else self.fio_opts['offset']
iosperzone = iosperzone + 1
tokens = line.split(',')
offset = int(tokens[4])
if offset < zonestart or offset >= zonestart + self.fio_opts['zonerange']:
print(f"Offset {offset} outside of zone starting at {zonestart}")
return
# skip next section if norandommap is enabled with no
# random_generator or with a random_generator != lfsr
if 'norandommap' in self.fio_opts:
if 'random_generator' in self.fio_opts:
if self.fio_opts['random_generator'] != 'lfsr':
continue
else:
continue
# we either have a random map enabled or we
# are using an LFSR
# so all blocks should be unique and we should have
# covered the entire zone when iosperzone % iosperrange == 0
block = (offset - zonestart) / self.fio_opts['bs']
if block in zoneset:
print(f"Offset {offset} in zone already touched")
return
zoneset.add(block)
if iosperzone % iosperrange == 0:
if len(zoneset) != iosperrange:
print(f"Expected {iosperrange} blocks in zone but only saw {len(zoneset)}")
return
zoneset = set()
TEST_LIST = [ # randommap enabled
{
"test_id": 1,
"fio_opts": {
"zonerange": 4096,
"zonesize": 4096,
"bs": 4096,
"offset": 8*4096,
"size": 16*4096,
"io_size": 16*4096,
},
"test_class": StridedTest,
},
{
"test_id": 2,
"fio_opts": {
"zonerange": 4096,
"zonesize": 4096,
"bs": 4096,
"size": 16*4096,
"io_size": 16*4096,
},
"test_class": StridedTest,
},
{
"test_id": 3,
"fio_opts": {
"zonerange": 16*1024*1024,
"zonesize": 16*1024*1024,
"bs": 4096,
"size": 256*1024*1024,
"io_size": 256*1024*204,
},
"test_class": StridedTest,
},
{
"test_id": 4,
"fio_opts": {
"zonerange": 4096,
"zonesize": 4*4096,
"bs": 4096,
"size": 16*4096,
"io_size": 16*4096,
},
"test_class": StridedTest,
},
{
"test_id": 5,
"fio_opts": {
"zonerange": 16*1024*1024,
"zonesize": 32*1024*1024,
"bs": 4096,
"size": 256*1024*1024,
"io_size": 256*1024*204,
},
"test_class": StridedTest,
},
{
"test_id": 6,
"fio_opts": {
"zonerange": 8192,
"zonesize": 4096,
"bs": 4096,
"size": 16*4096,
"io_size": 16*4096,
},
"test_class": StridedTest,
},
{
"test_id": 7,
"fio_opts": {
"zonerange": 16*1024*1024,
"zonesize": 8*1024*1024,
"bs": 4096,
"size": 256*1024*1024,
"io_size": 256*1024*204,
},
"test_class": StridedTest,
},
# lfsr
{
"test_id": 8,
"fio_opts": {
"random_generator": "lfsr",
"zonerange": 4096*1024,
"zonesize": 4096*1024,
"bs": 4096,
"offset": 8*4096*1024,
"size": 16*4096*1024,
"io_size": 16*4096*1024,
},
"test_class": StridedTest,
},
{
"test_id": 9,
"fio_opts": {
"random_generator": "lfsr",
"zonerange": 4096*1024,
"zonesize": 4096*1024,
"bs": 4096,
"size": 16*4096*1024,
"io_size": 16*4096*1024,
},
"test_class": StridedTest,
},
{
"test_id": 10,
"fio_opts": {
"random_generator": "lfsr",
"zonerange": 16*1024*1024,
"zonesize": 16*1024*1024,
"bs": 4096,
"size": 256*1024*1024,
"io_size": 256*1024*204,
},
"test_class": StridedTest,
},
{
"test_id": 11,
"fio_opts": {
"random_generator": "lfsr",
"zonerange": 4096*1024,
"zonesize": 4*4096*1024,
"bs": 4096,
"size": 16*4096*1024,
"io_size": 16*4096*1024,
},
"test_class": StridedTest,
},
{
"test_id": 12,
"fio_opts": {
"random_generator": "lfsr",
"zonerange": 16*1024*1024,
"zonesize": 32*1024*1024,
"bs": 4096,
"size": 256*1024*1024,
"io_size": 256*1024*204,
},
"test_class": StridedTest,
},
{
"test_id": 13,
"fio_opts": {
"random_generator": "lfsr",
"zonerange": 8192*1024,
"zonesize": 4096*1024,
"bs": 4096,
"size": 16*4096*1024,
"io_size": 16*4096*1024,
},
"test_class": StridedTest,
},
{
"test_id": 14,
"fio_opts": {
"random_generator": "lfsr",
"zonerange": 16*1024*1024,
"zonesize": 8*1024*1024,
"bs": 4096,
"size": 256*1024*1024,
"io_size": 256*1024*204,
},
"test_class": StridedTest,
},
# norandommap
{
"test_id": 15,
"fio_opts": {
"norandommap": 1,
"zonerange": 4096,
"zonesize": 4096,
"bs": 4096,
"offset": 8*4096,
"size": 16*4096,
"io_size": 16*4096,
},
"test_class": StridedTest,
},
{
"test_id": 16,
"fio_opts": {
"norandommap": 1,
"zonerange": 4096,
"zonesize": 4096,
"bs": 4096,
"size": 16*4096,
"io_size": 16*4096,
},
"test_class": StridedTest,
},
{
"test_id": 17,
"fio_opts": {
"norandommap": 1,
"zonerange": 16*1024*1024,
"zonesize": 16*1024*1024,
"bs": 4096,
"size": 256*1024*1024,
"io_size": 256*1024*204,
},
"test_class": StridedTest,
},
{
"test_id": 18,
"fio_opts": {
"norandommap": 1,
"zonerange": 4096,
"zonesize": 8192,
"bs": 4096,
"size": 16*4096,
"io_size": 16*4096,
},
"test_class": StridedTest,
},
{
"test_id": 19,
"fio_opts": {
"norandommap": 1,
"zonerange": 16*1024*1024,
"zonesize": 32*1024*1024,
"bs": 4096,
"size": 256*1024*1024,
"io_size": 256*1024*204,
},
"test_class": StridedTest,
},
{
"test_id": 20,
"fio_opts": {
"norandommap": 1,
"zonerange": 8192,
"zonesize": 4096,
"bs": 4096,
"size": 16*4096,
"io_size": 16*4096,
},
"test_class": StridedTest,
},
{
"test_id": 21,
"fio_opts": {
"norandommap": 1,
"zonerange": 16*1024*1024,
"zonesize": 8*1024*1024,
"bs": 4096,
"size": 256*1024*1024,
"io_size": 256*1024*1024,
},
"test_class": StridedTest,
},
]
def parse_args():
"""Parse command-line arguments."""
parser = argparse.ArgumentParser()
parser.add_argument('-f', '--fio', help='path to file executable (e.g., ./fio)')
parser.add_argument('-a', '--artifact-root', help='artifact root directory')
parser.add_argument('-s', '--skip', nargs='+', type=int,
help='list of test(s) to skip')
parser.add_argument('-o', '--run-only', nargs='+', type=int,
help='list of test(s) to run, skipping all others')
parser.add_argument('--dut',
help='target file/device to test.')
args = parser.parse_args()
return args
def main():
"""Run zonemode=strided tests."""
args = parse_args()
artifact_root = args.artifact_root if args.artifact_root else \
f"strided-test-{time.strftime('%Y%m%d-%H%M%S')}"
os.mkdir(artifact_root)
print(f"Artifact directory is {artifact_root}")
if args.fio:
fio_path = str(Path(args.fio).absolute())
else:
fio_path = 'fio'
print(f"fio path is {fio_path}")
if args.dut:
statinfo = os.stat(args.dut)
filesize = statinfo.st_size
if filesize == 0:
f = os.open(args.dut, os.O_RDONLY)
filesize = os.lseek(f, 0, os.SEEK_END)
os.close(f)
for test in TEST_LIST:
if args.dut:
test['fio_opts']['filename'] = os.path.abspath(args.dut)
test['fio_opts']['filesize'] = filesize
else:
test['fio_opts']['filesize'] = test['fio_opts']['size']
test_env = {
'fio_path': fio_path,
'fio_root': str(Path(__file__).absolute().parent.parent),
'artifact_root': artifact_root,
'basename': 'strided',
}
_, failed, _ = run_fio_tests(TEST_LIST, test_env, args)
sys.exit(failed)
if __name__ == '__main__':
main()