Skip to content

Commit 603be44

Browse files
committed
verify: add --verify_type option for crash-consistent verification
This patch introduces a new --verify_type option to support crash-consistent offline verification scenarios. The primary motivation is to handle cases where write operations are interrupted by device failures or unexpected power loss during the write phase, requiring verification to be limited to data that was properly persisted before the interruption. Key features: - New --verify_type=flush option filters verification candidates based on fsync completion timing - Tracks fsync completion timestamps during write phase - During verification, excludes writes that completed after the last fsync - Always includes FUA (Force Unit Access) writes regardless of timing, as they bypass cache and are immediately persistent - Works with existing verify_state_save mechanism for offline verification Use case: This is particularly useful for testing storage durability guarantees in scenarios such as: 1. Simulated power failures during write workloads 2. Device error injection testing and espeically with such ioengines which directly communicates with the device directly as io_uring_cmd does. This enhancement enables more realistic testing of data durability in storage systems by ensuring only properly synchronized data is verified after simulated failures. Examples: 1. write phase [global] ioengine=io_uring_cmd cmd_type=nvme filename=/dev/ng0n1 rw=write bs=4k verify=pattern verify_pattern=%o iodepth=32 size=32k fsync=3 [test] verify_type=flush do_verify=0 verify_state_save=1 2. read phase [global] ioengine=io_uring_cmd cmd_type=nvme filename=/dev/ng0n1 rw=write bs=4k verify=pattern verify_pattern=%o iodepth=32 size=32k fsync=3 [test] verify_type=flush do_verify=1 verify_only=1 verify_state_load=1 Signed-off-by: Minwoo Im <[email protected]> Signed-off-by: Minwoo Im <[email protected]>
1 parent 60c19ed commit 603be44

File tree

16 files changed

+215
-14
lines changed

16 files changed

+215
-14
lines changed

HOWTO.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3989,6 +3989,33 @@ Verification
39893989
used to speed up the process of writing each block on a device with its
39903990
offset. Default: 0 (disabled).
39913991

3992+
.. option:: verify_type=str
3993+
3994+
Controls which write operations are included during the verification
3995+
phase. This option only affects offline verification when using
3996+
:option:`verify_state_save` to save completion state and later verify
3997+
with a separate job. The allowed values are:
3998+
3999+
**flush**
4000+
Only verify writes that completed at or before the last
4001+
fsync operation. This mode filters out writes that
4002+
completed after the last fsync, which may not be
4003+
persistent on storage. Writes with the Force Unit
4004+
Access (FUA) flag are always included regardless of
4005+
fsync timing, as they bypass the cache and are
4006+
immediately persistent. This is useful for testing data
4007+
persistence guarantees across power failures or system
4008+
crashes. fio tracks fsync completion times and write
4009+
completion times during the write phase. During
4010+
verification, only writes that meet the fsync timing
4011+
criteria are verified. This allows testing scenarios
4012+
where only data that was properly synced before a
4013+
simulated failure should be verified.
4014+
This option requires :option:`verify_state_save` to be
4015+
enabled and is only effective during offline
4016+
verification (separate verify job). Default: none
4017+
(verify all completed writes).
4018+
39924019
.. option:: verify_fatal=bool
39934020

39944021
Normally fio will keep checking the entire contents before quitting on a

backend.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1259,7 +1259,7 @@ static int init_file_completion_logging(struct thread_data *td,
12591259

12601260
for_each_file(td, f, i) {
12611261
f->last_write_comp = scalloc(td->last_write_comp_depth,
1262-
sizeof(uint64_t));
1262+
sizeof(struct fio_write_comp));
12631263
if (!f->last_write_comp)
12641264
goto cleanup;
12651265
}

cconv.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ int convert_thread_options_to_cpu(struct thread_options *o,
177177
o->sync_io = le32_to_cpu(top->sync_io);
178178
o->write_hint = le32_to_cpu(top->write_hint);
179179
o->verify = le32_to_cpu(top->verify);
180+
o->verify_type = le32_to_cpu(top->verify_type);
180181
o->do_verify = le32_to_cpu(top->do_verify);
181182
o->experimental_verify = le32_to_cpu(top->experimental_verify);
182183
o->verify_state = le32_to_cpu(top->verify_state);
@@ -441,6 +442,7 @@ void convert_thread_options_to_net(struct thread_options_pack *top,
441442
top->sync_io = cpu_to_le32(o->sync_io);
442443
top->write_hint = cpu_to_le32(o->write_hint);
443444
top->verify = cpu_to_le32(o->verify);
445+
top->verify_type = cpu_to_le32(o->verify_type);
444446
top->do_verify = cpu_to_le32(o->do_verify);
445447
top->experimental_verify = cpu_to_le32(o->experimental_verify);
446448
top->verify_state = cpu_to_le32(o->verify_state);

engines/io_uring.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,10 @@ static int fio_ioring_cmd_prep(struct thread_data *td, struct io_u *io_u)
556556
io_u_set(td, io_u, IO_U_F_VER_IN_DEV);
557557
}
558558

559+
/* Mark FUA writes for verification state tracking */
560+
if (io_u->ddir == DDIR_WRITE && o->writefua)
561+
io_u_set(td, io_u, IO_U_F_FUA);
562+
559563
return fio_nvme_uring_cmd_prep(cmd, io_u,
560564
o->nonvectored ? NULL : &ld->iovecs[io_u->index],
561565
dsm, read_opcode, ld->write_opcode,

engines/sg.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,10 @@ static int fio_sgio_prep(struct thread_data *td, struct io_u *io_u)
668668
fio_sgio_rw_lba(hdr, lba, nr_blocks,
669669
o->write_mode == FIO_SG_WRITE_SAME_NDOB);
670670

671+
/* Mark FUA writes for verification state tracking */
672+
if (o->writefua)
673+
io_u_set(td, io_u, IO_U_F_FUA);
674+
671675
} else if (io_u->ddir == DDIR_TRIM) {
672676
struct sgio_trim *st;
673677

file.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ enum fio_file_flags {
3737
FIO_FILE_smalloc = 1 << 9, /* smalloc file/file_name */
3838
};
3939

40+
/* Flags for fio_write_comp.flags */
41+
#define FIO_WRITE_COMP_FUA 0x1 /* Write had Force Unit Access flag */
42+
4043
enum file_lock_mode {
4144
FILE_LOCK_NONE,
4245
FILE_LOCK_EXCLUSIVE,
@@ -126,8 +129,15 @@ struct fio_file {
126129
* Tracks the last iodepth number of completed writes, if data
127130
* verification is enabled
128131
*/
129-
uint64_t *last_write_comp;
132+
struct fio_write_comp {
133+
uint64_t offset;
134+
uint64_t completion_time_nsec;
135+
uint32_t flags; /* I/O flags including FUA */
136+
uint32_t flush_count; /* FLUSH count at completion time */
137+
} *last_write_comp;
130138
unsigned int last_write_idx;
139+
uint64_t last_flush_time_nsec; /* Last FLUSH completion timestamp */
140+
unsigned int flush_count; /* Count of completed FLUSH operations */
131141

132142
/*
133143
* For use by the io engine to store offset

fio.1

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3712,6 +3712,34 @@ Recreate an instance of the \fBverify_pattern\fR every
37123712
up the process of writing each block on a device with its offset. Default:
37133713
0 (disabled).
37143714
.TP
3715+
.BI verify_type \fR=\fPstr
3716+
Controls which write operations are included during the verification phase.
3717+
This option only affects offline verification when using \fBverify_state_save\fR
3718+
to save completion state and later verify with a separate job. The allowed
3719+
values are:
3720+
.RS
3721+
.RS
3722+
.TP
3723+
.B flush
3724+
Only verify writes that completed at or before the last fsync operation.
3725+
This mode filters out writes that completed after the last fsync, which may
3726+
not be persistent on storage. Writes with the Force Unit Access (FUA) flag
3727+
are always included regardless of fsync timing, as they bypass the cache and
3728+
are immediately persistent. This is useful for testing data persistence
3729+
guarantees across power failures or system crashes.
3730+
.RE
3731+
.P
3732+
When \fBverify_type=flush\fR is used, fio tracks fsync completion times and
3733+
write completion times during the write phase. During verification, only
3734+
writes that meet the fsync timing criteria are verified. This allows testing
3735+
scenarios where only data that was properly synced before a simulated
3736+
failure should be verified.
3737+
.P
3738+
This option requires \fBverify_state_save\fR to be enabled and is only
3739+
effective during offline verification (separate verify job). Default: none
3740+
(verify all completed writes).
3741+
.RE
3742+
.TP
37153743
.BI verify_fatal \fR=\fPbool
37163744
Normally fio will keep checking the entire contents before quitting on a
37173745
block verification failure. If this option is set, fio will exit the job on

io_u.c

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2063,7 +2063,7 @@ static void account_io_completion(struct thread_data *td, struct io_u *io_u,
20632063
}
20642064

20652065
static void file_log_write_comp(const struct thread_data *td, struct fio_file *f,
2066-
uint64_t offset, unsigned int bytes)
2066+
uint64_t offset, unsigned int bytes, struct io_u *io_u)
20672067
{
20682068
int idx;
20692069

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

20812081
idx = f->last_write_idx++;
2082-
f->last_write_comp[idx] = offset;
2082+
f->last_write_comp[idx].offset = offset;
2083+
f->last_write_comp[idx].completion_time_nsec = ntime_since_now(&io_u->start_time);
2084+
f->last_write_comp[idx].flags = 0;
2085+
f->last_write_comp[idx].flush_count = f->flush_count;
2086+
2087+
/* Check if this is a FUA write */
2088+
if (io_u && (io_u->flags & IO_U_F_FUA))
2089+
f->last_write_comp[idx].flags |= FIO_WRITE_COMP_FUA;
2090+
20832091
if (f->last_write_idx == td->last_write_comp_depth)
20842092
f->last_write_idx = 0;
20852093
}
20862094

2095+
static void file_log_flush_comp(struct fio_file *f, struct io_u *io_u)
2096+
{
2097+
if (!f)
2098+
return;
2099+
2100+
/* Track the last FLUSH completion timestamp */
2101+
f->last_flush_time_nsec = ntime_since_now(&io_u->start_time);
2102+
2103+
/* Increment FLUSH counter */
2104+
f->flush_count++;
2105+
}
2106+
20872107
static bool should_account(struct thread_data *td)
20882108
{
20892109
return ramp_time_over(td) && (td->runstate == TD_RUNNING ||
@@ -2125,7 +2145,10 @@ static void io_completed(struct thread_data *td, struct io_u **io_u_ptr,
21252145
if (ddir_sync(ddir)) {
21262146
if (io_u->error)
21272147
goto error;
2148+
2149+
/* Log flush completion */
21282150
if (f) {
2151+
file_log_flush_comp(f, io_u);
21292152
f->first_write = -1ULL;
21302153
f->last_write = -1ULL;
21312154
}
@@ -2164,7 +2187,7 @@ static void io_completed(struct thread_data *td, struct io_u **io_u_ptr,
21642187
}
21652188

21662189
if (ddir == DDIR_WRITE)
2167-
file_log_write_comp(td, f, io_u->offset, bytes);
2190+
file_log_write_comp(td, f, io_u->offset, bytes, io_u);
21682191

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

24652488
if (ret < 0)
24662489
io_u->error = errno;
2490+
else {
2491+
/* Record FLUSH completion timing for verification state */
2492+
file_log_flush_comp(io_u->file, io_u);
2493+
}
24672494

24682495
return ret;
24692496
}

io_u.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ enum {
2424
IO_U_F_PATTERN_DONE = 1 << 8,
2525
IO_U_F_DEVICE_ERROR = 1 << 9,
2626
IO_U_F_VER_IN_DEV = 1 << 10, /* Verify data in device */
27+
IO_U_F_FUA = 1 << 11, /* Force Unit Access flag */
2728
};
2829

2930
/*

options.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3240,6 +3240,28 @@ struct fio_option fio_options[FIO_MAX_OPTS] = {
32403240
.category = FIO_OPT_C_IO,
32413241
.group = FIO_OPT_G_VERIFY,
32423242
},
3243+
{
3244+
.name = "verify_type",
3245+
.lname = "Verify type",
3246+
.type = FIO_OPT_STR,
3247+
.off1 = offsetof(struct thread_options, verify_type),
3248+
.help = "Verification filter type",
3249+
.def = "none",
3250+
.parent = "verify",
3251+
.hide = 1,
3252+
.category = FIO_OPT_C_IO,
3253+
.group = FIO_OPT_G_VERIFY,
3254+
.posval = {
3255+
{ .ival = "none",
3256+
.oval = VERIFY_NONE,
3257+
.help = "No verification filtering",
3258+
},
3259+
{ .ival = "flush",
3260+
.oval = VERIFY_TYPE_FLUSH,
3261+
.help = "Verify only writes that completed before flush",
3262+
},
3263+
},
3264+
},
32433265
{
32443266
.name = "verifysort",
32453267
.lname = "Verify sort",

0 commit comments

Comments
 (0)