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
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
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
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
© 2016 - 2025 Red Hat, Inc.