[PATCH v4 2/2] cgroup: selftests: Add tests for freezer time

Tiffany Yang posted 2 patches 1 month, 1 week ago
[PATCH v4 2/2] cgroup: selftests: Add tests for freezer time
Posted by Tiffany Yang 1 month, 1 week ago
Test cgroup v2 freezer time stat. Freezer time accounting should
be independent of other cgroups in the hierarchy and should increase
iff a cgroup is CGRP_FREEZE (regardless of whether it reaches
CGRP_FROZEN).

Skip these tests on systems without freeze time accounting.

Signed-off-by: Tiffany Yang <ynaffit@google.com>
Cc: Michal Koutný <mkoutny@suse.com>
---
v3 -> v4:
* Clean up logic around skipping selftests and decrease granularity of
  sleep times, as suggested by Michal.
---
 tools/testing/selftests/cgroup/test_freezer.c | 663 ++++++++++++++++++
 1 file changed, 663 insertions(+)

diff --git a/tools/testing/selftests/cgroup/test_freezer.c b/tools/testing/selftests/cgroup/test_freezer.c
index 8730645d363a..dfb763819581 100644
--- a/tools/testing/selftests/cgroup/test_freezer.c
+++ b/tools/testing/selftests/cgroup/test_freezer.c
@@ -804,6 +804,662 @@ static int test_cgfreezer_vfork(const char *root)
 	return ret;
 }
 
+/*
+ * Get the current frozen_usec for the cgroup.
+ */
+static long cg_check_freezetime(const char *cgroup)
+{
+	return cg_read_key_long(cgroup, "cgroup.stat.local",
+				"frozen_usec ");
+}
+
+/*
+ * Test that the freeze time will behave as expected for an empty cgroup.
+ */
+static int test_cgfreezer_time_empty(const char *root)
+{
+	int ret = KSFT_FAIL;
+	char *cgroup = NULL;
+	long prev, curr;
+
+	cgroup = cg_name(root, "cg_time_test_empty");
+	if (!cgroup)
+		goto cleanup;
+
+	/*
+	 * 1) Create an empty cgroup and check that its freeze time
+	 *    is 0.
+	 */
+	if (cg_create(cgroup))
+		goto cleanup;
+
+	curr = cg_check_freezetime(cgroup);
+	if (curr < 0) {
+		ret = KSFT_SKIP;
+		goto cleanup;
+	}
+	if (curr > 0) {
+		debug("Expect time (%ld) to be 0\n", curr);
+		goto cleanup;
+	}
+
+	if (cg_freeze_nowait(cgroup, true))
+		goto cleanup;
+
+	/*
+	 * 2) Sleep for 1000 us. Check that the freeze time is at
+	 *    least 1000 us.
+	 */
+	usleep(1000);
+	curr = cg_check_freezetime(cgroup);
+	if (curr < 1000) {
+		debug("Expect time (%ld) to be at least 1000 us\n",
+		      curr);
+		goto cleanup;
+	}
+
+	/*
+	 * 3) Unfreeze the cgroup. Check that the freeze time is
+	 *    larger than at 2).
+	 */
+	if (cg_freeze_nowait(cgroup, false))
+		goto cleanup;
+	prev = curr;
+	curr = cg_check_freezetime(cgroup);
+	if (curr <= prev) {
+		debug("Expect time (%ld) to be more than previous check (%ld)\n",
+		      curr, prev);
+		goto cleanup;
+	}
+
+	/*
+	 * 4) Check the freeze time again to ensure that it has not
+	 *    changed.
+	 */
+	prev = curr;
+	curr = cg_check_freezetime(cgroup);
+	if (curr != prev) {
+		debug("Expect time (%ld) to be unchanged from previous check (%ld)\n",
+		      curr, prev);
+		goto cleanup;
+	}
+
+	ret = KSFT_PASS;
+
+cleanup:
+	if (cgroup)
+		cg_destroy(cgroup);
+	free(cgroup);
+	return ret;
+}
+
+/*
+ * A simple test for cgroup freezer time accounting. This test follows
+ * the same flow as test_cgfreezer_time_empty, but with a single process
+ * in the cgroup.
+ */
+static int test_cgfreezer_time_simple(const char *root)
+{
+	int ret = KSFT_FAIL;
+	char *cgroup = NULL;
+	long prev, curr;
+
+	cgroup = cg_name(root, "cg_time_test_simple");
+	if (!cgroup)
+		goto cleanup;
+
+	/*
+	 * 1) Create a cgroup and check that its freeze time is 0.
+	 */
+	if (cg_create(cgroup))
+		goto cleanup;
+
+	curr = cg_check_freezetime(cgroup);
+	if (curr < 0) {
+		ret = KSFT_SKIP;
+		goto cleanup;
+	}
+	if (curr > 0) {
+		debug("Expect time (%ld) to be 0\n", curr);
+		goto cleanup;
+	}
+
+	/*
+	 * 2) Populate the cgroup with one child and check that the
+	 *    freeze time is still 0.
+	 */
+	cg_run_nowait(cgroup, child_fn, NULL);
+	prev = curr;
+	curr = cg_check_freezetime(cgroup);
+	if (curr > prev) {
+		debug("Expect time (%ld) to be 0\n", curr);
+		goto cleanup;
+	}
+
+	if (cg_freeze_nowait(cgroup, true))
+		goto cleanup;
+
+	/*
+	 * 3) Sleep for 1000 us. Check that the freeze time is at
+	 *    least 1000 us.
+	 */
+	usleep(1000);
+	prev = curr;
+	curr = cg_check_freezetime(cgroup);
+	if (curr < 1000) {
+		debug("Expect time (%ld) to be at least 1000 us\n",
+		      curr);
+		goto cleanup;
+	}
+
+	/*
+	 * 4) Unfreeze the cgroup. Check that the freeze time is
+	 *    larger than at 3).
+	 */
+	if (cg_freeze_nowait(cgroup, false))
+		goto cleanup;
+	prev = curr;
+	curr = cg_check_freezetime(cgroup);
+	if (curr <= prev) {
+		debug("Expect time (%ld) to be more than previous check (%ld)\n",
+		      curr, prev);
+		goto cleanup;
+	}
+
+	/*
+	 * 5) Sleep for 1000 us. Check that the freeze time is the
+	 *    same as at 4).
+	 */
+	usleep(1000);
+	prev = curr;
+	curr = cg_check_freezetime(cgroup);
+	if (curr != prev) {
+		debug("Expect time (%ld) to be unchanged from previous check (%ld)\n",
+		      curr, prev);
+		goto cleanup;
+	}
+
+	ret = KSFT_PASS;
+
+cleanup:
+	if (cgroup)
+		cg_destroy(cgroup);
+	free(cgroup);
+	return ret;
+}
+
+/*
+ * Test that freezer time accounting works as expected, even while we're
+ * populating a cgroup with processes.
+ */
+static int test_cgfreezer_time_populate(const char *root)
+{
+	int ret = KSFT_FAIL;
+	char *cgroup = NULL;
+	long prev, curr;
+	int i;
+
+	cgroup = cg_name(root, "cg_time_test_populate");
+	if (!cgroup)
+		goto cleanup;
+
+	if (cg_create(cgroup))
+		goto cleanup;
+
+	curr = cg_check_freezetime(cgroup);
+	if (curr < 0) {
+		ret = KSFT_SKIP;
+		goto cleanup;
+	}
+	if (curr > 0) {
+		debug("Expect time (%ld) to be 0\n", curr);
+		goto cleanup;
+	}
+
+	/*
+	 * 1) Populate the cgroup with 100 processes. Check that
+	 *    the freeze time is 0.
+	 */
+	for (i = 0; i < 100; i++)
+		cg_run_nowait(cgroup, child_fn, NULL);
+	prev = curr;
+	curr = cg_check_freezetime(cgroup);
+	if (curr != prev) {
+		debug("Expect time (%ld) to be 0\n", curr);
+		goto cleanup;
+	}
+
+	/*
+	 * 2) Wait for the group to become fully populated. Check
+	 *    that the freeze time is 0.
+	 */
+	if (cg_wait_for_proc_count(cgroup, 100))
+		goto cleanup;
+	prev = curr;
+	curr = cg_check_freezetime(cgroup);
+	if (curr != prev) {
+		debug("Expect time (%ld) to be 0\n", curr);
+		goto cleanup;
+	}
+
+	/*
+	 * 3) Freeze the cgroup and then populate it with 100 more
+	 *    processes. Check that the freeze time continues to grow.
+	 */
+	if (cg_freeze_nowait(cgroup, true))
+		goto cleanup;
+	prev = curr;
+	curr = cg_check_freezetime(cgroup);
+	if (curr <= prev) {
+		debug("Expect time (%ld) to be more than previous check (%ld)\n",
+		      curr, prev);
+		goto cleanup;
+	}
+
+	for (i = 0; i < 100; i++)
+		cg_run_nowait(cgroup, child_fn, NULL);
+	prev = curr;
+	curr = cg_check_freezetime(cgroup);
+	if (curr <= prev) {
+		debug("Expect time (%ld) to be more than previous check (%ld)\n",
+		      curr, prev);
+		goto cleanup;
+	}
+
+	/*
+	 * 4) Wait for the group to become fully populated. Check
+	 *    that the freeze time is larger than at 3).
+	 */
+	if (cg_wait_for_proc_count(cgroup, 200))
+		goto cleanup;
+	prev = curr;
+	curr = cg_check_freezetime(cgroup);
+	if (curr <= prev) {
+		debug("Expect time (%ld) to be more than previous check (%ld)\n",
+		      curr, prev);
+		goto cleanup;
+	}
+
+	/*
+	 * 5) Unfreeze the cgroup. Check that the freeze time is
+	 *    larger than at 4).
+	 */
+	if (cg_freeze_nowait(cgroup, false))
+		goto cleanup;
+	prev = curr;
+	curr = cg_check_freezetime(cgroup);
+	if (curr <= prev) {
+		debug("Expect time (%ld) to be more than previous check (%ld)\n",
+		      curr, prev);
+		goto cleanup;
+	}
+
+	/*
+	 * 6) Kill the processes. Check that the freeze time is the
+	 *    same as it was at 5).
+	 */
+	if (cg_killall(cgroup))
+		goto cleanup;
+	prev = curr;
+	curr = cg_check_freezetime(cgroup);
+	if (curr != prev) {
+		debug("Expect time (%ld) to be unchanged from previous check (%ld)\n",
+		      curr, prev);
+		goto cleanup;
+	}
+
+	/*
+	 * 7) Freeze and unfreeze the cgroup. Check that the freeze
+	 *    time is larger than it was at 6).
+	 */
+	if (cg_freeze_nowait(cgroup, true))
+		goto cleanup;
+	if (cg_freeze_nowait(cgroup, false))
+		goto cleanup;
+	prev = curr;
+	curr = cg_check_freezetime(cgroup);
+	if (curr <= prev) {
+		debug("Expect time (%ld) to be more than previous check (%ld)\n",
+		      curr, prev);
+		goto cleanup;
+	}
+
+	ret = KSFT_PASS;
+
+cleanup:
+	if (cgroup)
+		cg_destroy(cgroup);
+	free(cgroup);
+	return ret;
+}
+
+/*
+ * Test that frozen time for a cgroup continues to work as expected,
+ * even as processes are migrated. Frozen cgroup A's freeze time should
+ * continue to increase and running cgroup B's should stay 0.
+ */
+static int test_cgfreezer_time_migrate(const char *root)
+{
+	long prev_A, curr_A, curr_B;
+	char *cgroup[2] = {0};
+	int ret = KSFT_FAIL;
+	int pid;
+
+	cgroup[0] = cg_name(root, "cg_time_test_migrate_A");
+	if (!cgroup[0])
+		goto cleanup;
+
+	cgroup[1] = cg_name(root, "cg_time_test_migrate_B");
+	if (!cgroup[1])
+		goto cleanup;
+
+	if (cg_create(cgroup[0]))
+		goto cleanup;
+
+	if (cg_check_freezetime(cgroup[0]) < 0) {
+		ret = KSFT_SKIP;
+		goto cleanup;
+	}
+
+	if (cg_create(cgroup[1]))
+		goto cleanup;
+
+	pid = cg_run_nowait(cgroup[0], child_fn, NULL);
+	if (pid < 0)
+		goto cleanup;
+
+	if (cg_wait_for_proc_count(cgroup[0], 1))
+		goto cleanup;
+
+	curr_A = cg_check_freezetime(cgroup[0]);
+	if (curr_A) {
+		debug("Expect time (%ld) to be 0\n", curr_A);
+		goto cleanup;
+	}
+	curr_B = cg_check_freezetime(cgroup[1]);
+	if (curr_B) {
+		debug("Expect time (%ld) to be 0\n", curr_B);
+		goto cleanup;
+	}
+
+	/*
+	 * Freeze cgroup A.
+	 */
+	if (cg_freeze_wait(cgroup[0], true))
+		goto cleanup;
+	prev_A = curr_A;
+	curr_A = cg_check_freezetime(cgroup[0]);
+	if (curr_A <= prev_A) {
+		debug("Expect time (%ld) to be > 0\n", curr_A);
+		goto cleanup;
+	}
+
+	/*
+	 * Migrate from A (frozen) to B (running).
+	 */
+	if (cg_enter(cgroup[1], pid))
+		goto cleanup;
+
+	usleep(1000);
+	curr_B = cg_check_freezetime(cgroup[1]);
+	if (curr_B) {
+		debug("Expect time (%ld) to be 0\n", curr_B);
+		goto cleanup;
+	}
+
+	prev_A = curr_A;
+	curr_A = cg_check_freezetime(cgroup[0]);
+	if (curr_A <= prev_A) {
+		debug("Expect time (%ld) to be more than previous check (%ld)\n",
+		      curr_A, prev_A);
+		goto cleanup;
+	}
+
+	ret = KSFT_PASS;
+
+cleanup:
+	if (cgroup[0])
+		cg_destroy(cgroup[0]);
+	free(cgroup[0]);
+	if (cgroup[1])
+		cg_destroy(cgroup[1]);
+	free(cgroup[1]);
+	return ret;
+}
+
+/*
+ * The test creates a cgroup and freezes it. Then it creates a child cgroup.
+ * After that it checks that the child cgroup has a non-zero freeze time
+ * that is less than the parent's. Next, it freezes the child, unfreezes
+ * the parent, and sleeps. Finally, it checks that the child's freeze
+ * time has grown larger than the parent's.
+ */
+static int test_cgfreezer_time_parent(const char *root)
+{
+	char *parent, *child = NULL;
+	int ret = KSFT_FAIL;
+	long ptime, ctime;
+
+	parent = cg_name(root, "cg_test_parent_A");
+	if (!parent)
+		goto cleanup;
+
+	child = cg_name(parent, "cg_test_parent_B");
+	if (!child)
+		goto cleanup;
+
+	if (cg_create(parent))
+		goto cleanup;
+
+	if (cg_check_freezetime(parent) < 0) {
+		ret = KSFT_SKIP;
+		goto cleanup;
+	}
+
+	if (cg_freeze_wait(parent, true))
+		goto cleanup;
+
+	usleep(1000);
+	if (cg_create(child))
+		goto cleanup;
+
+	if (cg_check_frozen(child, true))
+		goto cleanup;
+
+	/*
+	 * Since the parent was frozen the entire time the child cgroup
+	 * was being created, we expect the parent's freeze time to be
+	 * larger than the child's.
+	 *
+	 * Ideally, we would be able to check both times simultaneously,
+	 * but here we get the child's after we get the parent's.
+	 */
+	ptime = cg_check_freezetime(parent);
+	ctime = cg_check_freezetime(child);
+	if (ptime <= ctime) {
+		debug("Expect ptime (%ld) > ctime (%ld)\n", ptime, ctime);
+		goto cleanup;
+	}
+
+	if (cg_freeze_nowait(child, true))
+		goto cleanup;
+
+	if (cg_freeze_wait(parent, false))
+		goto cleanup;
+
+	if (cg_check_frozen(child, true))
+		goto cleanup;
+
+	usleep(100000);
+
+	ctime = cg_check_freezetime(child);
+	ptime = cg_check_freezetime(parent);
+
+	if (ctime <= ptime) {
+		debug("Expect ctime (%ld) > ptime (%ld)\n", ctime, ptime);
+		goto cleanup;
+	}
+
+	ret = KSFT_PASS;
+
+cleanup:
+	if (child)
+		cg_destroy(child);
+	free(child);
+	if (parent)
+		cg_destroy(parent);
+	free(parent);
+	return ret;
+}
+
+/*
+ * The test creates a parent cgroup and a child cgroup. Then, it freezes
+ * the child and checks that the child's freeze time is greater than the
+ * parent's, which should be zero.
+ */
+static int test_cgfreezer_time_child(const char *root)
+{
+	char *parent, *child = NULL;
+	int ret = KSFT_FAIL;
+	long ptime, ctime;
+
+	parent = cg_name(root, "cg_test_child_A");
+	if (!parent)
+		goto cleanup;
+
+	child = cg_name(parent, "cg_test_child_B");
+	if (!child)
+		goto cleanup;
+
+	if (cg_create(parent))
+		goto cleanup;
+
+	if (cg_check_freezetime(parent) < 0) {
+		ret = KSFT_SKIP;
+		goto cleanup;
+	}
+
+	if (cg_create(child))
+		goto cleanup;
+
+	if (cg_freeze_wait(child, true))
+		goto cleanup;
+
+	ctime = cg_check_freezetime(child);
+	ptime = cg_check_freezetime(parent);
+	if (ptime != 0) {
+		debug("Expect ptime (%ld) to be 0\n", ptime);
+		goto cleanup;
+	}
+
+	if (ctime <= ptime) {
+		debug("Expect ctime (%ld) <= ptime (%ld)\n", ctime, ptime);
+		goto cleanup;
+	}
+
+	ret = KSFT_PASS;
+
+cleanup:
+	if (child)
+		cg_destroy(child);
+	free(child);
+	if (parent)
+		cg_destroy(parent);
+	free(parent);
+	return ret;
+}
+
+/*
+ * The test creates the following hierarchy:
+ *    A
+ *    |
+ *    B
+ *    |
+ *    C
+ *
+ * Then it freezes the cgroups in the order C, B, A.
+ * Then it unfreezes the cgroups in the order A, B, C.
+ * Then it checks that C's freeze time is larger than B's and
+ * that B's is larger than A's.
+ */
+static int test_cgfreezer_time_nested(const char *root)
+{
+	char *cgroup[3] = {0};
+	int ret = KSFT_FAIL;
+	long time[3] = {0};
+	int i;
+
+	cgroup[0] = cg_name(root, "cg_test_time_A");
+	if (!cgroup[0])
+		goto cleanup;
+
+	cgroup[1] = cg_name(cgroup[0], "B");
+	if (!cgroup[1])
+		goto cleanup;
+
+	cgroup[2] = cg_name(cgroup[1], "C");
+	if (!cgroup[2])
+		goto cleanup;
+
+	if (cg_create(cgroup[0]))
+		goto cleanup;
+
+	if (cg_check_freezetime(cgroup[0]) < 0) {
+		ret = KSFT_SKIP;
+		goto cleanup;
+	}
+
+	if (cg_create(cgroup[1]))
+		goto cleanup;
+
+	if (cg_create(cgroup[2]))
+		goto cleanup;
+
+	if (cg_freeze_nowait(cgroup[2], true))
+		goto cleanup;
+
+	if (cg_freeze_nowait(cgroup[1], true))
+		goto cleanup;
+
+	if (cg_freeze_nowait(cgroup[0], true))
+		goto cleanup;
+
+	usleep(1000);
+
+	if (cg_freeze_nowait(cgroup[0], false))
+		goto cleanup;
+
+	if (cg_freeze_nowait(cgroup[1], false))
+		goto cleanup;
+
+	if (cg_freeze_nowait(cgroup[2], false))
+		goto cleanup;
+
+	time[2] = cg_check_freezetime(cgroup[2]);
+	time[1] = cg_check_freezetime(cgroup[1]);
+	time[0] = cg_check_freezetime(cgroup[0]);
+
+	if (time[2] <= time[1]) {
+		debug("Expect C's time (%ld) > B's time (%ld)", time[2], time[1]);
+		goto cleanup;
+	}
+
+	if (time[1] <= time[0]) {
+		debug("Expect B's time (%ld) > A's time (%ld)", time[1], time[0]);
+		goto cleanup;
+	}
+
+	ret = KSFT_PASS;
+
+cleanup:
+	for (i = 2; i >= 0 && cgroup[i]; i--) {
+		cg_destroy(cgroup[i]);
+		free(cgroup[i]);
+	}
+
+	return ret;
+}
+
 #define T(x) { x, #x }
 struct cgfreezer_test {
 	int (*fn)(const char *root);
@@ -819,6 +1475,13 @@ struct cgfreezer_test {
 	T(test_cgfreezer_stopped),
 	T(test_cgfreezer_ptraced),
 	T(test_cgfreezer_vfork),
+	T(test_cgfreezer_time_empty),
+	T(test_cgfreezer_time_simple),
+	T(test_cgfreezer_time_populate),
+	T(test_cgfreezer_time_migrate),
+	T(test_cgfreezer_time_parent),
+	T(test_cgfreezer_time_child),
+	T(test_cgfreezer_time_nested),
 };
 #undef T
 
-- 
2.51.0.rc2.233.g662b1ed5c5-goog
Re: [PATCH v4 2/2] cgroup: selftests: Add tests for freezer time
Posted by Chen Ridong 1 month, 1 week ago

On 2025/8/22 9:37, Tiffany Yang wrote:
> Test cgroup v2 freezer time stat. Freezer time accounting should
> be independent of other cgroups in the hierarchy and should increase
> iff a cgroup is CGRP_FREEZE (regardless of whether it reaches
> CGRP_FROZEN).
> 
> Skip these tests on systems without freeze time accounting.
> 
> Signed-off-by: Tiffany Yang <ynaffit@google.com>
> Cc: Michal Koutný <mkoutny@suse.com>
> ---
> v3 -> v4:
> * Clean up logic around skipping selftests and decrease granularity of
>   sleep times, as suggested by Michal.
> ---
>  tools/testing/selftests/cgroup/test_freezer.c | 663 ++++++++++++++++++
>  1 file changed, 663 insertions(+)
> 
> diff --git a/tools/testing/selftests/cgroup/test_freezer.c b/tools/testing/selftests/cgroup/test_freezer.c
> index 8730645d363a..dfb763819581 100644
> --- a/tools/testing/selftests/cgroup/test_freezer.c
> +++ b/tools/testing/selftests/cgroup/test_freezer.c
> @@ -804,6 +804,662 @@ static int test_cgfreezer_vfork(const char *root)
>  	return ret;
>  }
>  
> +/*
> + * Get the current frozen_usec for the cgroup.
> + */
> +static long cg_check_freezetime(const char *cgroup)
> +{
> +	return cg_read_key_long(cgroup, "cgroup.stat.local",
> +				"frozen_usec ");
> +}
> +
> +/*
> + * Test that the freeze time will behave as expected for an empty cgroup.
> + */
> +static int test_cgfreezer_time_empty(const char *root)
> +{
> +	int ret = KSFT_FAIL;
> +	char *cgroup = NULL;
> +	long prev, curr;
> +
> +	cgroup = cg_name(root, "cg_time_test_empty");
> +	if (!cgroup)
> +		goto cleanup;
> +
> +	/*
> +	 * 1) Create an empty cgroup and check that its freeze time
> +	 *    is 0.
> +	 */
> +	if (cg_create(cgroup))
> +		goto cleanup;
> +
> +	curr = cg_check_freezetime(cgroup);
> +	if (curr < 0) {
> +		ret = KSFT_SKIP;
> +		goto cleanup;
> +	}
> +	if (curr > 0) {
> +		debug("Expect time (%ld) to be 0\n", curr);
> +		goto cleanup;
> +	}
> +

Perhaps we can simply use if (curr != 0) for the condition?

> +	if (cg_freeze_nowait(cgroup, true))
> +		goto cleanup;
> +
> +	/*
> +	 * 2) Sleep for 1000 us. Check that the freeze time is at
> +	 *    least 1000 us.
> +	 */
> +	usleep(1000);
> +	curr = cg_check_freezetime(cgroup);
> +	if (curr < 1000) {
> +		debug("Expect time (%ld) to be at least 1000 us\n",
> +		      curr);
> +		goto cleanup;
> +	}
> +
> +	/*
> +	 * 3) Unfreeze the cgroup. Check that the freeze time is
> +	 *    larger than at 2).
> +	 */
> +	if (cg_freeze_nowait(cgroup, false))
> +		goto cleanup;
> +	prev = curr;
> +	curr = cg_check_freezetime(cgroup);
> +	if (curr <= prev) {
> +		debug("Expect time (%ld) to be more than previous check (%ld)\n",
> +		      curr, prev);
> +		goto cleanup;
> +	}
> +
> +	/*
> +	 * 4) Check the freeze time again to ensure that it has not
> +	 *    changed.
> +	 */
> +	prev = curr;
> +	curr = cg_check_freezetime(cgroup);
> +	if (curr != prev) {
> +		debug("Expect time (%ld) to be unchanged from previous check (%ld)\n",
> +		      curr, prev);
> +		goto cleanup;
> +	}
> +
> +	ret = KSFT_PASS;
> +
> +cleanup:
> +	if (cgroup)
> +		cg_destroy(cgroup);
> +	free(cgroup);
> +	return ret;
> +}
> +
> +/*
> + * A simple test for cgroup freezer time accounting. This test follows
> + * the same flow as test_cgfreezer_time_empty, but with a single process
> + * in the cgroup.
> + */
> +static int test_cgfreezer_time_simple(const char *root)
> +{
> +	int ret = KSFT_FAIL;
> +	char *cgroup = NULL;
> +	long prev, curr;
> +
> +	cgroup = cg_name(root, "cg_time_test_simple");
> +	if (!cgroup)
> +		goto cleanup;
> +
> +	/*
> +	 * 1) Create a cgroup and check that its freeze time is 0.
> +	 */
> +	if (cg_create(cgroup))
> +		goto cleanup;
> +
> +	curr = cg_check_freezetime(cgroup);
> +	if (curr < 0) {
> +		ret = KSFT_SKIP;
> +		goto cleanup;
> +	}
> +	if (curr > 0) {
> +		debug("Expect time (%ld) to be 0\n", curr);
> +		goto cleanup;
> +	}
> +
> +	/*
> +	 * 2) Populate the cgroup with one child and check that the
> +	 *    freeze time is still 0.
> +	 */
> +	cg_run_nowait(cgroup, child_fn, NULL);
> +	prev = curr;
> +	curr = cg_check_freezetime(cgroup);
> +	if (curr > prev) {
> +		debug("Expect time (%ld) to be 0\n", curr);
> +		goto cleanup;
> +	}
> +
> +	if (cg_freeze_nowait(cgroup, true))
> +		goto cleanup;
> +
> +	/*
> +	 * 3) Sleep for 1000 us. Check that the freeze time is at
> +	 *    least 1000 us.
> +	 */
> +	usleep(1000);
> +	prev = curr;
> +	curr = cg_check_freezetime(cgroup);
> +	if (curr < 1000) {
> +		debug("Expect time (%ld) to be at least 1000 us\n",
> +		      curr);
> +		goto cleanup;
> +	}
> +
> +	/*
> +	 * 4) Unfreeze the cgroup. Check that the freeze time is
> +	 *    larger than at 3).
> +	 */
> +	if (cg_freeze_nowait(cgroup, false))
> +		goto cleanup;
> +	prev = curr;
> +	curr = cg_check_freezetime(cgroup);
> +	if (curr <= prev) {
> +		debug("Expect time (%ld) to be more than previous check (%ld)\n",
> +		      curr, prev);
> +		goto cleanup;
> +	}
> +
> +	/*
> +	 * 5) Sleep for 1000 us. Check that the freeze time is the
> +	 *    same as at 4).
> +	 */
> +	usleep(1000);
> +	prev = curr;
> +	curr = cg_check_freezetime(cgroup);
> +	if (curr != prev) {
> +		debug("Expect time (%ld) to be unchanged from previous check (%ld)\n",
> +		      curr, prev);
> +		goto cleanup;
> +	}
> +
> +	ret = KSFT_PASS;
> +
> +cleanup:
> +	if (cgroup)
> +		cg_destroy(cgroup);
> +	free(cgroup);
> +	return ret;
> +}
> +
> +/*
> + * Test that freezer time accounting works as expected, even while we're
> + * populating a cgroup with processes.
> + */
> +static int test_cgfreezer_time_populate(const char *root)
> +{
> +	int ret = KSFT_FAIL;
> +	char *cgroup = NULL;
> +	long prev, curr;
> +	int i;
> +
> +	cgroup = cg_name(root, "cg_time_test_populate");
> +	if (!cgroup)
> +		goto cleanup;
> +
> +	if (cg_create(cgroup))
> +		goto cleanup;
> +
> +	curr = cg_check_freezetime(cgroup);
> +	if (curr < 0) {
> +		ret = KSFT_SKIP;
> +		goto cleanup;
> +	}
> +	if (curr > 0) {
> +		debug("Expect time (%ld) to be 0\n", curr);
> +		goto cleanup;
> +	}
> +
> +	/*
> +	 * 1) Populate the cgroup with 100 processes. Check that
> +	 *    the freeze time is 0.
> +	 */
> +	for (i = 0; i < 100; i++)
> +		cg_run_nowait(cgroup, child_fn, NULL);
> +	prev = curr;
> +	curr = cg_check_freezetime(cgroup);
> +	if (curr != prev) {
> +		debug("Expect time (%ld) to be 0\n", curr);
> +		goto cleanup;
> +	}
> +
> +	/*
> +	 * 2) Wait for the group to become fully populated. Check
> +	 *    that the freeze time is 0.
> +	 */
> +	if (cg_wait_for_proc_count(cgroup, 100))
> +		goto cleanup;
> +	prev = curr;
> +	curr = cg_check_freezetime(cgroup);
> +	if (curr != prev) {
> +		debug("Expect time (%ld) to be 0\n", curr);
> +		goto cleanup;
> +	}
> +
> +	/*
> +	 * 3) Freeze the cgroup and then populate it with 100 more
> +	 *    processes. Check that the freeze time continues to grow.
> +	 */
> +	if (cg_freeze_nowait(cgroup, true))
> +		goto cleanup;
> +	prev = curr;
> +	curr = cg_check_freezetime(cgroup);
> +	if (curr <= prev) {
> +		debug("Expect time (%ld) to be more than previous check (%ld)\n",
> +		      curr, prev);
> +		goto cleanup;
> +	}
> +
> +	for (i = 0; i < 100; i++)
> +		cg_run_nowait(cgroup, child_fn, NULL);
> +	prev = curr;
> +	curr = cg_check_freezetime(cgroup);
> +	if (curr <= prev) {
> +		debug("Expect time (%ld) to be more than previous check (%ld)\n",
> +		      curr, prev);
> +		goto cleanup;
> +	}
> +
> +	/*
> +	 * 4) Wait for the group to become fully populated. Check
> +	 *    that the freeze time is larger than at 3).
> +	 */
> +	if (cg_wait_for_proc_count(cgroup, 200))
> +		goto cleanup;
> +	prev = curr;
> +	curr = cg_check_freezetime(cgroup);
> +	if (curr <= prev) {
> +		debug("Expect time (%ld) to be more than previous check (%ld)\n",
> +		      curr, prev);
> +		goto cleanup;
> +	}
> +
> +	/*
> +	 * 5) Unfreeze the cgroup. Check that the freeze time is
> +	 *    larger than at 4).
> +	 */
> +	if (cg_freeze_nowait(cgroup, false))
> +		goto cleanup;
> +	prev = curr;
> +	curr = cg_check_freezetime(cgroup);
> +	if (curr <= prev) {
> +		debug("Expect time (%ld) to be more than previous check (%ld)\n",
> +		      curr, prev);
> +		goto cleanup;
> +	}
> +
> +	/*
> +	 * 6) Kill the processes. Check that the freeze time is the
> +	 *    same as it was at 5).
> +	 */
> +	if (cg_killall(cgroup))
> +		goto cleanup;
> +	prev = curr;
> +	curr = cg_check_freezetime(cgroup);
> +	if (curr != prev) {
> +		debug("Expect time (%ld) to be unchanged from previous check (%ld)\n",
> +		      curr, prev);
> +		goto cleanup;
> +	}
> +
> +	/*
> +	 * 7) Freeze and unfreeze the cgroup. Check that the freeze
> +	 *    time is larger than it was at 6).
> +	 */
> +	if (cg_freeze_nowait(cgroup, true))
> +		goto cleanup;
> +	if (cg_freeze_nowait(cgroup, false))
> +		goto cleanup;
> +	prev = curr;
> +	curr = cg_check_freezetime(cgroup);
> +	if (curr <= prev) {
> +		debug("Expect time (%ld) to be more than previous check (%ld)\n",
> +		      curr, prev);
> +		goto cleanup;
> +	}
> +
> +	ret = KSFT_PASS;
> +
> +cleanup:
> +	if (cgroup)
> +		cg_destroy(cgroup);
> +	free(cgroup);
> +	return ret;
> +}
> +
> +/*
> + * Test that frozen time for a cgroup continues to work as expected,
> + * even as processes are migrated. Frozen cgroup A's freeze time should
> + * continue to increase and running cgroup B's should stay 0.
> + */
> +static int test_cgfreezer_time_migrate(const char *root)
> +{
> +	long prev_A, curr_A, curr_B;
> +	char *cgroup[2] = {0};
> +	int ret = KSFT_FAIL;
> +	int pid;
> +
> +	cgroup[0] = cg_name(root, "cg_time_test_migrate_A");
> +	if (!cgroup[0])
> +		goto cleanup;
> +
> +	cgroup[1] = cg_name(root, "cg_time_test_migrate_B");
> +	if (!cgroup[1])
> +		goto cleanup;
> +
> +	if (cg_create(cgroup[0]))
> +		goto cleanup;
> +
> +	if (cg_check_freezetime(cgroup[0]) < 0) {
> +		ret = KSFT_SKIP;
> +		goto cleanup;
> +	}
> +
> +	if (cg_create(cgroup[1]))
> +		goto cleanup;
> +
> +	pid = cg_run_nowait(cgroup[0], child_fn, NULL);
> +	if (pid < 0)
> +		goto cleanup;
> +
> +	if (cg_wait_for_proc_count(cgroup[0], 1))
> +		goto cleanup;
> +
> +	curr_A = cg_check_freezetime(cgroup[0]);
> +	if (curr_A) {
> +		debug("Expect time (%ld) to be 0\n", curr_A);
> +		goto cleanup;
> +	}
> +	curr_B = cg_check_freezetime(cgroup[1]);
> +	if (curr_B) {
> +		debug("Expect time (%ld) to be 0\n", curr_B);
> +		goto cleanup;
> +	}
> +
> +	/*
> +	 * Freeze cgroup A.
> +	 */
> +	if (cg_freeze_wait(cgroup[0], true))
> +		goto cleanup;
> +	prev_A = curr_A;
> +	curr_A = cg_check_freezetime(cgroup[0]);
> +	if (curr_A <= prev_A) {
> +		debug("Expect time (%ld) to be > 0\n", curr_A);
> +		goto cleanup;
> +	}
> +
> +	/*
> +	 * Migrate from A (frozen) to B (running).
> +	 */
> +	if (cg_enter(cgroup[1], pid))
> +		goto cleanup;
> +
> +	usleep(1000);
> +	curr_B = cg_check_freezetime(cgroup[1]);
> +	if (curr_B) {
> +		debug("Expect time (%ld) to be 0\n", curr_B);
> +		goto cleanup;
> +	}
> +
> +	prev_A = curr_A;
> +	curr_A = cg_check_freezetime(cgroup[0]);
> +	if (curr_A <= prev_A) {
> +		debug("Expect time (%ld) to be more than previous check (%ld)\n",
> +		      curr_A, prev_A);
> +		goto cleanup;
> +	}
> +
> +	ret = KSFT_PASS;
> +
> +cleanup:
> +	if (cgroup[0])
> +		cg_destroy(cgroup[0]);
> +	free(cgroup[0]);
> +	if (cgroup[1])
> +		cg_destroy(cgroup[1]);
> +	free(cgroup[1]);
> +	return ret;
> +}
> +
> +/*
> + * The test creates a cgroup and freezes it. Then it creates a child cgroup.
> + * After that it checks that the child cgroup has a non-zero freeze time
> + * that is less than the parent's. Next, it freezes the child, unfreezes
> + * the parent, and sleeps. Finally, it checks that the child's freeze
> + * time has grown larger than the parent's.
> + */
> +static int test_cgfreezer_time_parent(const char *root)
> +{
> +	char *parent, *child = NULL;
> +	int ret = KSFT_FAIL;
> +	long ptime, ctime;
> +
> +	parent = cg_name(root, "cg_test_parent_A");
> +	if (!parent)
> +		goto cleanup;
> +
> +	child = cg_name(parent, "cg_test_parent_B");
> +	if (!child)
> +		goto cleanup;
> +
> +	if (cg_create(parent))
> +		goto cleanup;
> +
> +	if (cg_check_freezetime(parent) < 0) {
> +		ret = KSFT_SKIP;
> +		goto cleanup;
> +	}
> +
> +	if (cg_freeze_wait(parent, true))
> +		goto cleanup;
> +
> +	usleep(1000);
> +	if (cg_create(child))
> +		goto cleanup;
> +
> +	if (cg_check_frozen(child, true))
> +		goto cleanup;
> +
> +	/*
> +	 * Since the parent was frozen the entire time the child cgroup
> +	 * was being created, we expect the parent's freeze time to be
> +	 * larger than the child's.
> +	 *
> +	 * Ideally, we would be able to check both times simultaneously,
> +	 * but here we get the child's after we get the parent's.
> +	 */
> +	ptime = cg_check_freezetime(parent);
> +	ctime = cg_check_freezetime(child);
> +	if (ptime <= ctime) {
> +		debug("Expect ptime (%ld) > ctime (%ld)\n", ptime, ctime);
> +		goto cleanup;
> +	}
> +
> +	if (cg_freeze_nowait(child, true))
> +		goto cleanup;
> +
> +	if (cg_freeze_wait(parent, false))
> +		goto cleanup;
> +
> +	if (cg_check_frozen(child, true))
> +		goto cleanup;
> +
> +	usleep(100000);
> +
> +	ctime = cg_check_freezetime(child);
> +	ptime = cg_check_freezetime(parent);
> +
> +	if (ctime <= ptime) {
> +		debug("Expect ctime (%ld) > ptime (%ld)\n", ctime, ptime);
> +		goto cleanup;
> +	}
> +
> +	ret = KSFT_PASS;
> +
> +cleanup:
> +	if (child)
> +		cg_destroy(child);
> +	free(child);
> +	if (parent)
> +		cg_destroy(parent);
> +	free(parent);
> +	return ret;
> +}
> +
> +/*
> + * The test creates a parent cgroup and a child cgroup. Then, it freezes
> + * the child and checks that the child's freeze time is greater than the
> + * parent's, which should be zero.
> + */
> +static int test_cgfreezer_time_child(const char *root)
> +{
> +	char *parent, *child = NULL;
> +	int ret = KSFT_FAIL;
> +	long ptime, ctime;
> +
> +	parent = cg_name(root, "cg_test_child_A");
> +	if (!parent)
> +		goto cleanup;
> +
> +	child = cg_name(parent, "cg_test_child_B");
> +	if (!child)
> +		goto cleanup;
> +
> +	if (cg_create(parent))
> +		goto cleanup;
> +
> +	if (cg_check_freezetime(parent) < 0) {
> +		ret = KSFT_SKIP;
> +		goto cleanup;
> +	}
> +
> +	if (cg_create(child))
> +		goto cleanup;
> +
> +	if (cg_freeze_wait(child, true))
> +		goto cleanup;
> +
> +	ctime = cg_check_freezetime(child);
> +	ptime = cg_check_freezetime(parent);
> +	if (ptime != 0) {
> +		debug("Expect ptime (%ld) to be 0\n", ptime);
> +		goto cleanup;
> +	}
> +
> +	if (ctime <= ptime) {
> +		debug("Expect ctime (%ld) <= ptime (%ld)\n", ctime, ptime);
> +		goto cleanup;
> +	}
> +
> +	ret = KSFT_PASS;
> +
> +cleanup:
> +	if (child)
> +		cg_destroy(child);
> +	free(child);
> +	if (parent)
> +		cg_destroy(parent);
> +	free(parent);
> +	return ret;
> +}
> +
> +/*
> + * The test creates the following hierarchy:
> + *    A
> + *    |
> + *    B
> + *    |
> + *    C
> + *
> + * Then it freezes the cgroups in the order C, B, A.
> + * Then it unfreezes the cgroups in the order A, B, C.
> + * Then it checks that C's freeze time is larger than B's and
> + * that B's is larger than A's.
> + */
> +static int test_cgfreezer_time_nested(const char *root)
> +{
> +	char *cgroup[3] = {0};
> +	int ret = KSFT_FAIL;
> +	long time[3] = {0};
> +	int i;
> +
> +	cgroup[0] = cg_name(root, "cg_test_time_A");
> +	if (!cgroup[0])
> +		goto cleanup;
> +
> +	cgroup[1] = cg_name(cgroup[0], "B");
> +	if (!cgroup[1])
> +		goto cleanup;
> +
> +	cgroup[2] = cg_name(cgroup[1], "C");
> +	if (!cgroup[2])
> +		goto cleanup;
> +
> +	if (cg_create(cgroup[0]))
> +		goto cleanup;
> +
> +	if (cg_check_freezetime(cgroup[0]) < 0) {
> +		ret = KSFT_SKIP;
> +		goto cleanup;
> +	}
> +
> +	if (cg_create(cgroup[1]))
> +		goto cleanup;
> +
> +	if (cg_create(cgroup[2]))
> +		goto cleanup;
> +
> +	if (cg_freeze_nowait(cgroup[2], true))
> +		goto cleanup;
> +
> +	if (cg_freeze_nowait(cgroup[1], true))
> +		goto cleanup;
> +
> +	if (cg_freeze_nowait(cgroup[0], true))
> +		goto cleanup;
> +
> +	usleep(1000);
> +
> +	if (cg_freeze_nowait(cgroup[0], false))
> +		goto cleanup;
> +
> +	if (cg_freeze_nowait(cgroup[1], false))
> +		goto cleanup;
> +
> +	if (cg_freeze_nowait(cgroup[2], false))
> +		goto cleanup;
> +
> +	time[2] = cg_check_freezetime(cgroup[2]);
> +	time[1] = cg_check_freezetime(cgroup[1]);
> +	time[0] = cg_check_freezetime(cgroup[0]);
> +
> +	if (time[2] <= time[1]) {
> +		debug("Expect C's time (%ld) > B's time (%ld)", time[2], time[1]);
> +		goto cleanup;
> +	}
> +
> +	if (time[1] <= time[0]) {
> +		debug("Expect B's time (%ld) > A's time (%ld)", time[1], time[0]);
> +		goto cleanup;
> +	}
> +
> +	ret = KSFT_PASS;
> +
> +cleanup:
> +	for (i = 2; i >= 0 && cgroup[i]; i--) {
> +		cg_destroy(cgroup[i]);
> +		free(cgroup[i]);
> +	}
> +
> +	return ret;
> +}
> +
>  #define T(x) { x, #x }
>  struct cgfreezer_test {
>  	int (*fn)(const char *root);
> @@ -819,6 +1475,13 @@ struct cgfreezer_test {
>  	T(test_cgfreezer_stopped),
>  	T(test_cgfreezer_ptraced),
>  	T(test_cgfreezer_vfork),
> +	T(test_cgfreezer_time_empty),
> +	T(test_cgfreezer_time_simple),
> +	T(test_cgfreezer_time_populate),
> +	T(test_cgfreezer_time_migrate),
> +	T(test_cgfreezer_time_parent),
> +	T(test_cgfreezer_time_child),
> +	T(test_cgfreezer_time_nested),
>  };
>  #undef T
>  

-- 
Best regards,
Ridong

Re: [PATCH v4 2/2] cgroup: selftests: Add tests for freezer time
Posted by Tiffany Yang 1 month, 1 week ago
Thanks for taking the time to look at these patches!

Chen Ridong <chenridong@huaweicloud.com> writes:

> On 2025/8/22 9:37, Tiffany Yang wrote:
>> Test cgroup v2 freezer time stat. Freezer time accounting should
>> be independent of other cgroups in the hierarchy and should increase
>> iff a cgroup is CGRP_FREEZE (regardless of whether it reaches
>> CGRP_FROZEN).

...
>> +	if (curr < 0) {
>> +		ret = KSFT_SKIP;
>> +		goto cleanup;
>> +	}
>> +	if (curr > 0) {
>> +		debug("Expect time (%ld) to be 0\n", curr);
>> +		goto cleanup;
>> +	}
>> +

> Perhaps we can simply use if (curr != 0) for the condition?


Here we have 2 separate conditions because in the case where curr < 0,
it means that the interface is not available and we should skip this
test instead of failing it. In the case where curr > 0, the feature is
not working correctly, and the test should fail as a result.

>> +	if (cg_freeze_nowait(cgroup, true))
>> +		goto cleanup;
>> +
>> +	/*
>> +	 * 2) Sleep for 1000 us. Check that the freeze time is at
>> +	 *    least 1000 us.
>> +	 */
>> +	usleep(1000);
>> +	curr = cg_check_freezetime(cgroup);
>> +	if (curr < 1000) {
>> +		debug("Expect time (%ld) to be at least 1000 us\n",
>> +		      curr);
>> +		goto cleanup;
>> +	}
>> +
>> +	/*
>> +	 * 3) Unfreeze the cgroup. Check that the freeze time is
>> +	 *    larger than at 2).
>> +	 */
>> +	if (cg_freeze_nowait(cgroup, false))
>> +		goto cleanup;
>> +	prev = curr;
>> +	curr = cg_check_freezetime(cgroup);
>> +	if (curr <= prev) {
>> +		debug("Expect time (%ld) to be more than previous check (%ld)\n",
>> +		      curr, prev);
>> +		goto cleanup;
>> +	}
>> +
>> +	/*
>> +	 * 4) Check the freeze time again to ensure that it has not
>> +	 *    changed.
>> +	 */
>> +	prev = curr;
>> +	curr = cg_check_freezetime(cgroup);
>> +	if (curr != prev) {
>> +		debug("Expect time (%ld) to be unchanged from previous check (%ld)\n",
>> +		      curr, prev);
>> +		goto cleanup;
>> +	}
>> +
>> +	ret = KSFT_PASS;
>> +
>> +cleanup:
>> +	if (cgroup)
>> +		cg_destroy(cgroup);
>> +	free(cgroup);
>> +	return ret;
>> +}
>> +
>> +/*
>> + * A simple test for cgroup freezer time accounting. This test follows
>> + * the same flow as test_cgfreezer_time_empty, but with a single process
>> + * in the cgroup.
>> + */
>> +static int test_cgfreezer_time_simple(const char *root)
>> +{
>> +	int ret = KSFT_FAIL;
>> +	char *cgroup = NULL;
>> +	long prev, curr;
>> +
>> +	cgroup = cg_name(root, "cg_time_test_simple");
>> +	if (!cgroup)
>> +		goto cleanup;
>> +
>> +	/*
>> +	 * 1) Create a cgroup and check that its freeze time is 0.
>> +	 */
>> +	if (cg_create(cgroup))
>> +		goto cleanup;
>> +
>> +	curr = cg_check_freezetime(cgroup);
>> +	if (curr < 0) {
>> +		ret = KSFT_SKIP;
>> +		goto cleanup;
>> +	}
>> +	if (curr > 0) {
>> +		debug("Expect time (%ld) to be 0\n", curr);
>> +		goto cleanup;
>> +	}
>> +
>> +	/*
>> +	 * 2) Populate the cgroup with one child and check that the
>> +	 *    freeze time is still 0.
>> +	 */
>> +	cg_run_nowait(cgroup, child_fn, NULL);
>> +	prev = curr;
>> +	curr = cg_check_freezetime(cgroup);
>> +	if (curr > prev) {
>> +		debug("Expect time (%ld) to be 0\n", curr);
>> +		goto cleanup;
>> +	}
>> +
>> +	if (cg_freeze_nowait(cgroup, true))
>> +		goto cleanup;
>> +
>> +	/*
>> +	 * 3) Sleep for 1000 us. Check that the freeze time is at
>> +	 *    least 1000 us.
>> +	 */
>> +	usleep(1000);
>> +	prev = curr;
>> +	curr = cg_check_freezetime(cgroup);
>> +	if (curr < 1000) {
>> +		debug("Expect time (%ld) to be at least 1000 us\n",
>> +		      curr);
>> +		goto cleanup;
>> +	}
>> +
>> +	/*
>> +	 * 4) Unfreeze the cgroup. Check that the freeze time is
>> +	 *    larger than at 3).
>> +	 */
>> +	if (cg_freeze_nowait(cgroup, false))
>> +		goto cleanup;
>> +	prev = curr;
>> +	curr = cg_check_freezetime(cgroup);
>> +	if (curr <= prev) {
>> +		debug("Expect time (%ld) to be more than previous check (%ld)\n",
>> +		      curr, prev);
>> +		goto cleanup;
>> +	}
>> +
>> +	/*
>> +	 * 5) Sleep for 1000 us. Check that the freeze time is the
>> +	 *    same as at 4).
>> +	 */
>> +	usleep(1000);
>> +	prev = curr;
>> +	curr = cg_check_freezetime(cgroup);
>> +	if (curr != prev) {
>> +		debug("Expect time (%ld) to be unchanged from previous check (%ld)\n",
>> +		      curr, prev);
>> +		goto cleanup;
>> +	}
>> +
>> +	ret = KSFT_PASS;
>> +
>> +cleanup:
>> +	if (cgroup)
>> +		cg_destroy(cgroup);
>> +	free(cgroup);
>> +	return ret;
>> +}
>> +
>> +/*
>> + * Test that freezer time accounting works as expected, even while we're
>> + * populating a cgroup with processes.
>> + */
>> +static int test_cgfreezer_time_populate(const char *root)
>> +{
>> +	int ret = KSFT_FAIL;
>> +	char *cgroup = NULL;
>> +	long prev, curr;
>> +	int i;
>> +
>> +	cgroup = cg_name(root, "cg_time_test_populate");
>> +	if (!cgroup)
>> +		goto cleanup;
>> +
>> +	if (cg_create(cgroup))
>> +		goto cleanup;
>> +
>> +	curr = cg_check_freezetime(cgroup);
>> +	if (curr < 0) {
>> +		ret = KSFT_SKIP;
>> +		goto cleanup;
>> +	}
>> +	if (curr > 0) {
>> +		debug("Expect time (%ld) to be 0\n", curr);
>> +		goto cleanup;
>> +	}
>> +
>> +	/*
>> +	 * 1) Populate the cgroup with 100 processes. Check that
>> +	 *    the freeze time is 0.
>> +	 */
>> +	for (i = 0; i < 100; i++)
>> +		cg_run_nowait(cgroup, child_fn, NULL);
>> +	prev = curr;
>> +	curr = cg_check_freezetime(cgroup);
>> +	if (curr != prev) {
>> +		debug("Expect time (%ld) to be 0\n", curr);
>> +		goto cleanup;
>> +	}
>> +
>> +	/*
>> +	 * 2) Wait for the group to become fully populated. Check
>> +	 *    that the freeze time is 0.
>> +	 */
>> +	if (cg_wait_for_proc_count(cgroup, 100))
>> +		goto cleanup;
>> +	prev = curr;
>> +	curr = cg_check_freezetime(cgroup);
>> +	if (curr != prev) {
>> +		debug("Expect time (%ld) to be 0\n", curr);
>> +		goto cleanup;
>> +	}
>> +
>> +	/*
>> +	 * 3) Freeze the cgroup and then populate it with 100 more
>> +	 *    processes. Check that the freeze time continues to grow.
>> +	 */
>> +	if (cg_freeze_nowait(cgroup, true))
>> +		goto cleanup;
>> +	prev = curr;
>> +	curr = cg_check_freezetime(cgroup);
>> +	if (curr <= prev) {
>> +		debug("Expect time (%ld) to be more than previous check (%ld)\n",
>> +		      curr, prev);
>> +		goto cleanup;
>> +	}
>> +
>> +	for (i = 0; i < 100; i++)
>> +		cg_run_nowait(cgroup, child_fn, NULL);
>> +	prev = curr;
>> +	curr = cg_check_freezetime(cgroup);
>> +	if (curr <= prev) {
>> +		debug("Expect time (%ld) to be more than previous check (%ld)\n",
>> +		      curr, prev);
>> +		goto cleanup;
>> +	}
>> +
>> +	/*
>> +	 * 4) Wait for the group to become fully populated. Check
>> +	 *    that the freeze time is larger than at 3).
>> +	 */
>> +	if (cg_wait_for_proc_count(cgroup, 200))
>> +		goto cleanup;
>> +	prev = curr;
>> +	curr = cg_check_freezetime(cgroup);
>> +	if (curr <= prev) {
>> +		debug("Expect time (%ld) to be more than previous check (%ld)\n",
>> +		      curr, prev);
>> +		goto cleanup;
>> +	}
>> +
>> +	/*
>> +	 * 5) Unfreeze the cgroup. Check that the freeze time is
>> +	 *    larger than at 4).
>> +	 */
>> +	if (cg_freeze_nowait(cgroup, false))
>> +		goto cleanup;
>> +	prev = curr;
>> +	curr = cg_check_freezetime(cgroup);
>> +	if (curr <= prev) {
>> +		debug("Expect time (%ld) to be more than previous check (%ld)\n",
>> +		      curr, prev);
>> +		goto cleanup;
>> +	}
>> +
>> +	/*
>> +	 * 6) Kill the processes. Check that the freeze time is the
>> +	 *    same as it was at 5).
>> +	 */
>> +	if (cg_killall(cgroup))
>> +		goto cleanup;
>> +	prev = curr;
>> +	curr = cg_check_freezetime(cgroup);
>> +	if (curr != prev) {
>> +		debug("Expect time (%ld) to be unchanged from previous check (%ld)\n",
>> +		      curr, prev);
>> +		goto cleanup;
>> +	}
>> +
>> +	/*
>> +	 * 7) Freeze and unfreeze the cgroup. Check that the freeze
>> +	 *    time is larger than it was at 6).
>> +	 */
>> +	if (cg_freeze_nowait(cgroup, true))
>> +		goto cleanup;
>> +	if (cg_freeze_nowait(cgroup, false))
>> +		goto cleanup;
>> +	prev = curr;
>> +	curr = cg_check_freezetime(cgroup);
>> +	if (curr <= prev) {
>> +		debug("Expect time (%ld) to be more than previous check (%ld)\n",
>> +		      curr, prev);
>> +		goto cleanup;
>> +	}
>> +
>> +	ret = KSFT_PASS;
>> +
>> +cleanup:
>> +	if (cgroup)
>> +		cg_destroy(cgroup);
>> +	free(cgroup);
>> +	return ret;
>> +}
>> +
>> +/*
>> + * Test that frozen time for a cgroup continues to work as expected,
>> + * even as processes are migrated. Frozen cgroup A's freeze time should
>> + * continue to increase and running cgroup B's should stay 0.
>> + */
>> +static int test_cgfreezer_time_migrate(const char *root)
>> +{
>> +	long prev_A, curr_A, curr_B;
>> +	char *cgroup[2] = {0};
>> +	int ret = KSFT_FAIL;
>> +	int pid;
>> +
>> +	cgroup[0] = cg_name(root, "cg_time_test_migrate_A");
>> +	if (!cgroup[0])
>> +		goto cleanup;
>> +
>> +	cgroup[1] = cg_name(root, "cg_time_test_migrate_B");
>> +	if (!cgroup[1])
>> +		goto cleanup;
>> +
>> +	if (cg_create(cgroup[0]))
>> +		goto cleanup;
>> +
>> +	if (cg_check_freezetime(cgroup[0]) < 0) {
>> +		ret = KSFT_SKIP;
>> +		goto cleanup;
>> +	}
>> +
>> +	if (cg_create(cgroup[1]))
>> +		goto cleanup;
>> +
>> +	pid = cg_run_nowait(cgroup[0], child_fn, NULL);
>> +	if (pid < 0)
>> +		goto cleanup;
>> +
>> +	if (cg_wait_for_proc_count(cgroup[0], 1))
>> +		goto cleanup;
>> +
>> +	curr_A = cg_check_freezetime(cgroup[0]);
>> +	if (curr_A) {
>> +		debug("Expect time (%ld) to be 0\n", curr_A);
>> +		goto cleanup;
>> +	}
>> +	curr_B = cg_check_freezetime(cgroup[1]);
>> +	if (curr_B) {
>> +		debug("Expect time (%ld) to be 0\n", curr_B);
>> +		goto cleanup;
>> +	}
>> +
>> +	/*
>> +	 * Freeze cgroup A.
>> +	 */
>> +	if (cg_freeze_wait(cgroup[0], true))
>> +		goto cleanup;
>> +	prev_A = curr_A;
>> +	curr_A = cg_check_freezetime(cgroup[0]);
>> +	if (curr_A <= prev_A) {
>> +		debug("Expect time (%ld) to be > 0\n", curr_A);
>> +		goto cleanup;
>> +	}
>> +
>> +	/*
>> +	 * Migrate from A (frozen) to B (running).
>> +	 */
>> +	if (cg_enter(cgroup[1], pid))
>> +		goto cleanup;
>> +
>> +	usleep(1000);
>> +	curr_B = cg_check_freezetime(cgroup[1]);
>> +	if (curr_B) {
>> +		debug("Expect time (%ld) to be 0\n", curr_B);
>> +		goto cleanup;
>> +	}
>> +
>> +	prev_A = curr_A;
>> +	curr_A = cg_check_freezetime(cgroup[0]);
>> +	if (curr_A <= prev_A) {
>> +		debug("Expect time (%ld) to be more than previous check (%ld)\n",
>> +		      curr_A, prev_A);
>> +		goto cleanup;
>> +	}
>> +
>> +	ret = KSFT_PASS;
>> +
>> +cleanup:
>> +	if (cgroup[0])
>> +		cg_destroy(cgroup[0]);
>> +	free(cgroup[0]);
>> +	if (cgroup[1])
>> +		cg_destroy(cgroup[1]);
>> +	free(cgroup[1]);
>> +	return ret;
>> +}
>> +
>> +/*
>> + * The test creates a cgroup and freezes it. Then it creates a child  
>> cgroup.
>> + * After that it checks that the child cgroup has a non-zero freeze time
>> + * that is less than the parent's. Next, it freezes the child, unfreezes
>> + * the parent, and sleeps. Finally, it checks that the child's freeze
>> + * time has grown larger than the parent's.
>> + */
>> +static int test_cgfreezer_time_parent(const char *root)
>> +{
>> +	char *parent, *child = NULL;
>> +	int ret = KSFT_FAIL;
>> +	long ptime, ctime;
>> +
>> +	parent = cg_name(root, "cg_test_parent_A");
>> +	if (!parent)
>> +		goto cleanup;
>> +
>> +	child = cg_name(parent, "cg_test_parent_B");
>> +	if (!child)
>> +		goto cleanup;
>> +
>> +	if (cg_create(parent))
>> +		goto cleanup;
>> +
>> +	if (cg_check_freezetime(parent) < 0) {
>> +		ret = KSFT_SKIP;
>> +		goto cleanup;
>> +	}
>> +
>> +	if (cg_freeze_wait(parent, true))
>> +		goto cleanup;
>> +
>> +	usleep(1000);
>> +	if (cg_create(child))
>> +		goto cleanup;
>> +
>> +	if (cg_check_frozen(child, true))
>> +		goto cleanup;
>> +
>> +	/*
>> +	 * Since the parent was frozen the entire time the child cgroup
>> +	 * was being created, we expect the parent's freeze time to be
>> +	 * larger than the child's.
>> +	 *
>> +	 * Ideally, we would be able to check both times simultaneously,
>> +	 * but here we get the child's after we get the parent's.
>> +	 */
>> +	ptime = cg_check_freezetime(parent);
>> +	ctime = cg_check_freezetime(child);
>> +	if (ptime <= ctime) {
>> +		debug("Expect ptime (%ld) > ctime (%ld)\n", ptime, ctime);
>> +		goto cleanup;
>> +	}
>> +
>> +	if (cg_freeze_nowait(child, true))
>> +		goto cleanup;
>> +
>> +	if (cg_freeze_wait(parent, false))
>> +		goto cleanup;
>> +
>> +	if (cg_check_frozen(child, true))
>> +		goto cleanup;
>> +
>> +	usleep(100000);
>> +
>> +	ctime = cg_check_freezetime(child);
>> +	ptime = cg_check_freezetime(parent);
>> +
>> +	if (ctime <= ptime) {
>> +		debug("Expect ctime (%ld) > ptime (%ld)\n", ctime, ptime);
>> +		goto cleanup;
>> +	}
>> +
>> +	ret = KSFT_PASS;
>> +
>> +cleanup:
>> +	if (child)
>> +		cg_destroy(child);
>> +	free(child);
>> +	if (parent)
>> +		cg_destroy(parent);
>> +	free(parent);
>> +	return ret;
>> +}
>> +
>> +/*
>> + * The test creates a parent cgroup and a child cgroup. Then, it freezes
>> + * the child and checks that the child's freeze time is greater than the
>> + * parent's, which should be zero.
>> + */
>> +static int test_cgfreezer_time_child(const char *root)
>> +{
>> +	char *parent, *child = NULL;
>> +	int ret = KSFT_FAIL;
>> +	long ptime, ctime;
>> +
>> +	parent = cg_name(root, "cg_test_child_A");
>> +	if (!parent)
>> +		goto cleanup;
>> +
>> +	child = cg_name(parent, "cg_test_child_B");
>> +	if (!child)
>> +		goto cleanup;
>> +
>> +	if (cg_create(parent))
>> +		goto cleanup;
>> +
>> +	if (cg_check_freezetime(parent) < 0) {
>> +		ret = KSFT_SKIP;
>> +		goto cleanup;
>> +	}
>> +
>> +	if (cg_create(child))
>> +		goto cleanup;
>> +
>> +	if (cg_freeze_wait(child, true))
>> +		goto cleanup;
>> +
>> +	ctime = cg_check_freezetime(child);
>> +	ptime = cg_check_freezetime(parent);
>> +	if (ptime != 0) {
>> +		debug("Expect ptime (%ld) to be 0\n", ptime);
>> +		goto cleanup;
>> +	}
>> +
>> +	if (ctime <= ptime) {
>> +		debug("Expect ctime (%ld) <= ptime (%ld)\n", ctime, ptime);
>> +		goto cleanup;
>> +	}
>> +
>> +	ret = KSFT_PASS;
>> +
>> +cleanup:
>> +	if (child)
>> +		cg_destroy(child);
>> +	free(child);
>> +	if (parent)
>> +		cg_destroy(parent);
>> +	free(parent);
>> +	return ret;
>> +}
>> +
>> +/*
>> + * The test creates the following hierarchy:
>> + *    A
>> + *    |
>> + *    B
>> + *    |
>> + *    C
>> + *
>> + * Then it freezes the cgroups in the order C, B, A.
>> + * Then it unfreezes the cgroups in the order A, B, C.
>> + * Then it checks that C's freeze time is larger than B's and
>> + * that B's is larger than A's.
>> + */
>> +static int test_cgfreezer_time_nested(const char *root)
>> +{
>> +	char *cgroup[3] = {0};
>> +	int ret = KSFT_FAIL;
>> +	long time[3] = {0};
>> +	int i;
>> +
>> +	cgroup[0] = cg_name(root, "cg_test_time_A");
>> +	if (!cgroup[0])
>> +		goto cleanup;
>> +
>> +	cgroup[1] = cg_name(cgroup[0], "B");
>> +	if (!cgroup[1])
>> +		goto cleanup;
>> +
>> +	cgroup[2] = cg_name(cgroup[1], "C");
>> +	if (!cgroup[2])
>> +		goto cleanup;
>> +
>> +	if (cg_create(cgroup[0]))
>> +		goto cleanup;
>> +
>> +	if (cg_check_freezetime(cgroup[0]) < 0) {
>> +		ret = KSFT_SKIP;
>> +		goto cleanup;
>> +	}
>> +
>> +	if (cg_create(cgroup[1]))
>> +		goto cleanup;
>> +
>> +	if (cg_create(cgroup[2]))
>> +		goto cleanup;
>> +
>> +	if (cg_freeze_nowait(cgroup[2], true))
>> +		goto cleanup;
>> +
>> +	if (cg_freeze_nowait(cgroup[1], true))
>> +		goto cleanup;
>> +
>> +	if (cg_freeze_nowait(cgroup[0], true))
>> +		goto cleanup;
>> +
>> +	usleep(1000);
>> +
>> +	if (cg_freeze_nowait(cgroup[0], false))
>> +		goto cleanup;
>> +
>> +	if (cg_freeze_nowait(cgroup[1], false))
>> +		goto cleanup;
>> +
>> +	if (cg_freeze_nowait(cgroup[2], false))
>> +		goto cleanup;
>> +
>> +	time[2] = cg_check_freezetime(cgroup[2]);
>> +	time[1] = cg_check_freezetime(cgroup[1]);
>> +	time[0] = cg_check_freezetime(cgroup[0]);
>> +
>> +	if (time[2] <= time[1]) {
>> +		debug("Expect C's time (%ld) > B's time (%ld)", time[2], time[1]);
>> +		goto cleanup;
>> +	}
>> +
>> +	if (time[1] <= time[0]) {
>> +		debug("Expect B's time (%ld) > A's time (%ld)", time[1], time[0]);
>> +		goto cleanup;
>> +	}
>> +
>> +	ret = KSFT_PASS;
>> +
>> +cleanup:
>> +	for (i = 2; i >= 0 && cgroup[i]; i--) {
>> +		cg_destroy(cgroup[i]);
>> +		free(cgroup[i]);
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>>   #define T(x) { x, #x }
>>   struct cgfreezer_test {
>>   	int (*fn)(const char *root);
>> @@ -819,6 +1475,13 @@ struct cgfreezer_test {
>>   	T(test_cgfreezer_stopped),
>>   	T(test_cgfreezer_ptraced),
>>   	T(test_cgfreezer_vfork),
>> +	T(test_cgfreezer_time_empty),
>> +	T(test_cgfreezer_time_simple),
>> +	T(test_cgfreezer_time_populate),
>> +	T(test_cgfreezer_time_migrate),
>> +	T(test_cgfreezer_time_parent),
>> +	T(test_cgfreezer_time_child),
>> +	T(test_cgfreezer_time_nested),
>>   };
>>   #undef T


-- 
Tiffany Y. Yang
Re: [PATCH v4 2/2] cgroup: selftests: Add tests for freezer time
Posted by Chen Ridong 1 month, 1 week ago

On 2025/8/23 2:50, Tiffany Yang wrote:
>> Perhaps we can simply use if (curr != 0) for the condition?
> 
> 
> Here we have 2 separate conditions because in the case where curr < 0,
> it means that the interface is not available and we should skip this
> test instead of failing it. In the case where curr > 0, the feature is
> not working correctly, and the test should fail as a result.

Thank you for your explanation.
See now.

-- 
Best regards,
Ridong