Change memcpy() calls to assignments
[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         if 'rw' not in self.fio_opts:
59             return
60
61         if not self.passed:
62             return
63
64         job = self.json_data['jobs'][0]
65
66         if self.fio_opts['rw'] in ['read', 'randread']:
67             self.passed = self.check_all_ddirs(['read'], job)
68         elif self.fio_opts['rw'] in ['write', 'randwrite']:
69             if 'verify' not in self.fio_opts:
70                 self.passed = self.check_all_ddirs(['write'], job)
71             else:
72                 self.passed = self.check_all_ddirs(['read', 'write'], job)
73         elif self.fio_opts['rw'] in ['trim', 'randtrim']:
74             self.passed = self.check_all_ddirs(['trim'], job)
75         elif self.fio_opts['rw'] in ['readwrite', 'randrw']:
76             self.passed = self.check_all_ddirs(['read', 'write'], job)
77         elif self.fio_opts['rw'] in ['trimwrite', 'randtrimwrite']:
78             self.passed = self.check_all_ddirs(['trim', 'write'], job)
79         else:
80             print(f"Unhandled rw value {self.fio_opts['rw']}")
81             self.passed = False
82
83         if job['iodepth_level']['8'] < 95:
84             print("Did not achieve requested iodepth")
85             self.passed = False
86
87
88 TEST_LIST = [
89     {
90         "test_id": 1,
91         "fio_opts": {
92             "rw": 'read',
93             "timebased": 1,
94             "runtime": 3,
95             "output-format": "json",
96             },
97         "test_class": PassThruTest,
98     },
99     {
100         "test_id": 2,
101         "fio_opts": {
102             "rw": 'randread',
103             "timebased": 1,
104             "runtime": 3,
105             "output-format": "json",
106             },
107         "test_class": PassThruTest,
108     },
109     {
110         "test_id": 3,
111         "fio_opts": {
112             "rw": 'write',
113             "timebased": 1,
114             "runtime": 3,
115             "output-format": "json",
116             },
117         "test_class": PassThruTest,
118     },
119     {
120         "test_id": 4,
121         "fio_opts": {
122             "rw": 'randwrite',
123             "timebased": 1,
124             "runtime": 3,
125             "output-format": "json",
126             },
127         "test_class": PassThruTest,
128     },
129     {
130         "test_id": 5,
131         "fio_opts": {
132             "rw": 'trim',
133             "timebased": 1,
134             "runtime": 3,
135             "output-format": "json",
136             },
137         "test_class": PassThruTest,
138     },
139     {
140         "test_id": 6,
141         "fio_opts": {
142             "rw": 'randtrim',
143             "timebased": 1,
144             "runtime": 3,
145             "output-format": "json",
146             },
147         "test_class": PassThruTest,
148     },
149     {
150         "test_id": 7,
151         "fio_opts": {
152             "rw": 'write',
153             "io_size": 1024*1024,
154             "verify": "crc32c",
155             "output-format": "json",
156             },
157         "test_class": PassThruTest,
158     },
159     {
160         "test_id": 8,
161         "fio_opts": {
162             "rw": 'randwrite',
163             "io_size": 1024*1024,
164             "verify": "crc32c",
165             "output-format": "json",
166             },
167         "test_class": PassThruTest,
168     },
169     {
170         "test_id": 9,
171         "fio_opts": {
172             "rw": 'readwrite',
173             "timebased": 1,
174             "runtime": 3,
175             "output-format": "json",
176             },
177         "test_class": PassThruTest,
178     },
179     {
180         "test_id": 10,
181         "fio_opts": {
182             "rw": 'randrw',
183             "timebased": 1,
184             "runtime": 3,
185             "output-format": "json",
186             },
187         "test_class": PassThruTest,
188     },
189     {
190         "test_id": 11,
191         "fio_opts": {
192             "rw": 'trimwrite',
193             "timebased": 1,
194             "runtime": 3,
195             "output-format": "json",
196             },
197         "test_class": PassThruTest,
198     },
199     {
200         "test_id": 12,
201         "fio_opts": {
202             "rw": 'randtrimwrite',
203             "timebased": 1,
204             "runtime": 3,
205             "output-format": "json",
206             },
207         "test_class": PassThruTest,
208     },
209     {
210         "test_id": 13,
211         "fio_opts": {
212             "rw": 'randread',
213             "timebased": 1,
214             "runtime": 3,
215             "fixedbufs": 1,
216             "nonvectored": 1,
217             "force_async": 1,
218             "registerfiles": 1,
219             "sqthread_poll": 1,
220             "output-format": "json",
221             },
222         "test_class": PassThruTest,
223     },
224     {
225         "test_id": 14,
226         "fio_opts": {
227             "rw": 'randwrite',
228             "timebased": 1,
229             "runtime": 3,
230             "fixedbufs": 1,
231             "nonvectored": 1,
232             "force_async": 1,
233             "registerfiles": 1,
234             "sqthread_poll": 1,
235             "output-format": "json",
236             },
237         "test_class": PassThruTest,
238     },
239     {
240         # We can't enable fixedbufs because for trim-only
241         # workloads fio actually does not allocate any buffers
242         "test_id": 15,
243         "fio_opts": {
244             "rw": 'randtrim',
245             "timebased": 1,
246             "runtime": 3,
247             "fixedbufs": 0,
248             "nonvectored": 1,
249             "force_async": 1,
250             "registerfiles": 1,
251             "sqthread_poll": 1,
252             "output-format": "json",
253             },
254         "test_class": PassThruTest,
255     },
256 ]
257
258 def parse_args():
259     """Parse command-line arguments."""
260
261     parser = argparse.ArgumentParser()
262     parser.add_argument('-f', '--fio', help='path to file executable (e.g., ./fio)')
263     parser.add_argument('-a', '--artifact-root', help='artifact root directory')
264     parser.add_argument('-s', '--skip', nargs='+', type=int,
265                         help='list of test(s) to skip')
266     parser.add_argument('-o', '--run-only', nargs='+', type=int,
267                         help='list of test(s) to run, skipping all others')
268     parser.add_argument('--dut', help='target NVMe character device to test '
269                         '(e.g., /dev/ng0n1). WARNING: THIS IS A DESTRUCTIVE TEST', required=True)
270     args = parser.parse_args()
271
272     return args
273
274
275 def main():
276     """Run tests using fio's io_uring_cmd ioengine to send NVMe pass through commands."""
277
278     args = parse_args()
279
280     artifact_root = args.artifact_root if args.artifact_root else \
281         f"nvmept-test-{time.strftime('%Y%m%d-%H%M%S')}"
282     os.mkdir(artifact_root)
283     print(f"Artifact directory is {artifact_root}")
284
285     if args.fio:
286         fio_path = str(Path(args.fio).absolute())
287     else:
288         fio_path = 'fio'
289     print(f"fio path is {fio_path}")
290
291     for test in TEST_LIST:
292         test['fio_opts']['filename'] = args.dut
293
294     test_env = {
295               'fio_path': fio_path,
296               'fio_root': str(Path(__file__).absolute().parent.parent),
297               'artifact_root': artifact_root,
298               'basename': 'nvmept',
299               }
300
301     _, failed, _ = run_fio_tests(TEST_LIST, test_env, args)
302     sys.exit(failed)
303
304
305 if __name__ == '__main__':
306     main()