#!/usr/bin/env python3 from subprocess import check_call, call, check_output, Popen, PIPE, CalledProcessError import re import sys import signal import os.path import time import argparse fio_template = """ [global] thread=1 invalidate=1 rw=%(testtype)s time_based=1 runtime=%(runtime)s ioengine=libaio direct=1 bs=%(blocksize)d iodepth=%(iodepth)d norandommap=%(norandommap)d numjobs=%(numjobs)s %(verify)s verify_dump=1 """ verify_template = """ do_verify=1 verify=crc32c-intel """ fio_job_template = """ [job%(jobnumber)d] filename=%(device)s """ def interrupt_handler(signum, frame): fio.terminate() print("FIO terminated") sys.exit(0) def main(io_size, protocol, queue_depth, test_type, runtime, num_jobs, verify): global fio if protocol == "nvmf": devices = get_nvmf_target_devices() elif protocol == "iscsi": devices = get_iscsi_target_devices() configure_devices(devices) try: fio_executable = check_output("which fio", shell=True).split()[0] except CalledProcessError as e: sys.stderr.write(str(e)) sys.stderr.write("\nCan't find the fio binary, please install it.\n") sys.exit(1) device_paths = ['/dev/' + dev for dev in devices] print("Device paths:") print(device_paths) sys.stdout.flush() signal.signal(signal.SIGTERM, interrupt_handler) signal.signal(signal.SIGINT, interrupt_handler) fio = Popen([fio_executable, '-'], stdin=PIPE) fio.communicate(create_fio_config(io_size, queue_depth, device_paths, test_type, runtime, num_jobs, verify).encode()) fio.stdin.close() rc = fio.wait() print("FIO completed with code %d\n" % rc) sys.stdout.flush() sys.exit(rc) def get_iscsi_target_devices(): output = check_output('iscsiadm -m session -P 3', shell=True) return re.findall("Attached scsi disk (sd[a-z]+)", output.decode("ascii")) def get_nvmf_target_devices(): output = str(check_output('lsblk -l -o NAME', shell=True).decode()) return re.findall("(nvme[0-9]+n[0-9]+)\n", output) def create_fio_config(size, q_depth, devices, test, run_time, num_jobs, verify): norandommap = 0 if not verify: verifyfio = "" norandommap = 1 else: verifyfio = verify_template fiofile = fio_template % {"blocksize": size, "iodepth": q_depth, "testtype": test, "runtime": run_time, "norandommap": norandommap, "verify": verifyfio, "numjobs": num_jobs} for (i, dev) in enumerate(devices): fiofile += fio_job_template % {"jobnumber": i, "device": dev} return fiofile def set_device_parameter(devices, filename_template, value): valid_value = True for dev in devices: filename = filename_template % dev f = open(filename, 'r+b') try: f.write(value.encode()) f.close() except OSError: valid_value = False continue return valid_value def configure_devices(devices): for dev in devices: retry = 30 while retry > 0: if os.path.exists("/sys/block/%s/queue/nomerges" % dev): break else: retry = retry - 1 time.sleep(0.1) set_device_parameter(devices, "/sys/block/%s/queue/nomerges", "2") set_device_parameter(devices, "/sys/block/%s/queue/nr_requests", "128") requested_qd = 128 qd = requested_qd while qd > 0: try: set_device_parameter(devices, "/sys/block/%s/device/queue_depth", str(qd)) break except IOError: qd = qd - 1 if qd == 0: print("Could not set block device queue depths.") elif qd < requested_qd: print("Requested queue_depth {} but only {} is supported.".format(str(requested_qd), str(qd))) if not set_device_parameter(devices, "/sys/block/%s/queue/scheduler", "noop"): set_device_parameter(devices, "/sys/block/%s/queue/scheduler", "none") if __name__ == "__main__": parser = argparse.ArgumentParser(description="fio.py") parser.add_argument("-i", "--io-size", type=int, help="The desired I/O size in bytes.", required=True) parser.add_argument("-p", "--protocol", type=str, help="The protocol we are testing against. One of iscsi or nvmf.", required=True) parser.add_argument("-d", "--queue-depth", type=int, help="The desired queue depth for each job.", required=True) parser.add_argument("-t", "--test-type", type=str, help="The fio I/O pattern to run. e.g. read, randwrite, randrw.", required=True) parser.add_argument("-r", "--runtime", type=int, help="Time in seconds to run the workload.", required=True) parser.add_argument("-n", "--num-jobs", type=int, help="The number of fio jobs to run in your workload. default 1.", default=1) parser.add_argument("-v", "--verify", action="store_true", help="Supply this argument to verify the I/O.", default=False) args = parser.parse_args() if args.protocol.lower() != "nvmf" and args.protocol.lower() != "iscsi": parser.error("Protocol must be one of the following: nvmf, iscsi.") main(args.io_size, args.protocol, args.queue_depth, args.test_type, args.runtime, args.num_jobs, args.verify)