summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/cgroup/test_memcontrol.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/testing/selftests/cgroup/test_memcontrol.c')
-rw-r--r--tools/testing/selftests/cgroup/test_memcontrol.c252
1 files changed, 240 insertions, 12 deletions
diff --git a/tools/testing/selftests/cgroup/test_memcontrol.c b/tools/testing/selftests/cgroup/test_memcontrol.c
index 16f5d74ae762..b43da9bc20c4 100644
--- a/tools/testing/selftests/cgroup/test_memcontrol.c
+++ b/tools/testing/selftests/cgroup/test_memcontrol.c
@@ -10,6 +10,7 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
+#include <sys/inotify.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
@@ -18,12 +19,92 @@
#include <errno.h>
#include <sys/mman.h>
-#include "../kselftest.h"
+#include "kselftest.h"
#include "cgroup_util.h"
+#define MEMCG_SOCKSTAT_WAIT_RETRIES 30
+
static bool has_localevents;
static bool has_recursiveprot;
+int get_temp_fd(void)
+{
+ return open(".", O_TMPFILE | O_RDWR | O_EXCL);
+}
+
+int alloc_pagecache(int fd, size_t size)
+{
+ char buf[PAGE_SIZE];
+ struct stat st;
+ int i;
+
+ if (fstat(fd, &st))
+ goto cleanup;
+
+ size += st.st_size;
+
+ if (ftruncate(fd, size))
+ goto cleanup;
+
+ for (i = 0; i < size; i += sizeof(buf))
+ read(fd, buf, sizeof(buf));
+
+ return 0;
+
+cleanup:
+ return -1;
+}
+
+int alloc_anon(const char *cgroup, void *arg)
+{
+ size_t size = (unsigned long)arg;
+ char *buf, *ptr;
+
+ buf = malloc(size);
+ for (ptr = buf; ptr < buf + size; ptr += PAGE_SIZE)
+ *ptr = 0;
+
+ free(buf);
+ return 0;
+}
+
+int is_swap_enabled(void)
+{
+ char buf[PAGE_SIZE];
+ const char delim[] = "\n";
+ int cnt = 0;
+ char *line;
+
+ if (read_text("/proc/swaps", buf, sizeof(buf)) <= 0)
+ return -1;
+
+ for (line = strtok(buf, delim); line; line = strtok(NULL, delim))
+ cnt++;
+
+ return cnt > 1;
+}
+
+int set_oom_adj_score(int pid, int score)
+{
+ char path[PATH_MAX];
+ int fd, len;
+
+ sprintf(path, "/proc/%d/oom_score_adj", pid);
+
+ fd = open(path, O_WRONLY | O_APPEND);
+ if (fd < 0)
+ return fd;
+
+ len = dprintf(fd, "%d", score);
+ if (len < 0) {
+ close(fd);
+ return len;
+ }
+
+ close(fd);
+ return 0;
+}
+
/*
* This test creates two nested cgroups with and without enabling
* the memory controller.
@@ -380,10 +461,11 @@ static bool reclaim_until(const char *memcg, long goal);
*
* Then it checks actual memory usages and expects that:
* A/B memory.current ~= 50M
- * A/B/C memory.current ~= 29M
- * A/B/D memory.current ~= 21M
- * A/B/E memory.current ~= 0
- * A/B/F memory.current = 0
+ * A/B/C memory.current ~= 29M [memory.events:low > 0]
+ * A/B/D memory.current ~= 21M [memory.events:low > 0]
+ * A/B/E memory.current ~= 0 [memory.events:low == 0 if !memory_recursiveprot,
+ * undefined otherwise]
+ * A/B/F memory.current = 0 [memory.events:low == 0]
* (for origin of the numbers, see model in memcg_protection.m.)
*
* After that it tries to allocate more than there is
@@ -495,10 +577,10 @@ static int test_memcg_protection(const char *root, bool min)
for (i = 0; i < ARRAY_SIZE(children); i++)
c[i] = cg_read_long(children[i], "memory.current");
- if (!values_close(c[0], MB(29), 10))
+ if (!values_close(c[0], MB(29), 15))
goto cleanup;
- if (!values_close(c[1], MB(21), 10))
+ if (!values_close(c[1], MB(21), 20))
goto cleanup;
if (c[3] != 0)
@@ -525,7 +607,14 @@ static int test_memcg_protection(const char *root, bool min)
goto cleanup;
}
+ /*
+ * Child 2 has memory.low=0, but some low protection may still be
+ * distributed down from its parent with memory.low=50M if cgroup2
+ * memory_recursiveprot mount option is enabled. Ignore the low
+ * event count in this case.
+ */
for (i = 0; i < ARRAY_SIZE(children); i++) {
+ int ignore_low_events_index = has_recursiveprot ? 2 : -1;
int no_low_events_index = 1;
long low, oom;
@@ -534,6 +623,8 @@ static int test_memcg_protection(const char *root, bool min)
if (oom)
goto cleanup;
+ if (i == ignore_low_events_index)
+ continue;
if (i <= no_low_events_index && low <= 0)
goto cleanup;
if (i > no_low_events_index && low)
@@ -1190,8 +1281,11 @@ static int tcp_server(const char *cgroup, void *arg)
saddr.sin6_port = htons(srv_args->port);
sk = socket(AF_INET6, SOCK_STREAM, 0);
- if (sk < 0)
+ if (sk < 0) {
+ /* Pass back errno to the ctl_fd */
+ write(ctl_fd, &errno, sizeof(errno));
return ret;
+ }
if (setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0)
goto cleanup;
@@ -1296,6 +1390,7 @@ static int test_memcg_sock(const char *root)
int bind_retries = 5, ret = KSFT_FAIL, pid, err;
unsigned short port;
char *memcg;
+ long sock_post = -1;
memcg = cg_name(root, "memcg_test");
if (!memcg)
@@ -1321,6 +1416,12 @@ static int test_memcg_sock(const char *root)
goto cleanup;
close(args.ctl[0]);
+ /* Skip if address family not supported by protocol */
+ if (err == EAFNOSUPPORT) {
+ ret = KSFT_SKIP;
+ goto cleanup;
+ }
+
if (!err)
break;
if (err != EADDRINUSE)
@@ -1344,7 +1445,22 @@ static int test_memcg_sock(const char *root)
if (cg_read_long(memcg, "memory.current") < 0)
goto cleanup;
- if (cg_read_key_long(memcg, "memory.stat", "sock "))
+ /*
+ * memory.stat is updated asynchronously via the memcg rstat
+ * flushing worker, which runs periodically (every 2 seconds,
+ * see FLUSH_TIME). On a busy system, the "sock " counter may
+ * stay non-zero for a short period of time after the TCP
+ * connection is closed and all socket memory has been
+ * uncharged.
+ *
+ * Poll memory.stat for up to 3 seconds (~FLUSH_TIME plus some
+ * scheduling slack) and require that the "sock " counter
+ * eventually drops to zero.
+ */
+ sock_post = cg_read_key_long_poll(memcg, "memory.stat", "sock ", 0,
+ MEMCG_SOCKSTAT_WAIT_RETRIES,
+ DEFAULT_WAIT_INTERVAL_US);
+ if (sock_post)
goto cleanup;
ret = KSFT_PASS;
@@ -1537,6 +1653,115 @@ cleanup:
return ret;
}
+static int read_event(int inotify_fd, int expected_event, int expected_wd)
+{
+ struct inotify_event event;
+ ssize_t len = 0;
+
+ len = read(inotify_fd, &event, sizeof(event));
+ if (len < (ssize_t)sizeof(event))
+ return -1;
+
+ if (event.mask != expected_event || event.wd != expected_wd) {
+ fprintf(stderr,
+ "event does not match expected values: mask %d (expected %d) wd %d (expected %d)\n",
+ event.mask, expected_event, event.wd, expected_wd);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int test_memcg_inotify_delete_file(const char *root)
+{
+ int ret = KSFT_FAIL;
+ char *memcg = NULL;
+ int fd, wd;
+
+ memcg = cg_name(root, "memcg_test_0");
+
+ if (!memcg)
+ goto cleanup;
+
+ if (cg_create(memcg))
+ goto cleanup;
+
+ fd = inotify_init1(0);
+ if (fd == -1)
+ goto cleanup;
+
+ wd = inotify_add_watch(fd, cg_control(memcg, "memory.events"), IN_DELETE_SELF);
+ if (wd == -1)
+ goto cleanup;
+
+ if (cg_destroy(memcg))
+ goto cleanup;
+ free(memcg);
+ memcg = NULL;
+
+ if (read_event(fd, IN_DELETE_SELF, wd))
+ goto cleanup;
+
+ if (read_event(fd, IN_IGNORED, wd))
+ goto cleanup;
+
+ ret = KSFT_PASS;
+
+cleanup:
+ if (fd >= 0)
+ close(fd);
+ if (memcg)
+ cg_destroy(memcg);
+ free(memcg);
+
+ return ret;
+}
+
+static int test_memcg_inotify_delete_dir(const char *root)
+{
+ int ret = KSFT_FAIL;
+ char *memcg = NULL;
+ int fd, wd;
+
+ memcg = cg_name(root, "memcg_test_0");
+
+ if (!memcg)
+ goto cleanup;
+
+ if (cg_create(memcg))
+ goto cleanup;
+
+ fd = inotify_init1(0);
+ if (fd == -1)
+ goto cleanup;
+
+ wd = inotify_add_watch(fd, memcg, IN_DELETE_SELF);
+ if (wd == -1)
+ goto cleanup;
+
+ if (cg_destroy(memcg))
+ goto cleanup;
+ free(memcg);
+ memcg = NULL;
+
+ if (read_event(fd, IN_DELETE_SELF, wd))
+ goto cleanup;
+
+ if (read_event(fd, IN_IGNORED, wd))
+ goto cleanup;
+
+ ret = KSFT_PASS;
+
+cleanup:
+ if (fd >= 0)
+ close(fd);
+ if (memcg)
+ cg_destroy(memcg);
+ free(memcg);
+
+ return ret;
+}
+
#define T(x) { x, #x }
struct memcg_test {
int (*fn)(const char *root);
@@ -1556,14 +1781,18 @@ struct memcg_test {
T(test_memcg_oom_group_leaf_events),
T(test_memcg_oom_group_parent_events),
T(test_memcg_oom_group_score_events),
+ T(test_memcg_inotify_delete_file),
+ T(test_memcg_inotify_delete_dir),
};
#undef T
int main(int argc, char **argv)
{
char root[PATH_MAX];
- int i, proc_status, ret = EXIT_SUCCESS;
+ int i, proc_status;
+ ksft_print_header();
+ ksft_set_plan(ARRAY_SIZE(tests));
if (cg_find_unified_root(root, sizeof(root), NULL))
ksft_exit_skip("cgroup v2 isn't mounted\n");
@@ -1597,11 +1826,10 @@ int main(int argc, char **argv)
ksft_test_result_skip("%s\n", tests[i].name);
break;
default:
- ret = EXIT_FAILURE;
ksft_test_result_fail("%s\n", tests[i].name);
break;
}
}
- return ret;
+ ksft_finished();
}