docs: change listed type for log_window_value to str
[fio.git] / t / nvmept.py
1 #!/usr/bin/env python3
2 """
3 # nvmept.py
4 #
5 # Test fio's io_uring_cmd ioengine with NVMe pass-through commands.
6 #
7 # USAGE
8 # see python3 nvmept.py --help
9 #
10 # EXAMPLES
11 # python3 t/nvmept.py --dut /dev/ng0n1
12 # python3 t/nvmept.py --dut /dev/ng1n1 -f ./fio
13 #
14 # REQUIREMENTS
15 # Python 3.6
16 #
17 """
18 import os
19 import sys
20 import time
21 import argparse
22 from pathlib import Path
23 from fiotestlib import FioJobCmdTest, run_fio_tests
24
25
26 class PassThruTest(FioJobCmdTest):
27     """
28     NVMe pass-through test class. Check to make sure output for selected data
29     direction(s) is non-zero and that zero data appears for other directions.
30     """
31
32     def setup(self, parameters):
33         """Setup a test."""
34
35         fio_args = [
36             "--name=nvmept",
37             "--ioengine=io_uring_cmd",
38             "--cmd_type=nvme",
39             "--iodepth=8",
40             "--iodepth_batch=4",
41             "--iodepth_batch_complete=4",
42             f"--filename={self.fio_opts['filename']}",
43             f"--rw={self.fio_opts['rw']}",
44             f"--output={self.filenames['output']}",
45             f"--output-format={self.fio_opts['output-format']}",
46         ]
47         for opt in ['fixedbufs', 'nonvectored', 'force_async', 'registerfiles',
48                     'sqthread_poll', 'sqthread_poll_cpu', 'hipri', 'nowait',
49                     'time_based', 'runtime', 'verify', 'io_size']:
50             if opt in self.fio_opts:
51                 option = f"--{opt}={self.fio_opts[opt]}"
52                 fio_args.append(option)
53
54         super().setup(fio_args)
55
56
57     def check_result(self):
58         super().check_result()
59
60         if 'rw' not in self.fio_opts:
61             return
62
63         if not self.passed:
64             return
65
66         job = self.json_data['jobs'][0]
67
68         if self.fio_opts['rw'] in ['read', 'randread']:
69             self.passed = self.check_all_ddirs(['read'], job)
70         elif self.fio_opts['rw'] in ['write', 'randwrite']:
71             if 'verify' not in self.fio_opts:
72                 self.passed = self.check_all_ddirs(['write'], job)
73             else:
74                 self.passed = self.check_all_ddirs(['read', 'write'], job)
75         elif self.fio_opts['rw'] in ['trim', 'randtrim']:
76             self.passed = self.check_all_ddirs(['trim'], job)
77         elif self.fio_opts['rw'] in ['readwrite', 'randrw']:
78             self.passed = self.check_all_ddirs(['read', 'write'], job)
79         elif self.fio_opts['rw'] in ['trimwrite', 'randtrimwrite']:
80             self.passed = self.check_all_ddirs(['trim', 'write'], job)
81         else:
82             print(f"Unhandled rw value {self.fio_opts['rw']}")
83             self.passed = False
84
85         if job['iodepth_level']['8'] < 95:
86             print("Did not achieve requested iodepth")
87             self.passed = False
88
89
90 TEST_LIST = [
91     {
92         "test_id": 1,
93         "fio_opts": {
94             "rw": 'read',
95             "timebased": 1,
96             "runtime": 3,
97             "output-format": "json",
98             },
99         "test_class": PassThruTest,
100     },
101     {
102         "test_id": 2,
103         "fio_opts": {
104             "rw": 'randread',
105             "timebased": 1,
106             "runtime": 3,
107             "output-format": "json",
108             },
109         "test_class": PassThruTest,
110     },
111     {
112         "test_id": 3,
113         "fio_opts": {
114             "rw": 'write',
115             "timebased": 1,
116             "runtime": 3,
117             "output-format": "json",
118             },
119         "test_class": PassThruTest,
120     },
121     {
122         "test_id": 4,
123         "fio_opts": {
124             "rw": 'randwrite',
125             "timebased": 1,
126             "runtime": 3,
127             "output-format": "json",
128             },
129         "test_class": PassThruTest,
130     },
131     {
132         "test_id": 5,
133         "fio_opts": {
134             "rw": 'trim',
135             "timebased": 1,
136             "runtime": 3,
137             "output-format": "json",
138             },
139         "test_class": PassThruTest,
140     },
141     {
142         "test_id": 6,
143         "fio_opts": {
144             "rw": 'randtrim',
145             "timebased": 1,
146             "runtime": 3,
147             "output-format": "json",
148             },
149         "test_class": PassThruTest,
150     },
151     {
152         "test_id": 7,
153         "fio_opts": {
154             "rw": 'write',
155             "io_size": 1024*1024,
156             "verify": "crc32c",
157             "output-format": "json",
158             },
159         "test_class": PassThruTest,
160     },
161     {
162         "test_id": 8,
163         "fio_opts": {
164             "rw": 'randwrite',
165             "io_size": 1024*1024,
166             "verify": "crc32c",
167             "output-format": "json",
168             },
169         "test_class": PassThruTest,
170     },
171     {
172         "test_id": 9,
173         "fio_opts": {
174             "rw": 'readwrite',
175             "timebased": 1,
176             "runtime": 3,
177             "output-format": "json",
178             },
179         "test_class": PassThruTest,
180     },
181     {
182         "test_id": 10,
183         "fio_opts": {
184             "rw": 'randrw',
185             "timebased": 1,
186             "runtime": 3,
187             "output-format": "json",
188             },
189         "test_class": PassThruTest,
190     },
191     {
192         "test_id": 11,
193         "fio_opts": {
194             "rw": 'trimwrite',
195             "timebased": 1,
196             "runtime": 3,
197             "output-format": "json",
198             },
199         "test_class": PassThruTest,
200     },
201     {
202         "test_id": 12,
203         "fio_opts": {
204             "rw": 'randtrimwrite',
205             "timebased": 1,
206             "runtime": 3,
207             "output-format": "json",
208             },
209         "test_class": PassThruTest,
210     },
211     {
212         "test_id": 13,
213         "fio_opts": {
214             "rw": 'randread',
215             "timebased": 1,
216             "runtime": 3,
217             "fixedbufs": 1,
218             "nonvectored": 1,
219             "force_async": 1,
220             "registerfiles": 1,
221             "sqthread_poll": 1,
222             "output-format": "json",
223             },
224         "test_class": PassThruTest,
225     },
226     {
227         "test_id": 14,
228         "fio_opts": {
229             "rw": 'randwrite',
230             "timebased": 1,
231             "runtime": 3,
232             "fixedbufs": 1,
233             "nonvectored": 1,
234             "force_async": 1,
235             "registerfiles": 1,
236             "sqthread_poll": 1,
237             "output-format": "json",
238             },
239         "test_class": PassThruTest,
240     },
241     {
242         # We can't enable fixedbufs because for trim-only
243         # workloads fio actually does not allocate any buffers
244         "test_id": 15,
245         "fio_opts": {
246             "rw": 'randtrim',
247             "timebased": 1,
248             "runtime": 3,
249             "fixedbufs": 0,
250             "nonvectored": 1,
251             "force_async": 1,
252             "registerfiles": 1,
253             "sqthread_poll": 1,
254             "output-format": "json",
255             },
256         "test_class": PassThruTest,
257     },
258 ]
259
260 def parse_args():
261     """Parse command-line arguments."""
262
263     parser = argparse.ArgumentParser()
264     parser.add_argument('-f', '--fio', help='path to file executable (e.g., ./fio)')
265     parser.add_argument('-a', '--artifact-root', help='artifact root directory')
266     parser.add_argument('-s', '--skip', nargs='+', type=int,
267                         help='list of test(s) to skip')
268     parser.add_argument('-o', '--run-only', nargs='+', type=int,
269                         help='list of test(s) to run, skipping all others')
270     parser.add_argument('--dut', help='target NVMe character device to test '
271                         '(e.g., /dev/ng0n1). WARNING: THIS IS A DESTRUCTIVE TEST', required=True)
272     args = parser.parse_args()
273
274     return args
275
276
277 def main():
278     """Run tests using fio's io_uring_cmd ioengine to send NVMe pass through commands."""
279
280     args = parse_args()
281
282     artifact_root = args.artifact_root if args.artifact_root else \
283         f"nvmept-test-{time.strftime('%Y%m%d-%H%M%S')}"
284     os.mkdir(artifact_root)
285     print(f"Artifact directory is {artifact_root}")
286
287     if args.fio:
288         fio_path = str(Path(args.fio).absolute())
289     else:
290         fio_path = 'fio'
291     print(f"fio path is {fio_path}")
292
293     for test in TEST_LIST:
294         test['fio_opts']['filename'] = args.dut
295
296     test_env = {
297               'fio_path': fio_path,
298               'fio_root': str(Path(__file__).absolute().parent.parent),
299               'artifact_root': artifact_root,
300               'basename': 'nvmept',
301               }
302
303     _, failed, _ = run_fio_tests(TEST_LIST, test_env, args)
304     sys.exit(failed)
305
306
307 if __name__ == '__main__':
308     main()