* [LTP] [PATCH 0/2] cgroup: Add tests for cgroupv2 cpu.max.burst
@ 2026-09-28 5:17 Samir Mulani
2026-09-28 5:17 ` [LTP] [PATCH 1/2] cgroup: Add cgroup_burst01 test Samir Mulani
2026-09-28 5:17 ` [LTP] [PATCH 2/2] cgroup: Add invalid burst test Samir Mulani
0 siblings, 2 replies; 4+ messages in thread
From: Samir Mulani @ 2026-09-28 5:17 UTC (permalink / raw)
To: ltp; +Cc: Samir Mulani
Hi all,
This patch series adds two new testcases to test the cgroupv2 cpu.max.burst
interface:
Patch 1 adds cpu_max_burst01 to test the basic functionality of cpu.max.burst.
It checks default values, read/write boundaries, stat accounting (nr_bursts
and burst_usec in cpu.stat), dynamic updates, and making sure the burst value
is preserved when quota is removed or period is changed.
Patch 2 adds cpu_max_burst_invalid01 to test invalid inputs to cpu.max.burst.
It checks that values like burst > quota, negative values, sum overflow,
garbage strings, and trailing characters are rejected with EINVAL and do
not change the saved burst value.
Both tests are written using the new LTP API and require CONFIG_CFS_BANDWIDTH=y
and the cgroupv2 cpu controller.
Thanks,
Samir Mulani
Samir Mulani (2):
cgroup: Add cgroup_burst01 test
cgroup: Add invalid burst test
runtest/controllers | 2 +
.../kernel/controllers/cgroup/.gitignore | 2 +
.../controllers/cgroup/cpu_max_burst01.c | 650 ++++++++++++++++++
.../cgroup/cpu_max_burst_invalid01.c | 417 +++++++++++
4 files changed, 1071 insertions(+)
create mode 100644 testcases/kernel/controllers/cgroup/cpu_max_burst01.c
create mode 100644 testcases/kernel/controllers/cgroup/cpu_max_burst_invalid01.c
--
2.53.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 4+ messages in thread
* [LTP] [PATCH 1/2] cgroup: Add cgroup_burst01 test
2026-09-28 5:17 [LTP] [PATCH 0/2] cgroup: Add tests for cgroupv2 cpu.max.burst Samir Mulani
@ 2026-09-28 5:17 ` Samir Mulani
2026-09-29 7:14 ` [LTP] " linuxtestproject.agent
2026-09-28 5:17 ` [LTP] [PATCH 2/2] cgroup: Add invalid burst test Samir Mulani
1 sibling, 1 reply; 4+ messages in thread
From: Samir Mulani @ 2026-09-28 5:17 UTC (permalink / raw)
To: ltp; +Cc: Samir Mulani
I added cpu_max_burst01 to test the cpu.max.burst interface in
cgroupv2. This interface controls the burst budget for CFS bandwidth
throttling. When a cgroup uses less than its quota in a period, the
kernel saves up to (quota + burst) microseconds; a later busy period
can use that saved credit to run beyond the normal quota.
Test scenarios:
TC1 - cpu.max.burst defaults to 0; writing a non-zero value and
then clearing it back to 0 works correctly.
TC2 - Writing burst = quota/2 and burst = quota both read back the
correct value.
TC3 - A worker underruns to build up burst credit, then overloads
to consume it; cpu.stat nr_bursts and burst_usec counters
must increase. Also checks that burst can be updated while
tasks are running.
TC4 - The burst value is preserved after quota is removed
(cpu.max set to "max").
TC5 - The burst value is preserved after the period is changed.
Requires CONFIG_CFS_BANDWIDTH=y and cgroupv2 cpu controller.
Signed-off-by: Samir Mulani <samir@linux.ibm.com>
---
runtest/controllers | 1 +
.../kernel/controllers/cgroup/.gitignore | 1 +
.../controllers/cgroup/cpu_max_burst01.c | 650 ++++++++++++++++++
3 files changed, 652 insertions(+)
create mode 100644 testcases/kernel/controllers/cgroup/cpu_max_burst01.c
diff --git a/runtest/controllers b/runtest/controllers
index 93c52c439..aa93ea5af 100644
--- a/runtest/controllers
+++ b/runtest/controllers
@@ -3,6 +3,7 @@ cgroup_core01 cgroup_core01
cgroup_core02 cgroup_core02
cgroup_core03 cgroup_core03
cgroup cgroup_regression_test.sh
+cpu_max_burst01 cpu_max_burst01
memcg_regression memcg_regression_test.sh
memcg_test_3 memcg_test_3
memcg_failcnt memcg_failcnt.sh
diff --git a/testcases/kernel/controllers/cgroup/.gitignore b/testcases/kernel/controllers/cgroup/.gitignore
index 9f1d1ada9..2506388fd 100644
--- a/testcases/kernel/controllers/cgroup/.gitignore
+++ b/testcases/kernel/controllers/cgroup/.gitignore
@@ -3,3 +3,4 @@
/cgroup_core01
/cgroup_core02
/cgroup_core03
+/cpu_max_burst01
diff --git a/testcases/kernel/controllers/cgroup/cpu_max_burst01.c b/testcases/kernel/controllers/cgroup/cpu_max_burst01.c
new file mode 100644
index 000000000..afb8befa6
--- /dev/null
+++ b/testcases/kernel/controllers/cgroup/cpu_max_burst01.c
@@ -0,0 +1,650 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Samir Mulani, IBM Corporation.
+ */
+
+/*\
+ * Functional tests for the cgroupv2 cpu.max.burst interface.
+ *
+ * cpu.max.burst controls the burst budget for CFS bandwidth throttling.
+ * When a cgroup uses less than its quota in a period, the kernel retains
+ * up to (quota + burst) microseconds in the runtime pool. A later busy
+ * period may draw from that retained credit, temporarily running beyond
+ * the nominal quota rate.
+ *
+ * Kernel accounting (kernel/sched/fair.c __refill_cfs_bandwidth_runtime):
+ *
+ * At every period boundary:
+ * runtime += quota
+ * surplus = runtime_snap - runtime
+ * if (surplus > 0):
+ * burst_time += surplus
+ * nr_burst++
+ * runtime = min(runtime, quota + burst)
+ * runtime_snap = runtime
+ *
+ * Test scenarios:
+ * TC 1 - Default value and write-zero: cpu.max.burst defaults to 0;
+ * setting burst to non-zero then back to 0 is accepted.
+ * TC 2 - Write/readback, burst == quota boundary accepted, and
+ * burst > quota rejected with EINVAL.
+ * TC 3 - Functional burst accounting: a worker underruns to fill the
+ * pool, then overloads to trigger burst accounting; nr_bursts
+ * and burst_usec in cpu.stat must both increase.
+ * TC 4 - Burst preserved after quota cleared (cpu.max = "max").
+ * TC 5 - Burst preserved after period change.
+ *
+ * Requires: CONFIG_CFS_BANDWIDTH=y, cgroup v2 cpu controller.
+ */
+
+#define _GNU_SOURCE
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "tst_test.h"
+#include "tst_safe_clocks.h"
+#include "tst_safe_file_at.h"
+
+#define PERIOD_US 100000ULL
+#define QUOTA_US 20000ULL
+#define BURST_US 10000ULL
+
+static struct tst_cg_group *cg_child;
+
+static int cg_dirfd(const struct tst_cg_group *cg)
+{
+ int fd = tst_cg_group_unified_dir_fd(cg);
+
+ if (fd < 0)
+ tst_brk(TBROK, "tst_cg_group_unified_dir_fd returned %d", fd);
+ return fd;
+}
+
+static unsigned long long cg_read_ull(const struct tst_cg_group *cg,
+ const char *fname)
+{
+ char buf[64];
+ char *endp;
+ unsigned long long val;
+
+ SAFE_FILE_READAT(cg_dirfd(cg), fname, buf, sizeof(buf) - 1);
+
+ val = strtoull(buf, &endp, 10);
+ if (endp == buf)
+ tst_brk(TBROK, "strtoull failed on '%s' from %s", buf, fname);
+ return val;
+}
+
+static void cg_write_ull(const struct tst_cg_group *cg,
+ const char *fname, unsigned long long val)
+{
+ SAFE_FILE_PRINTFAT(cg_dirfd(cg), fname, "%llu", val);
+}
+
+static void cg_write_str_safe(const struct tst_cg_group *cg,
+ const char *fname, const char *str)
+{
+ SAFE_FILE_PRINTFAT(cg_dirfd(cg), fname, "%s", str);
+}
+
+/*
+ * cg_write_str() - attempt a write that may legitimately fail.
+ * Returns 0 on success, errno on failure.
+ */
+static int cg_write_str(const struct tst_cg_group *cg,
+ const char *fname, const char *str)
+{
+ int fd, saved = 0;
+ ssize_t n;
+
+ fd = SAFE_OPENAT(cg_dirfd(cg), fname, O_WRONLY);
+ n = write(fd, str, strlen(str));
+ if (n < 0)
+ saved = errno;
+ SAFE_CLOSE(fd);
+ return saved;
+}
+
+static void set_cpu_max(const struct tst_cg_group *cg,
+ unsigned long long quota_us,
+ unsigned long long period_us)
+{
+ char buf[64];
+
+ if (quota_us == ULLONG_MAX)
+ snprintf(buf, sizeof(buf), "max %llu", period_us);
+ else
+ snprintf(buf, sizeof(buf), "%llu %llu", quota_us, period_us);
+
+ cg_write_str_safe(cg, "cpu.max", buf);
+}
+
+static unsigned long long read_burst(const struct tst_cg_group *cg)
+{
+ return cg_read_ull(cg, "cpu.max.burst");
+}
+
+static void write_burst(const struct tst_cg_group *cg,
+ unsigned long long burst_us)
+{
+ cg_write_ull(cg, "cpu.max.burst", burst_us);
+}
+
+/*
+ * try_write_burst() - attempt a burst write that may legitimately fail.
+ * Returns 0 on success, errno on failure.
+ */
+static int try_write_burst(const struct tst_cg_group *cg,
+ unsigned long long burst_us)
+{
+ char buf[32];
+
+ snprintf(buf, sizeof(buf), "%llu", burst_us);
+ return cg_write_str(cg, "cpu.max.burst", buf);
+}
+
+struct burst_stat {
+ unsigned long long nr_bursts;
+ unsigned long long burst_usec;
+};
+
+static int read_burst_stat(const struct tst_cg_group *cg,
+ struct burst_stat *s)
+{
+ char buf[4096];
+ char *line, *save;
+ ssize_t n;
+ int fd;
+ int got_bursts = 0, got_usec = 0;
+
+ memset(s, 0, sizeof(*s));
+
+ fd = SAFE_OPENAT(cg_dirfd(cg), "cpu.stat", O_RDONLY);
+ n = SAFE_READ(0, fd, buf, sizeof(buf) - 1);
+ SAFE_CLOSE(fd);
+
+ if (n <= 0)
+ return -1;
+ buf[n] = '\0';
+
+ for (line = strtok_r(buf, "\n", &save); line;
+ line = strtok_r(NULL, "\n", &save)) {
+ if (sscanf(line, "nr_bursts %llu", &s->nr_bursts) == 1)
+ got_bursts = 1;
+ if (sscanf(line, "burst_usec %llu", &s->burst_usec) == 1)
+ got_usec = 1;
+ }
+
+ return (got_bursts && got_usec) ? 0 : -1;
+}
+
+enum worker_cmd {
+ CMD_UNDERRUN,
+ CMD_OVERLOAD,
+ CMD_EXIT,
+};
+
+enum worker_ack { ACK_DONE };
+
+static int cmd_pipe[2] = { -1, -1 };
+static int ack_pipe[2] = { -1, -1 };
+static pid_t worker_pid = -1;
+
+static long long monotonic_us(void)
+{
+ struct timespec ts;
+
+ SAFE_CLOCK_GETTIME(CLOCK_MONOTONIC, &ts);
+ return (long long)ts.tv_sec * 1000000LL + ts.tv_nsec / 1000LL;
+}
+
+static long long process_cpu_us(void)
+{
+ struct timespec ts;
+
+ SAFE_CLOCK_GETTIME(CLOCK_PROCESS_CPUTIME_ID, &ts);
+ return (long long)ts.tv_sec * 1000000LL + ts.tv_nsec / 1000LL;
+}
+
+static void burn_cpu_for(long long dur_us)
+{
+ long long start = process_cpu_us();
+ volatile unsigned long v = 1;
+
+ while (process_cpu_us() - start < dur_us)
+ v = v * 1103515245UL + 12345UL;
+}
+
+/*
+ * worker_main() - command loop run by the forked child inside the cgroup.
+ *
+ * CMD_UNDERRUN:
+ * Runs 5 periods. In each period the worker burns 1 ms of CPU time then
+ * sleeps for the remainder of the period via clock_nanosleep(CLOCK_MONOTONIC).
+ * clock_nanosleep() genuinely blocks — the process goes off-CPU and consumes
+ * no quota while sleeping — so the cgroup uses ~1 ms out of 20 ms quota each
+ * period. After 2 periods the runtime pool reaches the (quota + burst = 30 ms)
+ * ceiling. nr_bursts stays 0 during this phase because the pool is never
+ * fully drained.
+ *
+ * CMD_OVERLOAD:
+ * Burns 4 x QUOTA_US = 80 ms of process CPU time to drain the pool
+ * across multiple period boundaries and trigger burst accounting.
+ */
+static void worker_main(void)
+{
+ enum worker_cmd cmd;
+ enum worker_ack ack = ACK_DONE;
+ ssize_t n;
+
+ SAFE_CLOSE(cmd_pipe[1]);
+ SAFE_CLOSE(ack_pipe[0]);
+
+ for (;;) {
+ n = SAFE_READ(1, cmd_pipe[0], &cmd, sizeof(cmd));
+ if (n != (ssize_t)sizeof(cmd))
+ break;
+
+ switch (cmd) {
+ case CMD_UNDERRUN:
+ {
+ int i;
+
+ for (i = 0; i < 5; i++) {
+ struct timespec deadline;
+ long long now_us;
+
+ /*
+ * Burn 1 ms of CPU quota, then
+ * genuinely sleep for the rest of the
+ * period. clock_nanosleep() blocks the
+ * process off-CPU so no quota is used
+ * while waiting.
+ */
+ burn_cpu_for(1000LL);
+
+ now_us = monotonic_us();
+ deadline.tv_sec =
+ (now_us + (long long)PERIOD_US)
+ / 1000000LL;
+ deadline.tv_nsec =
+ ((now_us + (long long)PERIOD_US)
+ % 1000000LL) * 1000LL;
+ while (clock_nanosleep(CLOCK_MONOTONIC,
+ TIMER_ABSTIME, &deadline,
+ NULL) == EINTR)
+ ;
+ }
+ }
+ break;
+
+ case CMD_OVERLOAD:
+ burn_cpu_for(4LL * (long long)QUOTA_US);
+ break;
+
+ case CMD_EXIT:
+ SAFE_CLOSE(cmd_pipe[0]);
+ SAFE_CLOSE(ack_pipe[1]);
+ exit(0);
+
+ default:
+ break;
+ }
+
+ SAFE_WRITE(1, ack_pipe[1], &ack, sizeof(ack));
+ }
+
+ exit(1);
+}
+
+static void worker_start(const struct tst_cg_group *cg)
+{
+ SAFE_PIPE(cmd_pipe);
+ SAFE_PIPE(ack_pipe);
+
+ worker_pid = SAFE_FORK();
+ if (!worker_pid) {
+ SAFE_CG_PRINTF(cg, "cgroup.procs", "%d", getpid());
+ worker_main();
+ exit(1);
+ }
+
+ SAFE_CLOSE(cmd_pipe[0]);
+ SAFE_CLOSE(ack_pipe[1]);
+}
+
+static void worker_send(enum worker_cmd cmd)
+{
+ enum worker_ack ack;
+
+ SAFE_WRITE(1, cmd_pipe[1], &cmd, sizeof(cmd));
+ if (cmd != CMD_EXIT)
+ SAFE_READ(1, ack_pipe[0], &ack, sizeof(ack));
+}
+
+static void worker_stop(void)
+{
+ if (worker_pid < 0)
+ return;
+
+ worker_send(CMD_EXIT);
+ SAFE_WAITPID(worker_pid, NULL, 0);
+ worker_pid = -1;
+
+ if (cmd_pipe[1] >= 0) {
+ SAFE_CLOSE(cmd_pipe[1]);
+ cmd_pipe[1] = -1;
+ }
+ if (ack_pipe[0] >= 0) {
+ SAFE_CLOSE(ack_pipe[0]);
+ ack_pipe[0] = -1;
+ }
+}
+
+/*
+ * tc1_default_and_zero - default value is 0; clearing back to 0 works.
+ *
+ * A freshly created cgroup must read 0 from cpu.max.burst. After writing
+ * a non-zero value, writing 0 must restore it to 0.
+ */
+static void tc1_default_and_zero(void)
+{
+ unsigned long long burst;
+
+ cg_child = tst_cg_group_mk(tst_cg, "burst_tc1");
+
+ burst = read_burst(cg_child);
+ if (burst != 0)
+ tst_res(TFAIL, "%s: expected default 0, got %llu", __func__, burst);
+ else
+ tst_res(TPASS, "%s: cpu.max.burst defaults to 0", __func__);
+
+ set_cpu_max(cg_child, QUOTA_US, PERIOD_US);
+ write_burst(cg_child, BURST_US);
+ write_burst(cg_child, 0);
+
+ burst = read_burst(cg_child);
+ if (burst == 0)
+ tst_res(TPASS, "%s: burst cleared back to 0", __func__);
+ else
+ tst_res(TFAIL, "%s: expected 0 after clear, got %llu us", __func__, burst);
+
+ cg_child = tst_cg_group_rm(cg_child);
+}
+
+/*
+ * tc2_write_readback_and_boundary - valid readback, burst == quota accepted,
+ * burst > quota rejected.
+ *
+ * Verifies write/readback correctness for burst = quota/2, confirms
+ * burst == quota is the upper valid boundary, and confirms burst > quota
+ * is rejected with EINVAL without corrupting the stored value.
+ */
+static void tc2_write_readback_and_boundary(void)
+{
+ const unsigned long long bad_burst = QUOTA_US + 1;
+ unsigned long long burst;
+ int err;
+
+ cg_child = tst_cg_group_mk(tst_cg, "burst_tc2");
+
+ set_cpu_max(cg_child, QUOTA_US, PERIOD_US);
+ write_burst(cg_child, BURST_US);
+
+ burst = read_burst(cg_child);
+ if (burst == BURST_US)
+ tst_res(TPASS, "tc2_write_readback: cpu.max.burst write/readback %llu us", burst);
+ else
+ tst_res(TFAIL, "tc2_write_readback: expected %llu us, got %llu us",
+ BURST_US, burst);
+
+ write_burst(cg_child, QUOTA_US);
+
+ burst = read_burst(cg_child);
+ if (burst == QUOTA_US)
+ tst_res(TPASS, "tc2_burst_equals_quota: burst == quota (%llu us) accepted", burst);
+ else
+ tst_res(TFAIL, "tc2_burst_equals_quota: expected %llu us, got %llu us",
+ QUOTA_US, burst);
+
+ /*
+ * Restore a known pre-seed value before the rejection test so that
+ * silent corruption of the stored burst is detectable.
+ */
+ write_burst(cg_child, BURST_US);
+
+ err = try_write_burst(cg_child, bad_burst);
+ burst = read_burst(cg_child);
+
+ if (!err) {
+ tst_res(TFAIL,
+ "tc2_burst_exceeds_quota: burst=%llu (quota+1) write succeeded, expected EINVAL",
+ bad_burst);
+ } else {
+ if (err != EINVAL) {
+ tst_res(TWARN,
+ "tc2_burst_exceeds_quota: got errno=%d (%s), expected EINVAL",
+ err, tst_strerrno(err));
+ }
+
+ if (burst != BURST_US) {
+ tst_res(TFAIL,
+ "tc2_burst_exceeds_quota: burst corrupted after rejected write (expected %llu us, got %llu us)",
+ BURST_US, burst);
+ } else {
+ tst_res(TPASS,
+ "tc2_burst_exceeds_quota: burst=%llu rejected (errno=%d), stored value unchanged (%llu us)",
+ bad_burst, err, burst);
+ }
+ }
+
+ cg_child = tst_cg_group_rm(cg_child);
+}
+
+/*
+ * tc3_functional_burst_accounting - verify nr_bursts and burst_usec increase.
+ *
+ * Configuration: quota=20ms burst=10ms period=100ms
+ * Pool ceiling: quota + burst = 30ms
+ *
+ * Phase A (underrun) - worker burns ~1 ms CPU per period over 5 periods:
+ * CPU usage ~1 ms/period << 20 ms quota. After 2 periods the pool reaches
+ * the 30 ms ceiling. nr_bursts stays 0: the pool fills but is never drained
+ * below runtime_snap, so the surplus at refill is never positive.
+ *
+ * Phase B (overload) - worker burns 4 x QUOTA_US = 80 ms process CPU time:
+ * Under 20 ms quota this spans >= 4 wall-clock periods.
+ * Period B1: pool=30ms, task consumes 30ms -> pool->0, throttled.
+ * Period B2 refill:
+ * runtime += 20ms -> 20ms
+ * surplus = snap(30) - 20 = +10ms > 0
+ * nr_burst++ , burst_time += 10ms
+ * runtime = min(20, 30) = 20ms
+ *
+ * The 'before' snapshot is taken after setup but BEFORE the worker starts,
+ * so it captures the zero baseline. Any burst activity from the underrun
+ * phase (there should be none) is already counted in 'before'. After the
+ * overload phase both counters must be strictly greater than 'before'.
+ */
+static void tc3_functional_burst_accounting(void)
+{
+ struct burst_stat before, after;
+ int stat_ok;
+
+ cg_child = tst_cg_group_mk(tst_cg, "burst_tc3");
+
+ set_cpu_max(cg_child, QUOTA_US, PERIOD_US);
+ write_burst(cg_child, BURST_US);
+
+ /*
+ * Snapshot BEFORE the worker starts. cpu.stat counters start at 0
+ * on a freshly created cgroup; capturing here gives a clean baseline
+ * that is unaffected by any scheduling activity inside the worker.
+ */
+ stat_ok = read_burst_stat(cg_child, &before);
+ if (stat_ok < 0) {
+ tst_res(TCONF,
+ "%s: nr_bursts/burst_usec absent from cpu.stat -- kernel may lack CONFIG_CFS_BANDWIDTH",
+ __func__);
+ cg_child = tst_cg_group_rm(cg_child);
+ return;
+ }
+
+ worker_start(cg_child);
+ worker_send(CMD_UNDERRUN);
+ worker_send(CMD_OVERLOAD);
+ worker_stop();
+
+ read_burst_stat(cg_child, &after);
+
+ tst_res(TINFO, "%s: nr_bursts %llu->%llu burst_usec %llu->%llu",
+ __func__,
+ before.nr_bursts, after.nr_bursts,
+ before.burst_usec, after.burst_usec);
+
+ if (after.nr_bursts > before.nr_bursts &&
+ after.burst_usec > before.burst_usec) {
+ tst_res(TPASS,
+ "%s: nr_bursts +%llu burst_usec +%llu us",
+ __func__,
+ after.nr_bursts - before.nr_bursts,
+ after.burst_usec - before.burst_usec);
+ } else {
+ tst_res(TFAIL,
+ "%s: burst accounting did not increase (nr_bursts %llu->%llu burst_usec %llu->%llu)",
+ __func__,
+ before.nr_bursts, after.nr_bursts,
+ before.burst_usec, after.burst_usec);
+ }
+
+ cg_child = tst_cg_group_rm(cg_child);
+}
+
+/*
+ * tc4_preserved_after_quota_clear - burst value survives quota removal.
+ *
+ * Setting cpu.max to "max <period>" (no quota) must not reset the stored
+ * burst value.
+ */
+static void tc4_preserved_after_quota_clear(void)
+{
+ unsigned long long burst_after;
+
+ cg_child = tst_cg_group_mk(tst_cg, "burst_tc4");
+
+ set_cpu_max(cg_child, QUOTA_US, PERIOD_US);
+ write_burst(cg_child, BURST_US);
+ set_cpu_max(cg_child, ULLONG_MAX, PERIOD_US);
+
+ burst_after = read_burst(cg_child);
+ if (burst_after == BURST_US)
+ tst_res(TPASS, "%s: burst preserved (%llu us) after quota cleared",
+ __func__, burst_after);
+ else
+ tst_res(TFAIL, "%s: expected %llu us after quota clear, got %llu us",
+ __func__, BURST_US, burst_after);
+
+ cg_child = tst_cg_group_rm(cg_child);
+}
+
+/*
+ * tc5_preserved_after_period_change - burst value survives period update.
+ *
+ * Updating only the period in cpu.max must not silently clear the burst value.
+ */
+static void tc5_preserved_after_period_change(void)
+{
+ const unsigned long long new_period = 200000ULL;
+ unsigned long long burst_after;
+
+ cg_child = tst_cg_group_mk(tst_cg, "burst_tc5");
+
+ set_cpu_max(cg_child, QUOTA_US, PERIOD_US);
+ write_burst(cg_child, BURST_US);
+ set_cpu_max(cg_child, QUOTA_US, new_period);
+
+ burst_after = read_burst(cg_child);
+ if (burst_after == BURST_US)
+ tst_res(TPASS, "%s: burst preserved (%llu us) after period changed to %llu us",
+ __func__, burst_after, new_period);
+ else
+ tst_res(TFAIL, "%s: expected %llu us after period change, got %llu us",
+ __func__, BURST_US, burst_after);
+
+ cg_child = tst_cg_group_rm(cg_child);
+}
+
+static void (*const tc_funcs[])(void) = {
+ tc1_default_and_zero,
+ tc2_write_readback_and_boundary,
+ tc3_functional_burst_accounting,
+ tc4_preserved_after_quota_clear,
+ tc5_preserved_after_period_change,
+};
+
+static void run_test(unsigned int n)
+{
+ tc_funcs[n]();
+}
+
+static void setup(void)
+{
+ int dirfd;
+
+ dirfd = tst_cg_group_unified_dir_fd(tst_cg);
+ if (dirfd < 0)
+ tst_brk(TBROK, "tst_cg_group_unified_dir_fd failed");
+
+ if (faccessat(dirfd, "cpu.max.burst", F_OK, 0) != 0)
+ tst_brk(TCONF,
+ "cpu.max.burst not present -- kernel requires CONFIG_CFS_BANDWIDTH=y");
+
+ if (faccessat(dirfd, "cpu.stat", F_OK, 0) != 0)
+ tst_brk(TCONF, "cpu.stat not present");
+
+ tst_res(TINFO, "quota=%llu us burst=%llu us period=%llu us",
+ QUOTA_US, BURST_US, PERIOD_US);
+}
+
+static void cleanup(void)
+{
+ if (worker_pid >= 0)
+ worker_stop();
+
+ if (cmd_pipe[1] >= 0) {
+ SAFE_CLOSE(cmd_pipe[1]);
+ cmd_pipe[1] = -1;
+ }
+ if (ack_pipe[0] >= 0) {
+ SAFE_CLOSE(ack_pipe[0]);
+ ack_pipe[0] = -1;
+ }
+
+ if (cg_child)
+ cg_child = tst_cg_group_rm(cg_child);
+}
+
+static struct tst_test test = {
+ .tcnt = ARRAY_SIZE(tc_funcs),
+ .test = run_test,
+ .setup = setup,
+ .cleanup = cleanup,
+ .needs_root = 1,
+ .forks_child = 1,
+ .timeout = 60,
+ .taint_check = TST_TAINT_W | TST_TAINT_D,
+ .needs_kconfigs = (const char *[]) {
+ "CONFIG_CFS_BANDWIDTH",
+ NULL
+ },
+ .needs_cgroup_ver = TST_CG_V2,
+ .needs_cgroup_ctrls = (const char *const []) { "cpu", NULL },
+};
--
2.53.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [LTP] [PATCH 2/2] cgroup: Add invalid burst test
2026-09-28 5:17 [LTP] [PATCH 0/2] cgroup: Add tests for cgroupv2 cpu.max.burst Samir Mulani
2026-09-28 5:17 ` [LTP] [PATCH 1/2] cgroup: Add cgroup_burst01 test Samir Mulani
@ 2026-09-28 5:17 ` Samir Mulani
1 sibling, 0 replies; 4+ messages in thread
From: Samir Mulani @ 2026-09-28 5:17 UTC (permalink / raw)
To: ltp; +Cc: Samir Mulani
I added cpu_max_burst_invalid01 to test that the cpu.max.burst
interface in cgroupv2 correctly rejects invalid input values. The
kernel's tg_set_bandwidth() in kernel/sched/core.c rejects bad burst
values with EINVAL and must not change the previously stored burst
value.
Test scenarios:
TC1 - Writing burst = quota+1 (exceeds quota) and burst = "-1"
(negative value) must both be rejected with EINVAL and the
burst value must remain unchanged.
TC2 - Writing a value where burst+quota exceeds the kernel MAX_BW
and writing "abc" (garbage string) must both be rejected with
EINVAL.
TC3 - Writing a value that overflows the nanosecond conversion and
writing "100xyz" (trailing garbage) must both be rejected with
EINVAL.
TC4 - After five consecutive rejected writes the burst value must
remain unchanged.
TC5 - With unlimited quota, burst > quota is accepted; after
re-enabling a finite quota the same burst value must be
rejected with EINVAL.
Requires CONFIG_CFS_BANDWIDTH=y and cgroupv2 cpu controller.
Signed-off-by: Samir Mulani <samir@linux.ibm.com>
---
runtest/controllers | 1 +
.../kernel/controllers/cgroup/.gitignore | 1 +
.../cgroup/cpu_max_burst_invalid01.c | 417 ++++++++++++++++++
3 files changed, 419 insertions(+)
create mode 100644 testcases/kernel/controllers/cgroup/cpu_max_burst_invalid01.c
diff --git a/runtest/controllers b/runtest/controllers
index aa93ea5af..b0c7ca3c4 100644
--- a/runtest/controllers
+++ b/runtest/controllers
@@ -4,6 +4,7 @@ cgroup_core02 cgroup_core02
cgroup_core03 cgroup_core03
cgroup cgroup_regression_test.sh
cpu_max_burst01 cpu_max_burst01
+cpu_max_burst_invalid01 cpu_max_burst_invalid01
memcg_regression memcg_regression_test.sh
memcg_test_3 memcg_test_3
memcg_failcnt memcg_failcnt.sh
diff --git a/testcases/kernel/controllers/cgroup/.gitignore b/testcases/kernel/controllers/cgroup/.gitignore
index 2506388fd..0cc5ead44 100644
--- a/testcases/kernel/controllers/cgroup/.gitignore
+++ b/testcases/kernel/controllers/cgroup/.gitignore
@@ -4,3 +4,4 @@
/cgroup_core02
/cgroup_core03
/cpu_max_burst01
+/cpu_max_burst_invalid01
diff --git a/testcases/kernel/controllers/cgroup/cpu_max_burst_invalid01.c b/testcases/kernel/controllers/cgroup/cpu_max_burst_invalid01.c
new file mode 100644
index 000000000..cca92e178
--- /dev/null
+++ b/testcases/kernel/controllers/cgroup/cpu_max_burst_invalid01.c
@@ -0,0 +1,417 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Samir Mulani, IBM Corporation
+ */
+
+/*\
+ * Negative / invalid-input tests for the cgroupv2 cpu.max.burst interface.
+ *
+ * The kernel's tg_set_bandwidth() (kernel/sched/core.c) imposes several
+ * hard constraints on the burst_us value written to cpu.max.burst. Every
+ * invalid write must be rejected with EINVAL and must not modify the
+ * previously stored burst value.
+ *
+ * Kernel validation constraints from tg_set_bandwidth():
+ * (A) burst_us > (U64_MAX / NSEC_PER_USEC) -> EINVAL (nsec overflow)
+ * (B) burst_us > quota_us -> EINVAL (exceeds quota)
+ * (C) burst_us + quota_us > MAX_BW -> EINVAL (sum overflow)
+ * (D) Non-numeric / garbage string -> EINVAL (parse error)
+ * (E) Negative integer written as string -> EINVAL (parse error)
+ *
+ * Test scenarios:
+ * TC 1 - Boundary and sign violations: burst = quota+1 (B) and burst = "-1"
+ * (E) must both be rejected with EINVAL, value unchanged.
+ * TC 2 - Overflow and garbage: burst+quota > MAX_BW (C) and burst = "abc"
+ * (D) must both be rejected with EINVAL, value unchanged.
+ * TC 3 - Numeric overflow variants: burst overflows nsec translation (A)
+ * and trailing garbage "100xyz" (D) must both be rejected.
+ * TC 4 - Burst stable after a sequence of rejected writes.
+ * TC 5 - Burst vs quota transition: unlimited -> finite quota.
+ *
+ * Requires: CONFIG_CFS_BANDWIDTH=y, cgroup v2 cpu controller.
+ */
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "tst_test.h"
+#include "tst_safe_file_at.h"
+
+#define PERIOD_US 100000ULL
+#define QUOTA_US 20000ULL
+#define BURST_US 10000ULL
+
+/* MAX_BW_US: kernel max_bw_runtime_us = (1 << BW_SHIFT) * USEC_PER_SEC */
+#define MAX_BW_US (((uint64_t)1 << 20) * 1000000ULL)
+
+static struct tst_cg_group *cg_child;
+
+static int cg_dirfd(const struct tst_cg_group *cg)
+{
+ int fd = tst_cg_group_unified_dir_fd(cg);
+
+ if (fd < 0)
+ tst_brk(TBROK, "tst_cg_group_unified_dir_fd returned %d", fd);
+ return fd;
+}
+
+static unsigned long long cg_read_ull(const struct tst_cg_group *cg,
+ const char *fname)
+{
+ char buf[64];
+ char *endp;
+ unsigned long long val;
+
+ SAFE_FILE_READAT(cg_dirfd(cg), fname, buf, sizeof(buf) - 1);
+
+ val = strtoull(buf, &endp, 10);
+ if (endp == buf)
+ tst_brk(TBROK, "strtoull failed on '%s' from %s", buf, fname);
+ return val;
+}
+
+/*
+ * cg_write_str() - write @str to @fname; returns errno on failure.
+ * Uses raw write(2) so callers can inspect the error without aborting.
+ */
+static int cg_write_str(const struct tst_cg_group *cg,
+ const char *fname, const char *str)
+{
+ int fd, saved = 0;
+ ssize_t n;
+
+ fd = SAFE_OPENAT(cg_dirfd(cg), fname, O_WRONLY);
+ n = write(fd, str, strlen(str));
+ if (n < 0)
+ saved = errno;
+ SAFE_CLOSE(fd);
+ return saved;
+}
+
+static void cg_write_str_safe(const struct tst_cg_group *cg,
+ const char *fname, const char *str)
+{
+ SAFE_FILE_PRINTFAT(cg_dirfd(cg), fname, "%s", str);
+}
+
+static void set_cpu_max(const struct tst_cg_group *cg,
+ unsigned long long quota_us,
+ unsigned long long period_us)
+{
+ char buf[64];
+
+ snprintf(buf, sizeof(buf), "%llu %llu", quota_us, period_us);
+ cg_write_str_safe(cg, "cpu.max", buf);
+}
+
+static unsigned long long read_burst(const struct tst_cg_group *cg)
+{
+ return cg_read_ull(cg, "cpu.max.burst");
+}
+
+/* try_write_burst() - attempt a burst write; returns errno on failure. */
+static int try_write_burst(const struct tst_cg_group *cg,
+ unsigned long long burst_us)
+{
+ char buf[32];
+
+ snprintf(buf, sizeof(buf), "%llu", burst_us);
+ return cg_write_str(cg, "cpu.max.burst", buf);
+}
+
+/* try_write_burst_str() - write a raw string; returns errno on failure. */
+static int try_write_burst_str(const struct tst_cg_group *cg, const char *str)
+{
+ return cg_write_str(cg, "cpu.max.burst", str);
+}
+
+/*
+ * check_rejected() - verify the write returned EINVAL and that the stored
+ * burst value is unchanged (rejected writes must not corrupt kernel state).
+ */
+static void check_rejected(const char *label, int err,
+ unsigned long long expected_burst)
+{
+ unsigned long long actual_burst;
+
+ if (!err) {
+ tst_res(TFAIL, "%s: write succeeded unexpectedly (expected EINVAL)",
+ label);
+ return;
+ }
+
+ if (err != EINVAL) {
+ tst_res(TFAIL, "%s: got errno=%d (%s), expected EINVAL",
+ label, err, tst_strerrno(err));
+ return;
+ }
+
+ actual_burst = read_burst(cg_child);
+ if (actual_burst != expected_burst) {
+ tst_res(TFAIL, "%s: burst corrupted after rejected write (expected %llu us, got %llu us)",
+ label, expected_burst, actual_burst);
+ return;
+ }
+
+ tst_res(TPASS, "%s: write rejected (errno=EINVAL), burst unchanged (%llu us)",
+ label, actual_burst);
+}
+
+/*
+ * tc1_boundary_and_negative - burst > quota and negative integer rejected.
+ *
+ * quota+1 is the smallest violation; "-1" exercises the unsigned parser
+ * boundary. Both writes must return EINVAL and leave burst unchanged.
+ */
+static void tc1_boundary_and_negative(void)
+{
+ int err;
+
+ cg_child = tst_cg_group_mk(tst_cg, "inv_tc1");
+ set_cpu_max(cg_child, QUOTA_US, PERIOD_US);
+ cg_write_str_safe(cg_child, "cpu.max.burst", "10000");
+
+ err = try_write_burst(cg_child, QUOTA_US + 1);
+ check_rejected("burst=quota+1", err, BURST_US);
+
+ err = try_write_burst_str(cg_child, "-1");
+ check_rejected("burst=\"-1\"", err, BURST_US);
+
+ cg_child = tst_cg_group_rm(cg_child);
+}
+
+/*
+ * tc2_overflow_and_garbage - sum overflow and non-numeric string rejected.
+ *
+ * Uses burst <= quota so only the sum-overflow check fires; "abc" exercises
+ * the non-numeric parse path. Both must return EINVAL.
+ */
+static void tc2_overflow_and_garbage(void)
+{
+ const unsigned long long quota_large = MAX_BW_US / 2 + 1;
+ const unsigned long long burst_large = MAX_BW_US / 2;
+ char buf[64];
+ int fd, err;
+ ssize_t n;
+
+ cg_child = tst_cg_group_mk(tst_cg, "inv_tc2");
+
+ snprintf(buf, sizeof(buf), "%llu %llu", quota_large, PERIOD_US);
+ fd = SAFE_OPENAT(cg_dirfd(cg_child), "cpu.max", O_WRONLY);
+ n = write(fd, buf, strlen(buf));
+ SAFE_CLOSE(fd);
+ if (n < 0) {
+ tst_res(TCONF,
+ "%s: cannot set quota=%llu us (exceeds kernel MAX_BW), skipping",
+ __func__, quota_large);
+ } else {
+ err = try_write_burst(cg_child, burst_large);
+ if (!err) {
+ /*
+ * burst_large <= quota_large so the burst>quota check
+ * passed; the sum check did not fire. This kernel's
+ * MAX_BW is larger than our compile-time constant.
+ */
+ tst_res(TCONF,
+ "%s: burst=%llu accepted (kernel MAX_BW exceeds compile-time estimate), skipping",
+ __func__, burst_large);
+ /* Clear burst before reducing quota to avoid EINVAL. */
+ cg_write_str_safe(cg_child, "cpu.max.burst", "0");
+ } else {
+ check_rejected("burst+quota>MAX_BW", err, 0ULL);
+ }
+ }
+
+ /* Reset to a small quota; burst must be 0 at this point. */
+ set_cpu_max(cg_child, QUOTA_US, PERIOD_US);
+
+ err = try_write_burst_str(cg_child, "abc");
+ check_rejected("burst=\"abc\"", err, 0ULL);
+
+ cg_child = tst_cg_group_rm(cg_child);
+}
+
+/*
+ * tc3_nsec_overflow_and_trailing_garbage - nsec overflow and trailing
+ * garbage rejected.
+ *
+ * A value above U64_MAX/1000 overflows the internal ns conversion.
+ * "100xyz" must be rejected entirely, not silently truncated to 100.
+ */
+static void tc3_nsec_overflow_and_trailing_garbage(void)
+{
+ const unsigned long long overflow_val = (UINT64_MAX / 1000ULL) + 1ULL;
+ int err;
+
+ cg_child = tst_cg_group_mk(tst_cg, "inv_tc3");
+ set_cpu_max(cg_child, QUOTA_US, PERIOD_US);
+
+ err = try_write_burst(cg_child, overflow_val);
+ check_rejected("burst>U64_MAX/NSEC_PER_USEC", err, 0ULL);
+
+ err = try_write_burst_str(cg_child, "100xyz");
+ check_rejected("burst=\"100xyz\"", err, 0ULL);
+
+ cg_child = tst_cg_group_rm(cg_child);
+}
+
+/*
+ * tc4_stable_after_rejections - burst value stable after repeated rejected
+ * writes.
+ *
+ * Repeated invalid writes must not partially corrupt the stored burst value.
+ */
+static void tc4_stable_after_rejections(void)
+{
+ struct {
+ unsigned long long val;
+ const char *str;
+ const char *label;
+ } bad[] = {
+ { QUOTA_US + 1, NULL, "quota+1" },
+ { QUOTA_US * 5, NULL, "quota*5" },
+ { UINT64_MAX, NULL, "UINT64_MAX" },
+ { 0, "bad", "\"bad\"" },
+ { 0, "-999", "\"-999\"" },
+ };
+ unsigned long long burst_after;
+ unsigned int i;
+ int err;
+
+ cg_child = tst_cg_group_mk(tst_cg, "inv_tc4");
+ set_cpu_max(cg_child, QUOTA_US, PERIOD_US);
+ cg_write_str_safe(cg_child, "cpu.max.burst", "10000");
+
+ for (i = 0; i < ARRAY_SIZE(bad); i++) {
+ if (bad[i].str)
+ err = try_write_burst_str(cg_child, bad[i].str);
+ else
+ err = try_write_burst(cg_child, bad[i].val);
+
+ if (!err) {
+ tst_res(TFAIL,
+ "%s: bad write [%s] unexpectedly succeeded (expected EINVAL)",
+ __func__, bad[i].label);
+ continue;
+ }
+ if (err != EINVAL) {
+ tst_res(TFAIL,
+ "%s: bad write [%s] got errno=%d (%s), expected EINVAL",
+ __func__, bad[i].label, err, tst_strerrno(err));
+ }
+ }
+
+ burst_after = read_burst(cg_child);
+ if (burst_after == BURST_US)
+ tst_res(TPASS,
+ "%s: burst remains %llu us after %zu rejected writes",
+ __func__, BURST_US, ARRAY_SIZE(bad));
+ else
+ tst_res(TFAIL,
+ "%s: burst corrupted: expected %llu us, got %llu us",
+ __func__, BURST_US, burst_after);
+
+ cg_child = tst_cg_group_rm(cg_child);
+}
+
+/*
+ * tc5_quota_transition - burst > quota rejected after re-enabling finite quota.
+ *
+ * With unlimited quota the burst > quota check is skipped; re-enabling
+ * a finite quota must then cause the same burst value to be rejected.
+ */
+static void tc5_quota_transition(void)
+{
+ int err;
+ unsigned long long burst_after;
+
+ cg_child = tst_cg_group_mk(tst_cg, "inv_tc5");
+
+ set_cpu_max(cg_child, QUOTA_US, PERIOD_US);
+ cg_write_str_safe(cg_child, "cpu.max", "max 100000");
+
+ err = try_write_burst(cg_child, QUOTA_US + 1);
+ burst_after = read_burst(cg_child);
+
+ if (err != 0) {
+ tst_res(TCONF,
+ "%s: burst=%llu rejected with unlimited quota (errno=%d) -- kernel enforces stricter check, skip",
+ __func__, QUOTA_US + 1, err);
+ cg_child = tst_cg_group_rm(cg_child);
+ return;
+ }
+
+ if (burst_after != QUOTA_US + 1) {
+ tst_res(TFAIL,
+ "%s: burst readback mismatch: expected %llu, got %llu",
+ __func__, QUOTA_US + 1, burst_after);
+ cg_child = tst_cg_group_rm(cg_child);
+ return;
+ }
+
+ tst_res(TPASS,
+ "%s: burst=%llu accepted with unlimited quota",
+ __func__, QUOTA_US + 1);
+
+ cg_write_str_safe(cg_child, "cpu.max.burst", "0");
+ set_cpu_max(cg_child, QUOTA_US, PERIOD_US);
+
+ err = try_write_burst(cg_child, QUOTA_US + 1);
+ check_rejected("burst>quota after re-enable", err, 0ULL);
+
+ cg_child = tst_cg_group_rm(cg_child);
+}
+
+static void (*const tc_funcs[])(void) = {
+ tc1_boundary_and_negative,
+ tc2_overflow_and_garbage,
+ tc3_nsec_overflow_and_trailing_garbage,
+ tc4_stable_after_rejections,
+ tc5_quota_transition,
+};
+
+static void run_test(unsigned int n)
+{
+ tc_funcs[n]();
+}
+
+static void setup(void)
+{
+ int dirfd;
+
+ dirfd = tst_cg_group_unified_dir_fd(tst_cg);
+ if (dirfd < 0)
+ tst_brk(TBROK, "tst_cg_group_unified_dir_fd failed");
+
+ if (faccessat(dirfd, "cpu.max.burst", F_OK, 0) != 0)
+ tst_brk(TCONF, "cpu.max.burst not present -- kernel requires CONFIG_CFS_BANDWIDTH=y");
+}
+
+static void cleanup(void)
+{
+ if (cg_child)
+ cg_child = tst_cg_group_rm(cg_child);
+}
+
+static struct tst_test test = {
+ .tcnt = ARRAY_SIZE(tc_funcs),
+ .test = run_test,
+ .setup = setup,
+ .cleanup = cleanup,
+ .needs_root = 1,
+ .timeout = 30,
+ .taint_check = TST_TAINT_W | TST_TAINT_D,
+ .needs_kconfigs = (const char *[]) {
+ "CONFIG_CFS_BANDWIDTH",
+ NULL
+ },
+ .needs_cgroup_ver = TST_CG_V2,
+ .needs_cgroup_ctrls = (const char *const []) { "cpu", NULL },
+};
--
2.53.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [LTP] cgroup: Add cgroup_burst01 test
2026-09-28 5:17 ` [LTP] [PATCH 1/2] cgroup: Add cgroup_burst01 test Samir Mulani
@ 2026-09-29 7:14 ` linuxtestproject.agent
0 siblings, 0 replies; 4+ messages in thread
From: linuxtestproject.agent @ 2026-09-29 7:14 UTC (permalink / raw)
To: Samir Mulani; +Cc: ltp
Hi Samir,
On Mon, 28 Sep 2026 07:17:34 +0200, Samir Mulani wrote:
> cgroup: Add cgroup_burst01 test
--- [PATCH 1/2] ---
> cgroup: Add cgroup_burst01 test
The test and binary name is cpu_max_burst01. Update the subject line
to match the test name.
> TC3 - A worker underruns to build up burst credit, then overloads
> to consume it; cpu.stat nr_bursts and burst_usec counters
> must increase. Also checks that burst can be updated while
> tasks are running.
The test code never updates cpu.max.burst while tasks are running.
Update the commit message or add the missing check to the test.
> diff --git a/runtest/controllers b/runtest/controllers
> --- a/runtest/controllers
> +++ b/runtest/controllers
> @@ -3,6 +3,7 @@ cgroup_core01 cgroup_core01
> cgroup_core02 cgroup_core02
> cgroup_core03 cgroup_core03
> cgroup cgroup_regression_test.sh
> +cpu_max_burst01 cpu_max_burst01
Use a tab character instead of spaces between the test name and the
binary path.
> * Test scenarios:
> * TC 1 - Default value and write-zero: cpu.max.burst defaults to 0;
Insert a blank line before the list in the documentation comment block.
> if (err != EINVAL) {
> tst_res(TWARN,
> "tc2_burst_exceeds_quota: got errno=%d (%s), expected EINVAL",
> err, tst_strerrno(err));
> }
>
> if (burst != BURST_US) {
> tst_res(TFAIL,
> "tc2_burst_exceeds_quota: burst corrupted after rejected write (expected %llu us, got %llu us)",
> BURST_US, burst);
> } else {
> tst_res(TPASS,
> "tc2_burst_exceeds_quota: burst=%llu rejected (errno=%d), stored value unchanged (%llu us)",
> bad_burst, err, burst);
> }
If err != EINVAL, report TFAIL instead of TWARN. As written, receiving an
unexpected errno still reports TPASS when burst remains unchanged.
> read_burst_stat(cg_child, &after);
Check the return value of read_burst_stat(). If reading cpu.stat fails,
abort with TBROK rather than proceeding with zeroed stats to TFAIL.
> if (cmd_pipe[1] >= 0) {
> SAFE_CLOSE(cmd_pipe[1]);
> cmd_pipe[1] = -1;
> }
> if (ack_pipe[0] >= 0) {
> SAFE_CLOSE(ack_pipe[0]);
> ack_pipe[0] = -1;
> }
Use '!= -1' instead of '>= 0' to check file descriptor validity.
--- [PATCH 2/2] ---
> cgroup: Add invalid burst test
Name the test explicitly in the subject line (e.g. cgroup: Add
cpu_max_burst_invalid01 test).
> diff --git a/runtest/controllers b/runtest/controllers
> --- a/runtest/controllers
> +++ b/runtest/controllers
> @@ -4,6 +4,7 @@ cgroup_core02 cgroup_core02
> cgroup_core03 cgroup_core03
> cgroup cgroup_regression_test.sh
> cpu_max_burst01 cpu_max_burst01
> +cpu_max_burst_invalid01 cpu_max_burst_invalid01
Use a tab character instead of spaces between the test name and the
binary path.
> * Test scenarios:
> * TC 1 - Boundary and sign violations: burst = quota+1 (B) and burst = "-1"
Insert a blank line before the list in the documentation comment block.
> /* MAX_BW_US: kernel max_bw_runtime_us = (1 << BW_SHIFT) * USEC_PER_SEC */
> #define MAX_BW_US (((uint64_t)1 << 20) * 1000000ULL)
The kernel defines MAX_BW as ((1ULL << (64 - BW_SHIFT)) - 1), which is
(1ULL << 44) - 1. The current calculation is too small and does not exceed
the kernel limit, causing tc2 to always take the TCONF fallback path.
> static int cg_write_str(const struct tst_cg_group *cg,
> const char *fname, const char *str)
> {
> int fd, saved = 0;
> ssize_t n;
>
> fd = SAFE_OPENAT(cg_dirfd(cg), fname, O_WRONLY);
> n = write(fd, str, strlen(str));
Use TST_EXP_FAIL2(write(...), EINVAL) rather than calling bare write()
and propagating errno through helper return values.
Verdict - Needs revision
---
Note:
The agent can sometimes produce false positives although often its
findings are genuine. If you find issues with the review, please
comment this email or ignore the suggestions.
Regards,
LTP AI Reviewer
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-09-29 7:14 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-28 5:17 [LTP] [PATCH 0/2] cgroup: Add tests for cgroupv2 cpu.max.burst Samir Mulani
2026-09-28 5:17 ` [LTP] [PATCH 1/2] cgroup: Add cgroup_burst01 test Samir Mulani
2026-09-29 7:14 ` [LTP] " linuxtestproject.agent
2026-09-28 5:17 ` [LTP] [PATCH 2/2] cgroup: Add invalid burst test Samir Mulani
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.