1/*
2 * Copyright (C) 2012-2017  Red Hat, Inc.
3 *
4 * This program is free software;  you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY;  without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
12 * the GNU General Public License for more details.
13 *
14 * Descriptions:
15 *
16 * There are two tunables overcommit_memory and overcommit_ratio under
17 * /proc/sys/vm/, which can control memory overcommitment.
18 *
19 * The overcommit_memory contains a flag that enables memory
20 * overcommitment, it has three values:
21 * - When this flag is 0, the kernel attempts to estimate the amount
22 *   of free memory left when userspace requests more memory.
23 * - When this flag is 1, the kernel pretends there is always enough
24 *   memory until it actually runs out.
25 * - When this flag is 2, the kernel uses a "never overcommit" policy
26 *   that attempts to prevent any overcommit of memory.
27 *
28 * The overcommit_ratio tunable defines the amount by which the kernel
29 * overextends its memory resources in the event that overcommit_memory
30 * is set to the value of 2. The value in this file represents a
31 * percentage added to the amount of actual RAM in a system when
32 * considering whether to grant a particular memory request.
33 * The general formula for this tunable is:
34 * CommitLimit = SwapTotal + MemTotal * overcommit_ratio
35 * CommitLimit, SwapTotal and MemTotal can read from /proc/meminfo.
36 *
37 * The program is designed to test the two tunables:
38 *
39 * When overcommit_memory = 0, allocatable memory can't overextends
40 * the amount of free memory. I choose the three cases:
41 * a. less than free_total:    free_total / 2, alloc should pass.
42 * b. greater than free_total: free_total * 2, alloc should fail.
43 * c. equal to sum_total:      sum_tatal,      alloc should fail
44 *
45 * When overcommit_memory = 1, it can alloc enough much memory, I
46 * choose the three cases:
47 * a. less than sum_total:    sum_total / 2, alloc should pass
48 * b. equal to sum_total:     sum_total,     alloc should pass
49 * c. greater than sum_total: sum_total * 2, alloc should pass
50 * *note: sum_total = SwapTotal + MemTotal
51 *
52 * When overcommit_memory = 2, the total virtual address space on
53 * the system is limited to CommitLimit(Swap+RAM*overcommit_ratio)
54 * commit_left(allocatable memory) = CommitLimit - Committed_AS
55 * a. less than commit_left:    commit_left / 2, alloc should pass
56 * b. greater than commit_left: commit_left * 2, alloc should fail
57 * c. overcommit limit:         CommitLimit,     alloc should fail
58 * *note: CommitLimit is the current overcommit limit.
59 *        Committed_AS is the amount of memory that system has used.
60 * it couldn't choose 'equal to commit_left' as a case, because
61 * commit_left rely on Committed_AS, but the Committed_AS is not stable.
62 *
63 * References:
64 * - Documentation/sysctl/vm.txt
65 * - Documentation/vm/overcommit-accounting
66 */
67
68#include <errno.h>
69#include <stdio.h>
70#include <stdlib.h>
71#include <limits.h>
72#include "mem.h"
73
74#define DEFAULT_OVER_RATIO	50L
75#define EXPECT_PASS		0
76#define EXPECT_FAIL		1
77
78static char *R_opt;
79static struct tst_option options[] = {
80	{"R:", &R_opt, "  -R n    Percentage of overcommitting memory"},
81	{NULL, NULL, NULL}
82};
83
84static long old_overcommit_memory;
85static long old_overcommit_ratio;
86static long overcommit_ratio;
87static long sum_total;
88static long free_total;
89static long commit_limit;
90static long commit_left;
91
92static int heavy_malloc(long size);
93static void alloc_and_check(long size, int expect_result);
94static void update_mem(void);
95
96static void setup(void)
97{
98	long mem_total, swap_total;
99	struct rlimit lim;
100
101	if (access(PATH_SYSVM "overcommit_memory", F_OK) == -1 ||
102	    access(PATH_SYSVM "overcommit_ratio", F_OK) == -1)
103		tst_brk(TCONF, "The system "
104			 "can't support to test %s", TCID);
105
106	if (R_opt)
107		overcommit_ratio = SAFE_STRTOL(R_opt, 0, LONG_MAX);
108	else
109		overcommit_ratio = DEFAULT_OVER_RATIO;
110
111	old_overcommit_memory = get_sys_tune("overcommit_memory");
112	old_overcommit_ratio = get_sys_tune("overcommit_ratio");
113
114	mem_total = SAFE_READ_MEMINFO("MemTotal:");
115	tst_res(TINFO, "MemTotal is %ld kB", mem_total);
116	swap_total = SAFE_READ_MEMINFO("SwapTotal:");
117	tst_res(TINFO, "SwapTotal is %ld kB", swap_total);
118	sum_total = mem_total + swap_total;
119
120	commit_limit = SAFE_READ_MEMINFO("CommitLimit:");
121	tst_res(TINFO, "CommitLimit is %ld kB", commit_limit);
122
123	SAFE_GETRLIMIT(RLIMIT_AS, &lim);
124
125	if (lim.rlim_cur != RLIM_INFINITY) {
126		lim.rlim_cur = RLIM_INFINITY;
127		lim.rlim_max = RLIM_INFINITY;
128
129		tst_res(TINFO, "Increasing RLIM_AS to INFINITY");
130
131		SAFE_SETRLIMIT(RLIMIT_AS, &lim);
132	}
133
134	set_sys_tune("overcommit_ratio", overcommit_ratio, 1);
135}
136
137static void cleanup(void)
138{
139	set_sys_tune("overcommit_memory", old_overcommit_memory, 0);
140	set_sys_tune("overcommit_ratio", old_overcommit_ratio, 0);
141}
142
143static void overcommit_memory_test(void)
144{
145
146#if __WORDSIZE == 32
147	tst_brk(TCONF, "test is not designed for 32-bit system.");
148#endif
149	/* start to test overcommit_memory=2 */
150	set_sys_tune("overcommit_memory", 2, 1);
151
152	update_mem();
153	alloc_and_check(commit_left * 2, EXPECT_FAIL);
154	alloc_and_check(commit_limit, EXPECT_FAIL);
155	update_mem();
156	alloc_and_check(commit_left / 2, EXPECT_PASS);
157
158	/* start to test overcommit_memory=0 */
159	set_sys_tune("overcommit_memory", 0, 1);
160
161	update_mem();
162	alloc_and_check(free_total / 2, EXPECT_PASS);
163	update_mem();
164	alloc_and_check(free_total * 2, EXPECT_FAIL);
165	alloc_and_check(sum_total, EXPECT_FAIL);
166
167	/* start to test overcommit_memory=1 */
168	set_sys_tune("overcommit_memory", 1, 1);
169
170	alloc_and_check(sum_total / 2, EXPECT_PASS);
171	alloc_and_check(sum_total, EXPECT_PASS);
172	alloc_and_check(sum_total * 2, EXPECT_PASS);
173
174}
175
176static int heavy_malloc(long size)
177{
178	char *p;
179
180	p = malloc(size * KB);
181	if (p != NULL) {
182		tst_res(TINFO, "malloc %ld kB successfully", size);
183		free(p);
184		return 0;
185	} else {
186		tst_res(TINFO, "malloc %ld kB failed", size);
187		return 1;
188	}
189}
190
191static void alloc_and_check(long size, int expect_result)
192{
193	int result;
194
195	/* try to alloc size kB memory */
196	result = heavy_malloc(size);
197
198	switch (expect_result) {
199	case EXPECT_PASS:
200		if (result == 0)
201			tst_res(TPASS, "alloc passed as expected");
202		else
203			tst_res(TFAIL, "alloc failed, expected to pass");
204		break;
205	case EXPECT_FAIL:
206		if (result != 0)
207			tst_res(TPASS, "alloc failed as expected");
208		else
209			tst_res(TFAIL, "alloc passed, expected to fail");
210		break;
211	default:
212		tst_brk(TBROK, "Invaild numbler parameter: %d",
213			 expect_result);
214	}
215}
216
217static void update_mem(void)
218{
219	long mem_free, swap_free;
220	long committed;
221
222	mem_free = SAFE_READ_MEMINFO("MemFree:");
223	swap_free = SAFE_READ_MEMINFO("SwapFree:");
224	free_total = mem_free + swap_free;
225	commit_limit = SAFE_READ_MEMINFO("CommitLimit:");
226
227	if (get_sys_tune("overcommit_memory") == 2) {
228		committed = SAFE_READ_MEMINFO("Committed_AS:");
229		commit_left = commit_limit - committed;
230
231		if (commit_left < 0) {
232			tst_res(TINFO, "CommitLimit is %ld, Committed_AS"
233				 " is %ld", commit_limit, committed);
234			tst_brk(TBROK, "Unexpected error: "
235				 "CommitLimit < Committed_AS");
236		}
237	}
238}
239
240static struct tst_test test = {
241	.needs_root = 1,
242	.options = options,
243	.setup = setup,
244	.cleanup = cleanup,
245	.test_all = overcommit_memory_test,
246};
247