diff --git a/engines/nvme.c b/engines/nvme.c index 72934c8b1..33d874773 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -381,6 +381,11 @@ int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, case DDIR_TRIM: fio_nvme_uring_cmd_trim_prep(cmd, io_u, dsm); return 0; + case DDIR_SYNC: + case DDIR_DATASYNC: + cmd->opcode = nvme_cmd_flush; + cmd->nsid = data->nsid; + return 0; default: return -ENOTSUP; } diff --git a/engines/nvme.h b/engines/nvme.h index bc2370b8d..b5fef2fb2 100644 --- a/engines/nvme.h +++ b/engines/nvme.h @@ -73,6 +73,7 @@ enum nvme_admin_opcode { }; enum nvme_io_opcode { + nvme_cmd_flush = 0x00, nvme_cmd_write = 0x01, nvme_cmd_read = 0x02, nvme_cmd_write_uncor = 0x04, diff --git a/fio.h b/fio.h index 7d9927a00..4bb6cfa7f 100644 --- a/fio.h +++ b/fio.h @@ -258,8 +258,9 @@ struct thread_data { size_t orig_buffer_size; volatile int runstate; volatile bool terminate; - bool last_was_sync; - enum fio_ddir last_ddir; + + enum fio_ddir last_ddir_completed; + enum fio_ddir last_ddir_issued; int mmapfd; @@ -629,7 +630,7 @@ static inline bool multi_range_trim(struct thread_data *td, struct io_u *io_u) static inline bool should_fsync(struct thread_data *td) { - if (td->last_was_sync) + if (ddir_sync(td->last_ddir_issued)) return false; if (td_write(td) || td->o.override_sync) return true; diff --git a/io_u.c b/io_u.c index a090e1212..f81086b65 100644 --- a/io_u.c +++ b/io_u.c @@ -755,7 +755,7 @@ static enum fio_ddir get_rw_ddir(struct thread_data *td) * See if it's time to fsync/fdatasync/sync_file_range first, * and if not then move on to check regular I/Os. */ - if (should_fsync(td)) { + if (should_fsync(td) && td->last_ddir_issued == DDIR_WRITE) { if (td->o.fsync_blocks && td->io_issues[DDIR_WRITE] && !(td->io_issues[DDIR_WRITE] % td->o.fsync_blocks)) return DDIR_SYNC; @@ -815,7 +815,7 @@ static void set_rw_ddir(struct thread_data *td, struct io_u *io_u) if (td->o.zone_mode == ZONE_MODE_ZBD) ddir = zbd_adjust_ddir(td, io_u, ddir); - if (td_trimwrite(td)) { + if (td_trimwrite(td) && !ddir_sync(ddir)) { struct fio_file *f = io_u->file; if (f->last_start[DDIR_WRITE] == f->last_start[DDIR_TRIM]) ddir = DDIR_TRIM; @@ -1757,7 +1757,7 @@ static bool check_get_trim(struct thread_data *td, struct io_u *io_u) if (get_next_trim(td, io_u)) return true; } else if (!(td->io_hist_len % td->o.trim_backlog) && - td->last_ddir != DDIR_READ) { + td->last_ddir_completed != DDIR_READ) { td->trim_batch = td->o.trim_batch; if (!td->trim_batch) td->trim_batch = td->o.trim_backlog; @@ -1779,7 +1779,7 @@ static bool check_get_verify(struct thread_data *td, struct io_u *io_u) if (td->verify_batch) get_verify = 1; else if (!(td->io_hist_len % td->o.verify_backlog) && - td->last_ddir != DDIR_READ) { + td->last_ddir_completed != DDIR_READ) { td->verify_batch = td->o.verify_batch; if (!td->verify_batch) td->verify_batch = td->o.verify_backlog; @@ -2122,7 +2122,7 @@ static void io_completed(struct thread_data *td, struct io_u **io_u_ptr, return; } - td->last_ddir = ddir; + td->last_ddir_completed = ddir; if (!io_u->error && ddir_rw(ddir)) { unsigned long long bytes = io_u->xfer_buflen - io_u->resid; diff --git a/ioengines.c b/ioengines.c index 6b81dc772..dcd4164d4 100644 --- a/ioengines.c +++ b/ioengines.c @@ -437,7 +437,7 @@ enum fio_q_status td_io_queue(struct thread_data *td, struct io_u *io_u) td->ts.total_io_u[io_u->ddir]++; } - td->last_was_sync = ddir_sync(io_u->ddir); + td->last_ddir_issued = ddir; } else if (ret == FIO_Q_QUEUED) { td->io_u_queued++; @@ -448,7 +448,7 @@ enum fio_q_status td_io_queue(struct thread_data *td, struct io_u *io_u) if (td->io_u_queued >= td->o.iodepth_batch) td_io_commit(td); - td->last_was_sync = ddir_sync(io_u->ddir); + td->last_ddir_issued = ddir; } if (!td_ioengine_flagged(td, FIO_SYNCIO) && diff --git a/libfio.c b/libfio.c index d0c6bf8f5..2596ae5a9 100644 --- a/libfio.c +++ b/libfio.c @@ -101,7 +101,6 @@ static void reset_io_counters(struct thread_data *td, int all) td->zone_bytes = 0; - td->last_was_sync = false; td->rwmix_issues = 0; /* diff --git a/t/nvmept.py b/t/nvmept.py index 1ade64dc5..3d90f4bfc 100755 --- a/t/nvmept.py +++ b/t/nvmept.py @@ -87,6 +87,53 @@ def check_result(self): self.passed = False +class FlushTest(FioJobCmdTest): + def setup(self, parameters): + fio_args = [ + "--name=nvmept-flush", + "--ioengine=io_uring_cmd", + "--cmd_type=nvme", + "--randrepeat=0", + f"--filename={self.fio_opts['filename']}", + f"--rw={self.fio_opts['rw']}", + f"--output={self.filenames['output']}", + f"--output-format={self.fio_opts['output-format']}", + ] + + for opt in ['fixedbufs', 'nonvectored', 'force_async', 'registerfiles', + 'sqthread_poll', 'sqthread_poll_cpu', 'hipri', 'nowait', + 'time_based', 'runtime', 'verify', 'io_size', 'num_range', + 'iodepth', 'iodepth_batch', 'iodepth_batch_complete', + 'size', 'rate', 'bs', 'bssplit', 'bsrange', 'randrepeat', + 'buffer_pattern', 'verify_pattern', 'offset', 'fdp', + 'fdp_pli', 'fdp_pli_select', 'dataplacement', 'plid_select', + 'plids', 'dp_scheme', 'number_ios', 'read_iolog', 'fsync']: + if opt in self.fio_opts: + option = f"--{opt}={self.fio_opts[opt]}" + fio_args.append(option) + + super().setup(fio_args) + + def check_result(self): + super().check_result() + + job = self.json_data['jobs'][0] + + rw = self.fio_opts['rw'] + fsync = self.fio_opts['fsync'] + + nr_write = job['write']['total_ios'] + nr_sync = job['sync']['total_ios'] + + nr_sync_exp = nr_write // fsync + + # The actual number of DDIR_SYNC issued might miss one DDIR_SYNC command + # when the last command issued was DDIR_WRITE command. + if not ((nr_sync == nr_sync_exp) or (nr_sync + 1 == nr_sync_exp)): + logging.error(f"nr_write={nr_write}, nr_sync={nr_sync}, fsync={fsync}") + self.passed = False + + TEST_LIST = [ { "test_id": 1, @@ -255,6 +302,50 @@ def check_result(self): }, "test_class": PassThruTest, }, + { + "test_id": 16, + "fio_opts": { + "rw": 'read', + "bs": 4096, + "number_ios": 10, + "fsync": 1, + "output-format": "json", + }, + "test_class": FlushTest, + }, + { + "test_id": 17, + "fio_opts": { + "rw": 'write', + "bs": 4096, + "number_ios": 10, + "fsync": 1, + "output-format": "json", + }, + "test_class": FlushTest, + }, + { + "test_id": 18, + "fio_opts": { + "rw": 'readwrite', + "bs": 4096, + "number_ios": 10, + "fsync": 1, + "output-format": "json", + }, + "test_class": FlushTest, + }, + { + "test_id": 19, + "fio_opts": { + "rw": 'trimwrite', + "bs": 4096, + "number_ios": 10, + "fsync": 1, + "output-format": "json", + }, + "test_class": FlushTest, + }, ] def parse_args():