Merge branch 'libaio/actual_min_algo_update' of https://github.com/dpronin/fio
[fio.git] / t / strided.py
1 #!/usr/bin/env python3
2
3 """
4 # strided.py
5 #
6 # Test zonemode=strided. This uses the null ioengine when no file is
7 # specified. If a file is specified, use it for randdom read testing.
8 # Some of the zoneranges in the tests are 16MiB. So when using a file
9 # a minimum size of 64MiB is recommended.
10 #
11 # USAGE
12 # python strided.py fio-executable [-f file/device]
13 #
14 # EXAMPLES
15 # python t/strided.py ./fio
16 # python t/strided.py ./fio -f /dev/sda
17 # dd if=/dev/zero of=temp bs=1M count=64
18 # python t/strided.py ./fio -f temp
19 #
20 # ===TEST MATRIX===
21 #
22 # --zonemode=strided, zoneskip unset
23 #   w/ randommap and LFSR
24 #       zonesize=zonerange  all blocks in zonerange touched
25 #       zonesize>zonerange  all blocks touched and roll-over back into zone
26 #       zonesize<zonerange  all blocks inside zone
27 #
28 #   w/o randommap       all blocks inside zone
29 """
30
31 import os
32 import sys
33 import time
34 import argparse
35 from pathlib import Path
36 from fiotestlib import FioJobCmdTest, run_fio_tests
37
38
39 class StridedTest(FioJobCmdTest):
40     """Test zonemode=strided."""
41
42     def setup(self, parameters):
43         fio_args = [
44                     "--name=strided",
45                     "--zonemode=strided",
46                     "--log_offset=1",
47                     "--randrepeat=0",
48                     "--rw=randread",
49                     f"--write_iops_log={self.filenames['iopslog']}",
50                     f"--output={self.filenames['output']}",
51                     f"--zonerange={self.fio_opts['zonerange']}",
52                     f"--zonesize={self.fio_opts['zonesize']}",
53                     f"--bs={self.fio_opts['bs']}",
54                    ]
55
56         for opt in ['norandommap', 'random_generator', 'offset']:
57             if opt in self.fio_opts:
58                 option = f"--{opt}={self.fio_opts[opt]}"
59                 fio_args.append(option)
60
61         if 'filename' in self.fio_opts:
62             for opt in ['filename', 'filesize']:
63                 option = f"--{opt}={self.fio_opts[opt]}"
64                 fio_args.append(option)
65         else:
66             fio_args.append('--ioengine=null')
67             for opt in ['size', 'io_size', 'filesize']:
68                 option = f"--{opt}={self.fio_opts[opt]}"
69                 fio_args.append(option)
70
71         super().setup(fio_args)
72
73     def check_result(self):
74         zonestart = 0 if 'offset' not in self.fio_opts else self.fio_opts['offset']
75         iospersize = self.fio_opts['zonesize'] / self.fio_opts['bs']
76         iosperrange = self.fio_opts['zonerange'] / self.fio_opts['bs']
77         iosperzone = 0
78         lines = self.iops_log_lines.split('\n')
79         zoneset = set()
80
81         for line in lines:
82             if len(line) == 0:
83                 continue
84
85             if iosperzone == iospersize:
86                 # time to move to a new zone
87                 iosperzone = 0
88                 zoneset = set()
89                 zonestart += self.fio_opts['zonerange']
90                 if zonestart >= self.fio_opts['filesize']:
91                     zonestart = 0 if 'offset' not in self.fio_opts else self.fio_opts['offset']
92
93             iosperzone = iosperzone + 1
94             tokens = line.split(',')
95             offset = int(tokens[4])
96             if offset < zonestart or offset >= zonestart + self.fio_opts['zonerange']:
97                 print(f"Offset {offset} outside of zone starting at {zonestart}")
98                 return False
99
100             # skip next section if norandommap is enabled with no
101             # random_generator or with a random_generator != lfsr
102             if 'norandommap' in self.fio_opts:
103                 if 'random_generator' in self.fio_opts:
104                     if self.fio_opts['random_generator'] != 'lfsr':
105                         continue
106                 else:
107                     continue
108
109             # we either have a random map enabled or we
110             # are using an LFSR
111             # so all blocks should be unique and we should have
112             # covered the entire zone when iosperzone % iosperrange == 0
113             block = (offset - zonestart) / self.fio_opts['bs']
114             if block in zoneset:
115                 print(f"Offset {offset} in zone already touched")
116                 return False
117
118             zoneset.add(block)
119             if iosperzone % iosperrange == 0:
120                 if len(zoneset) != iosperrange:
121                     print(f"Expected {iosperrange} blocks in zone but only saw {len(zoneset)}")
122                     return False
123                 zoneset = set()
124
125         return True
126
127
128 TEST_LIST = [   # randommap enabled
129     {
130         "test_id": 1,
131         "fio_opts": {
132             "zonerange": 4096,
133             "zonesize": 4096,
134             "bs": 4096,
135             "offset": 8*4096,
136             "size": 16*4096,
137             "io_size": 16*4096,
138             },
139         "test_class": StridedTest,
140     },
141     {
142         "test_id": 2,
143         "fio_opts": {
144             "zonerange": 4096,
145             "zonesize": 4096,
146             "bs": 4096,
147             "size": 16*4096,
148             "io_size": 16*4096,
149             },
150         "test_class": StridedTest,
151     },
152     {
153         "test_id": 3,
154         "fio_opts": {
155             "zonerange": 16*1024*1024,
156             "zonesize": 16*1024*1024,
157             "bs": 4096,
158             "size": 256*1024*1024,
159             "io_size": 256*1024*204,
160             },
161         "test_class": StridedTest,
162     },
163     {
164         "test_id": 4,
165         "fio_opts": {
166             "zonerange": 4096,
167             "zonesize": 4*4096,
168             "bs": 4096,
169             "size": 16*4096,
170             "io_size": 16*4096,
171             },
172         "test_class": StridedTest,
173     },
174     {
175         "test_id": 5,
176         "fio_opts": {
177             "zonerange": 16*1024*1024,
178             "zonesize": 32*1024*1024,
179             "bs": 4096,
180             "size": 256*1024*1024,
181             "io_size": 256*1024*204,
182             },
183         "test_class": StridedTest,
184     },
185     {
186         "test_id": 6,
187         "fio_opts": {
188             "zonerange": 8192,
189             "zonesize": 4096,
190             "bs": 4096,
191             "size": 16*4096,
192             "io_size": 16*4096,
193             },
194         "test_class": StridedTest,
195     },
196     {
197         "test_id": 7,
198         "fio_opts": {
199             "zonerange": 16*1024*1024,
200             "zonesize": 8*1024*1024,
201             "bs": 4096,
202             "size": 256*1024*1024,
203             "io_size": 256*1024*204,
204             },
205         "test_class": StridedTest,
206     },
207             # lfsr
208     {
209         "test_id": 8,
210         "fio_opts": {
211             "random_generator": "lfsr",
212             "zonerange": 4096*1024,
213             "zonesize": 4096*1024,
214             "bs": 4096,
215             "offset": 8*4096*1024,
216             "size": 16*4096*1024,
217             "io_size": 16*4096*1024,
218             },
219         "test_class": StridedTest,
220     },
221     {
222         "test_id": 9,
223         "fio_opts": {
224             "random_generator": "lfsr",
225             "zonerange": 4096*1024,
226             "zonesize": 4096*1024,
227             "bs": 4096,
228             "size": 16*4096*1024,
229             "io_size": 16*4096*1024,
230             },
231         "test_class": StridedTest,
232     },
233     {
234         "test_id": 10,
235         "fio_opts": {
236             "random_generator": "lfsr",
237             "zonerange": 16*1024*1024,
238             "zonesize": 16*1024*1024,
239             "bs": 4096,
240             "size": 256*1024*1024,
241             "io_size": 256*1024*204,
242             },
243         "test_class": StridedTest,
244     },
245     {
246         "test_id": 11,
247         "fio_opts": {
248             "random_generator": "lfsr",
249             "zonerange": 4096*1024,
250             "zonesize": 4*4096*1024,
251             "bs": 4096,
252             "size": 16*4096*1024,
253             "io_size": 16*4096*1024,
254             },
255         "test_class": StridedTest,
256     },
257     {
258         "test_id": 12,
259         "fio_opts": {
260             "random_generator": "lfsr",
261             "zonerange": 16*1024*1024,
262             "zonesize": 32*1024*1024,
263             "bs": 4096,
264             "size": 256*1024*1024,
265             "io_size": 256*1024*204,
266             },
267         "test_class": StridedTest,
268     },
269     {
270         "test_id": 13,
271         "fio_opts": {
272             "random_generator": "lfsr",
273             "zonerange": 8192*1024,
274             "zonesize": 4096*1024,
275             "bs": 4096,
276             "size": 16*4096*1024,
277             "io_size": 16*4096*1024,
278             },
279         "test_class": StridedTest,
280     },
281     {
282         "test_id": 14,
283         "fio_opts": {
284             "random_generator": "lfsr",
285             "zonerange": 16*1024*1024,
286             "zonesize": 8*1024*1024,
287             "bs": 4096,
288             "size": 256*1024*1024,
289             "io_size": 256*1024*204,
290             },
291         "test_class": StridedTest,
292     },
293     # norandommap
294     {
295         "test_id": 15,
296         "fio_opts": {
297             "norandommap": 1,
298             "zonerange": 4096,
299             "zonesize": 4096,
300             "bs": 4096,
301             "offset": 8*4096,
302             "size": 16*4096,
303             "io_size": 16*4096,
304             },
305         "test_class": StridedTest,
306     },
307     {
308         "test_id": 16,
309         "fio_opts": {
310             "norandommap": 1,
311             "zonerange": 4096,
312             "zonesize": 4096,
313             "bs": 4096,
314             "size": 16*4096,
315             "io_size": 16*4096,
316             },
317         "test_class": StridedTest,
318     },
319     {
320         "test_id": 17,
321         "fio_opts": {
322             "norandommap": 1,
323             "zonerange": 16*1024*1024,
324             "zonesize": 16*1024*1024,
325             "bs": 4096,
326             "size": 256*1024*1024,
327             "io_size": 256*1024*204,
328             },
329         "test_class": StridedTest,
330     },
331     {
332         "test_id": 18,
333         "fio_opts": {
334             "norandommap": 1,
335             "zonerange": 4096,
336             "zonesize": 8192,
337             "bs": 4096,
338             "size": 16*4096,
339             "io_size": 16*4096,
340             },
341         "test_class": StridedTest,
342     },
343     {
344         "test_id": 19,
345         "fio_opts": {
346             "norandommap": 1,
347             "zonerange": 16*1024*1024,
348             "zonesize": 32*1024*1024,
349             "bs": 4096,
350             "size": 256*1024*1024,
351             "io_size": 256*1024*204,
352             },
353         "test_class": StridedTest,
354     },
355     {
356         "test_id": 20,
357         "fio_opts": {
358             "norandommap": 1,
359             "zonerange": 8192,
360             "zonesize": 4096,
361             "bs": 4096,
362             "size": 16*4096,
363             "io_size": 16*4096,
364             },
365         "test_class": StridedTest,
366     },
367     {
368         "test_id": 21,
369         "fio_opts": {
370             "norandommap": 1,
371             "zonerange": 16*1024*1024,
372             "zonesize": 8*1024*1024,
373             "bs": 4096,
374             "size": 256*1024*1024,
375             "io_size": 256*1024*1024,
376             },
377         "test_class": StridedTest,
378     },
379 ]
380
381
382 def parse_args():
383     """Parse command-line arguments."""
384
385     parser = argparse.ArgumentParser()
386     parser.add_argument('-f', '--fio', help='path to file executable (e.g., ./fio)')
387     parser.add_argument('-a', '--artifact-root', help='artifact root directory')
388     parser.add_argument('-s', '--skip', nargs='+', type=int,
389                         help='list of test(s) to skip')
390     parser.add_argument('-o', '--run-only', nargs='+', type=int,
391                         help='list of test(s) to run, skipping all others')
392     parser.add_argument('--dut',
393                         help='target file/device to test.')
394     args = parser.parse_args()
395
396     return args
397
398
399 def main():
400     """Run zonemode=strided tests."""
401
402     args = parse_args()
403
404     artifact_root = args.artifact_root if args.artifact_root else \
405         f"strided-test-{time.strftime('%Y%m%d-%H%M%S')}"
406     os.mkdir(artifact_root)
407     print(f"Artifact directory is {artifact_root}")
408
409     if args.fio:
410         fio_path = str(Path(args.fio).absolute())
411     else:
412         fio_path = 'fio'
413     print(f"fio path is {fio_path}")
414
415     if args.dut:
416         statinfo = os.stat(args.dut)
417         filesize = statinfo.st_size
418         if filesize == 0:
419             f = os.open(args.dut, os.O_RDONLY)
420             filesize = os.lseek(f, 0, os.SEEK_END)
421             os.close(f)
422
423     for test in TEST_LIST:
424         if args.dut:
425             test['fio_opts']['filename'] = os.path.abspath(args.dut)
426             test['fio_opts']['filesize'] = filesize
427         else:
428             test['fio_opts']['filesize'] = test['fio_opts']['size']
429
430     test_env = {
431               'fio_path': fio_path,
432               'fio_root': str(Path(__file__).absolute().parent.parent),
433               'artifact_root': artifact_root,
434               'basename': 'strided',
435               }
436
437     _, failed, _ = run_fio_tests(TEST_LIST, test_env, args)
438     sys.exit(failed)
439
440
441 if __name__ == '__main__':
442     main()