> diff --git a/tools/testing/selftests/bpf/prog_tests/memcg_stat_churn.c 
> b/tools/testing/selftests/bpf/prog_tests/memcg_stat_churn.c
> new file mode 100644
> index 000000000000..3e386d0b4c03
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/memcg_stat_churn.c

[ ... ]

> +static long long now_ns(void)
> +{
> +     struct timespec t;
> +
> +     clock_gettime(CLOCK_MONOTONIC, &t);
> +     return (long long)t.tv_sec * 1000000000LL + t.tv_nsec;
> +}
> +
> +/* ---- tree construction (same shape as memcg_stat_reader) --------------- 
> */
> +
> +static struct cg_node *nodes;
> +static int n_nodes;
> +static int n_leaves;
> +
> +static int add_node(const char *rel, bool is_leaf, int *keep_fd)
> +{
> +     int fd;
> +
> +     fd = create_and_get_cgroup(rel);
> +     if (fd < 0)
> +             return -1;
> +     if (keep_fd)
> +             *keep_fd = fd;
> +     else
> +             close(fd);
> +
> +     strncpy(nodes[n_nodes].rel, rel, sizeof(nodes[n_nodes].rel) - 1);
> +     nodes[n_nodes].rel[sizeof(nodes[n_nodes].rel) - 1] = '\0';
> +     nodes[n_nodes].id = get_cgroup_id(rel);
> +     nodes[n_nodes].is_leaf = is_leaf;
> +     if (is_leaf)
> +             n_leaves++;
> +     n_nodes++;
> +     return 0;
> +}
> +
> +/* Recursively create children of @rel. @rel must already exist and be 
> recorded. */
> +static int build_children(const char *rel, int fanout, int depth)
> +{
> +     char child[128];
> +     int i;
> +
> +     if (depth == 0)
> +             return 0;
> +
> +     /* Enable memory on this interior node so its children get a memcg. */
> +     if (enable_controllers(rel, "memory"))
> +             return -1;
> +
> +     for (i = 0; i < fanout; i++) {
> +             snprintf(child, sizeof(child), "%s/c%d", rel, i);
> +             if (add_node(child, depth == 1, NULL))
> +                     return -1;
> +             if (build_children(child, fanout, depth - 1))
> +                     return -1;
> +     }
> +     return 0;
> +}
> +
> +static size_t tree_capacity(int fanout, int depth)
> +{
> +     size_t total = 1, level = 1;
> +     int d;
> +
> +     for (d = 0; d < depth; d++) {
> +             level *= fanout;
> +             total += level;
> +     }
> +     return total;
> +}
> +
> +static int build_tree(int fanout, int depth, int *root_fd)
> +{
> +     n_nodes = 0;
> +     n_leaves = 0;
> +     nodes = calloc(tree_capacity(fanout, depth), sizeof(*nodes));
> +     if (!nodes)
> +             return -1;
> +
> +     if (add_node(SUBTREE_ROOT, depth == 0, root_fd))
> +             return -1;
> +     return build_children(SUBTREE_ROOT, fanout, depth);
> +}

[ ... ]

> +static void parse_stat(char *buf, struct file_snap *o)
> +{
> +     char *save, *line;
> +
> +     for (line = strtok_r(buf, "\n", &save); line;
> +          line = strtok_r(NULL, "\n", &save)) {
> +             unsigned long long val;
> +             char name[64];
> +
> +             if (sscanf(line, "%63s %llu", name, &val) != 2)
> +                     continue;
> +             o->full_sum += val;
> +             o->full_fields++;
> +             if (!strcmp(name, "anon"))
> +                     o->anon = val;
> +             else if (!strcmp(name, "file"))
> +                     o->file = val;
> +             else if (!strcmp(name, "shmem"))
> +                     o->shmem = val;
> +             else if (!strcmp(name, "file_mapped"))
> +                     o->file_mapped = val;
> +             else if (!strcmp(name, "pgfault"))
> +                     o->pgfault = val;
> +     }
> +}
> +
> +static int file_read_node(const char *rel, struct file_snap *o)
> +{
> +     char buf[8192];
> +
> +     memset(o, 0, sizeof(*o));
> +
> +     if (read_cgroup_file(rel, "memory.stat", buf, sizeof(buf)))
> +             return -1;
> +     parse_stat(buf, o);
> +
> +     if (!read_cgroup_file(rel, "memory.current", buf, sizeof(buf)))
> +             o->current = strtoull(buf, NULL, 10);
> +     if (!read_cgroup_file(rel, "memory.max", buf, sizeof(buf))) {
> +             if (!strncmp(buf, "max", 3))
> +                     o->max_is_max = true;
> +             else
> +                     o->max = strtoull(buf, NULL, 10);
> +     }
> +     return 0;
> +}

[ ... ]

> +static int drain_map(int mfd, struct memcg_stat_snapshot *out, int max)
> +{
> +     __u64 key = 0, next;
> +     int n = 0, err;
> +
> +     err = bpf_map_get_next_key(mfd, NULL, &next);
> +     while (err == 0) {
> +             if (n < max && !bpf_map_lookup_elem(mfd, &next, &out[n])) {
> +                     sink += out[n].anon + out[n].full_sum;
> +                     n++;
> +             }
> +             key = next;
> +             err = bpf_map_get_next_key(mfd, &key, &next);
> +     }
> +     return n;
> +}

[ ... ]

> +static bool memcg_kfuncs_available(void)
> +{
> +     struct btf *btf;
> +     bool ok;
> +
> +     btf = btf__load_vmlinux_btf();
> +     if (!btf)
> +             return false;
> +     ok = btf__find_by_name_kind(btf, "bpf_get_mem_cgroup", BTF_KIND_FUNC) > 
> 0;
> +     btf__free(btf);
> +     return ok;
> +}

This isn't a bug, but several of these helpers appear nearly identical
to the ones in prog_tests/memcg_stat_reader.c -- tree_capacity(),
build_tree(), add_node(), build_children(), drain_map(), bpf_walk_once(),
file_read_node(), parse_stat(), now_ns(), and memcg_kfuncs_available().

The commit message notes the BPF program, map, and snapshot struct are
reused, but the userspace tree-construction and file-reader helpers are
duplicated rather than shared. Could the common pieces move into a small
shared header so the two tests don't drift apart over time?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/28695985027

Reply via email to