From: Tao Cui <cui.tao@linux.dev>
To: tj@kernel.org, josef@toxicopanda.com, axboe@kernel.dk
Cc: cgroups@vger.kernel.org, linux-block@vger.kernel.org,
linux-kernel@vger.kernel.org, bpf@vger.kernel.org,
andrii@kernel.org, ast@kernel.org, daniel@iogearbox.net,
linux-kselftest@vger.kernel.org, cui.tao@linux.dev,
Tao Cui <cuitao@kylinos.cn>
Subject: [RFC PATCH 6/8] selftests/bpf: add iocost cost model test
Date: Tue, 8 Sep 2026 18:01:41 +0800 [thread overview]
Message-ID: <20260908100143.47598-7-cui.tao@linux.dev> (raw)
In-Reply-To: <20260908100143.47598-1-cui.tao@linux.dev>
From: Tao Cui <cuitao@kylinos.cn>
Add an example cost model implementing iocost_model_ops with the
builtin linear HDD formula at double cost, and a runner which
registers it as a struct_ops, switches a device to ctrl=bpf through
io.cost.model and verifies the readback, restoring ctrl=auto
afterwards. The runner first checks the negative case: writing
ctrl=bpf while no model is registered is rejected. Under the same
workload the doubled model should charge exactly twice the builtin
model, which makes it a convenient way to verify that accounting
goes through the BPF path; the accounting itself needs real IO and
is covered by the kernel-side validation described in the cover
letter. The runner is skipped unless $IOCOST_TEST_DEV gives a
major:minor of a device with iocost enabled.
Signed-off-by: Tao Cui <cuitao@kylinos.cn>
---
.../selftests/bpf/prog_tests/iocost_model.c | 124 ++++++++++++++++++
.../selftests/bpf/progs/iocost_model.c | 91 +++++++++++++
2 files changed, 215 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/iocost_model.c
create mode 100644 tools/testing/selftests/bpf/progs/iocost_model.c
diff --git a/tools/testing/selftests/bpf/prog_tests/iocost_model.c b/tools/testing/selftests/bpf/prog_tests/iocost_model.c
new file mode 100644
index 0000000000000..386026241d5b4
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/iocost_model.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Tao Cui */
+#include <test_progs.h>
+#include "iocost_model.skel.h"
+
+/*
+ * Switch a device to ctrl=bpf through io.cost.model and verify the
+ * readback, restoring ctrl=auto afterwards. Returns 0 on success.
+ * Shared by the example-model tests below.
+ */
+static int switch_to_bpf(const char *dev)
+{
+ char path[] = "/sys/fs/cgroup/io.cost.model";
+ char word[256], line[256], buf[300];
+ FILE *fp;
+ int err, found = 0;
+
+ fp = fopen(path, "r+");
+ if (!fp) {
+ TH_LOG("open %s: %s", path, strerror(errno));
+ return -1;
+ }
+
+ while (fgets(line, sizeof(line), fp)) {
+ if (sscanf(line, "%255s", word) == 1 && !strcmp(word, dev)) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ TH_LOG("%s has no iocost line", dev);
+ fclose(fp);
+ return -1;
+ }
+
+ snprintf(buf, sizeof(buf), "%s ctrl=bpf", dev);
+ err = fprintf(fp, "%s\n", buf);
+ if (err <= 0) {
+ TH_LOG("write ctrl=bpf failed");
+ fclose(fp);
+ return -1;
+ }
+ fflush(fp);
+
+ rewind(fp);
+ found = 0;
+ while (fgets(line, sizeof(line), fp)) {
+ if (sscanf(line, "%255s", word) == 1 && !strcmp(word, dev)) {
+ found = strstr(line, "ctrl=bpf") != NULL;
+ break;
+ }
+ }
+ if (!found)
+ TH_LOG("readback does not show ctrl=bpf");
+
+ fprintf(fp, "%s ctrl=auto\n", dev);
+ fclose(fp);
+ return found ? 0 : -1;
+}
+
+static int dev_has_iocost(void)
+{
+ FILE *fp = fopen("/sys/fs/cgroup/io.cost.qos", "r");
+
+ if (fp)
+ fclose(fp);
+ return fp != NULL;
+}
+
+/*
+ * Register the example cost model and switch a device to ctrl=bpf.
+ * IO accounting itself is not checked here; it needs a device doing
+ * real IO under iocost and is covered by the kernel-side validation
+ * described in the cover letter.
+ *
+ * Requires root, cgroup v2 and a device with iocost support. The
+ * device must be given as major:minor in $IOCOST_TEST_DEV, otherwise
+ * the test is skipped.
+ */
+void serial_test_iocost_model(void)
+{
+ struct iocost_model *skel;
+ char word[256], *dev;
+ int err;
+
+ dev = getenv("IOCOST_TEST_DEV");
+ if (!dev || geteuid() != 0) {
+ test__skip();
+ return;
+ }
+ if (!ASSERT_TRUE(dev_has_iocost(), "iocost_mounted"))
+ return;
+
+ /* negative: ctrl=bpf must be rejected while no model is
+ * registered, so a typo cannot silently disable cost model
+ * updates */
+ if (sscanf(dev, "%255s", word) != 1)
+ return;
+ {
+ char path[] = "/sys/fs/cgroup/io.cost.model";
+ char buf[300];
+ FILE *fp = fopen(path, "w");
+
+ snprintf(buf, sizeof(buf), "%s ctrl=bpf\n", word);
+ err = 0;
+ if (fp) {
+ err = fprintf(fp, "%s", buf) <= 0;
+ fclose(fp);
+ }
+ ASSERT_TRUE(err, "ctrl_bpf_without_model_rejected");
+ }
+
+ skel = iocost_model__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel_open_load"))
+ return;
+
+ /* attaching the struct_ops registers the model; a second
+ * registration of the same ops would fail with EBUSY */
+ err = iocost_model__attach(skel);
+ if (ASSERT_OK(err, "attach"))
+ ASSERT_OK(switch_to_bpf(dev), "switch_and_readback");
+
+ iocost_model__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/iocost_model.c b/tools/testing/selftests/bpf/progs/iocost_model.c
new file mode 100644
index 0000000000000..70f7987f8eb6a
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/iocost_model.c
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Tao Cui */
+/*
+ * Example iocost cost model: the builtin linear HDD formula with all
+ * costs doubled.
+ *
+ * The constants mirror what calc_lcoefs() derives from the AUTOP_HDD
+ * defaults (rbps=174019176 rseqiops=41708 rrandiops=370, w-side
+ * analog) in vtime units where 1s == 2^37, expressed with the same
+ * round-up divisions so they cannot drift from the kernel, so a
+ * device switched to ctrl=bpf with this model registered charges
+ * exactly twice the builtin model under the same workload, which
+ * makes it a convenient way to verify that accounting goes through
+ * the BPF path.
+ */
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+/* VTIME_PER_SEC comes from vmlinux.h (a BTF enum constant) */
+#define IOC_PAGE_SIZE 4096
+#define IOC_SECT_TO_PAGE_SHIFT 3 /* 512B sectors to 4kB pages */
+#define LCOEF_RANDIO_PAGES 4096 /* 16MB seek threshold */
+#define IOCOST_COST_F_MERGE (1ULL << 0) /* not in BTF: a plain macro */
+
+/* DIV64_U64_ROUND_UP / DIV_ROUND_UP_ULL equivalents, folded at
+ * compile time */
+#define RU(x, y) ((x) / (y) + (((x) % (y)) ? 1 : 0))
+
+#define RBPS 174019176ULL
+#define RSEQIOPS 41708ULL
+#define RRANDIOPS 370ULL
+#define WBPS 178075866ULL
+#define WSEQIOPS 42705ULL
+#define WRANDIOPS 378ULL
+
+#define RPAGE (RU(VTIME_PER_SEC, RU(RBPS, IOC_PAGE_SIZE)))
+#define RSEQIO (RU(VTIME_PER_SEC, RSEQIOPS) - RPAGE)
+#define RRANDIO (RU(VTIME_PER_SEC, RRANDIOPS) - RPAGE)
+#define WPAGE (RU(VTIME_PER_SEC, RU(WBPS, IOC_PAGE_SIZE)))
+#define WSEQIO (RU(VTIME_PER_SEC, WSEQIOPS) - WPAGE)
+#define WRANDIO (RU(VTIME_PER_SEC, WRANDIOPS) - WPAGE)
+
+static __always_inline u64 builtin_cost(u64 op, u64 nbytes, u64 sector,
+ u64 cursor, u64 flags)
+{
+ u64 pages, seek_pages = 0, base, coef_page;
+
+ if (!nbytes)
+ return 0;
+
+ switch (op) {
+ case REQ_OP_READ:
+ base = RSEQIO;
+ coef_page = RPAGE;
+ break;
+ case REQ_OP_WRITE:
+ base = WSEQIO;
+ coef_page = WPAGE;
+ break;
+ default:
+ return 0;
+ }
+
+ if (cursor) {
+ seek_pages = sector > cursor ? sector - cursor
+ : cursor - sector;
+ seek_pages >>= IOC_SECT_TO_PAGE_SHIFT;
+ if (seek_pages > LCOEF_RANDIO_PAGES)
+ base = (op == REQ_OP_READ) ? RRANDIO : WRANDIO;
+ }
+
+ pages = RU(nbytes, IOC_PAGE_SIZE);
+ if (flags & IOCOST_COST_F_MERGE)
+ base = 0;
+ return base + pages * coef_page;
+}
+
+SEC("struct_ops")
+u64 BPF_PROG(iocost_2x_calc_cost, u64 op, u64 nbytes, u64 sector,
+ u64 cursor, u64 iocg_id, u64 flags)
+{
+ return 2 * builtin_cost(op, nbytes, sector, cursor, flags);
+}
+
+SEC(".struct_ops")
+struct iocost_model_ops iocost_2x = {
+ .calc_cost = (void *)iocost_2x_calc_cost,
+};
+
+char LICENSE[] SEC("license") = "GPL";
--
2.43.0
next prev parent reply other threads:[~2026-09-08 10:03 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-08 10:01 [RFC PATCH 0/8] blk-iocost: BPF struct_ops cost model Tao Cui
2026-09-08 10:01 ` [RFC PATCH 1/8] blk-iocost: add iocost_ioc_tick tracepoint for per-period device summary Tao Cui
2026-09-08 10:01 ` [RFC PATCH 2/8] blk-iocost: define iocost_model_ops cost model interface Tao Cui
2026-09-08 10:13 ` sashiko-bot
2026-09-08 20:31 ` Tejun Heo
2026-09-08 10:01 ` [RFC PATCH 3/8] blk-iocost: implement BPF struct_ops registration Tao Cui
2026-09-08 20:31 ` Tejun Heo
2026-09-08 10:01 ` [RFC PATCH 4/8] blk-iocost: dispatch cost calculation to registered BPF model Tao Cui
2026-09-08 20:31 ` Tejun Heo
2026-09-08 10:01 ` [RFC PATCH 5/8] blk-iocost: add ctrl=bpf per-device opt-in Tao Cui
2026-09-08 10:01 ` Tao Cui [this message]
2026-09-08 10:20 ` [RFC PATCH 6/8] selftests/bpf: add iocost cost model test sashiko-bot
2026-09-08 20:31 ` Tejun Heo
2026-09-08 10:01 ` [RFC PATCH 7/8] selftests/bpf: add multi-stream sequentiality example model Tao Cui
2026-09-08 10:21 ` sashiko-bot
2026-09-08 20:31 ` Tejun Heo
2026-09-08 10:01 ` [RFC PATCH 8/8] docs: cgroup-v2: document io.cost ctrl=bpf option Tao Cui
2026-09-08 20:31 ` [RFC PATCH 0/8] blk-iocost: BPF struct_ops cost model Tejun Heo
2026-09-09 13:12 ` Tao Cui
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260908100143.47598-7-cui.tao@linux.dev \
--to=cui.tao@linux.dev \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=axboe@kernel.dk \
--cc=bpf@vger.kernel.org \
--cc=cgroups@vger.kernel.org \
--cc=cuitao@kylinos.cn \
--cc=daniel@iogearbox.net \
--cc=josef@toxicopanda.com \
--cc=linux-block@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=tj@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.