aboutsummaryrefslogtreecommitdiff
path: root/testcases/kernel/mem/tunable/overcommit_memory.c
blob: 7fe8fe14c3b9a6f8c8634f4803b815a95c04d511 (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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2012-2020 Linux Test Project
 * Copyright (c) 2012-2017 Red Hat, Inc.
 *
 * There are two tunables overcommit_memory and overcommit_ratio under
 * /proc/sys/vm/, which can control memory overcommitment.
 *
 * The overcommit_memory contains a flag that enables memory
 * overcommitment, it has three values:
 * - When this flag is 0, the kernel attempts to estimate the amount
 *   of free memory left when userspace requests more memory.
 * - When this flag is 1, the kernel pretends there is always enough
 *   memory until it actually runs out.
 * - When this flag is 2, the kernel uses a "never overcommit" policy
 *   that attempts to prevent any overcommit of memory.
 *
 * The overcommit_ratio tunable defines the amount by which the kernel
 * overextends its memory resources in the event that overcommit_memory
 * is set to the value of 2. The value in this file represents a
 * percentage added to the amount of actual RAM in a system when
 * considering whether to grant a particular memory request.
 * The general formula for this tunable is:
 * CommitLimit = SwapTotal + MemTotal * overcommit_ratio
 * CommitLimit, SwapTotal and MemTotal can read from /proc/meminfo.
 *
 * The program is designed to test the two tunables:
 *
 * When overcommit_memory = 0, allocatable memory can't overextend
 * the amount of total memory:
 * a. less than free_total:    free_total / 2, alloc should pass.
 * b. greater than sum_total:   sum_total * 2, alloc should fail.
 *
 * When overcommit_memory = 1, it can alloc enough much memory, I
 * choose the three cases:
 * a. less than sum_total:    sum_total / 2, alloc should pass
 * b. equal to sum_total:     sum_total,     alloc should pass
 * c. greater than sum_total: sum_total * 2, alloc should pass
 * *note: sum_total = SwapTotal + MemTotal
 *
 * When overcommit_memory = 2, the total virtual address space on
 * the system is limited to CommitLimit(Swap+RAM*overcommit_ratio)
 * commit_left(allocatable memory) = CommitLimit - Committed_AS
 * a. less than commit_left:    commit_left / 2, alloc should pass
 * b. overcommit limit:         CommitLimit + TotalBatchSize, should fail
 * c. greater than commit_left: commit_left * 2, alloc should fail
 * *note: CommitLimit is the current overcommit limit.
 *        Committed_AS is the amount of memory that system has used.
 * it couldn't choose 'equal to commit_left' as a case, because
 * commit_left rely on Committed_AS, but the Committed_AS is not stable.
 * *note2: TotalBatchSize is the total number of bytes, that can be
 *         accounted for in the per cpu counters for the vm_committed_as
 *         counter. Since the check used by malloc only looks at the
 *         global counter of vm_committed_as, it can overallocate a bit.
 *
 * References:
 * - Documentation/sysctl/vm.txt
 * - Documentation/vm/overcommit-accounting
 */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include "lapi/abisize.h"
#include "mem.h"

#define DEFAULT_OVER_RATIO	50L
#define EXPECT_PASS		0
#define EXPECT_FAIL		1

static char *R_opt;
static long old_overcommit_memory = -1;
static long old_overcommit_ratio = -1;
static long overcommit_ratio;
static long sum_total;
static long free_total;
static long commit_limit;
static long commit_left;
static long total_batch_size;

static int heavy_malloc(long size);
static void alloc_and_check(long size, int expect_result);
static void update_mem(void);
static void update_mem_commit(void);
static void calculate_total_batch_size(void);

static void setup(void)
{
	long mem_total, swap_total;
	struct rlimit lim;

	if (access(PATH_SYSVM "overcommit_memory", F_OK) == -1 ||
	    access(PATH_SYSVM "overcommit_ratio", F_OK) == -1)
		tst_brk(TCONF, "system doesn't support overcommit_memory");

	if (R_opt)
		overcommit_ratio = SAFE_STRTOL(R_opt, 0, LONG_MAX);
	else
		overcommit_ratio = DEFAULT_OVER_RATIO;

	old_overcommit_memory = get_sys_tune("overcommit_memory");
	old_overcommit_ratio = get_sys_tune("overcommit_ratio");

	mem_total = SAFE_READ_MEMINFO("MemTotal:");
	tst_res(TINFO, "MemTotal is %ld kB", mem_total);
	swap_total = SAFE_READ_MEMINFO("SwapTotal:");
	tst_res(TINFO, "SwapTotal is %ld kB", swap_total);
	sum_total = mem_total + swap_total;

	commit_limit = SAFE_READ_MEMINFO("CommitLimit:");
	tst_res(TINFO, "CommitLimit is %ld kB", commit_limit);

	SAFE_GETRLIMIT(RLIMIT_AS, &lim);

	if (lim.rlim_cur != RLIM_INFINITY) {
		lim.rlim_cur = RLIM_INFINITY;
		lim.rlim_max = RLIM_INFINITY;

		tst_res(TINFO, "Increasing RLIM_AS to INFINITY");

		SAFE_SETRLIMIT(RLIMIT_AS, &lim);
	}

	set_sys_tune("overcommit_ratio", overcommit_ratio, 1);

	calculate_total_batch_size();
	tst_res(TINFO, "TotalBatchSize is %ld kB", total_batch_size);
}

static void cleanup(void)
{
	if (old_overcommit_memory != -1)
		set_sys_tune("overcommit_memory", old_overcommit_memory, 0);
	if (old_overcommit_ratio != -1)
		set_sys_tune("overcommit_ratio", old_overcommit_ratio, 0);
}

static void overcommit_memory_test(void)
{

#ifdef TST_ABI32
	tst_brk(TCONF, "test is not designed for 32-bit system.");
#endif
	/* start to test overcommit_memory=2 */
	set_sys_tune("overcommit_memory", 2, 1);

	update_mem_commit();
	alloc_and_check(commit_left * 2, EXPECT_FAIL);
	alloc_and_check(commit_limit + total_batch_size, EXPECT_FAIL);
	update_mem_commit();
	alloc_and_check(commit_left / 2, EXPECT_PASS);

	/* start to test overcommit_memory=0 */
	set_sys_tune("overcommit_memory", 0, 1);

	update_mem();
	alloc_and_check(free_total / 2, EXPECT_PASS);
	alloc_and_check(sum_total * 2, EXPECT_FAIL);

	/* start to test overcommit_memory=1 */
	set_sys_tune("overcommit_memory", 1, 1);

	alloc_and_check(sum_total / 2, EXPECT_PASS);
	alloc_and_check(sum_total, EXPECT_PASS);
	alloc_and_check(sum_total * 2, EXPECT_PASS);

}

static int heavy_malloc(long size)
{
	char *p;

	p = malloc(size * KB);
	if (p != NULL) {
		tst_res(TINFO, "malloc %ld kB successfully", size);
		free(p);
		return 0;
	} else {
		tst_res(TINFO, "malloc %ld kB failed", size);
		return 1;
	}
}

static void alloc_and_check(long size, int expect_result)
{
	int result;

	/* try to alloc size kB memory */
	result = heavy_malloc(size);

	switch (expect_result) {
	case EXPECT_PASS:
		if (result == 0)
			tst_res(TPASS, "alloc passed as expected");
		else
			tst_res(TFAIL, "alloc failed, expected to pass");
		break;
	case EXPECT_FAIL:
		if (result != 0)
			tst_res(TPASS, "alloc failed as expected");
		else
			tst_res(TFAIL, "alloc passed, expected to fail");
		break;
	default:
		tst_brk(TBROK, "Invalid number parameter: %d",
			 expect_result);
	}
}

static void update_mem(void)
{
	long mem_free, swap_free;

	mem_free = SAFE_READ_MEMINFO("MemFree:");
	swap_free = SAFE_READ_MEMINFO("SwapFree:");
	free_total = mem_free + swap_free;
}

static void update_mem_commit(void)
{
	long committed;

	commit_limit = SAFE_READ_MEMINFO("CommitLimit:");
	committed = SAFE_READ_MEMINFO("Committed_AS:");
	commit_left = commit_limit - committed;

	if (commit_left < 0) {
		tst_res(TINFO, "CommitLimit is %ld, Committed_AS is %ld",
			commit_limit, committed);

		if (overcommit_ratio > old_overcommit_ratio) {
			tst_brk(TBROK, "Unexpected error: "
				"CommitLimit < Committed_AS");
		}

		tst_brk(TCONF, "Specified overcommit_ratio %ld <= default %ld, "
			"so it's possible for CommitLimit < Committed_AS and skip test",
			overcommit_ratio, old_overcommit_ratio);
	}
}

static void calculate_total_batch_size(void)
{
	struct sysinfo info;
	long ncpus = tst_ncpus_conf();
	long pagesize = getpagesize();
	SAFE_SYSINFO(&info);

	/* see linux source mm/mm_init.c mm_compute_batch() (This is in pages) */
	long batch_size = MAX(ncpus * 2L,
	                      MAX(32L,
	                          MIN((long)INT32_MAX,
	                              (long)(info.totalram / pagesize) / ncpus / 256
	                          )
	                      )
	                  );

	/* there are ncpu separate counters, that can all grow up to
	 * batch_size. So the maximum error for __vm_enough_memory is
	 * batch_size * ncpus. */
	total_batch_size = (batch_size * ncpus * pagesize) / KB;
}

static struct tst_test test = {
	.needs_root = 1,
	.options = (struct tst_option[]) {
		{"R:", &R_opt, "Percentage of overcommitting memory"},
		{}
	},
	.setup = setup,
	.cleanup = cleanup,
	.test_all = overcommit_memory_test,
};