[LTP] [PATCH v6] memcg/memcontrol05: add cgroup v2 task migration charge accounting test
Cyril Hrubis
chrubis@suse.cz
Fri Aug 28 16:32:44 CEST 2026
Hi!
> +enum checkpoints {
> + WORKER_ALLOC_DONE,
> + WORKER_RESUME,
> + WORKER_ALLOC2_DONE,
> + WORKER_EXIT,
> +};
> +
> +static struct tst_cg_group *group_a;
> +static struct tst_cg_group *group_b;
> +static pid_t worker_pid;
> +
> +enum worker_stage {
> + STAGE_NOT_STARTED,
> + STAGE_WAITING_RESUME, /* worker blocked on WORKER_RESUME */
> + STAGE_WAITING_EXIT, /* worker blocked on WORKER_EXIT */
> + STAGE_DONE, /* worker reaped */
> +};
First of all since we serialize only two processes, all that is needed
is single checkpoint.
> +static enum worker_stage worker_stage;
> +
> +static void touch_pages(char *buf, size_t size)
> +{
> + size_t i;
> +
> + for (i = 0; i < size; i += getpagesize())
> + buf[i] = 1;
> +}
> +
> +static void worker(void)
> +{
> + char *buf1, *buf2;
> +
> + SAFE_CG_PRINTF(group_a, "cgroup.procs", "%d", getpid());
> +
> + buf1 = SAFE_MMAP(NULL, ALLOC_SIZE, PROT_READ | PROT_WRITE,
> + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> + touch_pages(buf1, ALLOC_SIZE);
> +
> + TST_CHECKPOINT_WAKE(WORKER_ALLOC_DONE);
> +
> + TST_CHECKPOINT_WAIT(WORKER_RESUME);
Should be just TST_CHECKPOINT_WAKE_AND_WAIT(0);
> + buf2 = SAFE_MMAP(NULL, ALLOC_SIZE2, PROT_READ | PROT_WRITE,
> + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> + touch_pages(buf2, ALLOC_SIZE2);
> +
> + TST_CHECKPOINT_WAKE(WORKER_ALLOC2_DONE);
> +
> + TST_CHECKPOINT_WAIT(WORKER_EXIT);
Here as well.
> + SAFE_MUNMAP(buf1, ALLOC_SIZE);
> + SAFE_MUNMAP(buf2, ALLOC_SIZE2);
> +}
With the checkpoints unified to a single checkpoint we do not need to
track the worker_stage either.
> +static void test_memcg_task_migration(void)
> +{
> + long baseline_b, after_migrate, after_migrate_a, after_alloc2, current_a;
> +
> + worker_pid = 0;
> + worker_stage = STAGE_NOT_STARTED;
> +
> + group_a = tst_cg_group_mk(tst_cg, "group_a");
> + group_b = tst_cg_group_mk(tst_cg, "group_b");
> +
> + if (SAFE_CG_HAS(tst_cg, "memory.swap.max")) {
> + SAFE_CG_PRINT(group_a, "memory.swap.max", "0");
> + SAFE_CG_PRINT(group_b, "memory.swap.max", "0");
> + }
> +
> + SAFE_CG_SCANF(group_b, "memory.current", "%ld", &baseline_b);
> + tst_res(TINFO, "group_b baseline memory.current=%ld", baseline_b);
> +
> + worker_pid = SAFE_FORK();
> + if (!worker_pid) {
> + worker();
> + exit(0);
> + }
> + worker_stage = STAGE_WAITING_RESUME;
> +
> + TST_CHECKPOINT_WAIT(WORKER_ALLOC_DONE);
> +
> + SAFE_CG_SCANF(group_a, "memory.current", "%ld", ¤t_a);
> + tst_res(TINFO, "group_a memory.current=%ld after alloc", current_a);
> + if (current_a < (long)ALLOC_SIZE) {
> + tst_res(TFAIL,
> + "group_a memory.current (%ld) < ALLOC_SIZE (%ld)",
> + current_a, (long)ALLOC_SIZE);
> + goto done;
> + }
> + tst_res(TPASS,
> + "group_a memory.current (%ld) >= ALLOC_SIZE (%ld)",
> + current_a, (long)ALLOC_SIZE);
This whole block should be TST_EXP_LE_LU()
> + SAFE_CG_PRINTF(group_b, "cgroup.procs", "%d", worker_pid);
> + tst_res(TINFO, "Migrated worker PID %d to group_b", worker_pid);
> +
> + SAFE_CG_SCANF(group_b, "memory.current", "%ld", &after_migrate);
> +
> + tst_res(TINFO,
> + "group_b memory.current=%ld after migration (baseline=%ld)",
> + after_migrate, baseline_b);
> +
> + TST_EXP_EXPR(after_migrate <= baseline_b + (long)MB(4),
> + "group_b memory.current (%ld) not increased after migration (baseline=%ld)",
> + after_migrate, baseline_b);
Where did the MB(4) came from? Magic constants like that surely wouldn't
work universally.
> + SAFE_CG_SCANF(group_a, "memory.current", "%ld", &after_migrate_a);
> + tst_res(TINFO, "group_a memory.current=%ld after migration", after_migrate_a);
> + TST_EXP_EXPR(after_migrate_a >= (long)ALLOC_SIZE,
> + "group_a memory.current (%ld) still holds pre-migration charges (>= ALLOC_SIZE %ld)",
> + after_migrate_a, (long)ALLOC_SIZE);
> +
> + TST_CHECKPOINT_WAKE(WORKER_RESUME);
> + worker_stage = STAGE_WAITING_EXIT;
> + TST_CHECKPOINT_WAIT(WORKER_ALLOC2_DONE);
Here as well should be just TST_CHECKPOINT_WAKE_AND_WAIT(0);
> + SAFE_CG_SCANF(group_b, "memory.current", "%ld", &after_alloc2);
> + tst_res(TINFO, "group_b memory.current=%ld after second alloc (baseline=%ld)",
> + after_alloc2, after_migrate);
> +
> + TST_EXP_EXPR(after_alloc2 >= after_migrate + (long)ALLOC_SIZE2,
> + "group_b memory.current (%ld) increased by >= ALLOC_SIZE2 (%ld)",
> + after_alloc2, (long)ALLOC_SIZE2);
> +
> +done:
> + if (worker_stage == STAGE_WAITING_RESUME) {
> + TST_CHECKPOINT_WAKE(WORKER_RESUME);
> + worker_stage = STAGE_WAITING_EXIT;
> + TST_CHECKPOINT_WAIT(WORKER_ALLOC2_DONE);
> + }
> + if (worker_stage == STAGE_WAITING_EXIT) {
> + TST_CHECKPOINT_WAKE(WORKER_EXIT);
> + tst_reap_children();
> + worker_pid = 0;
> + worker_stage = STAGE_DONE;
> + }
I would be way easier to SAFE_KILL() and SAFE_WAITPID() the worker
process. And the same in the test setup.
> + group_a = tst_cg_group_rm(group_a);
> + group_b = tst_cg_group_rm(group_b);
> +}
> +
> +static void cleanup(void)
> +{
> + if (worker_stage == STAGE_WAITING_RESUME) {
> + TST_CHECKPOINT_WAKE(WORKER_RESUME);
> + worker_stage = STAGE_WAITING_EXIT;
> + TST_CHECKPOINT_WAIT(WORKER_ALLOC2_DONE);
> + }
> + if (worker_stage == STAGE_WAITING_EXIT) {
> + TST_CHECKPOINT_WAKE(WORKER_EXIT);
> + tst_reap_children();
> + }
> +
> + if (group_a)
> + group_a = tst_cg_group_rm(group_a);
> + if (group_b)
> + group_b = tst_cg_group_rm(group_b);
> +}
> +
> +static struct tst_test test = {
> + .test_all = test_memcg_task_migration,
> + .cleanup = cleanup,
> + .forks_child = 1,
> + .needs_root = 1,
> + .needs_checkpoints = 1,
> + .needs_cgroup_ver = TST_CG_V2,
> + .needs_cgroup_ctrls = (const char *const []){ "memory", NULL },
> + .min_mem_avail = MIN_MEM_AVAIL,
> +};
> --
> 2.55.0
>
>
> --
> Mailing list info: https://lists.linux.it/listinfo/ltp
--
Cyril Hrubis
chrubis@suse.cz
More information about the ltp
mailing list