summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/cgroup/test_hugetlb_memcg.c
blob: 80d05d50a42db30a8eb39a42388f576f5ae7f516 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
// SPDX-License-Identifier: GPL-2.0
#include <linux/limits.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include "../kselftest.h"
#include "cgroup_util.h"

#define ADDR ((void *)(0x0UL))
#define FLAGS (MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB)
/* mapping 8 MBs == 4 hugepages */
#define LENGTH (8UL*1024*1024)
#define PROTECTION (PROT_READ | PROT_WRITE)

/* borrowed from mm/hmm-tests.c */
static long get_hugepage_size(void)
{
	int fd;
	char buf[2048];
	int len;
	char *p, *q, *path = "/proc/meminfo", *tag = "Hugepagesize:";
	long val;

	fd = open(path, O_RDONLY);
	if (fd < 0) {
		/* Error opening the file */
		return -1;
	}

	len = read(fd, buf, sizeof(buf));
	close(fd);
	if (len < 0) {
		/* Error in reading the file */
		return -1;
	}
	if (len == sizeof(buf)) {
		/* Error file is too large */
		return -1;
	}
	buf[len] = '\0';

	/* Search for a tag if provided */
	if (tag) {
		p = strstr(buf, tag);
		if (!p)
			return -1; /* looks like the line we want isn't there */
		p += strlen(tag);
	} else
		p = buf;

	val = strtol(p, &q, 0);
	if (*q != ' ') {
		/* Error parsing the file */
		return -1;
	}

	return val;
}

static int set_file(const char *path, long value)
{
	FILE *file;
	int ret;

	file = fopen(path, "w");
	if (!file)
		return -1;
	ret = fprintf(file, "%ld\n", value);
	fclose(file);
	return ret;
}

static int set_nr_hugepages(long value)
{
	return set_file("/proc/sys/vm/nr_hugepages", value);
}

static unsigned int check_first(char *addr)
{
	return *(unsigned int *)addr;
}

static void write_data(char *addr)
{
	unsigned long i;

	for (i = 0; i < LENGTH; i++)
		*(addr + i) = (char)i;
}

static int hugetlb_test_program(const char *cgroup, void *arg)
{
	char *test_group = (char *)arg;
	void *addr;
	long old_current, expected_current, current;
	int ret = EXIT_FAILURE;

	old_current = cg_read_long(test_group, "memory.current");
	set_nr_hugepages(20);
	current = cg_read_long(test_group, "memory.current");
	if (current - old_current >= MB(2)) {
		ksft_print_msg(
			"setting nr_hugepages should not increase hugepage usage.\n");
		ksft_print_msg("before: %ld, after: %ld\n", old_current, current);
		return EXIT_FAILURE;
	}

	addr = mmap(ADDR, LENGTH, PROTECTION, FLAGS, 0, 0);
	if (addr == MAP_FAILED) {
		ksft_print_msg("fail to mmap.\n");
		return EXIT_FAILURE;
	}
	current = cg_read_long(test_group, "memory.current");
	if (current - old_current >= MB(2)) {
		ksft_print_msg("mmap should not increase hugepage usage.\n");
		ksft_print_msg("before: %ld, after: %ld\n", old_current, current);
		goto out_failed_munmap;
	}
	old_current = current;

	/* read the first page */
	check_first(addr);
	expected_current = old_current + MB(2);
	current = cg_read_long(test_group, "memory.current");
	if (!values_close(expected_current, current, 5)) {
		ksft_print_msg("memory usage should increase by around 2MB.\n");
		ksft_print_msg(
			"expected memory: %ld, actual memory: %ld\n",
			expected_current, current);
		goto out_failed_munmap;
	}

	/* write to the whole range */
	write_data(addr);
	current = cg_read_long(test_group, "memory.current");
	expected_current = old_current + MB(8);
	if (!values_close(expected_current, current, 5)) {
		ksft_print_msg("memory usage should increase by around 8MB.\n");
		ksft_print_msg(
			"expected memory: %ld, actual memory: %ld\n",
			expected_current, current);
		goto out_failed_munmap;
	}

	/* unmap the whole range */
	munmap(addr, LENGTH);
	current = cg_read_long(test_group, "memory.current");
	expected_current = old_current;
	if (!values_close(expected_current, current, 5)) {
		ksft_print_msg("memory usage should go back down.\n");
		ksft_print_msg(
			"expected memory: %ld, actual memory: %ld\n",
			expected_current, current);
		return ret;
	}

	ret = EXIT_SUCCESS;
	return ret;

out_failed_munmap:
	munmap(addr, LENGTH);
	return ret;
}

static int test_hugetlb_memcg(char *root)
{
	int ret = KSFT_FAIL;
	char *test_group;

	test_group = cg_name(root, "hugetlb_memcg_test");
	if (!test_group || cg_create(test_group)) {
		ksft_print_msg("fail to create cgroup.\n");
		goto out;
	}

	if (cg_write(test_group, "memory.max", "100M")) {
		ksft_print_msg("fail to set cgroup memory limit.\n");
		goto out;
	}

	/* disable swap */
	if (cg_write(test_group, "memory.swap.max", "0")) {
		ksft_print_msg("fail to disable swap.\n");
		goto out;
	}

	if (!cg_run(test_group, hugetlb_test_program, (void *)test_group))
		ret = KSFT_PASS;
out:
	cg_destroy(test_group);
	free(test_group);
	return ret;
}

int main(int argc, char **argv)
{
	char root[PATH_MAX];
	int ret = EXIT_SUCCESS, has_memory_hugetlb_acc;

	has_memory_hugetlb_acc = proc_mount_contains("memory_hugetlb_accounting");
	if (has_memory_hugetlb_acc < 0)
		ksft_exit_skip("Failed to query cgroup mount option\n");
	else if (!has_memory_hugetlb_acc)
		ksft_exit_skip("memory hugetlb accounting is disabled\n");

	/* Unit is kB! */
	if (get_hugepage_size() != 2048) {
		ksft_print_msg("test_hugetlb_memcg requires 2MB hugepages\n");
		ksft_test_result_skip("test_hugetlb_memcg\n");
		return ret;
	}

	if (cg_find_unified_root(root, sizeof(root), NULL))
		ksft_exit_skip("cgroup v2 isn't mounted\n");

	switch (test_hugetlb_memcg(root)) {
	case KSFT_PASS:
		ksft_test_result_pass("test_hugetlb_memcg\n");
		break;
	case KSFT_SKIP:
		ksft_test_result_skip("test_hugetlb_memcg\n");
		break;
	default:
		ret = EXIT_FAILURE;
		ksft_test_result_fail("test_hugetlb_memcg\n");
		break;
	}

	return ret;
}