Skip to content

Commit 03c7deb

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]>
1 parent 60c19ed commit 03c7deb

File tree

13 files changed

+188
-10
lines changed

13 files changed

+188
-10
lines changed

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 && (ld->cdw12_flags[DDIR_WRITE] & (1 << 30)))
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,

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

fio.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,8 @@ struct thread_data {
294294
unsigned int trim_batch;
295295
bool trim_verify;
296296

297+
struct timespec flush_completion_time;
298+
297299
struct thread_io_list *vstate;
298300

299301
int shm_id;

io_u.c

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2063,9 +2063,10 @@ 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;
2069+
uint64_t now_nsec;
20692070

20702071
if (!f)
20712072
return;
@@ -2078,12 +2079,37 @@ static void file_log_write_comp(const struct thread_data *td, struct fio_file *f
20782079
if (!f->last_write_comp)
20792080
return;
20802081

2082+
now_nsec = utime_since_now(&io_u->start_time) * 1000; /* Convert to nanoseconds */
2083+
20812084
idx = f->last_write_idx++;
2082-
f->last_write_comp[idx] = offset;
2085+
f->last_write_comp[idx].offset = offset;
2086+
f->last_write_comp[idx].completion_time_nsec = now_nsec;
2087+
f->last_write_comp[idx].flags = 0;
2088+
f->last_write_comp[idx].flush_count = f->flush_count;
2089+
2090+
/* Check if this is a FUA write */
2091+
if (io_u && (io_u->flags & IO_U_F_FUA))
2092+
f->last_write_comp[idx].flags |= FIO_WRITE_COMP_FUA;
2093+
20832094
if (f->last_write_idx == td->last_write_comp_depth)
20842095
f->last_write_idx = 0;
20852096
}
20862097

2098+
static void file_log_flush_comp(struct fio_file *f, struct io_u *io_u)
2099+
{
2100+
uint64_t completion_time;
2101+
2102+
if (!f)
2103+
return;
2104+
2105+
/* Track the last FLUSH completion timestamp */
2106+
completion_time = utime_since_now(&io_u->start_time) * 1000;
2107+
f->last_flush_time_nsec = completion_time;
2108+
2109+
/* Increment FLUSH counter */
2110+
f->flush_count++;
2111+
}
2112+
20872113
static bool should_account(struct thread_data *td)
20882114
{
20892115
return ramp_time_over(td) && (td->runstate == TD_RUNNING ||
@@ -2125,7 +2151,14 @@ static void io_completed(struct thread_data *td, struct io_u **io_u_ptr,
21252151
if (ddir_sync(ddir)) {
21262152
if (io_u->error)
21272153
goto error;
2154+
2155+
/* Record flush completion time for verify_type=flush */
2156+
if (td->o.verify_type == VERIFY_TYPE_FLUSH)
2157+
fio_gettime(&td->flush_completion_time, NULL);
2158+
2159+
/* Log flush completion */
21282160
if (f) {
2161+
file_log_flush_comp(f, io_u);
21292162
f->first_write = -1ULL;
21302163
f->last_write = -1ULL;
21312164
}
@@ -2164,7 +2197,7 @@ static void io_completed(struct thread_data *td, struct io_u **io_u_ptr,
21642197
}
21652198

21662199
if (ddir == DDIR_WRITE)
2167-
file_log_write_comp(td, f, io_u->offset, bytes);
2200+
file_log_write_comp(td, f, io_u->offset, bytes, io_u);
21682201

21692202
if (should_account(td))
21702203
account_io_completion(td, io_u, icd, ddir, bytes);
@@ -2464,6 +2497,10 @@ int do_io_u_sync(const struct thread_data *td, struct io_u *io_u)
24642497

24652498
if (ret < 0)
24662499
io_u->error = errno;
2500+
else {
2501+
/* Record FLUSH completion timing for verification state */
2502+
file_log_flush_comp(io_u->file, io_u);
2503+
}
24672504

24682505
return ret;
24692506
}

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",

thread_options.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ struct thread_options {
141141
unsigned int sync_io;
142142
unsigned int write_hint;
143143
unsigned int verify;
144+
unsigned int verify_type;
144145
unsigned int do_verify;
145146
unsigned int verify_interval;
146147
unsigned int verify_offset;
@@ -475,6 +476,7 @@ struct thread_options_pack {
475476
uint32_t sync_io;
476477
uint32_t write_hint;
477478
uint32_t verify;
479+
uint32_t verify_type;
478480
uint32_t do_verify;
479481
uint32_t verify_interval;
480482
uint32_t verify_offset;
@@ -519,7 +521,7 @@ struct thread_options_pack {
519521

520522
struct zone_split zone_split[DDIR_RWDIR_CNT][ZONESPLIT_MAX];
521523
uint32_t zone_split_nr[DDIR_RWDIR_CNT];
522-
uint32_t pad2;
524+
uint64_t pad2;
523525

524526
fio_fp64_t zipf_theta;
525527
fio_fp64_t pareto_h;

0 commit comments

Comments
 (0)