The following changes since commit d5c4f97458d59689c3d1a13831519617d000fb19: arch-arm: Consider armv7ve arch as well (2019-11-05 06:41:55 -0700) are available in the Git repository at: git://git.kernel.dk/fio.git master for you to fetch changes up to cc16261082a4d0f027cef9d019ad023129bf6012: Merge branch 'testing' of https://github.com/vincentkfu/fio (2019-11-06 17:04:17 -0700) ---------------------------------------------------------------- Jens Axboe (1): Merge branch 'testing' of https://github.com/vincentkfu/fio Vincent Fu (12): t/iee754: add return value t/readonly: replace shell script with python script t/strided.py: change LFSR tests t/steadystate_tests: better support automated testing t/sgunmap-test.py: drop six.moves dependency t/stest: non-zero exit value on failure t/jobs: fixup t0002 test jobs t/jobs: use current directory for test file for t0003 and t0004 t/jobs: drop time_based in t0007 t/jobs: clean up t0009 and use only 4 CPUs t/jobs: fix t0011 syntax error t/run-fio-tests: a script to automate running fio tests t/ieee754.c | 15 +- t/jobs/readonly-r.fio | 5 - t/jobs/readonly-t.fio | 5 - t/jobs/readonly-w.fio | 5 - ...t0002-13af05ae-post => t0002-13af05ae-post.fio} | 48 +- .../{t0002-13af05ae-pre => t0002-13af05ae-pre.fio} | 46 +- t/jobs/t0003-0ae2c6e1-post.fio | 2 +- t/jobs/t0003-0ae2c6e1-pre.fio | 2 +- t/jobs/t0004-8a99fdf6.fio | 2 +- t/jobs/t0007-37cf9e3c.fio | 1 - t/jobs/t0009-f8b0bd10.fio | 6 +- t/jobs/t0011-5d2788d5.fio | 2 +- t/readonly.py | 138 +++++ t/readonly.sh | 84 --- t/run-fio-tests.py | 676 +++++++++++++++++++++ t/sgunmap-test.py | 1 - t/steadystate_tests.py | 40 +- t/stest.c | 15 +- t/strided.py | 36 +- 19 files changed, 936 insertions(+), 193 deletions(-) delete mode 100644 t/jobs/readonly-r.fio delete mode 100644 t/jobs/readonly-t.fio delete mode 100644 t/jobs/readonly-w.fio rename t/jobs/{t0002-13af05ae-post => t0002-13af05ae-post.fio} (85%) rename t/jobs/{t0002-13af05ae-pre => t0002-13af05ae-pre.fio} (85%) create mode 100755 t/readonly.py delete mode 100755 t/readonly.sh create mode 100755 t/run-fio-tests.py --- Diff of recent changes: diff --git a/t/ieee754.c b/t/ieee754.c index 3898ab74..b6526394 100644 --- a/t/ieee754.c +++ b/t/ieee754.c @@ -1,21 +1,26 @@ #include <stdio.h> #include "../lib/ieee754.h" -static double values[] = { -17.23, 17.23, 123.4567, 98765.4321, 0.0 }; +static double values[] = { -17.23, 17.23, 123.4567, 98765.4321, + 3.14159265358979323, 0.0 }; int main(int argc, char *argv[]) { uint64_t i; - double f; - int j; + double f, delta; + int j, differences = 0; j = 0; do { i = fio_double_to_uint64(values[j]); f = fio_uint64_to_double(i); - printf("%f -> %f\n", values[j], f); + delta = values[j] - f; + printf("%26.20lf -> %26.20lf, delta = %26.20lf\n", values[j], + f, delta); + if (f != values[j]) + differences++; j++; } while (values[j] != 0.0); - return 0; + return differences; } diff --git a/t/jobs/readonly-r.fio b/t/jobs/readonly-r.fio deleted file mode 100644 index 34ba9b5e..00000000 --- a/t/jobs/readonly-r.fio +++ /dev/null @@ -1,5 +0,0 @@ -[test] -filename=${DUT} -rw=randread -time_based -runtime=1s diff --git a/t/jobs/readonly-t.fio b/t/jobs/readonly-t.fio deleted file mode 100644 index f3e093c1..00000000 --- a/t/jobs/readonly-t.fio +++ /dev/null @@ -1,5 +0,0 @@ -[test] -filename=${DUT} -rw=randtrim -time_based -runtime=1s diff --git a/t/jobs/readonly-w.fio b/t/jobs/readonly-w.fio deleted file mode 100644 index 26029ef2..00000000 --- a/t/jobs/readonly-w.fio +++ /dev/null @@ -1,5 +0,0 @@ -[test] -filename=${DUT} -rw=randwrite -time_based -runtime=1s diff --git a/t/jobs/t0002-13af05ae-post b/t/jobs/t0002-13af05ae-post.fio similarity index 85% rename from t/jobs/t0002-13af05ae-post rename to t/jobs/t0002-13af05ae-post.fio index b7d5bab9..d141d406 100644 --- a/t/jobs/t0002-13af05ae-post +++ b/t/jobs/t0002-13af05ae-post.fio @@ -1,24 +1,24 @@ -[global] -ioengine=libaio -direct=1 -filename=/dev/fioa -iodepth=128 -size=1G -loops=1 -group_reporting=1 -readwrite=read -do_verify=1 -verify=md5 -verify_fatal=1 -numjobs=1 -thread -bssplit=512/50:1M/50 - -[thread0] -offset=0G - -[thread-mix0] -offset=4G -size=1G -readwrite=rw -bsrange=512:1M +[global] +ioengine=libaio +direct=1 +filename=t0002file +iodepth=128 +size=1G +loops=1 +group_reporting=1 +readwrite=read +do_verify=1 +verify=md5 +verify_fatal=1 +numjobs=1 +thread +bssplit=512/50:1M/50 + +[thread0] +offset=0G + +[thread-mix0] +offset=4G +size=1G +readwrite=rw +bsrange=512:1M diff --git a/t/jobs/t0002-13af05ae-pre b/t/jobs/t0002-13af05ae-pre.fio similarity index 85% rename from t/jobs/t0002-13af05ae-pre rename to t/jobs/t0002-13af05ae-pre.fio index 77dd48fd..0e044d47 100644 --- a/t/jobs/t0002-13af05ae-pre +++ b/t/jobs/t0002-13af05ae-pre.fio @@ -1,23 +1,23 @@ -[global] -ioengine=libaio -direct=1 -filename=/dev/fioa -iodepth=128 -size=1G -loops=1 -group_reporting=1 -readwrite=write -do_verify=0 -verify=md5 -numjobs=1 -thread -bssplit=512/50:1M/50 - -[thread0] -offset=0G - -[thread-mix0] -offset=4G -readwrite=rw -size=1G -bsrange=512:1M +[global] +ioengine=libaio +direct=1 +filename=t0002file +iodepth=128 +size=1G +loops=1 +group_reporting=1 +readwrite=write +do_verify=0 +verify=md5 +numjobs=1 +thread +bssplit=512/50:1M/50 + +[thread0] +offset=0G + +[thread-mix0] +offset=4G +readwrite=rw +size=1G +bsrange=512:1M diff --git a/t/jobs/t0003-0ae2c6e1-post.fio b/t/jobs/t0003-0ae2c6e1-post.fio index 8bc4f05a..4e7887a3 100644 --- a/t/jobs/t0003-0ae2c6e1-post.fio +++ b/t/jobs/t0003-0ae2c6e1-post.fio @@ -3,7 +3,7 @@ [global] ioengine=libaio direct=1 -filename=/tmp/foo +filename=foo iodepth=128 size=1M loops=1 diff --git a/t/jobs/t0003-0ae2c6e1-pre.fio b/t/jobs/t0003-0ae2c6e1-pre.fio index 46f452cb..a9a9f319 100644 --- a/t/jobs/t0003-0ae2c6e1-pre.fio +++ b/t/jobs/t0003-0ae2c6e1-pre.fio @@ -1,7 +1,7 @@ [global] ioengine=libaio direct=1 -filename=/tmp/foo +filename=foo iodepth=128 size=10M loops=1 diff --git a/t/jobs/t0004-8a99fdf6.fio b/t/jobs/t0004-8a99fdf6.fio index 09ae9b26..0fc3e0de 100644 --- a/t/jobs/t0004-8a99fdf6.fio +++ b/t/jobs/t0004-8a99fdf6.fio @@ -3,7 +3,7 @@ [global] ioengine=libaio direct=1 -filename=/tmp/foo +filename=foo iodepth=128 size=10M loops=1 diff --git a/t/jobs/t0007-37cf9e3c.fio b/t/jobs/t0007-37cf9e3c.fio index fd70c21c..d3c98751 100644 --- a/t/jobs/t0007-37cf9e3c.fio +++ b/t/jobs/t0007-37cf9e3c.fio @@ -4,7 +4,6 @@ size=128mb rw=read:512k bs=1m -time_based norandommap write_iolog=log direct=1 diff --git a/t/jobs/t0009-f8b0bd10.fio b/t/jobs/t0009-f8b0bd10.fio index 90e07ad8..20f376e6 100644 --- a/t/jobs/t0009-f8b0bd10.fio +++ b/t/jobs/t0009-f8b0bd10.fio @@ -16,21 +16,21 @@ numjobs=1 #numjobs=24 # number_ios=1 # runtime=216000 -runtime=3600 +#runtime=3600 time_based=1 group_reporting=1 thread gtod_reduce=1 iodepth_batch=4 iodepth_batch_complete=4 -cpus_allowed=0-5 +cpus_allowed=0-3 cpus_allowed_policy=split rw=randwrite verify=crc32c-intel verify_backlog=1m do_verify=1 verify_async=6 -verify_async_cpus=0-5 +verify_async_cpus=0-3 runtime=1m [4_KiB_RR_drive_r] diff --git a/t/jobs/t0011-5d2788d5.fio b/t/jobs/t0011-5d2788d5.fio index 09861f7f..50daf612 100644 --- a/t/jobs/t0011-5d2788d5.fio +++ b/t/jobs/t0011-5d2788d5.fio @@ -1,7 +1,7 @@ # Expected results: no parse warnings, runs and with roughly 1/8 iops between # the two jobs. # Buggy result: parse warning on flow value overflow, no 1/8 division between - jobs. +# jobs. # [global] bs=4k diff --git a/t/readonly.py b/t/readonly.py new file mode 100755 index 00000000..43686c9c --- /dev/null +++ b/t/readonly.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (c) 2019 Western Digital Corporation or its affiliates. +# +# +# readonly.py +# +# Do some basic tests of the --readonly paramter +# +# USAGE +# python readonly.py [-f fio-executable] +# +# EXAMPLES +# python t/readonly.py +# python t/readonly.py -f ./fio +# +# REQUIREMENTS +# Python 3.5+ +# +# + +import sys +import argparse +import subprocess + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument('-f', '--fio', + help='path to fio executable (e.g., ./fio)') + args = parser.parse_args() + + return args + + +def run_fio(fio, test, index): + fio_args = [ + "--name=readonly", + "--ioengine=null", + "--time_based", + "--runtime=1s", + "--size=1M", + "--rw={rw}".format(**test), + ] + if 'readonly-pre' in test: + fio_args.insert(0, "--readonly") + if 'readonly-post' in test: + fio_args.append("--readonly") + + output = subprocess.run([fio] + fio_args, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + return output + + +def check_output(output, test): + expect_error = False + if 'readonly-pre' in test or 'readonly-post' in test: + if 'write' in test['rw'] or 'trim' in test['rw']: + expect_error = True + +# print(output.stdout) +# print(output.stderr) + + if output.returncode == 0: + if expect_error: + return False + else: + return True + else: + if expect_error: + return True + else: + return False + + +if __name__ == '__main__': + args = parse_args() + + tests = [ + { + "rw": "randread", + "readonly-pre": 1, + }, + { + "rw": "randwrite", + "readonly-pre": 1, + }, + { + "rw": "randtrim", + "readonly-pre": 1, + }, + { + "rw": "randread", + "readonly-post": 1, + }, + { + "rw": "randwrite", + "readonly-post": 1, + }, + { + "rw": "randtrim", + "readonly-post": 1, + }, + { + "rw": "randread", + }, + { + "rw": "randwrite", + }, + { + "rw": "randtrim", + }, + ] + + index = 1 + passed = 0 + failed = 0 + + if args.fio: + fio_path = args.fio + else: + fio_path = 'fio' + + for test in tests: + output = run_fio(fio_path, test, index) + status = check_output(output, test) + print("Test {0} {1}".format(index, ("PASSED" if status else "FAILED"))) + if status: + passed = passed + 1 + else: + failed = failed + 1 + index = index + 1 + + print("{0} tests passed, {1} failed".format(passed, failed)) + + sys.exit(failed) diff --git a/t/readonly.sh b/t/readonly.sh deleted file mode 100755 index d7094146..00000000 --- a/t/readonly.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/bash -# -# Do some basic test of the --readonly parameter -# -# DUT should be a device that accepts read, write, and trim operations -# -# Example usage: -# -# DUT=/dev/fioa t/readonly.sh -# -TESTNUM=1 - -# -# The first parameter is the return code -# The second parameter is 0 if the return code should be 0 -# positive if the return code should be positive -# -check () { - echo "********************" - - if [ $2 -gt 0 ]; then - if [ $1 -eq 0 ]; then - echo "Test $TESTNUM failed" - echo "********************" - exit 1 - else - echo "Test $TESTNUM passed" - fi - else - if [ $1 -gt 0 ]; then - echo "Test $TESTNUM failed" - echo "********************" - exit 1 - else - echo "Test $TESTNUM passed" - fi - fi - - echo "********************" - echo - TESTNUM=$((TESTNUM+1)) -} - -./fio --name=test --filename=$DUT --rw=randread --readonly --time_based --runtime=1s &> /dev/null -check $? 0 -./fio --name=test --filename=$DUT --rw=randwrite --readonly --time_based --runtime=1s &> /dev/null -check $? 1 -./fio --name=test --filename=$DUT --rw=randtrim --readonly --time_based --runtime=1s &> /dev/null -check $? 1 - -./fio --name=test --filename=$DUT --readonly --rw=randread --time_based --runtime=1s &> /dev/null -check $? 0 -./fio --name=test --filename=$DUT --readonly --rw=randwrite --time_based --runtime=1s &> /dev/null -check $? 1 -./fio --name=test --filename=$DUT --readonly --rw=randtrim --time_based --runtime=1s &> /dev/null -check $? 1 - -./fio --name=test --filename=$DUT --rw=randread --time_based --runtime=1s &> /dev/null -check $? 0 -./fio --name=test --filename=$DUT --rw=randwrite --time_based --runtime=1s &> /dev/null -check $? 0 -./fio --name=test --filename=$DUT --rw=randtrim --time_based --runtime=1s &> /dev/null -check $? 0 - -./fio t/jobs/readonly-r.fio --readonly &> /dev/null -check $? 0 -./fio t/jobs/readonly-w.fio --readonly &> /dev/null -check $? 1 -./fio t/jobs/readonly-t.fio --readonly &> /dev/null -check $? 1 - -./fio --readonly t/jobs/readonly-r.fio &> /dev/null -check $? 0 -./fio --readonly t/jobs/readonly-w.fio &> /dev/null -check $? 1 -./fio --readonly t/jobs/readonly-t.fio &> /dev/null -check $? 1 - -./fio t/jobs/readonly-r.fio &> /dev/null -check $? 0 -./fio t/jobs/readonly-w.fio &> /dev/null -check $? 0 -./fio t/jobs/readonly-t.fio &> /dev/null -check $? 0 diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py new file mode 100755 index 00000000..1b8ca0a2 --- /dev/null +++ b/t/run-fio-tests.py @@ -0,0 +1,676 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (c) 2019 Western Digital Corporation or its affiliates. +# +""" +# run-fio-tests.py +# +# Automate running of fio tests +# +# USAGE +# python3 run-fio-tests.py [-r fio-root] [-f fio-path] [-a artifact-root] +# [--skip # # #...] [--run-only # # #...] +# +# +# EXAMPLE +# # git clone [fio-repository] +# # cd fio +# # make -j +# # python3 t/run-fio-tests.py +# +# +# REQUIREMENTS +# - Python 3 +# - Linux (libaio ioengine, zbd tests, etc) +# - The artifact directory must be on a file system that accepts 512-byte IO +# (t0002, t0003, t0004). +# - The artifact directory needs to be on an SSD. Otherwise tests that carry +# out file-based IO will trigger a timeout (t0006). +# - 4 CPUs (t0009) +# - SciPy (steadystate_tests.py) +# - libzbc (zbd tests) +# - root privileges (zbd test) +# - kernel 4.19 or later for zoned null block devices (zbd tests) +# - CUnit support (unittests) +# +""" + +# +# TODO run multiple tests simultaneously +# TODO Add sgunmap tests (requires SAS SSD) +# TODO automatically detect dependencies and skip tests accordingly +# + +import os +import sys +import json +import time +import logging +import argparse +import subprocess +from pathlib import Path + + +class FioTest(object): + """Base for all fio tests.""" + + def __init__(self, exe_path, parameters, success): + self.exe_path = exe_path + self.parameters = parameters + self.success = success + self.output = {} + self.artifact_root = None + self.testnum = None + self.test_dir = None + self.passed = True + self.failure_reason = '' + + def setup(self, artifact_root, testnum): + self.artifact_root = artifact_root + self.testnum = testnum + self.test_dir = os.path.join(artifact_root, "{:04d}".format(testnum)) + if not os.path.exists(self.test_dir): + os.mkdir(self.test_dir) + + self.command_file = os.path.join( + self.test_dir, + "{0}.command".format(os.path.basename(self.exe_path))) + self.stdout_file = os.path.join( + self.test_dir, + "{0}.stdout".format(os.path.basename(self.exe_path))) + self.stderr_file = os.path.join( + self.test_dir, + "{0}.stderr".format(os.path.basename(self.exe_path))) + self.exticode_file = os.path.join( + self.test_dir, + "{0}.exitcode".format(os.path.basename(self.exe_path))) + + def run(self): + raise NotImplementedError() + + def check_result(self): + raise NotImplementedError() + + +class FioExeTest(FioTest): + """Test consists of an executable binary or script""" + + def __init__(self, exe_path, parameters, success): + """Construct a FioExeTest which is a FioTest consisting of an + executable binary or script. + + exe_path: location of executable binary or script + parameters: list of parameters for executable + success: Definition of test success + """ + + FioTest.__init__(self, exe_path, parameters, success) + + def setup(self, artifact_root, testnum): + super(FioExeTest, self).setup(artifact_root, testnum) + + def run(self): + if self.parameters: + command = [self.exe_path] + self.parameters + else: + command = [self.exe_path] + command_file = open(self.command_file, "w+") + command_file.write("%s\n" % command) + command_file.close() + + stdout_file = open(self.stdout_file, "w+") + stderr_file = open(self.stderr_file, "w+") + exticode_file = open(self.exticode_file, "w+") + try: + # Avoid using subprocess.run() here because when a timeout occurs, + # fio will be stopped with SIGKILL. This does not give fio a + # chance to clean up and means that child processes may continue + # running and submitting IO. + proc = subprocess.Popen(command, + stdout=stdout_file, + stderr=stderr_file, + cwd=self.test_dir, + universal_newlines=True) + proc.communicate(timeout=self.success['timeout']) + exticode_file.write('{0}\n'.format(proc.returncode)) + logging.debug("return code: %d" % proc.returncode) + self.output['proc'] = proc + except subprocess.TimeoutExpired: + proc.terminate() + proc.communicate() + assert proc.poll() + self.output['failure'] = 'timeout' + except Exception: + if not proc.poll(): + proc.terminate() + proc.communicate() + self.output['failure'] = 'exception' + self.output['exc_info'] = sys.exc_info() + finally: + stdout_file.close() + stderr_file.close() + exticode_file.close() + + def check_result(self): + if 'proc' not in self.output: + if self.output['failure'] == 'timeout': + self.failure_reason = "{0} timeout,".format(self.failure_reason) + else: + assert self.output['failure'] == 'exception' + self.failure_reason = '{0} exception: {1}, {2}'.format( + self.failure_reason, self.output['exc_info'][0], + self.output['exc_info'][1]) + + self.passed = False + return + + if 'zero_return' in self.success: + if self.success['zero_return']: + if self.output['proc'].returncode != 0: + self.passed = False + self.failure_reason = "{0} non-zero return code,".format(self.failure_reason) + else: + if self.output['proc'].returncode == 0: + self.failure_reason = "{0} zero return code,".format(self.failure_reason) + self.passed = False + + if 'stderr_empty' in self.success: + stderr_size = os.path.getsize(self.stderr_file) + if self.success['stderr_empty']: + if stderr_size != 0: + self.failure_reason = "{0} stderr not empty,".format(self.failure_reason) + self.passed = False + else: + if stderr_size == 0: + self.failure_reason = "{0} stderr empty,".format(self.failure_reason) + self.passed = False + + +class FioJobTest(FioExeTest): + """Test consists of a fio job""" + + def __init__(self, fio_path, fio_job, success, fio_pre_job=None, + fio_pre_success=None, output_format="normal"): + """Construct a FioJobTest which is a FioExeTest consisting of a + single fio job file with an optional setup step. + + fio_path: location of fio executable + fio_job: location of fio job file + success: Definition of test success + fio_pre_job: fio job for preconditioning + fio_pre_success: Definition of test success for fio precon job + output_format: normal (default), json, jsonplus, or terse + """ + + self.fio_job = fio_job + self.fio_pre_job = fio_pre_job + self.fio_pre_success = fio_pre_success if fio_pre_success else success + self.output_format = output_format + self.precon_failed = False + self.json_data = None + self.fio_output = "{0}.output".format(os.path.basename(self.fio_job)) + self.fio_args = [ + "--output-format={0}".format(self.output_format), + "--output={0}".format(self.fio_output), + self.fio_job, + ] + FioExeTest.__init__(self, fio_path, self.fio_args, success) + + def setup(self, artifact_root, testnum): + super(FioJobTest, self).setup(artifact_root, testnum) + + self.command_file = os.path.join( + self.test_dir, + "{0}.command".format(os.path.basename(self.fio_job))) + self.stdout_file = os.path.join( + self.test_dir, + "{0}.stdout".format(os.path.basename(self.fio_job))) + self.stderr_file = os.path.join( + self.test_dir, + "{0}.stderr".format(os.path.basename(self.fio_job))) + self.exticode_file = os.path.join( + self.test_dir, + "{0}.exitcode".format(os.path.basename(self.fio_job))) + + def run_pre_job(self): + precon = FioJobTest(self.exe_path, self.fio_pre_job, + self.fio_pre_success, + output_format=self.output_format) + precon.setup(self.artifact_root, self.testnum) + precon.run() + precon.check_result() + self.precon_failed = not precon.passed + self.failure_reason = precon.failure_reason + + def run(self): + if self.fio_pre_job: + self.run_pre_job() + + if not self.precon_failed: + super(FioJobTest, self).run() + else: + logging.debug("precondition step failed") + + def check_result(self): + if self.precon_failed: + self.passed = False + self.failure_reason = "{0} precondition step failed,".format(self.failure_reason) + return + + super(FioJobTest, self).check_result() + + if 'json' in self.output_format: + output_file = open(os.path.join(self.test_dir, self.fio_output), "r") + file_data = output_file.read() + output_file.close() + try: + self.json_data = json.loads(file_data) + except json.JSONDecodeError: + self.failure_reason = "{0} unable to decode JSON data,".format(self.failure_reason) + self.passed = False + + +class FioJobTest_t0005(FioJobTest): + """Test consists of fio test job t0005 + Confirm that read['io_kbytes'] == write['io_kbytes'] == 102400""" + + def check_result(self): + super(FioJobTest_t0005, self).check_result() + + if not self.passed: + return + + if self.json_data['jobs'][0]['read']['io_kbytes'] != 102400: + self.failure_reason = "{0} bytes read mismatch,".format(self.failure_reason) + self.passed = False + if self.json_data['jobs'][0]['write']['io_kbytes'] != 102400: + self.failure_reason = "{0} bytes written mismatch,".format(self.failure_reason) + self.passed = False + + +class FioJobTest_t0006(FioJobTest): + """Test consists of fio test job t0006 + Confirm that read['io_kbytes'] ~ 2*write['io_kbytes']""" + + def check_result(self): + super(FioJobTest_t0006, self).check_result() + + if not self.passed: + return + + ratio = self.json_data['jobs'][0]['read']['io_kbytes'] \ + / self.json_data['jobs'][0]['write']['io_kbytes'] + logging.debug("ratio: %f" % ratio) + if ratio < 1.99 or ratio > 2.01: + self.failure_reason = "{0} read/write ratio mismatch,".format(self.failure_reason) + self.passed = False + + +class FioJobTest_t0007(FioJobTest): + """Test consists of fio test job t0007 + Confirm that read['io_kbytes'] = 87040""" + + def check_result(self): + super(FioJobTest_t0007, self).check_result() + + if not self.passed: + return + + if self.json_data['jobs'][0]['read']['io_kbytes'] != 87040: + self.failure_reason = "{0} bytes read mismatch,".format(self.failure_reason) + self.passed = False + + +class FioJobTest_t0008(FioJobTest): + """Test consists of fio test job t0008 + Confirm that read['io_kbytes'] = 32768 and that + write['io_kbytes'] ~ 16568 + + I did runs with fio-ae2fafc8 and saw write['io_kbytes'] values of + 16585, 16588. With two runs of fio-3.16 I obtained 16568""" + + def check_result(self): + super(FioJobTest_t0008, self).check_result() + + if not self.passed: + return + + ratio = self.json_data['jobs'][0]['write']['io_kbytes'] / 16568 + logging.debug("ratio: %f" % ratio) + + if ratio < 0.99 or ratio > 1.01: + self.failure_reason = "{0} bytes written mismatch,".format(self.failure_reason) + self.passed = False + if self.json_data['jobs'][0]['read']['io_kbytes'] != 32768: + self.failure_reason = "{0} bytes read mismatch,".format(self.failure_reason) + self.passed = False + + +class FioJobTest_t0009(FioJobTest): + """Test consists of fio test job t0009 + Confirm that runtime >= 60s""" + + def check_result(self): + super(FioJobTest_t0009, self).check_result() + + if not self.passed: + return + + logging.debug('elapsed: %d' % self.json_data['jobs'][0]['elapsed']) + + if self.json_data['jobs'][0]['elapsed'] < 60: + self.failure_reason = "{0} elapsed time mismatch,".format(self.failure_reason) + self.passed = False + + +class FioJobTest_t0011(FioJobTest): + """Test consists of fio test job t0009 + Confirm that job0 iops == 1000 + and that job1_iops / job0_iops ~ 8 + With two runs of fio-3.16 I observed a ratio of 8.3""" + + def check_result(self): + super(FioJobTest_t0011, self).check_result() + + if not self.passed: + return + + iops1 = self.json_data['jobs'][0]['read']['iops'] + iops2 = self.json_data['jobs'][1]['read']['iops'] + ratio = iops2 / iops1 + logging.debug("ratio: %f" % ratio) + + if iops1 < 999 or iops1 > 1001: + self.failure_reason = "{0} iops value mismatch,".format(self.failure_reason) + self.passed = False + + if ratio < 7 or ratio > 9: + self.failure_reason = "{0} iops ratio mismatch,".format(self.failure_reason) + self.passed = False + + +SUCCESS_DEFAULT = { + 'zero_return': True, + 'stderr_empty': True, + 'timeout': 300, + } +SUCCESS_NONZERO = { + 'zero_return': False, + 'stderr_empty': False, + 'timeout': 300, + } +SUCCESS_STDERR = { + 'zero_return': True, + 'stderr_empty': False, + 'timeout': 300, + } +TEST_LIST = [ + { + 'test_id': 1, + 'test_class': FioJobTest, + 'job': 't0001-52c58027.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + }, + { + 'test_id': 2, + 'test_class': FioJobTest, + 'job': 't0002-13af05ae-post.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': 't0002-13af05ae-pre.fio', + 'pre_success': None, + }, + { + 'test_id': 3, + 'test_class': FioJobTest, + 'job': 't0003-0ae2c6e1-post.fio', + 'success': SUCCESS_NONZERO, + 'pre_job': 't0003-0ae2c6e1-pre.fio', + 'pre_success': SUCCESS_DEFAULT, + }, + { + 'test_id': 4, + 'test_class': FioJobTest, + 'job': 't0004-8a99fdf6.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + }, + { + 'test_id': 5, + 'test_class': FioJobTest_t0005, + 'job': 't0005-f7078f7b.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'output_format': 'json', + }, + { + 'test_id': 6, + 'test_class': FioJobTest_t0006, + 'job': 't0006-82af2a7c.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'output_format': 'json', + }, + { + 'test_id': 7, + 'test_class': FioJobTest_t0007, + 'job': 't0007-37cf9e3c.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'output_format': 'json', + }, + { + 'test_id': 8, + 'test_class': FioJobTest_t0008, + 'job': 't0008-ae2fafc8.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'output_format': 'json', + }, + { + 'test_id': 9, + 'test_class': FioJobTest_t0009, + 'job': 't0009-f8b0bd10.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'output_format': 'json', + }, + { + 'test_id': 10, + 'test_class': FioJobTest, + 'job': 't0010-b7aae4ba.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + }, + { + 'test_id': 11, + 'test_class': FioJobTest_t0011, + 'job': 't0011-5d2788d5.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'output_format': 'json', + }, + { + 'test_id': 1000, + 'test_class': FioExeTest, + 'exe': 't/axmap', + 'parameters': None, + 'success': SUCCESS_DEFAULT, + }, + { + 'test_id': 1001, + 'test_class': FioExeTest, + 'exe': 't/ieee754', + 'parameters': None, + 'success': SUCCESS_DEFAULT, + }, + { + 'test_id': 1002, + 'test_class': FioExeTest, + 'exe': 't/lfsr-test', + 'parameters': ['0xFFFFFF', '0', '0', 'verify'], + 'success': SUCCESS_STDERR, + }, + { + 'test_id': 1003, + 'test_class': FioExeTest, + 'exe': 't/readonly.py', + 'parameters': ['-f', '{fio_path}'], + 'success': SUCCESS_DEFAULT, + }, + { + 'test_id': 1004, + 'test_class': FioExeTest, + 'exe': 't/steadystate_tests.py', + 'parameters': ['{fio_path}'], + 'success': SUCCESS_DEFAULT, + }, + { + 'test_id': 1005, + 'test_class': FioExeTest, + 'exe': 't/stest', + 'parameters': None, + 'success': SUCCESS_STDERR, + }, + { + 'test_id': 1006, + 'test_class': FioExeTest, + 'exe': 't/strided.py', + 'parameters': ['{fio_path}'], + 'success': SUCCESS_DEFAULT, + }, + { + 'test_id': 1007, + 'test_class': FioExeTest, + 'exe': 't/zbd/run-tests-against-regular-nullb', + 'parameters': None, + 'success': SUCCESS_DEFAULT, + }, + { + 'test_id': 1008, + 'test_class': FioExeTest, + 'exe': 't/zbd/run-tests-against-zoned-nullb', + 'parameters': None, + 'success': SUCCESS_DEFAULT, + }, + { + 'test_id': 1009, + 'test_class': FioExeTest, + 'exe': 'unittests/unittest', + 'parameters': None, + 'success': SUCCESS_DEFAULT, + }, +] + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument('-r', '--fio-root', + help='fio root path') + parser.add_argument('-f', '--fio', + help='path to fio 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') + args = parser.parse_args() + + return args + + +def main(): + logging.basicConfig(level=logging.INFO) + + args = parse_args() + if args.fio_root: + fio_root = args.fio_root + else: + fio_root = Path(__file__).absolute().parent.parent + logging.debug("fio_root: %s" % fio_root) + + if args.fio: + fio_path = args.fio + else: + fio_path = os.path.join(fio_root, "fio") + logging.debug("fio_path: %s" % fio_path) + + artifact_root = args.artifact_root if args.artifact_root else \ + "fio-test-{0}".format(time.strftime("%Y%m%d-%H%M%S")) + os.mkdir(artifact_root) + print("Artifact directory is %s" % artifact_root) + + passed = 0 + failed = 0 + skipped = 0 + + for config in TEST_LIST: + if (args.skip and config['test_id'] in args.skip) or \ + (args.run_only and config['test_id'] not in args.run_only): + skipped = skipped + 1 + print("Test {0} SKIPPED".format(config['test_id'])) + continue + + if issubclass(config['test_class'], FioJobTest): + if config['pre_job']: + fio_pre_job = os.path.join(fio_root, 't', 'jobs', + config['pre_job']) + else: + fio_pre_job = None + if config['pre_success']: + fio_pre_success = config['pre_success'] + else: + fio_pre_success = None + if 'output_format' in config: + output_format = config['output_format'] + else: + output_format = 'normal' + test = config['test_class']( + fio_path, + os.path.join(fio_root, 't', 'jobs', config['job']), + config['success'], + fio_pre_job=fio_pre_job, + fio_pre_success=fio_pre_success, + output_format=output_format) + elif issubclass(config['test_class'], FioExeTest): + exe_path = os.path.join(fio_root, config['exe']) + if config['parameters']: + parameters = [p.format(fio_path=fio_path) for p in config['parameters']] + else: + parameters = None + test = config['test_class'](exe_path, parameters, + config['success']) + else: + print("Test {0} FAILED: unable to process test config".format(config['test_id'])) + failed = failed + 1 + continue + + test.setup(artifact_root, config['test_id']) + test.run() + test.check_result() + if test.passed: + result = "PASSED" + passed = passed + 1 + else: + result = "FAILED: {0}".format(test.failure_reason) + failed = failed + 1 + print("Test {0} {1}".format(config['test_id'], result)) + + print("{0} test(s) passed, {1} failed, {2} skipped".format(passed, failed, skipped)) + + sys.exit(failed) + + +if __name__ == '__main__': + main() diff --git a/t/sgunmap-test.py b/t/sgunmap-test.py index d2caa5fd..f8f10ab3 100755 --- a/t/sgunmap-test.py +++ b/t/sgunmap-test.py @@ -45,7 +45,6 @@ import json import argparse import traceback import subprocess -from six.moves import range def parse_args(): diff --git a/t/steadystate_tests.py b/t/steadystate_tests.py index 50254dcc..53b0f35e 100755 --- a/t/steadystate_tests.py +++ b/t/steadystate_tests.py @@ -1,5 +1,5 @@ -#!/usr/bin/python2.7 -# Note: this script is python2 and python 3 compatible. +#!/usr/bin/env python +# Note: this script is python2 and python3 compatible. # # steadystate_tests.py # @@ -24,12 +24,10 @@ from __future__ import print_function import os import sys import json -import uuid import pprint import argparse import subprocess from scipy import stats -from six.moves import range def parse_args(): parser = argparse.ArgumentParser() @@ -53,7 +51,7 @@ def check(data, iops, slope, pct, limit, dur, criterion): m, intercept, r_value, p_value, std_err = stats.linregress(x,data) m = abs(m) if pct: - target = m / mean * 100 + target = (m / mean * 100) if mean != 0 else 0 criterion = criterion[:-1] else: target = m @@ -68,7 +66,11 @@ def check(data, iops, slope, pct, limit, dur, criterion): target = maxdev criterion = float(criterion) - return (abs(target - criterion) / criterion < 0.005), target < limit, mean, target + if criterion == 0.0: + objsame = False + else: + objsame = abs(target - criterion) / criterion < 0.005 + return (objsame, target < limit, mean, target) if __name__ == '__main__': @@ -76,6 +78,9 @@ if __name__ == '__main__': pp = pprint.PrettyPrinter(indent=4) + passed = 0 + failed = 0 + # # test option parsing # @@ -96,8 +101,10 @@ if __name__ == '__main__': output = subprocess.check_output([args.fio] + test['args']) if test['output'] in output.decode(): print("PASSED '{0}' found with arguments {1}".format(test['output'], test['args'])) + passed = passed + 1 else: print("FAILED '{0}' NOT found with arguments {1}".format(test['output'], test['args'])) + failed = failed + 1 # # test some read workloads @@ -119,7 +126,7 @@ if __name__ == '__main__': if args.read == None: if os.name == 'posix': args.read = '/dev/zero' - extra = [ "--size=134217728" ] # 128 MiB + extra = [ "--size=128M" ] else: print("ERROR: file for read testing must be specified on non-posix systems") sys.exit(1) @@ -129,7 +136,7 @@ if __name__ == '__main__': jobnum = 0 for job in reads: - tf = uuid.uuid4().hex + tf = "steadystate_job{0}.json".format(jobnum) parameters = [ "--name=job{0}".format(jobnum) ] parameters.extend(extra) parameters.extend([ "--thread", @@ -160,10 +167,10 @@ if __name__ == '__main__': output = subprocess.call([args.fio] + parameters) with open(tf, 'r') as source: jsondata = json.loads(source.read()) - os.remove(tf) + source.close() for jsonjob in jsondata['jobs']: - line = "job {0}".format(jsonjob['job options']['name']) + line = "{0}".format(jsonjob['job options']['name']) if job['s']: if jsonjob['steadystate']['attained'] == 1: # check runtime >= ss_dur + ss_ramp, check criterion, check criterion < limit @@ -171,6 +178,7 @@ if __name__ == '__main__': actual = jsonjob['read']['runtime'] if mintime > actual: line = 'FAILED ' + line + ' ss attained, runtime {0} < ss_dur {1} + ss_ramp {2}'.format(actual, job['ss_dur'], job['ss_ramp']) + failed = failed + 1 else: line = line + ' ss attained, runtime {0} > ss_dur {1} + ss_ramp {2},'.format(actual, job['ss_dur'], job['ss_ramp']) objsame, met, mean, target = check(data=jsonjob['steadystate']['data'], @@ -182,11 +190,14 @@ if __name__ == '__main__': criterion=jsonjob['steadystate']['criterion']) if not objsame: line = 'FAILED ' + line + ' fio criterion {0} != calculated criterion {1} '.format(jsonjob['steadystate']['criterion'], target) + failed = failed + 1 else: if met: line = 'PASSED ' + line + ' target {0} < limit {1}'.format(target, job['ss_limit']) + passed = passed + 1 else: line = 'FAILED ' + line + ' target {0} < limit {1} but fio reports ss not attained '.format(target, job['ss_limit']) + failed = failed + 1 else: # check runtime, confirm criterion calculation, and confirm that criterion was not met expected = job['timeout'] * 1000 @@ -205,22 +216,31 @@ if __name__ == '__main__': if not objsame: if actual > (job['ss_dur'] + job['ss_ramp'])*1000: line = 'FAILED ' + line + ' fio criterion {0} != calculated criterion {1} '.format(jsonjob['steadystate']['criterion'], target) + failed = failed + 1 else: line = 'PASSED ' + line + ' fio criterion {0} == 0.0 since ss_dur + ss_ramp has not elapsed '.format(jsonjob['steadystate']['criterion']) + passed = passed + 1 else: if met: line = 'FAILED ' + line + ' target {0} < threshold {1} but fio reports ss not attained '.format(target, job['ss_limit']) + failed = failed + 1 else: line = 'PASSED ' + line + ' criterion {0} > threshold {1}'.format(target, job['ss_limit']) + passed = passed + 1 else: expected = job['timeout'] * 1000 actual = jsonjob['read']['runtime'] if abs(expected - actual) < 10: result = 'PASSED ' + passed = passed + 1 else: result = 'FAILED ' + failed = failed + 1 line = result + line + ' no ss, expected runtime {0} ~= actual runtime {1}'.format(expected, actual) print(line) if 'steadystate' in jsonjob: pp.pprint(jsonjob['steadystate']) jobnum += 1 + + print("{0} test(s) PASSED, {1} test(s) FAILED".format(passed,failed)) + sys.exit(failed) diff --git a/t/stest.c b/t/stest.c index 515ae5a5..c6bf2d1e 100644 --- a/t/stest.c +++ b/t/stest.c @@ -25,7 +25,7 @@ static FLIST_HEAD(list); static int do_rand_allocs(void) { - unsigned int size, nr, rounds = 0; + unsigned int size, nr, rounds = 0, ret = 0; unsigned long total; struct elem *e; bool error; @@ -41,6 +41,7 @@ static int do_rand_allocs(void) e = smalloc(size); if (!e) { printf("fail at %lu, size %u\n", total, size); + ret++; break; } e->magic1 = MAGIC1; @@ -65,6 +66,7 @@ static int do_rand_allocs(void) e = smalloc(LARGESMALLOC); if (!e) { error = true; + ret++; printf("failure allocating %u bytes at %lu allocated during sfree phase\n", LARGESMALLOC, total); } @@ -74,18 +76,21 @@ static int do_rand_allocs(void) } } - return 0; + return ret; } int main(int argc, char *argv[]) { + int ret; + arch_init(argv); sinit(); debug_init(); - do_rand_allocs(); - smalloc_debug(0); /* free and total blocks should match */ + ret = do_rand_allocs(); + smalloc_debug(0); /* TODO: check that free and total blocks + ** match */ scleanup(); - return 0; + return ret; } diff --git a/t/strided.py b/t/strided.py index 47ce5523..c159dc0b 100755 --- a/t/strided.py +++ b/t/strided.py @@ -202,20 +202,20 @@ if __name__ == '__main__': # lfsr { "random_generator": "lfsr", - "zonerange": 4096, - "zonesize": 4096, + "zonerange": 4096*1024, + "zonesize": 4096*1024, "bs": 4096, - "offset": 8*4096, - "size": 16*4096, - "io_size": 16*4096, + "offset": 8*4096*1024, + "size": 16*4096*1024, + "io_size": 16*4096*1024, }, { "random_generator": "lfsr", - "zonerange": 4096, - "zonesize": 4096, + "zonerange": 4096*1024, + "zonesize": 4096*1024, "bs": 4096, - "size": 16*4096, - "io_size": 16*4096, + "size": 16*4096*1024, + "io_size": 16*4096*1024, }, { "random_generator": "lfsr", @@ -227,11 +227,11 @@ if __name__ == '__main__': }, { "random_generator": "lfsr", - "zonerange": 4096, - "zonesize": 4*4096, + "zonerange": 4096*1024, + "zonesize": 4*4096*1024, "bs": 4096, - "size": 16*4096, - "io_size": 16*4096, + "size": 16*4096*1024, + "io_size": 16*4096*1024, }, { "random_generator": "lfsr", @@ -243,11 +243,11 @@ if __name__ == '__main__': }, { "random_generator": "lfsr", - "zonerange": 8192, - "zonesize": 4096, + "zonerange": 8192*1024, + "zonesize": 4096*1024, "bs": 4096, - "size": 16*4096, - "io_size": 16*4096, + "size": 16*4096*1024, + "io_size": 16*4096*1024, }, { "random_generator": "lfsr", @@ -313,7 +313,7 @@ if __name__ == '__main__': "zonesize": 8*1024*1024, "bs": 4096, "size": 256*1024*1024, - "io_size": 256*1024*204, + "io_size": 256*1024*1024, }, ]