[LTP] [PATCH 1/2] cgroup: Add cgroup_burst01 test

Samir Mulani samir@linux.ibm.com
Mon Sep 28 07:17:34 CEST 2026


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



More information about the ltp mailing list