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