[LTP] [PATCH v3 5/5] cgroup: Add memcontrol02
Richard Palethorpe
rpalethorpe@suse.de
Tue Jan 4 16:26:02 CET 2022
Hello,
Cyril Hrubis <chrubis@suse.cz> writes:
>> + *
>> + * [Description]
>> + *
>> + * Conversion of second kself test in cgroup/test_memcontrol.c.
>> + *
>> + * Original description:
>> + * "This test creates a memory cgroup, allocates some anonymous memory
>> + * and some pagecache and check memory.current and some memory.stat
>> + * values."
>> + *
>> + * Note that the V1 rss and cache counters were renamed to anon and
>> + * file in V2. Besides error reporting, this test differs from the
>> + * kselftest in the following ways:
>> + *
>> + * . It supports V1.
>> + * . It writes instead of reads to fill the page cache. Because no
>> + * pages were allocated on tmpfs.
>
> Shouldn't we actually run the test both for read/write and skip the read
> part of tmpfs?
>
> Well I guess that the pages are put into the page cache the same way
> regardless if they came from userspace write or as a request for data to
> be read from the disk, so probably not I guess.
I reckon there are a lot of ways to fill the page cache from
userland. mmap and madvise also come to mind. I don't know how many ways
there are to get/allocate a page from the page cache internally. I guess
it's possible to circumvent the accountancy.
I think for now just writing is good enough. This doesn't appear to be
the only test which measures the page cache. So I think we should look
at what the other tests do first.
>
>> + * . It runs on most filesystems available
>> + * . On EXFAT and extN we change the margine of error between all and file
> ^
> margin
>> + * memory to 50%. Because these allocate non-page-cache memory during writes.
>> + */
>> +#define _GNU_SOURCE
>> +
>> +#include <stdlib.h>
>> +#include <stdio.h>
>
> Do we really need stdio here?
Sorry, nope.
>
>> +#include "tst_test.h"
>> +#include "tst_cgroup.h"
>> +
>> +#define TMPDIR "mntdir"
>> +#define MB(x) (x << 20)
>> +
>> +static size_t page_size;
>> +static const struct tst_cgroup_group *cg_test;
>> +static int is_v1_memcg;
>> +static struct tst_cgroup_group *cg_child;
>> +static int fd;
>> +static int file_to_all_error = 10;
>> +
>> +/*
>> + * Checks if two given values differ by less than err% of their sum.
>> + */
>> +static inline int values_close(const ssize_t a,
>> + const ssize_t b,
>> + const ssize_t err)
>> +{
>> + return labs(a - b) <= (a + b) / 100 * err;
>> +}
>
> I guess that this crude integer version works only since we allocate
> reasonable large memory sizes.
I suppose I could multiply both sides of the inequality by
100... perhaps we could even use the FPU, I hear it's quite usable in
userland. :-p
>
>> +static void alloc_anon_50M_check(void)
>> +{
>> + const ssize_t size = MB(50);
>> + char *buf, *ptr;
>> + ssize_t anon, current;
>> + const char *const anon_key_fmt = is_v1_memcg ? "rss %zd" : "anon %zd";
>> +
>> + buf = SAFE_MALLOC(size);
>> + for (ptr = buf; ptr < buf + size; ptr += page_size)
>> + *ptr = 0;
>> +
>> + SAFE_CGROUP_SCANF(cg_child, "memory.current", "%zd", ¤t);
>> + TST_EXP_EXPR(current >= size,
>> + "(memory.current=%zd) >= (size=%zd)", current, size);
>> +
>> + SAFE_CGROUP_LINES_SCANF(cg_child, "memory.stat", anon_key_fmt, &anon);
>> +
>> + TST_EXP_EXPR(anon > 0, "(memory.stat.anon=%zd) > 0", anon);
>> + TST_EXP_EXPR(values_close(size, current, 3),
>> + "(size=%zd) ~= (memory.stat.anon=%zd)", size, current);
>> + TST_EXP_EXPR(values_close(anon, current, 3),
>> + "(memory.current=%zd) ~= (memory.stat.anon=%zd)",
>> + current, anon);
>> +}
>> +
>> +static void alloc_pagecache(const int fd, size_t size)
>> +{
>> + char buf[BUFSIZ];
>> + size_t i;
>
> We may as well fill the buffer with something so that this function will
> not trigger static analyzers.
+1
>
>> + for (i = 0; i < size; i += sizeof(buf))
>> + SAFE_WRITE(1, fd, buf, sizeof(buf));
>> +}
>> +
>> +static void alloc_pagecache_50M_check(void)
>> +{
>> + const size_t size = MB(50);
>> + size_t current, file;
>> + const char *const file_key_fmt = is_v1_memcg ? "cache %zd" : "file %zd";
>
> This may be a global initialized in the test setup.
Is it not easier to read when set in the context it is used? Otherwise
the key name is hidden in setup.
>
>> + TEST(open(TMPDIR"/tmpfile", O_RDWR | O_CREAT, 0600));
>> +
>> + if (TST_RET < 0) {
>> + if (TST_ERR == EOPNOTSUPP)
>> + tst_brk(TCONF, "O_TMPFILE not supported by FS");
>> +
>> + tst_brk(TBROK | TTERRNO,
>> + "open(%s, O_TMPFILE | O_RDWR | O_EXCL", TMPDIR"/.");
>> + }
>> + fd = TST_RET;
>> +
>> + SAFE_CGROUP_SCANF(cg_child, "memory.current", "%zu", ¤t);
>> + tst_res(TINFO, "Created temp file: memory.current=%zu", current);
>> +
>> + alloc_pagecache(fd, size);
>> +
>> + SAFE_CGROUP_SCANF(cg_child, "memory.current", "%zu", ¤t);
>> + TST_EXP_EXPR(current >= size,
>> + "(memory.current=%zu) >= (size=%zu)", current, size);
>> +
>> + SAFE_CGROUP_LINES_SCANF(cg_child, "memory.stat", file_key_fmt, &file);
>> + TST_EXP_EXPR(file > 0, "(memory.stat.file=%zd) > 0", file);
>> +
>> + TST_EXP_EXPR(values_close(file, current, file_to_all_error),
>> + "(memory.current=%zd) ~= (memory.stat.file=%zd)",
>> + current, file);
>> +
>> + SAFE_CLOSE(fd);
>> +}
>> +
>> +static void test_memcg_current(unsigned int n)
>> +{
>> + size_t current;
>> +
>> + cg_child = tst_cgroup_group_mk(cg_test, "child");
>> + SAFE_CGROUP_SCANF(cg_child, "memory.current", "%zu", ¤t);
>> + TST_EXP_EXPR(current == 0, "(current=%zu) == 0", current);
>> +
>> + if (!SAFE_FORK()) {
>> + SAFE_CGROUP_PRINTF(cg_child, "cgroup.procs", "%d", getpid());
>> +
>> + SAFE_CGROUP_SCANF(cg_child, "memory.current", "%zu", ¤t);
>> + tst_res(TINFO, "Added proc to memcg: memory.current=%zu",
>> + current);
>> +
>> + if (!n)
>> + alloc_anon_50M_check();
>> + else
>> + alloc_pagecache_50M_check();
>> + } else {
>> + tst_reap_children();
>> + cg_child = tst_cgroup_group_rm(cg_child);
>> + }
>> +}
>> +
>> +static void setup(void)
>> +{
>> + page_size = SAFE_SYSCONF(_SC_PAGESIZE);
>> +
>> + tst_cgroup_require("memory", NULL);
>> + cg_test = tst_cgroup_get_test_group();
>> +
>> + is_v1_memcg = TST_CGROUP_VER(cg_test, "memory") == TST_CGROUP_V1;
>
> I find this statement a bit confusing, maybe TST_CGGROUP_IS_V1() and
> TST_CGROUP_IS_V2() macros in the library would make it slightly
> better.
Yeah, I suppose this is proving to be tiresome.
>
>> + switch (tst_fs_type(TMPDIR)) {
>> + case TST_EXFAT_MAGIC:
>> + case TST_EXT234_MAGIC:
>> + file_to_all_error = 50;
>> + break;
>> + }
>> +}
>> +
>> +static void cleanup(void)
>> +{
>> + if (cg_child)
>> + cg_child = tst_cgroup_group_rm(cg_child);
>> +
>> + tst_cgroup_cleanup();
>> +}
>> +
>> +static struct tst_test test = {
>> + .setup = setup,
>> + .cleanup = cleanup,
>> + .tcnt = 2,
>> + .test = test_memcg_current,
>> + .mount_device = 1,
>> + .dev_min_size = 256,
>> + .mntpoint = TMPDIR,
>> + .all_filesystems = 1,
>> + .forks_child = 1,
>> + .needs_root = 1,
>> +};
>
> Generally minus typos and minor things it looks good, I guess that it
> can as well go in if you fix the typos as it is.
>
> Reviewed-by: Cyril Hrubis <chrubis@susec.cz>
--
Thank you,
Richard.
More information about the ltp
mailing list