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