Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions HOWTO.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4005,6 +4005,33 @@ Verification
used to speed up the process of writing each block on a device with its
offset. Default: 0 (disabled).

.. option:: verify_type=str

Controls which write operations are included during the verification
phase. This option only affects offline verification when using
:option:`verify_state_save` to save completion state and later verify
with a separate job. The allowed values are:

**flush**
Only verify writes that completed at or before the last
fsync operation. This mode filters out writes that
completed after the last fsync, which may not be
persistent on storage. Writes with the Force Unit
Access (FUA) flag are always included regardless of
fsync timing, as they bypass the cache and are
immediately persistent. This is useful for testing data
persistence guarantees across power failures or system
crashes. fio tracks fsync completion times and write
completion times during the write phase. During
verification, only writes that meet the fsync timing
criteria are verified. This allows testing scenarios
where only data that was properly synced before a
simulated failure should be verified.
This option requires :option:`verify_state_save` to be
enabled and is only effective during offline
verification (separate verify job). Default: none
(verify all completed writes).

.. option:: verify_fatal=bool

Normally fio will keep checking the entire contents before quitting on a
Expand Down
2 changes: 1 addition & 1 deletion backend.c
Original file line number Diff line number Diff line change
Expand Up @@ -1259,7 +1259,7 @@ static int init_file_completion_logging(struct thread_data *td,

for_each_file(td, f, i) {
f->last_write_comp = scalloc(td->last_write_comp_depth,
sizeof(uint64_t));
sizeof(struct fio_write_comp));
if (!f->last_write_comp)
goto cleanup;
}
Expand Down
2 changes: 2 additions & 0 deletions cconv.c
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ int convert_thread_options_to_cpu(struct thread_options *o,
o->sync_io = le32_to_cpu(top->sync_io);
o->write_hint = le32_to_cpu(top->write_hint);
o->verify = le32_to_cpu(top->verify);
o->verify_type = le32_to_cpu(top->verify_type);
o->do_verify = le32_to_cpu(top->do_verify);
o->experimental_verify = le32_to_cpu(top->experimental_verify);
o->verify_state = le32_to_cpu(top->verify_state);
Expand Down Expand Up @@ -443,6 +444,7 @@ void convert_thread_options_to_net(struct thread_options_pack *top,
top->sync_io = cpu_to_le32(o->sync_io);
top->write_hint = cpu_to_le32(o->write_hint);
top->verify = cpu_to_le32(o->verify);
top->verify_type = cpu_to_le32(o->verify_type);
top->do_verify = cpu_to_le32(o->do_verify);
top->experimental_verify = cpu_to_le32(o->experimental_verify);
top->verify_state = cpu_to_le32(o->verify_state);
Expand Down
4 changes: 4 additions & 0 deletions engines/io_uring.c
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,10 @@ static int fio_ioring_cmd_prep(struct thread_data *td, struct io_u *io_u)
io_u_set(td, io_u, IO_U_F_VER_IN_DEV);
}

/* Mark FUA writes for verification state tracking */
if (io_u->ddir == DDIR_WRITE && o->writefua)
io_u_set(td, io_u, IO_U_F_FUA);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be hidden in the DDIR_WRITE case of fio_nvme_uring_cmd_prep() in nvme.c?


return fio_nvme_uring_cmd_prep(cmd, io_u,
o->nonvectored ? NULL : &ld->iovecs[io_u->index],
dsm, read_opcode, ld->write_opcode,
Expand Down
4 changes: 4 additions & 0 deletions engines/sg.c
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,10 @@ static int fio_sgio_prep(struct thread_data *td, struct io_u *io_u)
fio_sgio_rw_lba(hdr, lba, nr_blocks,
o->write_mode == FIO_SG_WRITE_SAME_NDOB);

/* Mark FUA writes for verification state tracking */
if (o->writefua)
io_u_set(td, io_u, IO_U_F_FUA);

} else if (io_u->ddir == DDIR_TRIM) {
struct sgio_trim *st;

Expand Down
12 changes: 11 additions & 1 deletion file.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ enum fio_file_flags {
FIO_FILE_smalloc = 1 << 9, /* smalloc file/file_name */
};

/* Flags for fio_write_comp.flags */
#define FIO_WRITE_COMP_FUA 0x1 /* Write had Force Unit Access flag */

enum file_lock_mode {
FILE_LOCK_NONE,
FILE_LOCK_EXCLUSIVE,
Expand Down Expand Up @@ -126,8 +129,15 @@ struct fio_file {
* Tracks the last iodepth number of completed writes, if data
* verification is enabled
*/
uint64_t *last_write_comp;
struct fio_write_comp {
uint64_t offset;
uint64_t completion_time_nsec;
uint32_t flags; /* I/O flags including FUA */
uint32_t flush_count; /* FLUSH count at completion time */
} *last_write_comp;
unsigned int last_write_idx;
uint64_t last_flush_time_nsec; /* Last FLUSH completion timestamp */
unsigned int flush_count; /* Count of completed FLUSH operations */

/*
* For use by the io engine to store offset
Expand Down
28 changes: 28 additions & 0 deletions fio.1
Original file line number Diff line number Diff line change
Expand Up @@ -3734,6 +3734,34 @@ Recreate an instance of the \fBverify_pattern\fR every
up the process of writing each block on a device with its offset. Default:
0 (disabled).
.TP
.BI verify_type \fR=\fPstr
Controls which write operations are included during the verification phase.
This option only affects offline verification when using \fBverify_state_save\fR
to save completion state and later verify with a separate job. The allowed
values are:
.RS
.RS
.TP
.B flush
Only verify writes that completed at or before the last fsync operation.
This mode filters out writes that completed after the last fsync, which may
not be persistent on storage. Writes with the Force Unit Access (FUA) flag
are always included regardless of fsync timing, as they bypass the cache and
are immediately persistent. This is useful for testing data persistence
guarantees across power failures or system crashes.
.RE
.P
When \fBverify_type=flush\fR is used, fio tracks fsync completion times and
write completion times during the write phase. During verification, only
writes that meet the fsync timing criteria are verified. This allows testing
scenarios where only data that was properly synced before a simulated
failure should be verified.
.P
This option requires \fBverify_state_save\fR to be enabled and is only
effective during offline verification (separate verify job). Default: none
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need to explicitly list that none as one of the options above too.

(verify all completed writes).
.RE
.TP
.BI verify_fatal \fR=\fPbool
Normally fio will keep checking the entire contents before quitting on a
block verification failure. If this option is set, fio will exit the job on
Expand Down
33 changes: 30 additions & 3 deletions io_u.c
Original file line number Diff line number Diff line change
Expand Up @@ -2063,7 +2063,7 @@ static void account_io_completion(struct thread_data *td, struct io_u *io_u,
}

static void file_log_write_comp(const struct thread_data *td, struct fio_file *f,
uint64_t offset, unsigned int bytes)
uint64_t offset, unsigned int bytes, struct io_u *io_u)
{
int idx;

Expand All @@ -2079,11 +2079,31 @@ static void file_log_write_comp(const struct thread_data *td, struct fio_file *f
return;

idx = f->last_write_idx++;
f->last_write_comp[idx] = offset;
f->last_write_comp[idx].offset = offset;
f->last_write_comp[idx].completion_time_nsec = ntime_since_now(&io_u->start_time);
f->last_write_comp[idx].flags = 0;
f->last_write_comp[idx].flush_count = f->flush_count;

/* Check if this is a FUA write */
if (io_u && (io_u->flags & IO_U_F_FUA))
f->last_write_comp[idx].flags |= FIO_WRITE_COMP_FUA;

if (f->last_write_idx == td->last_write_comp_depth)
f->last_write_idx = 0;
}

static void file_log_flush_comp(struct fio_file *f, struct io_u *io_u)
{
if (!f)
return;

/* Track the last FLUSH completion timestamp */
f->last_flush_time_nsec = ntime_since_now(&io_u->start_time);

/* Increment FLUSH counter */
f->flush_count++;
}

static bool should_account(struct thread_data *td)
{
return ramp_time_over(td) && (td->runstate == TD_RUNNING ||
Expand Down Expand Up @@ -2125,7 +2145,10 @@ static void io_completed(struct thread_data *td, struct io_u **io_u_ptr,
if (ddir_sync(ddir)) {
if (io_u->error)
goto error;

/* Log flush completion */
if (f) {
file_log_flush_comp(f, io_u);
f->first_write = -1ULL;
f->last_write = -1ULL;
}
Expand Down Expand Up @@ -2164,7 +2187,7 @@ static void io_completed(struct thread_data *td, struct io_u **io_u_ptr,
}

if (ddir == DDIR_WRITE)
file_log_write_comp(td, f, io_u->offset, bytes);
file_log_write_comp(td, f, io_u->offset, bytes, io_u);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're going to pass the full io_u does it make sense to pass the offset separately rather than calculate it in the function?


if (should_account(td))
account_io_completion(td, io_u, icd, ddir, bytes);
Expand Down Expand Up @@ -2464,6 +2487,10 @@ int do_io_u_sync(const struct thread_data *td, struct io_u *io_u)

if (ret < 0)
io_u->error = errno;
else {
/* Record FLUSH completion timing for verification state */
file_log_flush_comp(io_u->file, io_u);
}

return ret;
}
Expand Down
1 change: 1 addition & 0 deletions io_u.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ enum {
IO_U_F_PATTERN_DONE = 1 << 8,
IO_U_F_DEVICE_ERROR = 1 << 9,
IO_U_F_VER_IN_DEV = 1 << 10, /* Verify data in device */
IO_U_F_FUA = 1 << 11, /* Force Unit Access flag */
};

/*
Expand Down
22 changes: 22 additions & 0 deletions options.c
Original file line number Diff line number Diff line change
Expand Up @@ -3256,6 +3256,28 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
.category = FIO_OPT_C_IO,
.group = FIO_OPT_G_VERIFY,
},
{
.name = "verify_type",
.lname = "Verify type",
.type = FIO_OPT_STR,
.off1 = offsetof(struct thread_options, verify_type),
.help = "Verification filter type",
.def = "none",
.parent = "verify",
.hide = 1,
.category = FIO_OPT_C_IO,
.group = FIO_OPT_G_VERIFY,
.posval = {
{ .ival = "none",
.oval = VERIFY_NONE,
.help = "No verification filtering",
},
{ .ival = "flush",
.oval = VERIFY_TYPE_FLUSH,
.help = "Verify only writes that completed before flush",
},
},
},
{
.name = "verifysort",
.lname = "Verify sort",
Expand Down
10 changes: 7 additions & 3 deletions t/verify-state.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@ static void show_s(struct thread_io_list *s, unsigned int no_s)
printf("Max completions per file:\t\t%lu\n", (unsigned long) s->max_no_comps_per_file);
printf("Number IOs:\t%llu\n", (unsigned long long) s->numberio);
printf("Index:\t\t%llu\n", (unsigned long long) s->index);
printf("Last flush count:\t%u\n", s->last_flush_count);

printf("Completions:\n");
if (!s->no_comps)
return;
for (i = s->no_comps - 1; i >= 0; i--) {
printf("\t(file=%2llu) %llu\n",
printf("\t(file=%2llu) %llu (flush_count=%u)\n",
(unsigned long long) s->comps[i].fileno,
(unsigned long long) s->comps[i].offset);
(unsigned long long) s->comps[i].offset,
s->comps[i].flush_count);
}
}

Expand All @@ -51,10 +53,12 @@ static void show(struct thread_io_list *s, size_t size)
s->nofiles = le32_to_cpu(s->nofiles);
s->numberio = le64_to_cpu(s->numberio);
s->index = le64_to_cpu(s->index);
s->last_flush_count = le32_to_cpu(s->last_flush_count);

for (i = 0; i < s->no_comps; i++) {
s->comps[i].fileno = le64_to_cpu(s->comps[i].fileno);
s->comps[i].offset = le64_to_cpu(s->comps[i].offset);
s->comps[i].flush_count = le32_to_cpu(s->comps[i].flush_count);
}

show_s(s, no_s);
Expand Down Expand Up @@ -92,7 +96,7 @@ static void show_verify_state(void *buf, size_t size)
return;
}

if (hdr->version == 0x04)
if (hdr->version == 0x05)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're going to need to bump this up even further after rebasing...

show(s, size);
else
log_err("Unsupported version %d\n", (int) hdr->version);
Expand Down
4 changes: 4 additions & 0 deletions thread_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ struct thread_options {
unsigned int sync_io;
unsigned int write_hint;
unsigned int verify;
unsigned int verify_type;
unsigned int do_verify;
unsigned int verify_interval;
unsigned int verify_offset;
Expand Down Expand Up @@ -189,6 +190,7 @@ struct thread_options {

struct zone_split *zone_split[DDIR_RWDIR_CNT];
unsigned int zone_split_nr[DDIR_RWDIR_CNT];
uint32_t pad2;

fio_fp64_t zipf_theta;
fio_fp64_t pareto_h;
Expand Down Expand Up @@ -477,6 +479,7 @@ struct thread_options_pack {
uint32_t sync_io;
uint32_t write_hint;
uint32_t verify;
uint32_t verify_type;
uint32_t do_verify;
uint32_t verify_interval;
uint32_t verify_offset;
Expand Down Expand Up @@ -521,6 +524,7 @@ struct thread_options_pack {

struct zone_split zone_split[DDIR_RWDIR_CNT][ZONESPLIT_MAX];
uint32_t zone_split_nr[DDIR_RWDIR_CNT];
uint32_t pad2;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After rebasing you'll have to see if you still need the pad.


fio_fp64_t zipf_theta;
fio_fp64_t pareto_h;
Expand Down
9 changes: 8 additions & 1 deletion verify-state.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ struct thread_rand_state {
struct file_comp {
uint64_t fileno;
uint64_t offset;
uint32_t flush_count; /* FLUSH count at completion time for ordering */
uint32_t flags; /* I/O flags including FUA */
};

struct thread_io_list {
Expand All @@ -37,6 +39,8 @@ struct thread_io_list {
uint32_t nofiles;
uint64_t numberio;
uint64_t index;
uint32_t last_flush_count; /* Last FLUSH count for ordering */
uint32_t padding; /* Padding for alignment */
struct thread_rand_state rand;
uint8_t name[64];
struct file_comp comps[0];
Expand All @@ -47,7 +51,7 @@ struct all_io_list {
struct thread_io_list state[0];
};

#define VSTATE_HDR_VERSION 0x04
#define VSTATE_HDR_VERSION 0x05 /* Incremented for FLUSH count support */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might need bumping after rebasing.


struct verify_state_hdr {
uint64_t version;
Expand All @@ -57,6 +61,9 @@ struct verify_state_hdr {

#define IO_LIST_ALL 0xffffffff

/* Flags for file_comp.flags */
#define FIO_COMP_FLAG_FUA 0x1 /* Write had Force Unit Access flag */

struct io_u;
extern struct all_io_list *get_all_io_list(int, size_t *);
extern void __verify_save_state(struct all_io_list *, const char *);
Expand Down
Loading