From: Samir Mulani <samir@linux.ibm.com>
To: ltp@lists.linux.it
Cc: Samir Mulani <samir@linux.ibm.com>
Subject: [LTP] [PATCH v1 4/4] sched/psi: Add psi_accounting01 test
Date: Sat, 24 Oct 2026 04:30:44 +0200 [thread overview]
Message-ID: <20261024023044.80055-5-samir@linux.ibm.com> (raw)
In-Reply-To: <20261024023044.80055-1-samir@linux.ibm.com>
Add a test case to verify that PSI correctly records stall time when
tasks experience CPU, memory, and I/O pressure.
The test creates a cgroup v2 to isolate the workload and checks that:
- Running two CPU-heavy tasks on a single CPU increases cpu "some" total.
- Touching memory above memory.high triggers reclaim and increases
memory "some" total.
- Doing synchronous O_DIRECT writes to a block device increases io "some"
total.
If the system cannot create enough memory or I/O pressure (for example, on
very fast storage or systems without a test block device), the test cleanly
reports TCONF instead of failing.
The test requires cgroup v2 with memory controller enabled.
Signed-off-by: Samir Mulani <samir@linux.ibm.com>
---
runtest/sched | 1 +
testcases/kernel/sched/psi/.gitignore | 1 +
testcases/kernel/sched/psi/psi_accounting01.c | 356 ++++++++++++++++++
3 files changed, 358 insertions(+)
create mode 100644 testcases/kernel/sched/psi/psi_accounting01.c
diff --git a/runtest/sched b/runtest/sched
index 0651fafd9..b6021411c 100644
--- a/runtest/sched
+++ b/runtest/sched
@@ -24,3 +24,4 @@ sched_football sched_football
psi_invalid_trigger01 psi_invalid_trigger01
psi_multiple_trigger01 psi_multiple_trigger01
psi_proc_format01 psi_proc_format01
+psi_accounting01 psi_accounting01
diff --git a/testcases/kernel/sched/psi/.gitignore b/testcases/kernel/sched/psi/.gitignore
index 8d1083ff2..67407cc1c 100644
--- a/testcases/kernel/sched/psi/.gitignore
+++ b/testcases/kernel/sched/psi/.gitignore
@@ -1,3 +1,4 @@
/psi_invalid_trigger01
/psi_multiple_trigger01
/psi_proc_format01
+/psi_accounting01
diff --git a/testcases/kernel/sched/psi/psi_accounting01.c b/testcases/kernel/sched/psi/psi_accounting01.c
new file mode 100644
index 000000000..8b08f5a14
--- /dev/null
+++ b/testcases/kernel/sched/psi/psi_accounting01.c
@@ -0,0 +1,356 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 IBM Corp.
+ * Author: Samir Mulani <samir@linux.ibm.com>
+ */
+
+/*\
+ * Verify that the PSI counters of a cgroup grow when its tasks are stalled.
+ *
+ * The test reads cpu.pressure, memory.pressure and io.pressure of a cgroup of
+ * its own, so load generated by the rest of the system does not influence the
+ * result. For every resource a workload is run by tasks inside the cgroup and
+ * the "some" total is expected to grow:
+ *
+ * - cpu: two busy tasks are pinned to the same CPU, so one of them is always
+ * runnable but not running. The total has to grow by at least 50ms.
+ * - memory: a task keeps touching fresh anonymous pages above memory.high of
+ * the cgroup, which makes it reclaim and get throttled. If memory.events
+ * does not show any new "high" event the workload did not create the stall
+ * and the test reports TCONF. Otherwise the task has been accounted as
+ * stalled, so the total has to grow.
+ * - io: a task does O_DIRECT writes to a block device and has to wait for
+ * their completion. If the writes did not block long enough the test reports
+ * TCONF. Otherwise the total has to grow by a quarter of the time that the
+ * writes were blocked.
+ *
+ * The format of the files in /proc/pressure/ is checked by psi_proc_format01.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <string.h>
+#include <sched.h>
+#include <sys/mman.h>
+
+#include "tst_test.h"
+#include "tst_timer.h"
+#include "tst_clocks.h"
+#include "tst_safe_file_at.h"
+#include "lapi/cpuset.h"
+
+#define PSI_PROBE "/proc/pressure/cpu"
+#define MNTPOINT "mntpoint"
+#define IO_FILE MNTPOINT "/psi_io"
+
+#define WORK_MS 2000
+#define CPU_MIN_DELTA_US 50000ULL
+#define MIN_IO_WAIT_US 200000LL
+/* Share of the measured IO wait that has to show up in io.pressure */
+#define IO_DELTA_DIVISOR 4
+
+#define MEM_HIGH (16 * 1024 * 1024)
+#define MEM_REGION (24 * 1024 * 1024)
+/*
+ * Memory above memory.high is throttled for up to two seconds after every
+ * batch of 64 pages (256KB), so the clock has to be checked after each one.
+ */
+#define MEM_STEP (256 * 1024)
+
+#define IO_SIZE (256 * 1024)
+
+static struct tst_cg_group *cg_psi;
+static int cg_fd = -1;
+static int pin_cpu;
+static long long *io_wait_us;
+
+static char io_buf[IO_SIZE] __attribute__((aligned(4096)));
+
+static unsigned long long read_some_total(const char *file)
+{
+ char buf[256];
+ unsigned long long total;
+
+ SAFE_FILE_READAT(cg_fd, file, buf, sizeof(buf));
+
+ if (sscanf(buf, "some avg10=%*f avg60=%*f avg300=%*f total=%llu",
+ &total) != 1)
+ tst_brk(TBROK, "Cannot parse %s: '%s'", file, buf);
+
+ return total;
+}
+
+static void check_delta(const char *file, unsigned long long before,
+ unsigned long long min_delta)
+{
+ unsigned long long after = read_some_total(file);
+
+ if (after < before) {
+ tst_res(TFAIL, "%s some total decreased: %llu -> %llu",
+ file, before, after);
+ return;
+ }
+
+ if (after - before >= min_delta) {
+ tst_res(TPASS, "%s some total grew by %llu us",
+ file, after - before);
+ } else {
+ tst_res(TFAIL,
+ "%s some total grew only by %llu us, expected at least %llu us",
+ file, after - before, min_delta);
+ }
+}
+
+static void pin_to_cpu(int cpu)
+{
+ long ncpus = tst_ncpus_max();
+ size_t size = CPU_ALLOC_SIZE(ncpus);
+ cpu_set_t *mask = CPU_ALLOC(ncpus);
+
+ if (!mask)
+ tst_brk(TBROK | TERRNO, "CPU_ALLOC() failed");
+
+ CPU_ZERO_S(size, mask);
+ CPU_SET_S(cpu, size, mask);
+
+ if (sched_setaffinity(0, size, mask))
+ tst_brk(TBROK | TERRNO, "sched_setaffinity(%d) failed", cpu);
+
+ CPU_FREE(mask);
+}
+
+static void join_cgroup(void)
+{
+ SAFE_CG_PRINTF(cg_psi, "cgroup.procs", "%d", getpid());
+}
+
+static void verify_cpu(void)
+{
+ unsigned long long before = read_some_total("cpu.pressure");
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ if (!SAFE_FORK()) {
+ join_cgroup();
+ pin_to_cpu(pin_cpu);
+
+ tst_timer_start(CLOCK_MONOTONIC);
+ while (!tst_timer_expired_ms(WORK_MS))
+ ;
+
+ exit(0);
+ }
+ }
+
+ tst_reap_children();
+ check_delta("cpu.pressure", before, CPU_MIN_DELTA_US);
+}
+
+static void verify_memory(void)
+{
+ unsigned long long before = read_some_total("memory.pressure");
+ long high_before, high_after;
+
+ /* The limit is only wanted here, it would throttle the other tests */
+ SAFE_CG_PRINTF(cg_psi, "memory.high", "%d", MEM_HIGH);
+ SAFE_CG_LINES_SCANF(cg_psi, "memory.events", "high %ld", &high_before);
+
+ if (!SAFE_FORK()) {
+ size_t off;
+ char *ptr;
+
+ join_cgroup();
+
+ ptr = SAFE_MMAP(NULL, MEM_REGION, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+ tst_timer_start(CLOCK_MONOTONIC);
+
+ while (!tst_timer_expired_ms(WORK_MS)) {
+ for (off = 0; off < MEM_REGION; off += MEM_STEP) {
+ memset(ptr + off, 0xab, MEM_STEP);
+
+ if (tst_timer_expired_ms(WORK_MS))
+ break;
+ }
+
+ /* Drop the pages so that they have to be charged again */
+ if (madvise(ptr, MEM_REGION, MADV_DONTNEED))
+ tst_brk(TBROK | TERRNO, "madvise() failed");
+ }
+
+ exit(0);
+ }
+
+ tst_reap_children();
+
+ SAFE_CG_LINES_SCANF(cg_psi, "memory.events", "high %ld", &high_after);
+ SAFE_CG_PRINT(cg_psi, "memory.high", "max");
+
+ if (high_after <= high_before) {
+ tst_res(TCONF, "Workload did not exceed memory.high (high=%ld)",
+ high_after);
+ return;
+ }
+
+ /*
+ * A "high" event is raised right before the task enters the memory
+ * stall accounting, so any growth is enough at this point.
+ */
+ check_delta("memory.pressure", before, 1);
+}
+
+static void verify_io(void)
+{
+ unsigned long long before = read_some_total("io.pressure");
+
+ *io_wait_us = 0;
+
+ if (!SAFE_FORK()) {
+ struct timespec start, end;
+ int fd;
+
+ join_cgroup();
+
+ memset(io_buf, 0xcd, sizeof(io_buf));
+ fd = SAFE_OPEN(IO_FILE, O_WRONLY | O_CREAT | O_DIRECT, 0600);
+
+ tst_timer_start(CLOCK_MONOTONIC);
+
+ while (!tst_timer_expired_ms(WORK_MS)) {
+ tst_clock_gettime(CLOCK_MONOTONIC, &start);
+
+ if (pwrite(fd, io_buf, IO_SIZE, 0) != IO_SIZE)
+ tst_brk(TBROK | TERRNO, "pwrite() failed");
+
+ tst_clock_gettime(CLOCK_MONOTONIC, &end);
+ *io_wait_us += tst_timespec_diff_us(end, start);
+ }
+
+ SAFE_CLOSE(fd);
+ SAFE_UNLINK(IO_FILE);
+ exit(0);
+ }
+
+ tst_reap_children();
+
+ if (*io_wait_us < MIN_IO_WAIT_US) {
+ tst_res(TCONF, "The workload was blocked on IO for only %lld us",
+ *io_wait_us);
+ return;
+ }
+
+ check_delta("io.pressure", before, *io_wait_us / IO_DELTA_DIVISOR);
+}
+
+static void (*const tests[])(void) = {
+ verify_cpu,
+ verify_memory,
+ verify_io,
+};
+
+static void run(unsigned int n)
+{
+ tests[n]();
+}
+
+static void check_pressure_file(const char *file)
+{
+ if (faccessat(cg_fd, file, R_OK, 0))
+ tst_brk(TCONF | TERRNO, "%s is not available in the cgroup", file);
+}
+
+static void setup(void)
+{
+ char buf[64];
+ long i, ncpus = tst_ncpus_max();
+ size_t size = CPU_ALLOC_SIZE(ncpus);
+ cpu_set_t *mask = CPU_ALLOC(ncpus);
+ int fd;
+
+ if (!mask)
+ tst_brk(TBROK | TERRNO, "CPU_ALLOC() failed");
+
+ fd = SAFE_OPEN(PSI_PROBE, O_RDONLY);
+
+ /* psi=0 on the kernel command line makes the interface return EOPNOTSUPP */
+ if (read(fd, buf, sizeof(buf)) < 0) {
+ if (errno == EOPNOTSUPP)
+ tst_brk(TCONF, "PSI is disabled (psi=0 on kernel command line)");
+
+ tst_brk(TBROK | TERRNO, "read(%s) failed", PSI_PROBE);
+ }
+
+ SAFE_CLOSE(fd);
+
+ /* Pin the busy tasks to a CPU we are actually allowed to use */
+ if (sched_getaffinity(0, size, mask))
+ tst_brk(TBROK | TERRNO, "sched_getaffinity() failed");
+
+ for (i = 0; i < ncpus; i++) {
+ if (CPU_ISSET_S(i, size, mask))
+ break;
+ }
+
+ if (i == ncpus)
+ tst_brk(TBROK, "No usable CPU found");
+
+ pin_cpu = i;
+ CPU_FREE(mask);
+
+ cg_psi = tst_cg_group_mk(tst_cg, "psi");
+ cg_fd = tst_cg_group_unified_dir_fd(cg_psi);
+
+ if (cg_fd < 0)
+ tst_brk(TBROK, "Cannot get the directory of the cgroup");
+
+ check_pressure_file("cpu.pressure");
+ check_pressure_file("memory.pressure");
+ check_pressure_file("io.pressure");
+
+ /* Not every filesystem supports O_DIRECT */
+ fd = open(IO_FILE, O_WRONLY | O_CREAT | O_DIRECT, 0600);
+ if (fd < 0) {
+ if (errno == EINVAL)
+ tst_brk(TCONF, "O_DIRECT is not supported by the filesystem");
+
+ tst_brk(TBROK | TERRNO, "open(%s) failed", IO_FILE);
+ }
+ SAFE_CLOSE(fd);
+ SAFE_UNLINK(IO_FILE);
+
+ io_wait_us = SAFE_MMAP(NULL, sizeof(*io_wait_us), PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+}
+
+static void cleanup(void)
+{
+ if (io_wait_us)
+ SAFE_MUNMAP(io_wait_us, sizeof(*io_wait_us));
+
+ if (cg_psi)
+ cg_psi = tst_cg_group_rm(cg_psi);
+}
+
+static struct tst_test test = {
+ .tcnt = ARRAY_SIZE(tests),
+ .test = run,
+ .setup = setup,
+ .cleanup = cleanup,
+ .forks_child = 1,
+ .needs_root = 1,
+ .timeout = 60,
+ .min_kver = "4.20",
+ .min_mem_avail = 256,
+ .needs_device = 1,
+ .mount_device = 1,
+ .mntpoint = MNTPOINT,
+ .needs_cgroup_ver = TST_CG_V2,
+ .needs_cgroup_ctrls = (const char *const[]) {"memory", NULL},
+ .needs_kconfigs = (const char *const[]) {
+ "CONFIG_PSI=y",
+ NULL
+ },
+};
+
--
2.53.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
prev parent reply other threads:[~2026-10-23 13:46 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-24 2:30 [LTP] [PATCH v1 0/4] sched/psi: Add tests for PSI interface Samir Mulani
2026-10-24 2:30 ` [LTP] [PATCH v1 1/4] sched/psi: Add psi_invalid_trigger01 test Samir Mulani
2026-10-23 17:55 ` [LTP] " linuxtestproject.agent
2026-10-24 2:30 ` [LTP] [PATCH v1 2/4] sched/psi: Add psi_multiple_trigger01 test Samir Mulani
2026-10-24 2:30 ` [LTP] [PATCH v1 3/4] sched/psi: Add psi_proc_format01 test Samir Mulani
2026-10-24 2:30 ` Samir Mulani [this message]
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=20261024023044.80055-5-samir@linux.ibm.com \
--to=samir@linux.ibm.com \
--cc=ltp@lists.linux.it \
/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