BPF List
 help / color / mirror / Atom feed
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


  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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox