Merge branch 'atomic-writes'
[fio.git] / t / nvmept_fdp.py
CommitLineData
ece3a998
VF
1#!/usr/bin/env python3
2#
3# Copyright 2024 Samsung Electronics Co., Ltd All Rights Reserved
4#
5# For conditions of distribution and use, see the accompanying COPYING file.
6#
7"""
8# nvmept_fdp.py
9#
10# Test fio's io_uring_cmd ioengine with NVMe pass-through FDP write commands.
11#
12# USAGE
13# see python3 nvmept_fdp.py --help
14#
15# EXAMPLES
16# python3 t/nvmept_fdp.py --dut /dev/ng0n1
17# python3 t/nvmept_fdp.py --dut /dev/ng1n1 -f ./fio
18#
19# REQUIREMENTS
20# Python 3.6
21# Device formatted with LBA data size 4096 bytes
22# Device with at least five placement IDs
23#
24# WARNING
25# This is a destructive test
26"""
27import os
28import sys
29import json
30import time
31import locale
32import logging
33import argparse
34import subprocess
35from pathlib import Path
36from fiotestlib import FioJobCmdTest, run_fio_tests
37from fiotestcommon import SUCCESS_NONZERO
38
791f6973
VF
39# This needs to match FIO_MAX_DP_IDS and DP_MAX_SCHEME_ENTRIES in
40# dataplacement.h
41FIO_MAX_DP_IDS = 128
42DP_MAX_SCHEME_ENTRIES = 32
ece3a998
VF
43
44class FDPTest(FioJobCmdTest):
45 """
46 NVMe pass-through test class. Check to make sure output for selected data
47 direction(s) is non-zero and that zero data appears for other directions.
48 """
49
50 def setup(self, parameters):
51 """Setup a test."""
52
53 fio_args = [
54 "--name=nvmept-fdp",
55 "--ioengine=io_uring_cmd",
56 "--cmd_type=nvme",
57 "--randrepeat=0",
58 f"--filename={self.fio_opts['filename']}",
59 f"--rw={self.fio_opts['rw']}",
60 f"--output={self.filenames['output']}",
61 f"--output-format={self.fio_opts['output-format']}",
62 ]
96566b09 63
ece3a998
VF
64 for opt in ['fixedbufs', 'nonvectored', 'force_async', 'registerfiles',
65 'sqthread_poll', 'sqthread_poll_cpu', 'hipri', 'nowait',
66 'time_based', 'runtime', 'verify', 'io_size', 'num_range',
67 'iodepth', 'iodepth_batch', 'iodepth_batch_complete',
68 'size', 'rate', 'bs', 'bssplit', 'bsrange', 'randrepeat',
69 'buffer_pattern', 'verify_pattern', 'offset', 'fdp',
70 'fdp_pli', 'fdp_pli_select', 'dataplacement', 'plid_select',
e6a96fa8 71 'plids', 'dp_scheme', 'number_ios', 'read_iolog']:
ece3a998
VF
72 if opt in self.fio_opts:
73 option = f"--{opt}={self.fio_opts[opt]}"
74 fio_args.append(option)
75
76 super().setup(fio_args)
77
78
79 def check_result(self):
80 try:
81 self._check_result()
82 finally:
83 if not update_all_ruhs(self.fio_opts['filename']):
84 logging.error("Could not reset device")
85 if not check_all_ruhs(self.fio_opts['filename']):
86 logging.error("Reclaim units have inconsistent RUAMW values")
87
88
89 def _check_result(self):
90
91 super().check_result()
92
93 if 'rw' not in self.fio_opts or \
94 not self.passed or \
95 'json' not in self.fio_opts['output-format']:
96 return
97
98 job = self.json_data['jobs'][0]
96566b09 99 rw_fio_opts = self.fio_opts['rw'].split(':')[0]
ece3a998 100
96566b09 101 if rw_fio_opts in ['read', 'randread']:
ece3a998 102 self.passed = self.check_all_ddirs(['read'], job)
96566b09 103 elif rw_fio_opts in ['write', 'randwrite']:
ece3a998
VF
104 if 'verify' not in self.fio_opts:
105 self.passed = self.check_all_ddirs(['write'], job)
106 else:
107 self.passed = self.check_all_ddirs(['read', 'write'], job)
96566b09 108 elif rw_fio_opts in ['trim', 'randtrim']:
ece3a998 109 self.passed = self.check_all_ddirs(['trim'], job)
96566b09 110 elif rw_fio_opts in ['readwrite', 'randrw']:
ece3a998 111 self.passed = self.check_all_ddirs(['read', 'write'], job)
96566b09 112 elif rw_fio_opts in ['trimwrite', 'randtrimwrite']:
ece3a998
VF
113 self.passed = self.check_all_ddirs(['trim', 'write'], job)
114 else:
115 logging.error("Unhandled rw value %s", self.fio_opts['rw'])
116 self.passed = False
117
118 if 'iodepth' in self.fio_opts:
119 # We will need to figure something out if any test uses an iodepth
120 # different from 8
121 if job['iodepth_level']['8'] < 95:
122 logging.error("Did not achieve requested iodepth")
123 self.passed = False
124 else:
125 logging.debug("iodepth 8 target met %s", job['iodepth_level']['8'])
126
127
128class FDPMultiplePLIDTest(FDPTest):
129 """
130 Write to multiple placement IDs.
131 """
132
133 def setup(self, parameters):
134 mapping = {
135 'nruhsd': FIO_FDP_NUMBER_PLIDS,
136 'max_ruamw': FIO_FDP_MAX_RUAMW,
95db41f1 137 'maxplid': FIO_FDP_NUMBER_PLIDS-1,
96566b09
HP
138 # parameters for 400, 401 tests
139 'hole_size': 64*1024,
791f6973 140 'nios_for_scheme': min(FIO_FDP_NUMBER_PLIDS//2, DP_MAX_SCHEME_ENTRIES),
ece3a998
VF
141 }
142 if 'number_ios' in self.fio_opts and isinstance(self.fio_opts['number_ios'], str):
143 self.fio_opts['number_ios'] = eval(self.fio_opts['number_ios'].format(**mapping))
96566b09
HP
144 if 'bs' in self.fio_opts and isinstance(self.fio_opts['bs'], str):
145 self.fio_opts['bs'] = eval(self.fio_opts['bs'].format(**mapping))
146 if 'rw' in self.fio_opts and isinstance(self.fio_opts['rw'], str):
147 self.fio_opts['rw'] = self.fio_opts['rw'].format(**mapping)
95db41f1
VF
148 if 'plids' in self.fio_opts and isinstance(self.fio_opts['plids'], str):
149 self.fio_opts['plids'] = self.fio_opts['plids'].format(**mapping)
150 if 'fdp_pli' in self.fio_opts and isinstance(self.fio_opts['fdp_pli'], str):
151 self.fio_opts['fdp_pli'] = self.fio_opts['fdp_pli'].format(**mapping)
ece3a998
VF
152
153 super().setup(parameters)
96566b09
HP
154
155 if 'dp_scheme' in self.fio_opts:
156 scheme_path = os.path.join(self.paths['test_dir'], self.fio_opts['dp_scheme'])
157 with open(scheme_path, mode='w') as f:
158 for i in range(mapping['nios_for_scheme']):
159 f.write(f'{mapping["hole_size"] * 2 * i}, {mapping["hole_size"] * 2 * (i+1)}, {i}\n')
e6a96fa8
HP
160
161 if 'read_iolog' in self.fio_opts:
162 read_iolog_path = os.path.join(self.paths['test_dir'], self.fio_opts['read_iolog'])
163 with open(read_iolog_path, mode='w') as f:
164 f.write('fio version 2 iolog\n')
165 f.write(f'{self.fio_opts["filename"]} add\n')
166 f.write(f'{self.fio_opts["filename"]} open\n')
167
168 for i in range(mapping['nios_for_scheme']):
169 f.write(f'{self.fio_opts["filename"]} write {mapping["hole_size"] * 2 * i} {mapping["hole_size"]}\n')
170
171 f.write(f'{self.fio_opts["filename"]} close')
96566b09 172
ece3a998
VF
173 def _check_result(self):
174 if 'fdp_pli' in self.fio_opts:
175 plid_list = self.fio_opts['fdp_pli'].split(',')
176 elif 'plids' in self.fio_opts:
177 plid_list = self.fio_opts['plids'].split(',')
178 else:
95db41f1 179 plid_list = [str(i) for i in range(FIO_FDP_NUMBER_PLIDS)]
ece3a998 180
95db41f1
VF
181 range_ids = []
182 for plid in plid_list:
183 if '-' in plid:
184 [start, end] = plid.split('-')
185 range_ids.extend(list(range(int(start), int(end)+1)))
186 else:
187 range_ids.append(int(plid))
188
189 plid_list = sorted(range_ids)
ece3a998
VF
190 logging.debug("plid_list: %s", str(plid_list))
191
192 fdp_status = get_fdp_status(self.fio_opts['filename'])
193
194 select = "roundrobin"
195 if 'fdp_pli_select' in self.fio_opts:
196 select = self.fio_opts['fdp_pli_select']
197 elif 'plid_select' in self.fio_opts:
198 select = self.fio_opts['plid_select']
199
200 if select == "roundrobin":
201 self._check_robin(plid_list, fdp_status)
202 elif select == "random":
203 self._check_random(plid_list, fdp_status)
96566b09
HP
204 elif select == "scheme":
205 self._check_scheme(plid_list, fdp_status)
ece3a998
VF
206 else:
207 logging.error("Unknown plid selection strategy %s", select)
208 self.passed = False
96566b09 209
ece3a998
VF
210 super()._check_result()
211
212 def _check_robin(self, plid_list, fdp_status):
213 """
214 With round robin we can know exactly how many writes each PLID will
215 receive.
216 """
217 ruamw = [FIO_FDP_MAX_RUAMW] * FIO_FDP_NUMBER_PLIDS
218
791f6973
VF
219 number_ios = self.fio_opts['number_ios'] % (len(plid_list)*FIO_FDP_MAX_RUAMW)
220 remainder = int(number_ios % len(plid_list))
221 whole = int((number_ios - remainder) / len(plid_list))
222 logging.debug("PLIDs in the list should show they have received %d writes; %d PLIDs will receive one extra",
ece3a998
VF
223 whole, remainder)
224
225 for plid in plid_list:
226 ruamw[plid] -= whole
227 if remainder:
228 ruamw[plid] -= 1
229 remainder -= 1
230 logging.debug("Expected ruamw values: %s", str(ruamw))
231
232 for idx, ruhs in enumerate(fdp_status['ruhss']):
791f6973
VF
233 if idx >= FIO_FDP_NUMBER_PLIDS:
234 break
235
ece3a998
VF
236 if ruhs['ruamw'] != ruamw[idx]:
237 logging.error("RUAMW mismatch with idx %d, pid %d, expected %d, observed %d", idx,
238 ruhs['pid'], ruamw[idx], ruhs['ruamw'])
239 self.passed = False
240 break
241
242 logging.debug("RUAMW match with idx %d, pid %d: ruamw=%d", idx, ruhs['pid'], ruamw[idx])
243
244 def _check_random(self, plid_list, fdp_status):
245 """
246 With random selection, a set of PLIDs will receive all the write
247 operations and the remainder will be untouched.
248 """
249
250 total_ruamw = 0
251 for plid in plid_list:
252 total_ruamw += fdp_status['ruhss'][plid]['ruamw']
253
254 expected = len(plid_list) * FIO_FDP_MAX_RUAMW - self.fio_opts['number_ios']
255 if total_ruamw != expected:
256 logging.error("Expected total ruamw %d for plids %s, observed %d", expected,
257 str(plid_list), total_ruamw)
258 self.passed = False
259 else:
260 logging.debug("Observed expected total ruamw %d for plids %s", expected, str(plid_list))
261
262 for idx, ruhs in enumerate(fdp_status['ruhss']):
263 if idx in plid_list:
264 continue
265 if ruhs['ruamw'] != FIO_FDP_MAX_RUAMW:
266 logging.error("Unexpected ruamw %d for idx %d, pid %d, expected %d", ruhs['ruamw'],
267 idx, ruhs['pid'], FIO_FDP_MAX_RUAMW)
268 self.passed = False
269 else:
270 logging.debug("Observed expected ruamw %d for idx %d, pid %d", ruhs['ruamw'], idx,
271 ruhs['pid'])
272
96566b09
HP
273 def _check_scheme(self, plid_list, fdp_status):
274 """
275 With scheme selection, a set of PLIDs touched by the scheme
276 """
277
278 PLID_IDX_POS = 2
279 plid_list_from_scheme = set()
280
281 scheme_path = os.path.join(self.paths['test_dir'], self.fio_opts['dp_scheme'])
282
283 with open(scheme_path) as f:
284 lines = f.readlines()
285 for line in lines:
286 line_elem = line.strip().replace(' ', '').split(',')
287 plid_list_from_scheme.add(int(line_elem[PLID_IDX_POS]))
288
289 logging.debug(f'plid_list_from_scheme: {plid_list_from_scheme}')
290
291 for idx, ruhs in enumerate(fdp_status['ruhss']):
292 if ruhs['pid'] in plid_list_from_scheme:
293 if ruhs['ruamw'] == FIO_FDP_MAX_RUAMW:
294 logging.error("pid %d should be touched by the scheme. But ruamw of it(%d) equals to %d",
295 ruhs['pid'], ruhs['ruamw'], FIO_FDP_MAX_RUAMW)
296 self.passed = False
297 else:
298 logging.debug("pid %d should be touched by the scheme. ruamw of it(%d) is under %d",
299 ruhs['pid'], ruhs['ruamw'], FIO_FDP_MAX_RUAMW)
300 else:
301 if ruhs['ruamw'] == FIO_FDP_MAX_RUAMW:
302 logging.debug("pid %d should not be touched by the scheme. ruamw of it(%d) equals to %d",
303 ruhs['pid'], ruhs['ruamw'], FIO_FDP_MAX_RUAMW)
304 else:
305 logging.error("pid %d should not be touched by the scheme. But ruamw of it(%d) is under %d",
306 ruhs['pid'], ruhs['ruamw'], FIO_FDP_MAX_RUAMW)
307 self.passed = False
308
ece3a998
VF
309
310class FDPSinglePLIDTest(FDPTest):
311 """
312 Write to a single placement ID only.
313 """
314
315 def _check_result(self):
316 if 'plids' in self.fio_opts:
317 plid = self.fio_opts['plids']
318 elif 'fdp_pli' in self.fio_opts:
319 plid = self.fio_opts['fdp_pli']
320 else:
321 plid = 0
322
323 fdp_status = get_fdp_status(self.fio_opts['filename'])
324 ruamw = fdp_status['ruhss'][plid]['ruamw']
325 lba_count = self.fio_opts['number_ios']
326
327 if FIO_FDP_MAX_RUAMW - lba_count != ruamw:
328 logging.error("FDP accounting mismatch for plid %d; expected ruamw %d, observed %d",
329 plid, FIO_FDP_MAX_RUAMW - lba_count, ruamw)
330 self.passed = False
331 else:
332 logging.debug("FDP accounting as expected for plid %d; ruamw = %d", plid, ruamw)
333
334 super()._check_result()
335
336
337class FDPReadTest(FDPTest):
338 """
339 Read workload test.
340 """
341
342 def _check_result(self):
343 ruamw = check_all_ruhs(self.fio_opts['filename'])
344
345 if ruamw != FIO_FDP_MAX_RUAMW:
346 logging.error("Read workload affected FDP ruamw")
347 self.passed = False
348 else:
349 logging.debug("Read workload did not disturb FDP ruamw")
350 super()._check_result()
351
352
353def get_fdp_status(dut):
354 """
355 Run the nvme-cli command to obtain FDP status and return result as a JSON
356 object.
357 """
358
359 cmd = f"sudo nvme fdp status --output-format=json {dut}"
360 cmd = cmd.split(' ')
361 cmd_result = subprocess.run(cmd, capture_output=True, check=False,
362 encoding=locale.getpreferredencoding())
363
364 if cmd_result.returncode != 0:
365 logging.error("Error obtaining device %s FDP status: %s", dut, cmd_result.stderr)
366 return False
367
368 return json.loads(cmd_result.stdout)
369
370
371def update_ruh(dut, plid):
372 """
373 Update reclaim unit handles with specified ID(s). This tells the device to
374 point the RUH to a new (empty) reclaim unit.
375 """
376
377 ids = ','.join(plid) if isinstance(plid, list) else plid
378 cmd = f"nvme fdp update --pids={ids} {dut}"
379 cmd = cmd.split(' ')
380 cmd_result = subprocess.run(cmd, capture_output=True, check=False,
381 encoding=locale.getpreferredencoding())
382
383 if cmd_result.returncode != 0:
384 logging.error("Error updating RUH %s ID(s) %s", dut, ids)
385 return False
386
387 return True
388
389
390def update_all_ruhs(dut):
391 """
392 Update all reclaim unit handles on the device.
393 """
394
395 fdp_status = get_fdp_status(dut)
396 for ruhs in fdp_status['ruhss']:
397 if not update_ruh(dut, ruhs['pid']):
398 return False
399
400 return True
401
402
403def check_all_ruhs(dut):
404 """
405 Check that all RUHs have the same value for reclaim unit available media
406 writes (RUAMW). Return the RUAMW value.
407 """
408
409 fdp_status = get_fdp_status(dut)
410 ruh_status = fdp_status['ruhss']
411
412 ruamw = ruh_status[0]['ruamw']
413 for ruhs in ruh_status:
414 if ruhs['ruamw'] != ruamw:
415 logging.error("RUAMW mismatch: found %d, expected %d", ruhs['ruamw'], ruamw)
416 return False
417
418 return ruamw
419
420
421TEST_LIST = [
422 # Write one LBA to one PLID using both the old and new sets of options
423 ## omit fdp_pli_select/plid_select
424 {
425 "test_id": 1,
426 "fio_opts": {
427 "rw": 'write',
428 "bs": 4096,
429 "number_ios": 1,
430 "verify": "crc32c",
431 "fdp": 1,
432 "fdp_pli": 3,
433 "output-format": "json",
434 },
435 "test_class": FDPSinglePLIDTest,
436 },
437 {
438 "test_id": 2,
439 "fio_opts": {
440 "rw": 'randwrite',
441 "bs": 4096,
442 "number_ios": 1,
443 "verify": "crc32c",
444 "dataplacement": "fdp",
445 "plids": 3,
446 "output-format": "json",
447 },
448 "test_class": FDPSinglePLIDTest,
449 },
450 ## fdp_pli_select/plid_select=roundrobin
451 {
452 "test_id": 3,
453 "fio_opts": {
454 "rw": 'write',
455 "bs": 4096,
456 "number_ios": 1,
457 "verify": "crc32c",
458 "fdp": 1,
459 "fdp_pli": 3,
460 "fdp_pli_select": "roundrobin",
461 "output-format": "json",
462 },
463 "test_class": FDPSinglePLIDTest,
464 },
465 {
466 "test_id": 4,
467 "fio_opts": {
468 "rw": 'randwrite',
469 "bs": 4096,
470 "number_ios": 1,
471 "verify": "crc32c",
472 "dataplacement": "fdp",
473 "plids": 3,
474 "plid_select": "roundrobin",
475 "output-format": "json",
476 },
477 "test_class": FDPSinglePLIDTest,
478 },
479 ## fdp_pli_select/plid_select=random
480 {
481 "test_id": 5,
482 "fio_opts": {
483 "rw": 'write',
484 "bs": 4096,
485 "number_ios": 1,
486 "verify": "crc32c",
487 "fdp": 1,
488 "fdp_pli": 3,
489 "fdp_pli_select": "random",
490 "output-format": "json",
491 },
492 "test_class": FDPSinglePLIDTest,
493 },
494 {
495 "test_id": 6,
496 "fio_opts": {
497 "rw": 'randwrite',
498 "bs": 4096,
499 "number_ios": 1,
500 "verify": "crc32c",
501 "dataplacement": "fdp",
502 "plids": 3,
503 "plid_select": "random",
504 "output-format": "json",
505 },
506 "test_class": FDPSinglePLIDTest,
507 },
508 # Write four LBAs to one PLID using both the old and new sets of options
509 ## omit fdp_pli_select/plid_select
510 {
511 "test_id": 7,
512 "fio_opts": {
513 "rw": 'write',
514 "bs": 4096,
515 "number_ios": 4,
516 "verify": "crc32c",
517 "fdp": 1,
518 "fdp_pli": 1,
519 "output-format": "json",
520 },
521 "test_class": FDPSinglePLIDTest,
522 },
523 {
524 "test_id": 8,
525 "fio_opts": {
526 "rw": 'randwrite',
527 "bs": 4096,
528 "number_ios": 4,
529 "verify": "crc32c",
530 "dataplacement": "fdp",
531 "plids": 1,
532 "output-format": "json",
533 },
534 "test_class": FDPSinglePLIDTest,
535 },
536 ## fdp_pli_select/plid_select=roundrobin
537 {
538 "test_id": 9,
539 "fio_opts": {
540 "rw": 'write',
541 "bs": 4096,
542 "number_ios": 4,
543 "verify": "crc32c",
544 "fdp": 1,
545 "fdp_pli": 1,
546 "fdp_pli_select": "roundrobin",
547 "output-format": "json",
548 },
549 "test_class": FDPSinglePLIDTest,
550 },
551 {
552 "test_id": 10,
553 "fio_opts": {
554 "rw": 'randwrite',
555 "bs": 4096,
556 "number_ios": 4,
557 "verify": "crc32c",
558 "dataplacement": "fdp",
559 "plids": 1,
560 "plid_select": "roundrobin",
561 "output-format": "json",
562 },
563 "test_class": FDPSinglePLIDTest,
564 },
565 ## fdp_pli_select/plid_select=random
566 {
567 "test_id": 11,
568 "fio_opts": {
569 "rw": 'write',
570 "bs": 4096,
571 "number_ios": 4,
572 "verify": "crc32c",
573 "fdp": 1,
574 "fdp_pli": 1,
575 "fdp_pli_select": "random",
576 "output-format": "json",
577 },
578 "test_class": FDPSinglePLIDTest,
579 },
580 {
581 "test_id": 12,
582 "fio_opts": {
583 "rw": 'randwrite',
584 "bs": 4096,
585 "number_ios": 4,
586 "verify": "crc32c",
587 "dataplacement": "fdp",
588 "plids": 1,
589 "plid_select": "random",
590 "output-format": "json",
591 },
592 "test_class": FDPSinglePLIDTest,
593 },
594 # Just a regular write without FDP directive--should land on plid 0
595 {
596 "test_id": 13,
597 "fio_opts": {
598 "rw": 'randwrite',
599 "bs": 4096,
600 "number_ios": 19,
601 "verify": "crc32c",
602 "output-format": "json",
603 },
604 "test_class": FDPSinglePLIDTest,
605 },
606 # Read workload
607 {
608 "test_id": 14,
609 "fio_opts": {
610 "rw": 'randread',
611 "bs": 4096,
612 "number_ios": 19,
613 "output-format": "json",
614 },
615 "test_class": FDPReadTest,
616 },
617 # write to multiple PLIDs using round robin to select PLIDs
618 ## write to all PLIDs using old and new sets of options
619 {
620 "test_id": 100,
621 "fio_opts": {
622 "rw": 'randwrite',
623 "bs": 4096,
624 "number_ios": "2*{nruhsd}+3",
625 "verify": "crc32c",
626 "fdp": 1,
627 "fdp_pli_select": "roundrobin",
628 "output-format": "json",
629 },
630 "test_class": FDPMultiplePLIDTest,
631 },
632 {
633 "test_id": 101,
634 "fio_opts": {
635 "rw": 'randwrite',
636 "bs": 4096,
637 "number_ios": "2*{nruhsd}+3",
638 "verify": "crc32c",
639 "dataplacement": "fdp",
640 "plid_select": "roundrobin",
641 "output-format": "json",
642 },
643 "test_class": FDPMultiplePLIDTest,
644 },
645 ## write to a subset of PLIDs using old and new sets of options
646 {
647 "test_id": 102,
648 "fio_opts": {
649 "rw": 'randwrite',
650 "bs": 4096,
651 "number_ios": "{nruhsd}+1",
652 "verify": "crc32c",
653 "fdp": 1,
654 "fdp_pli": "1,3",
655 "fdp_pli_select": "roundrobin",
656 "output-format": "json",
657 },
658 "test_class": FDPMultiplePLIDTest,
659 },
660 {
661 "test_id": 103,
662 "fio_opts": {
663 "rw": 'randwrite',
664 "bs": 4096,
665 "number_ios": "{nruhsd}+1",
666 "verify": "crc32c",
667 "dataplacement": "fdp",
668 "plids": "1,3",
669 "plid_select": "roundrobin",
670 "output-format": "json",
671 },
672 "test_class": FDPMultiplePLIDTest,
673 },
674 # write to multiple PLIDs using random selection of PLIDs
675 ## write to all PLIDs using old and new sets of options
676 {
677 "test_id": 200,
678 "fio_opts": {
679 "rw": 'randwrite',
680 "bs": 4096,
681 "number_ios": "{max_ruamw}-1",
682 "verify": "crc32c",
683 "fdp": 1,
684 "fdp_pli_select": "random",
685 "output-format": "json",
686 },
687 "test_class": FDPMultiplePLIDTest,
688 },
689 {
690 "test_id": 201,
691 "fio_opts": {
692 "rw": 'randwrite',
693 "bs": 4096,
694 "number_ios": "{max_ruamw}-1",
695 "verify": "crc32c",
696 "dataplacement": "fdp",
697 "plid_select": "random",
698 "output-format": "json",
699 },
700 "test_class": FDPMultiplePLIDTest,
701 },
702 ## write to a subset of PLIDs using old and new sets of options
703 {
704 "test_id": 202,
705 "fio_opts": {
706 "rw": 'randwrite',
707 "bs": 4096,
708 "number_ios": "{max_ruamw}-1",
709 "verify": "crc32c",
710 "fdp": 1,
711 "fdp_pli": "1,3,4",
712 "fdp_pli_select": "random",
713 "output-format": "json",
714 },
715 "test_class": FDPMultiplePLIDTest,
716 },
717 {
718 "test_id": 203,
719 "fio_opts": {
720 "rw": 'randwrite',
721 "bs": 4096,
722 "number_ios": "{max_ruamw}-1",
723 "verify": "crc32c",
724 "dataplacement": "fdp",
725 "plids": "1,3,4",
726 "plid_select": "random",
727 "output-format": "json",
728 },
729 "test_class": FDPMultiplePLIDTest,
730 },
95db41f1
VF
731 ### use 3-4 to specify plids
732 {
733 "test_id": 204,
734 "fio_opts": {
735 "rw": 'randwrite',
736 "bs": 4096,
737 "number_ios": "{max_ruamw}-1",
738 "verify": "crc32c",
739 "fdp": 1,
740 "fdp_pli": "1,3-4",
741 "fdp_pli_select": "random",
742 "output-format": "json",
743 },
744 "test_class": FDPMultiplePLIDTest,
745 },
746 {
747 "test_id": 205,
748 "fio_opts": {
749 "rw": 'randwrite',
750 "bs": 4096,
751 "number_ios": "{max_ruamw}-1",
752 "verify": "crc32c",
753 "dataplacement": "fdp",
754 "plids": "1,3-4",
755 "plid_select": "random",
756 "output-format": "json",
757 },
758 "test_class": FDPMultiplePLIDTest,
759 },
760 ### use 1-3 to specify plids
761 {
762 "test_id": 206,
763 "fio_opts": {
764 "rw": 'randwrite',
765 "bs": 4096,
766 "number_ios": "{max_ruamw}-1",
767 "verify": "crc32c",
768 "fdp": 1,
769 "fdp_pli": "1-3",
770 "fdp_pli_select": "random",
771 "output-format": "json",
772 },
773 "test_class": FDPMultiplePLIDTest,
774 },
775 {
776 "test_id": 207,
777 "fio_opts": {
778 "rw": 'randwrite',
779 "bs": 4096,
780 "number_ios": "{max_ruamw}-1",
781 "verify": "crc32c",
782 "dataplacement": "fdp",
783 "plids": "1-3",
784 "plid_select": "random",
785 "output-format": "json",
786 },
787 "test_class": FDPMultiplePLIDTest,
788 },
789 ### use multiple ranges to specify plids
790 {
791 "test_id": 208,
792 "fio_opts": {
793 "rw": 'randwrite',
794 "bs": 4096,
795 "number_ios": "{max_ruamw}-1",
796 "verify": "crc32c",
797 "fdp": 1,
798 "fdp_pli": "1-2,3-3",
799 "fdp_pli_select": "random",
800 "output-format": "json",
801 },
802 "test_class": FDPMultiplePLIDTest,
803 },
804 {
805 "test_id": 209,
806 "fio_opts": {
807 "rw": 'randwrite',
808 "bs": 4096,
809 "number_ios": "{max_ruamw}-1",
810 "verify": "crc32c",
811 "dataplacement": "fdp",
812 "plids": "1-2,3-3",
813 "plid_select": "random",
814 "output-format": "json",
815 },
816 "test_class": FDPMultiplePLIDTest,
817 },
818 {
819 "test_id": 210,
820 "fio_opts": {
821 "rw": 'randwrite',
822 "bs": 4096,
823 "number_ios": "{max_ruamw}-1",
824 "verify": "crc32c",
825 "fdp": 1,
826 "fdp_pli": "0-{maxplid}",
827 "fdp_pli_select": "random",
828 "output-format": "json",
829 },
830 "test_class": FDPMultiplePLIDTest,
831 },
832 {
833 "test_id": 211,
834 "fio_opts": {
835 "rw": 'randwrite',
836 "bs": 4096,
837 "number_ios": "{max_ruamw}-1",
838 "verify": "crc32c",
839 "dataplacement": "fdp",
840 "fdp_pli": "0-{maxplid}",
841 "plid_select": "random",
842 "output-format": "json",
843 },
844 "test_class": FDPMultiplePLIDTest,
845 },
ece3a998
VF
846 # Specify invalid options fdp=1 and dataplacement=none
847 {
848 "test_id": 300,
849 "fio_opts": {
850 "rw": 'write',
851 "bs": 4096,
852 "io_size": 4096,
853 "verify": "crc32c",
854 "fdp": 1,
855 "fdp_pli": 3,
856 "output-format": "normal",
857 "dataplacement": "none",
858 },
859 "test_class": FDPTest,
860 "success": SUCCESS_NONZERO,
861 },
862 # Specify invalid options fdp=1 and dataplacement=streams
863 {
864 "test_id": 301,
865 "fio_opts": {
866 "rw": 'write',
867 "bs": 4096,
868 "io_size": 4096,
869 "verify": "crc32c",
870 "fdp": 1,
871 "fdp_pli": 3,
872 "output-format": "normal",
873 "dataplacement": "streams",
874 },
875 "test_class": FDPTest,
876 "success": SUCCESS_NONZERO,
877 },
96566b09
HP
878 # Specify invalid options related to dataplacement scheme
879 ## using old and new sets of options
880 {
881 "test_id": 302,
882 "fio_opts": {
883 "rw": 'write',
884 "bs": 4096,
885 "io_size": 4096,
886 "verify": "crc32c",
887 "fdp": 1,
888 "fdp_pli": 3,
889 "fdp_pli_select": "scheme",
890 "output-format": "normal",
891 },
892 "test_class": FDPTest,
893 "success": SUCCESS_NONZERO,
894 },
895 {
896 "test_id": 303,
897 "fio_opts": {
898 "rw": 'write',
899 "bs": 4096,
900 "io_size": 4096,
901 "verify": "crc32c",
902 "dataplacement": "fdp",
903 "plids": 3,
904 "plid_select": "scheme",
905 "output-format": "normal",
906 },
907 "test_class": FDPTest,
908 "success": SUCCESS_NONZERO,
909 },
95db41f1
VF
910 ## Specify invalid ranges with start > end
911 {
912 "test_id": 304,
913 "fio_opts": {
914 "rw": 'write',
915 "bs": 4096,
916 "io_size": 4096,
917 "verify": "crc32c",
918 "fdp": 1,
919 "plids": "3-1",
920 "output-format": "normal",
921 },
922 "test_class": FDPTest,
923 "success": SUCCESS_NONZERO,
924 },
925 {
926 "test_id": 305,
927 "fio_opts": {
928 "rw": 'write',
929 "bs": 4096,
930 "io_size": 4096,
931 "verify": "crc32c",
932 "fdp": 1,
933 "fdp_pli": "3-1",
934 "output-format": "normal",
935 },
936 "test_class": FDPTest,
937 "success": SUCCESS_NONZERO,
938 },
939 ## Specify too many plids
940 {
941 "test_id": 306,
942 "fio_opts": {
943 "rw": 'write',
944 "bs": 4096,
945 "io_size": 4096,
946 "verify": "crc32c",
947 "fdp": 1,
948 "plids": "0-65535",
949 "output-format": "normal",
950 },
951 "test_class": FDPTest,
952 "success": SUCCESS_NONZERO,
953 },
954 {
955 "test_id": 307,
956 "fio_opts": {
957 "rw": 'write',
958 "bs": 4096,
959 "io_size": 4096,
960 "verify": "crc32c",
961 "fdp": 1,
962 "fdp_pli": "0-65535",
963 "output-format": "normal",
964 },
965 "test_class": FDPTest,
966 "success": SUCCESS_NONZERO,
967 },
96566b09
HP
968 # write to multiple PLIDs using scheme selection of PLIDs
969 ## using old and new sets of options
970 {
971 "test_id": 400,
972 "fio_opts": {
973 "rw": "write:{hole_size}",
974 "bs": "{hole_size}",
975 "number_ios": "{nios_for_scheme}",
976 "verify": "crc32c",
977 "fdp": 1,
978 "fdp_pli_select": "scheme",
979 "dp_scheme": "lba.scheme",
980 "output-format": "json",
981 },
982 "test_class": FDPMultiplePLIDTest,
983 },
984 {
985 "test_id": 401,
986 "fio_opts": {
987 "rw": "write:{hole_size}",
988 "bs": "{hole_size}",
989 "number_ios": "{nios_for_scheme}",
990 "verify": "crc32c",
991 "dataplacement": "fdp",
992 "plid_select": "scheme",
993 "dp_scheme": "lba.scheme",
994 "output-format": "json",
995 },
996 "test_class": FDPMultiplePLIDTest,
997 },
e6a96fa8
HP
998 # check whether dataplacement works while replaying iologs
999 {
1000 "test_id": 402,
1001 "fio_opts": {
1002 "rw": "write:{hole_size}",
1003 "bs": "{hole_size}",
1004 "number_ios": "{nios_for_scheme}",
1005 "verify": "crc32c",
1006 "read_iolog": "iolog",
1007 "dataplacement": "fdp",
1008 "plid_select": "scheme",
1009 "dp_scheme": "lba.scheme",
1010 "output-format": "json",
1011 },
1012 "test_class": FDPMultiplePLIDTest,
1013 },
ece3a998
VF
1014]
1015
1016def parse_args():
1017 """Parse command-line arguments."""
1018
1019 parser = argparse.ArgumentParser()
1020 parser.add_argument('-d', '--debug', help='Enable debug messages', action='store_true')
1021 parser.add_argument('-f', '--fio', help='path to file executable (e.g., ./fio)')
1022 parser.add_argument('-a', '--artifact-root', help='artifact root directory')
1023 parser.add_argument('-s', '--skip', nargs='+', type=int,
1024 help='list of test(s) to skip')
1025 parser.add_argument('-o', '--run-only', nargs='+', type=int,
1026 help='list of test(s) to run, skipping all others')
1027 parser.add_argument('--dut', help='target NVMe character device to test '
1028 '(e.g., /dev/ng0n1). WARNING: THIS IS A DESTRUCTIVE TEST', required=True)
1029 args = parser.parse_args()
1030
1031 return args
1032
1033
1034FIO_FDP_MAX_RUAMW = 0
1035FIO_FDP_NUMBER_PLIDS = 0
1036
1037def main():
1038 """Run tests using fio's io_uring_cmd ioengine to send NVMe pass through commands."""
1039 global FIO_FDP_MAX_RUAMW
1040 global FIO_FDP_NUMBER_PLIDS
1041
1042 args = parse_args()
1043
1044 if args.debug:
1045 logging.basicConfig(level=logging.DEBUG)
1046 else:
1047 logging.basicConfig(level=logging.INFO)
1048
1049 artifact_root = args.artifact_root if args.artifact_root else \
1050 f"nvmept-fdp-test-{time.strftime('%Y%m%d-%H%M%S')}"
1051 os.mkdir(artifact_root)
1052 print(f"Artifact directory is {artifact_root}")
1053
1054 if args.fio:
1055 fio_path = str(Path(args.fio).absolute())
1056 else:
1057 fio_path = 'fio'
1058 print(f"fio path is {fio_path}")
1059
1060 for test in TEST_LIST:
1061 test['fio_opts']['filename'] = args.dut
1062
1063 fdp_status = get_fdp_status(args.dut)
791f6973 1064 FIO_FDP_NUMBER_PLIDS = min(fdp_status['nruhsd'], 128)
ece3a998
VF
1065 update_all_ruhs(args.dut)
1066 FIO_FDP_MAX_RUAMW = check_all_ruhs(args.dut)
1067 if not FIO_FDP_MAX_RUAMW:
1068 sys.exit(-1)
1069
1070 test_env = {
1071 'fio_path': fio_path,
1072 'fio_root': str(Path(__file__).absolute().parent.parent),
1073 'artifact_root': artifact_root,
1074 'basename': 'nvmept-fdp',
1075 }
1076
1077 _, failed, _ = run_fio_tests(TEST_LIST, test_env, args)
1078 sys.exit(failed)
1079
1080
1081if __name__ == '__main__':
1082 main()