fio test coverage - vincentkfu/fio-blog GitHub Wiki
Fio's test coverage is far from complete. In fact seventy-three percent of fio's options are not exercised by its automated tests. This blog post is the first in a series that aims to construct a test development roadmap for fio. The first step is this post which describes the current state of test coverage for fio.
The scope of this blog post will be a list of fio's options accompanied by identification of the extent of automated testing for each option. The emphasis will be on end-to-end testing (as opposed to unit testing, for example). This assessment is based on my experience maintaining fio's continuous integration test suite and on the results of searching in fio's test scripts for each option. The focus of the assessment is on basic use cases for each option with most other options at their default values. Fio's options are so numerous that it would be impractical to exhaustively test all possible combinations of options. I classified option test coverage into four categories: basic, limited, no explicit testing, and none. Descriptions for each category and an overall summary are in the table below.
Test Coverage Summary
Label | Count | Percentage | Description |
---|---|---|---|
basic | 23 | 6% | Option is (near) fully exercised with other options typically at their default values |
limited | 32 | 9% | Option settings are partially exercised with other options typically at their default values |
no explicit testing | 44 | 12% | Option is used in the course of testing other options |
none | 263 | 73% | This option is essentially not used in any automated tests in the fio repository |
Total | 362 | 100% |
Fio version: fio-3.35
We definitely have our work cut out for us if we are seeking any reasonable
level of test coverage. Nearly three quarters of fio's options are listed in
the category none as they are not exercised at all by automated testing. Six
percent of fio's options have basic testing where most of the option's possible
values are tested with remaining options mostly at their defaults. An example
of this are the options related to setting random seeds where combinations of
the different options are tested in t/random_seed.py
. Nine percent of options
have limited testing where test cases include only a subset of its possible
values. The option cmdprio_percentage
, for example, is used in some test
cases in t/latency_percentiles.py
but the value is always set to 50 percent
(and there are no checks to see that the outcomes actually meet this
threshold). Finally, nine percent of options receive no explicit testing. This
category is for options such as filename
which are widely used in fio's test
cases but no where does any test explicitly check that fio actually used the
specified file or device.
Categories of Options
Does examining test coverage for different categories of options provide any additional insight? The documentation divides fio's options into 23 categories. The largest ones are I/O engine specific parameters (94 options), Command-line options (36 options), Measurements and reporting (33 options), and Target file/device (30 options).
The greatest contributor to the global absence of test coverage is I/O engine specific parameters. The table below shows test coverage for options falling within this category.
I/O Engine Specific Parameters Test Coverage
Coverage | Count | Percentage |
---|---|---|
basic | 0 | 0% |
limited | 2 | 2% |
no explicit testing | 0 | 0% |
none | 92 | 98% |
Total | 94 | 100% |
Fio has 94 I/O engine-specific parameters and has test coverage for only two of
them (limited testing for cmdprio_percentage
and cmdprio_bssplit
). The
remaining 92 options (e.g., fixedbufs
, hipri
, nfs_url
, etc). do not
appear in any of fio's automated test scripts. These I/O engine options
comprise approximately one-third of all of fio's options that are not exercised
by automated tests. I/O engine options tend to be narrow in scope since they
typically apply only to no more than a handful of I/O engines. This partially
amerliorates the lack of test coverage for all of fio's options, but even if we
remove I/O engine specific options from the equation, options with no test
coverage at all still significantly outnumber those that do appear in automated
tests.
Command-line options and options related to Buffers and memory also contribute
greatly to the global lack of test coverage. As the table below shows,
Command-line options have 75% of the 36 options absent from any automated test.
These include options such as status-interval
and trigger-file
. The
command-line options that do have testing include readonly
(basic features
tested in t/readonly.py
) and output
which receives no explicit testing but
is used widely across tests that inspect fio output.
Of the sixteen options related to buffers and memory, fifteen or 94 percent are
untested. These include options such as zero_buffers
and iomem
. The only
option from this set that is tested is buffer_pattern
which appears in
t/jobs/t0027.fio
and t/jobs/t0028-c6cade16.fio
.
Test Coverage for Low Test Coverage Categories
Coverage | Count | Percentage |
---|---|---|
Command-line options | ||
basic | 2 | 6% |
limited | 0 | 0% |
no explicit testing | 7 | 19% |
none | 27 | 75% |
Total | 36 | 100% |
Buffers and memory | ||
basic | 0 | 0% |
limited | 1 | 6% |
no explicit testing | 0 | 0% |
none | 15 | 94% |
Total | 16 | 100% |
The other side of the coin is categories with relatively high test coverage.
These are categories where more than half of the options do have test coverage.
The categories include I/O type and Verification. As the table below shows, for
I/O type options, 10 percent have basic testing, 14 percent have limited
testing, 34 percent have no explicit testing, and the remaining 41 percent are
not covered by any testing. I/O type options that appear in automated tests
include options like offset
and random_generator
which both receive limited
testing in strided.py
and the zbd test suite. Untested I/O type options
include random_distribution
and percentage_random
. random_distribution
allows users to specify non-uniform random distributions for random offsets
whereas percentage_random
specifies the mix of random and sequential offsets
for random workloads.
For Verification options, 32 percent have limited coverage, 21 percent have no
explicit testing, and 47 percent have no test coverage at all. Verification
options covered by automated testing include do_verify
and verify
both of
which receive limited by jobs in t/jobs
and the zbd test suite.
Untested Verification options include trim_percentage
and trim_backlog
both
of which are related to a fio feature that provides a means to trim blocks
after they are written.
Test Coverage for High Test Coverage Categories
Coverage | Count | Percentage |
---|---|---|
I/O type | ||
basic | 3 | 10% |
limited | 4 | 14% |
no explicit testing | 10 | 34% |
none | 12 | 41% |
Total | 30 | 100% |
Verification | ||
basic | 0 | 0% |
limited | 6 | 32% |
no explicit testing | 4 | 21% |
none | 9 | 47% |
Total | 19 | 100% |
Summary
The headline here is that fio's test coverage is poor and would benefit from significant improvement. Since fio is widely used by storage vendors and the Linux kernel community for validation, it is important for fio to function correctly. Part 2 of this blog post series will follow with a discussion of priorities for test development. Improving test coverage to any reasonable level will require substantial effort. How should we direct these efforts to obtain the greatest payoff?
The Appendix below contains a detailed breakdown of fio's option categories and how I classified each of fio's options with respect to test coverage. Raw data and scripts used for data analysis are available here.
Appendix
Frequency distribution of options by category
Category | Option count | Percentage |
---|---|---|
I/O engine specific parameters | 94 | 26% |
Command-line options | 36 | 10% |
Measurements and reporting | 33 | 9% |
Target file/device | 30 | 8% |
I/O type | 29 | 8% |
Threads, processes, and job synchronization | 23 | 6% |
Verification | 19 | 5% |
Buffers and memory | 16 | 4% |
I/O rate | 12 | 3% |
I/O replay | 12 | 3% |
Act profile options | 7 | 2% |
I/O depth | 7 | 2% |
Time-related parameters | 7 | 2% |
Block size | 6 | 2% |
Tiobench profile options | 5 | 1% |
I/O latency | 5 | 1% |
I/O size | 5 | 1% |
Steadystate | 4 | 1% |
Error handling | 4 | 1% |
Job description | 4 | 1% |
Units | 2 | 1% |
Predefined workloads | 1 | 0.3% |
I/O engine | 1 | 0.3% |
Coverage for each of fio's options
Category | Option | Coverage | Notes |
---|---|---|---|
Units | kb_base | none | |
unit_base | none | ||
Job description | name | no explicit testing | low priority |
description | none | low priority | |
loops | limited | a few tests in t/zbd/test-zbd-support | |
numjobs | no explicit testing | ||
Time-related parameters | runtime | no explicit testing | |
time_based | no explicit testing | ||
startdelay | none | ||
ramp_time | none | ||
clocksource | none | ||
gtod_reduce | none | ||
gtod_cpu | no explicit testing | ||
Target file/device | directory | none | |
filename | no explicit testing | ||
filename_format | none | ||
unique_filename | none | ||
opendir | none | ||
lockfile | none | ||
nrfiles | none | ||
openfiles | none | ||
file_service_type | none | ||
ioscheduler | none | ||
create_serialize | none | ||
create_fsync | none | ||
create_on_open | none | ||
create_only | none | ||
allow_file_create | limited | two appearances in t/zbd/test-zbd-support | |
allow_mounted_write | none | ||
pre_read | none | ||
unlink | none | ||
unlink_each_loop | none | ||
zonemode | basic | strided.py, t/zbd | |
zonerange | basic | ||
zonesize | basic | ||
zonecapacity | basic | ||
zoneskip | basic | ||
read_beyod_wp | basic | ||
max_open_zones | basic | ||
job_max_open_zones | none | ||
ignore_zone_limits | basic | ||
zone_reset_threshold | basic | ||
zone_reset_frequency | basic | ||
I/O type | direct | no explicit testing | |
buffered | no explicit testing | ||
readwrite | no explicit testing | test should include rw=randread:8 | |
rw_sequencer | no explicit testing | ||
unified_rw_reporting | limited | t/latency_percentiles | |
randrepeat | basic | t/random_seed.py | |
allrandrepeat | basic | t/random_seed.py | |
randseed | basic | t/random_seed.py | |
fallocate | none | ||
fadvise_hint | none | ||
write_hint | none | ||
offset | limited | t/strided.py, t/zbd, t/jobs/0002, t/jobs/0003 | |
offset_align | none | ||
offset_increment | no explicit testing | t/zbd | |
number_ios | no explicit testing | t/jobs/t0009 | |
fsync | limited | t/latency_percentiles | |
fdatasync | none | ||
write_barrier | none | ||
sync_file_range | none | ||
overwrite | none | ||
end_fsync | none | ||
fsync_on_close | none | ||
rwmixread | no explicit testing | t/latency_percentiles, t/steadystate_tests | |
rwmixwrite | no explicit testing | t/zbd | |
random_distribution | none | ||
percentage_random | none | ||
norandommap | no explicit testing | t/latency_percentiles, t/strided, t/zbd, t/jobs/0007,0009,0021,0022,0023 | |
softrandommap | no explicit testing | t/zbd | |
random_generator | limited | t/sgunmap-perf, t/strided, t/zbd, t/jobs/t0021 | |
Block size | blocksize | no explicit testing | |
blocksize_range | no explicit testing | t/zbd, t/jobs/0001,0002,0023,0024 | |
bssplit | no explicit testing | ||
blocksize_unaligned | none | ||
bs_is_seq_rand | none | ||
blockalign | none | ||
Buffers and memory | zero_buffers | none | |
refill_buffers | none | ||
scramble_buffers | none | ||
buffer_compress_percentage | none | ||
buffer_compress_chunk | none | ||
buffer_pattern | limited | t0027, t0028 | |
dedupe_percentage | none | ||
dedupe_mode | none | ||
dedupe_working_set_percentage | none | ||
dedupe_global | none | ||
invalidate | none | ||
sync | none | ||
iomem | none | ||
iomem_align | none | ||
hugepage-size | none | ||
lockmem | none | ||
I/O size | size | no explicit testing | t/latency_percentiles, t/strided, t/zbd, t/jobs |
io_size | no explicit testing | t/strided.py, t/zbd | |
filesize | no explicit testing | t/log_compression, t/random_seed, t-strided, t/jobs | |
file_append | none | ||
fill_device | none | ||
I/O engine | ioengine | no explicit testing | |
I/O engine specific parameters | cmdprio_percentage | limited | t/latency_percentiles |
cmdprio_class | none | ||
cmdprio | none | ||
cmdprio_bssplit | limited | ||
fixedbufs | none | ||
nonvectored | none | ||
force_async | none | ||
registerfiles | none | ||
sqthread_poll | none | ||
sqthread_poll_cpu | none | ||
cmd_type | none | ||
hipri | none | ||
userspace_reap | none | ||
hipri_percentage | none | ||
nowait | none | ||
fdp | none | ||
fdp_pli | none | ||
cpuload | none | ||
cpuchunks | none | ||
cpumode | none | ||
exit_on_io_done | none | ||
namenode | none | ||
port | none | ||
hostname | none | ||
serverip | none | ||
direct_write_to_pmem | none | ||
busy_wait_polling | none | ||
interface | none | ||
ttl | none | ||
nodelay | none | ||
protocol | none | ||
listen | none | ||
pingpong | none | ||
window_size | none | ||
mss | none | ||
donorname | none | ||
inplace | none | ||
clustername | none | ||
rbdname | none | ||
clientname | none | ||
conf | none | ||
busy_poll | none | ||
touch_objects | none | ||
pool | none | ||
cont | none | ||
chunk_size | none | ||
object_class | none | ||
skip_bad | none | ||
hdfsdirectory | none | ||
verb | none | ||
bindname | none | ||
stat_type | none | ||
readfua | none | ||
writefua | none | ||
sg_write_mode | none | ||
stream_id | none | ||
http_host | none | ||
http_user | none | ||
http_pass | none | ||
https | none | ||
http_mode | none | ||
http_s3_region | none | ||
http_s3_key | none | ||
http_s3_keyid | none | ||
http_s3_sse_customer_key | none | ||
http_s3_sse_customer_algorithm | none | ||
http_s3_storage_class | none | ||
http_swith_auth_token | none | ||
http_verbose | none | ||
uri | none | ||
gpu_dev_ids | none | ||
cuda_io | none | ||
nfs_url | none | ||
program | none | ||
arguments | none | ||
grace_time | none | ||
std_redirect | none | ||
xnvme_async | none | ||
xnvme_sync | none | ||
xnvme_admin | none | ||
xnvme_dev_nsid | none | ||
xnvme_dev_subnqn | none | ||
xnvme_mem | none | ||
xnvme_iovec | none | ||
libblkio_driver | none | ||
libblkio_path | none | ||
libblkio_pre_connect_props | none | ||
libblkio_num_entries | none | ||
libblkio_queue_size | none | ||
libblkio_pre_start_props | none | ||
libblkio_vectored | none | ||
libblkio_write_zeroes_on_trim | none | ||
libblkio_wait_mode | none | ||
libblkio_force_enable_completion_eventfd | none | ||
I/O depth | iodepth | no explicit testing | |
iodepth_batch_submit | limited | t/sgunmap-test.py | |
iodepth_batch_complete_min | limited | t/jobs/t0009 | |
iodepth_batch_complete_max | none | ||
iodepth_low | none | ||
serialize_overlap | limited | t/jobs/0013 | |
io_submit_mode | limited | t/jobs/0010. t/jobs/0013 | |
I/O rate | thinktime | none | |
thinktime_spin | none | ||
thinktime_blocks | none | ||
thinktime_blocks_types | none | ||
thinktime_iotime | none | ||
rate | none | ||
rate_min | none | ||
rate_iops | limited | t/jobs/0011 | |
rate_iops_min | none | ||
rate_process | none | ||
rate_ignore_thinktime | none | ||
rate_cycle | none | ||
I/O latency | latency_target | none | |
latency_window | none | ||
latency_percentile | none | ||
latency_run | none | ||
max_latency | none | ||
I/O replay | write_iolog | limited | t0007 |
read_iolog | none | ||
read_iolog_chunked | none | ||
merge_blktrace_file | none | ||
merge_blktrace_scalars | none | ||
merge_blktrace_iters | none | ||
replay_no_stall | none | ||
replay_time_scale | none | ||
replace_redirect | none | ||
replay_align | none | ||
replay_scale | none | ||
replay_skip | none | ||
Threads, processes, and job synchronization | thread | no explicit testing | t/jsonplus2csv_test, steadystate_tests, t/zbd, t/jobs |
wait_for | no explicit testing | t/zbd | |
nice | none | ||
prio | none | ||
prioclass | none | ||
cpus_allowed | no explicit testing | t/jobs/0009 | |
cpus_allowed_policy | no explicit testing | t/jobs/0009 | |
cpumask | none | ||
numa_cpu_nodes | none | ||
numa_mem_policy | none | ||
cgroup | none | ||
cgroup_weight | none | ||
cgroup_nodelete | none | ||
flow_id | limited | t0011, t0012, t0014 | |
flow | limited | t0011, t0012, t0014 | |
flow_sleep | no explicit testing | t0012 | |
stonewall/wait_for_previous | no explicit testing | ||
exitall | none | ||
exit_what | none | ||
exec_prerun | none | ||
exec_postrun | none | ||
uid | none | ||
gid | none | ||
Verification | verify_only | none | |
do_verify | limited | t/zbd, t0002-t0006, t0008, t0009, t0024-0027 | |
verify | limited | t/zbd, t0002-t0006, t0008, t0009, t0024-0027 | |
verify_offset | none | ||
verify_interval | no explicit testing | t0004 | |
verify_pattern | limited | t0006, t0027 | |
verify_fatal | limited | t0002, t0003 | |
verify_dump | no explicit testing | t0003, t0004 | |
verify_async | no explicit testing | t0009 | |
verify_async_cpus | no explicit testing | t0009 | |
verify_backlog | limited | t0005, t0006, t0008, t0009, t/zbd | |
verify_backlog_batch | none | ||
verify_state_save | none | ||
verify_state_load | none | ||
trim_percentage | none | ||
trim_verify_zero | none | ||
trim_backlog | none | ||
trim_backlog_batch | none | ||
experimental_verify | limited | t0025, t0026 | |
Steadystate | steadystate | basic | t/steadystate_tests.py |
steadystate_duration | basic | t/steadystate_tests.py | |
steadystate_ramp_time | basic | t/steadystate_tests.py | |
steadystate_check_interval | limited | t/steadystate_tests.py | |
Measurements and reporting | per_job_logs | limited | t0019-t0024, t/log_compression.py |
group_reporting | limited | t0002-t0004, t0009, t/latency_percentiles, t/zbd/test-zbd-support | |
new_group | none | ||
stats | none | ||
write_bw_log | limited | t0019-0024, t/log_compression.py | |
write_lat_log | limited | latency_percentiles.py | |
write_hist_log | none | ||
write_iops_log | limited | t0012, t0014, strided.py | |
log_entries | none | ||
log_avg_msec | limited | t0012, t0014 | |
log_hist_msec | none | ||
log_hist_coarseness | none | ||
log_max_value | none | ||
log_offset | limited | t0019-t0024, log_compression.py, strided.py | |
log_compression | basic | log_compression.py | |
log_compression_cpus | none | ||
log_store_complressed | basic | log_compression.py | |
log_unix_epoch | none | ||
log_alternate_epoch | none | ||
log_alternate_epoch_clock_id | none | ||
block_error_percentiles | none | ||
bwavgtime | none | ||
iopsavgtime | none | ||
disk_util | none | ||
disable_lat | no explicit testing | t/zbd | |
disable_clat | none | ||
disable_slat | none | ||
disable_bw_measurement | none | ||
slat_percentiles | basic | t/latency_percentiles | |
clat_percentiles | basic | t/latency_percentiles | |
lat_percentiles | basic | t/latency_percentiles | |
percentile_list | none | ||
significant_figures | no explicit testing | t/zbd/test-zbd-support | |
Error handling | exitall_on_error | no explicit testing | t/zbd/test-zbd-support |
continue_on_error | limited | t/zbd | |
ignore_error | none | ||
error_dump | none | ||
Predefined workloads | profile | none | |
Act profile options | device-names | none | |
load | none | ||
test-duration | none | ||
threads-per-queue | none | ||
read-req-num-512-blocks | none | ||
large-block-op-kbytes | none | ||
prep | none | ||
Tiobench profile options | size | none | |
block | none | ||
numruns | none | ||
dir | none | ||
threads | none | ||
Command-line options | debug | no explicit testing | t/random_seed, t/steadystate_tests, t/zbd/test-zbd-support |
parse-only | no explicit testing | t/steadystate_tests | |
merge-blktrace-only | none | ||
output | no explicit testing | various | |
output-format | no explicit testing | various | |
bandwidth-log | none | ||
minimal | none | ||
append-terse | none | ||
terse-version | none | ||
version | none | ||
help | none | ||
cpuclock-test | none | ||
crctest | none | ||
cmdhelp | none | ||
enghelp | none | ||
showcmd | none | ||
readonly | basic | t/readonly.py | |
eta | none | ||
eta-interval | none | ||
eta-newline | none | ||
status-interval | none | ||
section | none | ||
alloc-size | no explicit testing | t/zbd/test-zbd-support | |
warnings-fatal | none | ||
max-jobs | no explicit testing | various | |
server | none | ||
daemonize | none | ||
client | none | ||
remote-config | none | ||
idle-prof | none | ||
inflate-log | basic | t/log_compression.py | |
trigger-file | none | ||
trigger-timeout | none | ||
trigger | none | ||
trigger-remote | none | ||
aux-path | no explicit testing | t/zbd/test-zbd-support |