xref: /linux/tools/mm/thp_swap_allocator_test.c (revision a1ff5a7d78a036d6c2178ee5acd6ba4946243800)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * thp_swap_allocator_test
4  *
5  * The purpose of this test program is helping check if THP swpout
6  * can correctly get swap slots to swap out as a whole instead of
7  * being split. It randomly releases swap entries through madvise
8  * DONTNEED and swapin/out on two memory areas: a memory area for
9  * 64KB THP and the other area for small folios. The second memory
10  * can be enabled by "-s".
11  * Before running the program, we need to setup a zRAM or similar
12  * swap device by:
13  *  echo lzo > /sys/block/zram0/comp_algorithm
14  *  echo 64M > /sys/block/zram0/disksize
15  *  echo never > /sys/kernel/mm/transparent_hugepage/hugepages-2048kB/enabled
16  *  echo always > /sys/kernel/mm/transparent_hugepage/hugepages-64kB/enabled
17  *  mkswap /dev/zram0
18  *  swapon /dev/zram0
19  * The expected result should be 0% anon swpout fallback ratio w/ or
20  * w/o "-s".
21  *
22  * Author(s): Barry Song <v-songbaohua@oppo.com>
23  */
24 
25 #define _GNU_SOURCE
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <string.h>
30 #include <linux/mman.h>
31 #include <sys/mman.h>
32 #include <errno.h>
33 #include <time.h>
34 
35 #define MEMSIZE_MTHP (60 * 1024 * 1024)
36 #define MEMSIZE_SMALLFOLIO (4 * 1024 * 1024)
37 #define ALIGNMENT_MTHP (64 * 1024)
38 #define ALIGNMENT_SMALLFOLIO (4 * 1024)
39 #define TOTAL_DONTNEED_MTHP (16 * 1024 * 1024)
40 #define TOTAL_DONTNEED_SMALLFOLIO (1 * 1024 * 1024)
41 #define MTHP_FOLIO_SIZE (64 * 1024)
42 
43 #define SWPOUT_PATH \
44 	"/sys/kernel/mm/transparent_hugepage/hugepages-64kB/stats/swpout"
45 #define SWPOUT_FALLBACK_PATH \
46 	"/sys/kernel/mm/transparent_hugepage/hugepages-64kB/stats/swpout_fallback"
47 
aligned_alloc_mem(size_t size,size_t alignment)48 static void *aligned_alloc_mem(size_t size, size_t alignment)
49 {
50 	void *mem = NULL;
51 
52 	if (posix_memalign(&mem, alignment, size) != 0) {
53 		perror("posix_memalign");
54 		return NULL;
55 	}
56 	return mem;
57 }
58 
59 /*
60  * This emulates the behavior of native libc and Java heap,
61  * as well as process exit and munmap. It helps generate mTHP
62  * and ensures that iterations can proceed with mTHP, as we
63  * currently don't support large folios swap-in.
64  */
random_madvise_dontneed(void * mem,size_t mem_size,size_t align_size,size_t total_dontneed_size)65 static void random_madvise_dontneed(void *mem, size_t mem_size,
66 		size_t align_size, size_t total_dontneed_size)
67 {
68 	size_t num_pages = total_dontneed_size / align_size;
69 	size_t i;
70 	size_t offset;
71 	void *addr;
72 
73 	for (i = 0; i < num_pages; ++i) {
74 		offset = (rand() % (mem_size / align_size)) * align_size;
75 		addr = (char *)mem + offset;
76 		if (madvise(addr, align_size, MADV_DONTNEED) != 0)
77 			perror("madvise dontneed");
78 
79 		memset(addr, 0x11, align_size);
80 	}
81 }
82 
random_swapin(void * mem,size_t mem_size,size_t align_size,size_t total_swapin_size)83 static void random_swapin(void *mem, size_t mem_size,
84 		size_t align_size, size_t total_swapin_size)
85 {
86 	size_t num_pages = total_swapin_size / align_size;
87 	size_t i;
88 	size_t offset;
89 	void *addr;
90 
91 	for (i = 0; i < num_pages; ++i) {
92 		offset = (rand() % (mem_size / align_size)) * align_size;
93 		addr = (char *)mem + offset;
94 		memset(addr, 0x11, align_size);
95 	}
96 }
97 
read_stat(const char * path)98 static unsigned long read_stat(const char *path)
99 {
100 	FILE *file;
101 	unsigned long value;
102 
103 	file = fopen(path, "r");
104 	if (!file) {
105 		perror("fopen");
106 		return 0;
107 	}
108 
109 	if (fscanf(file, "%lu", &value) != 1) {
110 		perror("fscanf");
111 		fclose(file);
112 		return 0;
113 	}
114 
115 	fclose(file);
116 	return value;
117 }
118 
main(int argc,char * argv[])119 int main(int argc, char *argv[])
120 {
121 	int use_small_folio = 0, aligned_swapin = 0;
122 	void *mem1 = NULL, *mem2 = NULL;
123 	int i;
124 
125 	for (i = 1; i < argc; ++i) {
126 		if (strcmp(argv[i], "-s") == 0)
127 			use_small_folio = 1;
128 		else if (strcmp(argv[i], "-a") == 0)
129 			aligned_swapin = 1;
130 	}
131 
132 	mem1 = aligned_alloc_mem(MEMSIZE_MTHP, ALIGNMENT_MTHP);
133 	if (mem1 == NULL) {
134 		fprintf(stderr, "Failed to allocate large folios memory\n");
135 		return EXIT_FAILURE;
136 	}
137 
138 	if (madvise(mem1, MEMSIZE_MTHP, MADV_HUGEPAGE) != 0) {
139 		perror("madvise hugepage for mem1");
140 		free(mem1);
141 		return EXIT_FAILURE;
142 	}
143 
144 	if (use_small_folio) {
145 		mem2 = aligned_alloc_mem(MEMSIZE_SMALLFOLIO, ALIGNMENT_MTHP);
146 		if (mem2 == NULL) {
147 			fprintf(stderr, "Failed to allocate small folios memory\n");
148 			free(mem1);
149 			return EXIT_FAILURE;
150 		}
151 
152 		if (madvise(mem2, MEMSIZE_SMALLFOLIO, MADV_NOHUGEPAGE) != 0) {
153 			perror("madvise nohugepage for mem2");
154 			free(mem1);
155 			free(mem2);
156 			return EXIT_FAILURE;
157 		}
158 	}
159 
160 	/* warm-up phase to occupy the swapfile */
161 	memset(mem1, 0x11, MEMSIZE_MTHP);
162 	madvise(mem1, MEMSIZE_MTHP, MADV_PAGEOUT);
163 	if (use_small_folio) {
164 		memset(mem2, 0x11, MEMSIZE_SMALLFOLIO);
165 		madvise(mem2, MEMSIZE_SMALLFOLIO, MADV_PAGEOUT);
166 	}
167 
168 	/* iterations with newly created mTHP, swap-in, and swap-out */
169 	for (i = 0; i < 100; ++i) {
170 		unsigned long initial_swpout;
171 		unsigned long initial_swpout_fallback;
172 		unsigned long final_swpout;
173 		unsigned long final_swpout_fallback;
174 		unsigned long swpout_inc;
175 		unsigned long swpout_fallback_inc;
176 		double fallback_percentage;
177 
178 		initial_swpout = read_stat(SWPOUT_PATH);
179 		initial_swpout_fallback = read_stat(SWPOUT_FALLBACK_PATH);
180 
181 		/*
182 		 * The following setup creates a 1:1 ratio of mTHP to small folios
183 		 * since large folio swap-in isn't supported yet. Once we support
184 		 * mTHP swap-in, we'll likely need to reduce MEMSIZE_MTHP and
185 		 * increase MEMSIZE_SMALLFOLIO to maintain the ratio.
186 		 */
187 		random_swapin(mem1, MEMSIZE_MTHP,
188 				aligned_swapin ? ALIGNMENT_MTHP : ALIGNMENT_SMALLFOLIO,
189 				TOTAL_DONTNEED_MTHP);
190 		random_madvise_dontneed(mem1, MEMSIZE_MTHP, ALIGNMENT_MTHP,
191 				TOTAL_DONTNEED_MTHP);
192 
193 		if (use_small_folio) {
194 			random_swapin(mem2, MEMSIZE_SMALLFOLIO,
195 					ALIGNMENT_SMALLFOLIO,
196 					TOTAL_DONTNEED_SMALLFOLIO);
197 		}
198 
199 		if (madvise(mem1, MEMSIZE_MTHP, MADV_PAGEOUT) != 0) {
200 			perror("madvise pageout for mem1");
201 			free(mem1);
202 			if (mem2 != NULL)
203 				free(mem2);
204 			return EXIT_FAILURE;
205 		}
206 
207 		if (use_small_folio) {
208 			if (madvise(mem2, MEMSIZE_SMALLFOLIO, MADV_PAGEOUT) != 0) {
209 				perror("madvise pageout for mem2");
210 				free(mem1);
211 				free(mem2);
212 				return EXIT_FAILURE;
213 			}
214 		}
215 
216 		final_swpout = read_stat(SWPOUT_PATH);
217 		final_swpout_fallback = read_stat(SWPOUT_FALLBACK_PATH);
218 
219 		swpout_inc = final_swpout - initial_swpout;
220 		swpout_fallback_inc = final_swpout_fallback - initial_swpout_fallback;
221 
222 		fallback_percentage = (double)swpout_fallback_inc /
223 			(swpout_fallback_inc + swpout_inc) * 100;
224 
225 		printf("Iteration %d: swpout inc: %lu, swpout fallback inc: %lu, Fallback percentage: %.2f%%\n",
226 				i + 1, swpout_inc, swpout_fallback_inc, fallback_percentage);
227 	}
228 
229 	free(mem1);
230 	if (mem2 != NULL)
231 		free(mem2);
232 
233 	return EXIT_SUCCESS;
234 }
235