[PATCH] backend: correctly handle rate_iops combined with bssplit

Dmitry Fomichev <[email protected]> Wed, 11 Mar 2026 05:58:04 +0900
Newsgroups org.kernel.vger.fio
Message-ID <[email protected]>
Currently, rate_iops does not produce the expected I/O rate with
workloads that use 'bssplit' option. Consider the following example
configuration -

[global]
direct=1
time_based
runtime=30s
ioengine=io_uring
thread=1

[bssplit_rate_iops_repro]
filename=/dev/sdX
rw=randread
iodepth=8
bs=64K
rate_iops=50

This works correctly and ~50 IOPS I/O rate is logged during the run.

If we replace 'bs=64K' with the following bssplit option -

bssplit=32ki/20:64ki/40:256ki/10:512ki/25:1mi/5

in the configuration above, then some incorrect (much lower) IOPS values
are observed to be in effect at run time.

This problem happens because fio, in order to derive the required
I/O rate from 'rate_iops' value provided by the user, simply multiplies
the IOPS value by the minimum block size (min_bs). Once bps I/O rate is
calculated this way, the processing for 'rate' and 'rate_iops' becomes
identical.

This works if the I/O issued has the uniform min_bs, as in case of using
'bs=64K'. However, with 'bssplit' option in effect, fio may issue I/O
with sizes that are much different from min_bs. Yet the code in
usec_for_io() currently always calculates I/O issue delays based on
min_bs leading to incorrect IOPS being produced.

Fix this by modifying usec_for_io() function to check for
bssplit+rate_iops being in effect. For this case, derive the IOPS rate
from bps 'rate' member of thread data and then calculate the delay to
the next I/O using the IOPS value, not the bps rate.

Signed-off-by: Dmitry Fomichev <[email protected]>
---
 backend.c | 22 +++++++++++++++++++---
 1 file changed, 19 insertions(+), 3 deletions(-)

diff --git a/backend.c b/backend.c
index 6dd078c4..2a171bfa 100644
--- a/backend.c
+++ b/backend.c
@@ -842,15 +842,31 @@ static long long usec_for_io(struct thread_data *td, enum fio_ddir ddir)
 		}
 		td->last_usec[ddir] += val;
 		return td->last_usec[ddir];
-	} else if (bps) {
+	}
+
+	if (!bps)
+		return 0;
+
+	/*
+	 * For rate_iops option combined with bssplit, recover
+	 * the user provided IOPS value and calculate the I/O delay
+	 * based on this value, not on bps.
+	 */
+	if (!td->o.rate[ddir] && td->o.bssplit_nr[ddir]) {
+		uint64_t iops = bps / td->o.min_bs[ddir];
+
+		if (!iops)
+			return 0;
+
+		td->last_usec[ddir] += (int64_t)(1000000 / iops);
+		return td->last_usec[ddir];
+	} else {
 		uint64_t bytes = td->rate_io_issue_bytes[ddir];
 		uint64_t secs = bytes / bps;
 		uint64_t remainder = bytes % bps;
 
 		return remainder * 1000000 / bps + secs * 1000000;
 	}
-
-	return 0;
 }
 
 static void init_thinktime(struct thread_data *td)
-- 
2.37.3