|
| 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 <[email protected]> |
| 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 | +} |
0 commit comments