Linux Test Project
 help / color / mirror / Atom feed
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

  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