Thanks for taking the time to look at these patches!

Chen Ridong <chenrid...@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

Reply via email to