X-Git-Url: https://git.kernel.dk/?a=blobdiff_plain;f=t%2Frun-fio-tests.py;h=78f435211c9cc23b1f7fd9c6c39ce46768a82a43;hb=ef54f290e8d585a267bd3588ad92d1aedcb4246e;hp=ae2cb0968ff52609ad6801120bc2142e201ab38e;hpb=0a602473807bcdaf28a30441ca05b9e16b6ad128;p=fio.git diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index ae2cb096..78f43521 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -49,6 +49,7 @@ import shutil import logging import argparse import platform +import traceback import subprocess import multiprocessing from pathlib import Path @@ -310,21 +311,15 @@ class FioJobTest(FioExeTest): # # Sometimes fio informational messages are included at the top of the # JSON output, especially under Windows. Try to decode output as JSON - # data, lopping off up to the first four lines + # data, skipping everything until the first { # lines = file_data.splitlines() - for i in range(5): - file_data = '\n'.join(lines[i:]) - try: - self.json_data = json.loads(file_data) - except json.JSONDecodeError: - continue - else: - logging.debug("Test %d: skipped %d lines decoding JSON data", self.testnum, i) - return - - self.failure_reason = "{0} unable to decode JSON data,".format(self.failure_reason) - self.passed = False + file_data = '\n'.join(lines[lines.index("{"):]) + 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): @@ -420,6 +415,205 @@ class FioJobTest_t0009(FioJobTest): self.passed = False +class FioJobTest_t0012(FioJobTest): + """Test consists of fio test job t0012 + Confirm ratios of job iops are 1:5:10 + job1,job2,job3 respectively""" + + def check_result(self): + super(FioJobTest_t0012, self).check_result() + + if not self.passed: + return + + iops_files = [] + for i in range(1,4): + file_data, success = self.get_file(os.path.join(self.test_dir, "{0}_iops.{1}.log".format(os.path.basename(self.fio_job), i))) + + if not success: + self.failure_reason = "{0} unable to open output file,".format(self.failure_reason) + self.passed = False + return + + iops_files.append(file_data.splitlines()) + + # there are 9 samples for job1 and job2, 4 samples for job3 + iops1 = 0.0 + iops2 = 0.0 + iops3 = 0.0 + for i in range(9): + iops1 = iops1 + float(iops_files[0][i].split(',')[1]) + iops2 = iops2 + float(iops_files[1][i].split(',')[1]) + iops3 = iops3 + float(iops_files[2][i].split(',')[1]) + + ratio1 = iops3/iops2 + ratio2 = iops3/iops1 + logging.debug( + "sample {0}: job1 iops={1} job2 iops={2} job3 iops={3} job3/job2={4:.3f} job3/job1={5:.3f}".format( + i, iops1, iops2, iops3, ratio1, ratio2 + ) + ) + + # test job1 and job2 succeeded to recalibrate + if ratio1 < 1 or ratio1 > 3 or ratio2 < 7 or ratio2 > 13: + self.failure_reason = "{0} iops ratio mismatch iops1={1} iops2={2} iops3={3} expected r1~2 r2~10 got r1={4:.3f} r2={5:.3f},".format( + self.failure_reason, iops1, iops2, iops3, ratio1, ratio2 + ) + self.passed = False + return + + +class FioJobTest_t0014(FioJobTest): + """Test consists of fio test job t0014 + Confirm that job1_iops / job2_iops ~ 1:2 for entire duration + and that job1_iops / job3_iops ~ 1:3 for first half of duration. + + The test is about making sure the flow feature can + re-calibrate the activity dynamically""" + + def check_result(self): + super(FioJobTest_t0014, self).check_result() + + if not self.passed: + return + + iops_files = [] + for i in range(1,4): + file_data, success = self.get_file(os.path.join(self.test_dir, "{0}_iops.{1}.log".format(os.path.basename(self.fio_job), i))) + + if not success: + self.failure_reason = "{0} unable to open output file,".format(self.failure_reason) + self.passed = False + return + + iops_files.append(file_data.splitlines()) + + # there are 9 samples for job1 and job2, 4 samples for job3 + iops1 = 0.0 + iops2 = 0.0 + iops3 = 0.0 + for i in range(9): + if i < 4: + iops3 = iops3 + float(iops_files[2][i].split(',')[1]) + elif i == 4: + ratio1 = iops1 / iops2 + ratio2 = iops1 / iops3 + + + if ratio1 < 0.43 or ratio1 > 0.57 or ratio2 < 0.21 or ratio2 > 0.45: + self.failure_reason = "{0} iops ratio mismatch iops1={1} iops2={2} iops3={3}\ + expected r1~0.5 r2~0.33 got r1={4:.3f} r2={5:.3f},".format( + self.failure_reason, iops1, iops2, iops3, ratio1, ratio2 + ) + self.passed = False + + iops1 = iops1 + float(iops_files[0][i].split(',')[1]) + iops2 = iops2 + float(iops_files[1][i].split(',')[1]) + + ratio1 = iops1/iops2 + ratio2 = iops1/iops3 + logging.debug( + "sample {0}: job1 iops={1} job2 iops={2} job3 iops={3} job1/job2={4:.3f} job1/job3={5:.3f}".format( + i, iops1, iops2, iops3, ratio1, ratio2 + ) + ) + + # test job1 and job2 succeeded to recalibrate + if ratio1 < 0.43 or ratio1 > 0.57: + self.failure_reason = "{0} iops ratio mismatch iops1={1} iops2={2} expected ratio~0.5 got ratio={3:.3f},".format( + self.failure_reason, iops1, iops2, ratio1 + ) + self.passed = False + return + + +class FioJobTest_t0015(FioJobTest): + """Test consists of fio test jobs t0015 and t0016 + Confirm that mean(slat) + mean(clat) = mean(tlat)""" + + def check_result(self): + super(FioJobTest_t0015, self).check_result() + + if not self.passed: + return + + slat = self.json_data['jobs'][0]['read']['slat_ns']['mean'] + clat = self.json_data['jobs'][0]['read']['clat_ns']['mean'] + tlat = self.json_data['jobs'][0]['read']['lat_ns']['mean'] + logging.debug('Test %d: slat %f, clat %f, tlat %f', self.testnum, slat, clat, tlat) + + if abs(slat + clat - tlat) > 1: + self.failure_reason = "{0} slat {1} + clat {2} = {3} != tlat {4},".format( + self.failure_reason, slat, clat, slat+clat, tlat) + self.passed = False + + +class FioJobTest_t0019(FioJobTest): + """Test consists of fio test job t0019 + Confirm that all offsets were touched sequentially""" + + def check_result(self): + super(FioJobTest_t0019, self).check_result() + + bw_log_filename = os.path.join(self.test_dir, "test_bw.log") + file_data, success = self.get_file(bw_log_filename) + log_lines = file_data.split('\n') + + prev = -4096 + for line in log_lines: + if len(line.strip()) == 0: + continue + cur = int(line.split(',')[4]) + if cur - prev != 4096: + self.passed = False + self.failure_reason = "offsets {0}, {1} not sequential".format(prev, cur) + return + prev = cur + + if cur/4096 != 255: + self.passed = False + self.failure_reason = "unexpected last offset {0}".format(cur) + + +class FioJobTest_t0020(FioJobTest): + """Test consists of fio test job t0020 + Confirm that almost all offsets were touched non-sequentially""" + + def check_result(self): + super(FioJobTest_t0020, self).check_result() + + bw_log_filename = os.path.join(self.test_dir, "test_bw.log") + file_data, success = self.get_file(bw_log_filename) + log_lines = file_data.split('\n') + + seq_count = 0 + offsets = set() + + prev = int(log_lines[0].split(',')[4]) + for line in log_lines[1:]: + offsets.add(prev/4096) + if len(line.strip()) == 0: + continue + cur = int(line.split(',')[4]) + if cur - prev == 4096: + seq_count += 1 + prev = cur + + # 10 is an arbitrary threshold + if seq_count > 10: + self.passed = False + self.failure_reason = "too many ({0}) consecutive offsets".format(seq_count) + + if len(offsets) != 256: + self.passed = False + self.failure_reason += " number of offsets is {0} instead of 256".format(len(offsets)) + + for i in range(256): + if not i in offsets: + self.passed = False + self.failure_reason += " missing offset {0}".format(i*4096) + + class FioJobTest_iops_rate(FioJobTest): """Test consists of fio test job t0009 Confirm that job0 iops == 1000 @@ -433,16 +627,17 @@ class FioJobTest_iops_rate(FioJobTest): return iops1 = self.json_data['jobs'][0]['read']['iops'] + logging.debug("Test %d: iops1: %f", self.testnum, iops1) iops2 = self.json_data['jobs'][1]['read']['iops'] + logging.debug("Test %d: iops2: %f", self.testnum, iops2) ratio = iops2 / iops1 - logging.debug("Test %d: iops1: %f", self.testnum, iops1) logging.debug("Test %d: ratio: %f", self.testnum, ratio) - if iops1 < 995 or iops1 > 1005: + if iops1 < 950 or iops1 > 1050: self.failure_reason = "{0} iops value mismatch,".format(self.failure_reason) self.passed = False - if ratio < 7 or ratio > 9: + if ratio < 6 or ratio > 10: self.failure_reason = "{0} iops ratio mismatch,".format(self.failure_reason) self.passed = False @@ -453,6 +648,7 @@ class Requirements(object): _linux = False _libaio = False + _io_uring = False _zbd = False _root = False _zoned_nullb = False @@ -476,13 +672,22 @@ class Requirements(object): Requirements._zbd = "CONFIG_HAS_BLKZONED" in contents Requirements._libaio = "CONFIG_LIBAIO" in contents + contents, success = FioJobTest.get_file("/proc/kallsyms") + if not success: + print("Unable to open '/proc/kallsyms' to probe for io_uring support") + else: + Requirements._io_uring = "io_uring_setup" in contents + Requirements._root = (os.geteuid() == 0) if Requirements._zbd and Requirements._root: - subprocess.run(["modprobe", "null_blk"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - if os.path.exists("/sys/module/null_blk/parameters/zoned"): - Requirements._zoned_nullb = True + try: + subprocess.run(["modprobe", "null_blk"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + if os.path.exists("/sys/module/null_blk/parameters/zoned"): + Requirements._zoned_nullb = True + except Exception: + pass if platform.system() == "Windows": utest_exe = "unittest.exe" @@ -495,6 +700,7 @@ class Requirements(object): req_list = [Requirements.linux, Requirements.libaio, + Requirements.io_uring, Requirements.zbd, Requirements.root, Requirements.zoned_nullb, @@ -516,6 +722,11 @@ class Requirements(object): """Is libaio available?""" return Requirements._libaio, "libaio required" + @classmethod + def io_uring(cls): + """Is io_uring available?""" + return Requirements._io_uring, "io_uring required" + @classmethod def zbd(cls): """Is ZBD support available?""" @@ -677,15 +888,90 @@ TEST_LIST = [ }, { 'test_id': 12, - 'test_class': FioJobTest_iops_rate, + 'test_class': FioJobTest_t0012, 'job': 't0012.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, 'pre_success': None, 'output_format': 'json', 'requirements': [], - 'requirements': [Requirements.not_macos], - # mac os does not support CPU affinity + }, + { + 'test_id': 13, + 'test_class': FioJobTest, + 'job': 't0013.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'output_format': 'json', + 'requirements': [], + }, + { + 'test_id': 14, + 'test_class': FioJobTest_t0014, + 'job': 't0014.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'output_format': 'json', + 'requirements': [], + }, + { + 'test_id': 15, + 'test_class': FioJobTest_t0015, + 'job': 't0015-e78980ff.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'output_format': 'json', + 'requirements': [Requirements.linux, Requirements.libaio], + }, + { + 'test_id': 16, + 'test_class': FioJobTest_t0015, + 'job': 't0016-d54ae22.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'output_format': 'json', + 'requirements': [], + }, + { + 'test_id': 17, + 'test_class': FioJobTest_t0015, + 'job': 't0017.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'output_format': 'json', + 'requirements': [Requirements.not_windows], + }, + { + 'test_id': 18, + 'test_class': FioJobTest, + 'job': 't0018.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'requirements': [Requirements.linux, Requirements.io_uring], + }, + { + 'test_id': 19, + 'test_class': FioJobTest_t0019, + 'job': 't0019.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'requirements': [], + }, + { + 'test_id': 20, + 'test_class': FioJobTest_t0020, + 'job': 't0020.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'requirements': [], }, { 'test_id': 1000, @@ -746,8 +1032,8 @@ TEST_LIST = [ { 'test_id': 1007, 'test_class': FioExeTest, - 'exe': 't/zbd/run-tests-against-regular-nullb', - 'parameters': None, + 'exe': 't/zbd/run-tests-against-nullb', + 'parameters': ['-s', '1'], 'success': SUCCESS_DEFAULT, 'requirements': [Requirements.linux, Requirements.zbd, Requirements.root], @@ -755,8 +1041,8 @@ TEST_LIST = [ { 'test_id': 1008, 'test_class': FioExeTest, - 'exe': 't/zbd/run-tests-against-zoned-nullb', - 'parameters': None, + 'exe': 't/zbd/run-tests-against-nullb', + 'parameters': ['-s', '2'], 'success': SUCCESS_DEFAULT, 'requirements': [Requirements.linux, Requirements.zbd, Requirements.root, Requirements.zoned_nullb], @@ -829,9 +1115,9 @@ def main(): print("Invalid --pass-through argument '%s'" % arg) print("Syntax for --pass-through is TESTNUMBER:ARGUMENT") return - split = arg.split(":",1) + split = arg.split(":", 1) pass_through[int(split[0])] = split[1] - logging.debug("Pass-through arguments: %s" % pass_through) + logging.debug("Pass-through arguments: %s", pass_through) if args.fio_root: fio_root = args.fio_root @@ -891,6 +1177,7 @@ def main(): fio_pre_job=fio_pre_job, fio_pre_success=fio_pre_success, output_format=output_format) + desc = config['job'] elif issubclass(config['test_class'], FioExeTest): exe_path = os.path.join(fio_root, config['exe']) if config['parameters']: @@ -904,6 +1191,7 @@ def main(): parameters += pass_through[config['test_id']].split() test = config['test_class'](exe_path, parameters, config['success']) + desc = config['exe'] else: print("Test {0} FAILED: unable to process test config".format(config['test_id'])) failed = failed + 1 @@ -918,13 +1206,20 @@ def main(): if not reqs_met: break if not reqs_met: - print("Test {0} SKIPPED ({1})".format(config['test_id'], reason)) + print("Test {0} SKIPPED ({1}) {2}".format(config['test_id'], reason, desc)) skipped = skipped + 1 continue - test.setup(artifact_root, config['test_id']) - test.run() - test.check_result() + try: + test.setup(artifact_root, config['test_id']) + test.run() + test.check_result() + except KeyboardInterrupt: + break + except Exception as e: + test.passed = False + test.failure_reason += str(e) + logging.debug("Test %d exception:\n%s\n", config['test_id'], traceback.format_exc()) if test.passed: result = "PASSED" passed = passed + 1 @@ -935,7 +1230,7 @@ def main(): logging.debug("Test %d: stderr:\n%s", config['test_id'], contents) contents, _ = FioJobTest.get_file(test.stdout_file) logging.debug("Test %d: stdout:\n%s", config['test_id'], contents) - print("Test {0} {1}".format(config['test_id'], result)) + print("Test {0} {1} {2}".format(config['test_id'], result, desc)) print("{0} test(s) passed, {1} failed, {2} skipped".format(passed, failed, skipped))