[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <dbx8sehj8a25.fsf@ynaffit-andsys.c.googlers.com>
Date: Fri, 22 Aug 2025 11:50:58 -0700
From: Tiffany Yang <ynaffit@...gle.com>
To: Chen Ridong <chenridong@...weicloud.com>
Cc: linux-kernel@...r.kernel.org, John Stultz <jstultz@...gle.com>,
Thomas Gleixner <tglx@...utronix.de>, Stephen Boyd <sboyd@...nel.org>,
Anna-Maria Behnsen <anna-maria@...utronix.de>, Frederic Weisbecker <frederic@...nel.org>,
Tejun Heo <tj@...nel.org>, Johannes Weiner <hannes@...xchg.org>,
"Michal Koutný" <mkoutny@...e.com>, "Rafael J. Wysocki" <rafael@...nel.org>, Pavel Machek <pavel@...nel.org>,
Roman Gushchin <roman.gushchin@...ux.dev>, Chen Ridong <chenridong@...wei.com>,
kernel-team@...roid.com, Jonathan Corbet <corbet@....net>, Shuah Khan <shuah@...nel.org>,
cgroups@...r.kernel.org, linux-doc@...r.kernel.org,
linux-kselftest@...r.kernel.org
Subject: Re: [PATCH v4 2/2] cgroup: selftests: Add tests for freezer time
Thanks for taking the time to look at these patches!
Chen Ridong <chenridong@...weicloud.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
Powered by blists - more mailing lists