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