summaryrefslogtreecommitdiff
path: root/tools/mm/thp_swap_allocator_test.c
blob: 83afc52275a501c766b5fa42cb189f6dae282501 (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
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * thp_swap_allocator_test
 *
 * The purpose of this test program is helping check if THP swpout
 * can correctly get swap slots to swap out as a whole instead of
 * being split. It randomly releases swap entries through madvise
 * DONTNEED and swapin/out on two memory areas: a memory area for
 * 64KB THP and the other area for small folios. The second memory
 * can be enabled by "-s".
 * Before running the program, we need to setup a zRAM or similar
 * swap device by:
 *  echo lzo > /sys/block/zram0/comp_algorithm
 *  echo 64M > /sys/block/zram0/disksize
 *  echo never > /sys/kernel/mm/transparent_hugepage/hugepages-2048kB/enabled
 *  echo always > /sys/kernel/mm/transparent_hugepage/hugepages-64kB/enabled
 *  mkswap /dev/zram0
 *  swapon /dev/zram0
 * The expected result should be 0% anon swpout fallback ratio w/ or
 * w/o "-s".
 *
 * Author(s): Barry Song <v-songbaohua@oppo.com>
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <linux/mman.h>
#include <sys/mman.h>
#include <errno.h>
#include <time.h>

#define MEMSIZE_MTHP (60 * 1024 * 1024)
#define MEMSIZE_SMALLFOLIO (4 * 1024 * 1024)
#define ALIGNMENT_MTHP (64 * 1024)
#define ALIGNMENT_SMALLFOLIO (4 * 1024)
#define TOTAL_DONTNEED_MTHP (16 * 1024 * 1024)
#define TOTAL_DONTNEED_SMALLFOLIO (1 * 1024 * 1024)
#define MTHP_FOLIO_SIZE (64 * 1024)

#define SWPOUT_PATH \
	"/sys/kernel/mm/transparent_hugepage/hugepages-64kB/stats/swpout"
#define SWPOUT_FALLBACK_PATH \
	"/sys/kernel/mm/transparent_hugepage/hugepages-64kB/stats/swpout_fallback"

static void *aligned_alloc_mem(size_t size, size_t alignment)
{
	void *mem = NULL;

	if (posix_memalign(&mem, alignment, size) != 0) {
		perror("posix_memalign");
		return NULL;
	}
	return mem;
}

/*
 * This emulates the behavior of native libc and Java heap,
 * as well as process exit and munmap. It helps generate mTHP
 * and ensures that iterations can proceed with mTHP, as we
 * currently don't support large folios swap-in.
 */
static void random_madvise_dontneed(void *mem, size_t mem_size,
		size_t align_size, size_t total_dontneed_size)
{
	size_t num_pages = total_dontneed_size / align_size;
	size_t i;
	size_t offset;
	void *addr;

	for (i = 0; i < num_pages; ++i) {
		offset = (rand() % (mem_size / align_size)) * align_size;
		addr = (char *)mem + offset;
		if (madvise(addr, align_size, MADV_DONTNEED) != 0)
			perror("madvise dontneed");

		memset(addr, 0x11, align_size);
	}
}

static void random_swapin(void *mem, size_t mem_size,
		size_t align_size, size_t total_swapin_size)
{
	size_t num_pages = total_swapin_size / align_size;
	size_t i;
	size_t offset;
	void *addr;

	for (i = 0; i < num_pages; ++i) {
		offset = (rand() % (mem_size / align_size)) * align_size;
		addr = (char *)mem + offset;
		memset(addr, 0x11, align_size);
	}
}

static unsigned long read_stat(const char *path)
{
	FILE *file;
	unsigned long value;

	file = fopen(path, "r");
	if (!file) {
		perror("fopen");
		return 0;
	}

	if (fscanf(file, "%lu", &value) != 1) {
		perror("fscanf");
		fclose(file);
		return 0;
	}

	fclose(file);
	return value;
}

int main(int argc, char *argv[])
{
	int use_small_folio = 0, aligned_swapin = 0;
	void *mem1 = NULL, *mem2 = NULL;
	int i;

	for (i = 1; i < argc; ++i) {
		if (strcmp(argv[i], "-s") == 0)
			use_small_folio = 1;
		else if (strcmp(argv[i], "-a") == 0)
			aligned_swapin = 1;
	}

	mem1 = aligned_alloc_mem(MEMSIZE_MTHP, ALIGNMENT_MTHP);
	if (mem1 == NULL) {
		fprintf(stderr, "Failed to allocate large folios memory\n");
		return EXIT_FAILURE;
	}

	if (madvise(mem1, MEMSIZE_MTHP, MADV_HUGEPAGE) != 0) {
		perror("madvise hugepage for mem1");
		free(mem1);
		return EXIT_FAILURE;
	}

	if (use_small_folio) {
		mem2 = aligned_alloc_mem(MEMSIZE_SMALLFOLIO, ALIGNMENT_MTHP);
		if (mem2 == NULL) {
			fprintf(stderr, "Failed to allocate small folios memory\n");
			free(mem1);
			return EXIT_FAILURE;
		}

		if (madvise(mem2, MEMSIZE_SMALLFOLIO, MADV_NOHUGEPAGE) != 0) {
			perror("madvise nohugepage for mem2");
			free(mem1);
			free(mem2);
			return EXIT_FAILURE;
		}
	}

	/* warm-up phase to occupy the swapfile */
	memset(mem1, 0x11, MEMSIZE_MTHP);
	madvise(mem1, MEMSIZE_MTHP, MADV_PAGEOUT);
	if (use_small_folio) {
		memset(mem2, 0x11, MEMSIZE_SMALLFOLIO);
		madvise(mem2, MEMSIZE_SMALLFOLIO, MADV_PAGEOUT);
	}

	/* iterations with newly created mTHP, swap-in, and swap-out */
	for (i = 0; i < 100; ++i) {
		unsigned long initial_swpout;
		unsigned long initial_swpout_fallback;
		unsigned long final_swpout;
		unsigned long final_swpout_fallback;
		unsigned long swpout_inc;
		unsigned long swpout_fallback_inc;
		double fallback_percentage;

		initial_swpout = read_stat(SWPOUT_PATH);
		initial_swpout_fallback = read_stat(SWPOUT_FALLBACK_PATH);

		/*
		 * The following setup creates a 1:1 ratio of mTHP to small folios
		 * since large folio swap-in isn't supported yet. Once we support
		 * mTHP swap-in, we'll likely need to reduce MEMSIZE_MTHP and
		 * increase MEMSIZE_SMALLFOLIO to maintain the ratio.
		 */
		random_swapin(mem1, MEMSIZE_MTHP,
				aligned_swapin ? ALIGNMENT_MTHP : ALIGNMENT_SMALLFOLIO,
				TOTAL_DONTNEED_MTHP);
		random_madvise_dontneed(mem1, MEMSIZE_MTHP, ALIGNMENT_MTHP,
				TOTAL_DONTNEED_MTHP);

		if (use_small_folio) {
			random_swapin(mem2, MEMSIZE_SMALLFOLIO,
					ALIGNMENT_SMALLFOLIO,
					TOTAL_DONTNEED_SMALLFOLIO);
		}

		if (madvise(mem1, MEMSIZE_MTHP, MADV_PAGEOUT) != 0) {
			perror("madvise pageout for mem1");
			free(mem1);
			if (mem2 != NULL)
				free(mem2);
			return EXIT_FAILURE;
		}

		if (use_small_folio) {
			if (madvise(mem2, MEMSIZE_SMALLFOLIO, MADV_PAGEOUT) != 0) {
				perror("madvise pageout for mem2");
				free(mem1);
				free(mem2);
				return EXIT_FAILURE;
			}
		}

		final_swpout = read_stat(SWPOUT_PATH);
		final_swpout_fallback = read_stat(SWPOUT_FALLBACK_PATH);

		swpout_inc = final_swpout - initial_swpout;
		swpout_fallback_inc = final_swpout_fallback - initial_swpout_fallback;

		fallback_percentage = (double)swpout_fallback_inc /
			(swpout_fallback_inc + swpout_inc) * 100;

		printf("Iteration %d: swpout inc: %lu, swpout fallback inc: %lu, Fallback percentage: %.2f%%\n",
				i + 1, swpout_inc, swpout_fallback_inc, fallback_percentage);
	}

	free(mem1);
	if (mem2 != NULL)
		free(mem2);

	return EXIT_SUCCESS;
}