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 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.