From: Cyril Hrubis <chrubis@suse.cz>
To: Samir Mulani <samir@linux.ibm.com>
Cc: ltp@lists.linux.it
Subject: Re: [LTP] [PATCH 1/2] cgroup: Add cgroup_burst01 test
Date: Fri, 30 Oct 2026 15:18:54 +0100 [thread overview]
Message-ID: <auSnTigZauyGXB5k@yuki.lan> (raw)
In-Reply-To: <20260928051740.591819-2-samir@linux.ibm.com>
Hi!
> 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);
> +}
These are mostly mis-use of the cgroup API. There is SAFE_CG_PRINTF()
that must be use to write the values instead of rolling out your own
versions.
For reading values there is a TST_CG_SCANF().
> +/*
> + * 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;
> +}
The fd to a file inside of a particular cgroup can be obtained with
SAFE_CG_OPEN().
> +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);
> +}
These are also misuse of the API, the tests should use SAFE_CG_SCANF()
directly.
> +/*
> + * 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);
This should be replaced by SAFE_CG_READ().
> + 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;
> +}
This should use CPUTIME clock if it wants to ensure that the CPU was
really burned for a defined period.
> +/*
> + * 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.
> + */
The testcases must be described in the top-level documentation comment,
not in the code.
> +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__);
This should be just TST_EXP_EQ_LU().
> + 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);
This should be just TST_EXP_EQ_LU() as well.
> + 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.
> + */
Here as well.
> +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);
This should be just TST_EXP_EQ_LU() as well.
> + 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);
This should be just TST_EXP_EQ_LU() as well.
> + /*
> + * 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);
> + }
> + }
This is ugly spaghetti code.
Maybe it would be best to add a function to the library that would
propagate the error from write, e.g. TST_CG_PRINTF() that could be then
used together with TST_EXP_FAIL():
TST_EXP_FAIL(TST_CG_PRINTF(cg_child, "cpu.burst.max", "%llu", BURST_US + 1), EINVAL);
if (TST_PASS) {
SAFE_CG_SCANF(cg_child, "cpu.burst.max", "%llu", burst);
TST_EXP_EQ_LU(burst, BURST_US);
}
> + 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'.
> + */
Here as well, the description should be in the top level comment.
> +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);
> +}
The functional test is the most complex in the file. It would be better
if that test was split into a separate testcases.
> +/*
> + * 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.
> + */
Here as well.
> +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);
This should be TST_EXP_EQ_LU().
> + 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.
> + */
Here as well.
> +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);
This should be TST_EXP_EQ_LU().
> + 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");
This call can only fail on v1.
> + if (faccessat(dirfd, "cpu.max.burst", F_OK, 0) != 0)
> + tst_brk(TCONF,
> + "cpu.max.burst not present -- kernel requires CONFIG_CFS_BANDWIDTH=y");
This check is useless, we already have CONFIG_CFS_BANDWIDTH in
.needs_kconfig.
> + 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
--
Cyril Hrubis
chrubis@suse.cz
--
Mailing list info: https://lists.linux.it/listinfo/ltp
next prev parent reply other threads:[~2026-10-30 14:19 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
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-10-30 14:18 ` Cyril Hrubis [this message]
2026-09-28 5:17 ` [LTP] [PATCH 2/2] cgroup: Add invalid burst test Samir Mulani
2026-10-30 14:26 ` Cyril Hrubis
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=auSnTigZauyGXB5k@yuki.lan \
--to=chrubis@suse.cz \
--cc=ltp@lists.linux.it \
--cc=samir@linux.ibm.com \
/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