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 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 */ 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 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 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 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