// SPDX-License-Identifier: GPL-2.0-or-later
/*
* thp_swap_allocator_test
*
* The purpose of this test program is helping check if THP swpout
* can correctly get swap slots to swap out as a whole instead of
* being split. It randomly releases swap entries through madvise
* DONTNEED and swapin/out on two memory areas: a memory area for
* 64KB THP and the other area for small folios. The second memory
* can be enabled by "-s".
* Before running the program, we need to setup a zRAM or similar
* swap device by:
* echo lzo > /sys/block/zram0/comp_algorithm
* echo 64M > /sys/block/zram0/disksize
* echo never > /sys/kernel/mm/transparent_hugepage/hugepages-2048kB/enabled
* echo always > /sys/kernel/mm/transparent_hugepage/hugepages-64kB/enabled
* mkswap /dev/zram0
* swapon /dev/zram0
* The expected result should be 0% anon swpout fallback ratio w/ or
* w/o "-s".
*
* Author(s): Barry Song <[email protected]>
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <linux/mman.h>
#include <sys/mman.h>
#include <errno.h>
#include <time.h>
#define MEMSIZE_MTHP (60 * 1024 * 1024)
#define MEMSIZE_SMALLFOLIO (4 * 1024 * 1024)
#define ALIGNMENT_MTHP (64 * 1024)
#define ALIGNMENT_SMALLFOLIO (4 * 1024)
#define TOTAL_DONTNEED_MTHP (16 * 1024 * 1024)
#define TOTAL_DONTNEED_SMALLFOLIO (1 * 1024 * 1024)
#define MTHP_FOLIO_SIZE (64 * 1024)
#define SWPOUT_PATH \
"/sys/kernel/mm/transparent_hugepage/hugepages-64kB/stats/swpout"
#define SWPOUT_FALLBACK_PATH \
"/sys/kernel/mm/transparent_hugepage/hugepages-64kB/stats/swpout_fallback"
static void *aligned_alloc_mem(size_t size, size_t alignment)
{
void *mem = NULL;
if (posix_memalign(&mem, alignment, size) != 0) {
perror("posix_memalign");
return NULL;
}
return mem;
}
/*
* This emulates the behavior of native libc and Java heap,
* as well as process exit and munmap. It helps generate mTHP
* and ensures that iterations can proceed with mTHP, as we
* currently don't support large folios swap-in.
*/
static void random_madvise_dontneed(void *mem, size_t mem_size,
size_t align_size, size_t total_dontneed_size)
{
size_t num_pages = total_dontneed_size / align_size;
size_t i;
size_t offset;
void *addr;
for (i = 0; i < num_pages; ++i) {
offset = (rand() % (mem_size / align_size)) * align_size;
addr = (char *)mem + offset;
if (madvise(addr, align_size, MADV_DONTNEED) != 0)
perror("madvise dontneed");
memset(addr, 0x11, align_size);
}
}
static void random_swapin(void *mem, size_t mem_size,
size_t align_size, size_t total_swapin_size)
{
size_t num_pages = total_swapin_size / align_size;
size_t i;
size_t offset;
void *addr;
for (i = 0; i < num_pages; ++i) {
offset = (rand() % (mem_size / align_size)) * align_size;
addr = (char *)mem + offset;
memset(addr, 0x11, align_size);
}
}
static unsigned long read_stat(const char *path)
{
FILE *file;
unsigned long value;
file = fopen(path, "r");
if (!file) {
perror("fopen");
return 0;
}
if (fscanf(file, "%lu", &value) != 1) {
perror("fscanf");
fclose(file);
return 0;
}
fclose(file);
return value;
}
int main(int argc, char *argv[])
{
int use_small_folio = 0, aligned_swapin = 0;
void *mem1 = NULL, *mem2 = NULL;
int i;
for (i = 1; i < argc; ++i) {
if (strcmp(argv[i], "-s") == 0)
use_small_folio = 1;
else if (strcmp(argv[i], "-a") == 0)
aligned_swapin = 1;
}
mem1 = aligned_alloc_mem(MEMSIZE_MTHP, ALIGNMENT_MTHP);
if (mem1 == NULL) {
fprintf(stderr, "Failed to allocate large folios memory\n");
return EXIT_FAILURE;
}
if (madvise(mem1, MEMSIZE_MTHP, MADV_HUGEPAGE) != 0) {
perror("madvise hugepage for mem1");
free(mem1);
return EXIT_FAILURE;
}
if (use_small_folio) {
mem2 = aligned_alloc_mem(MEMSIZE_SMALLFOLIO, ALIGNMENT_MTHP);
if (mem2 == NULL) {
fprintf(stderr, "Failed to allocate small folios memory\n");
free(mem1);
return EXIT_FAILURE;
}
if (madvise(mem2, MEMSIZE_SMALLFOLIO, MADV_NOHUGEPAGE) != 0) {
perror("madvise nohugepage for mem2");
free(mem1);
free(mem2);
return EXIT_FAILURE;
}
}
/* warm-up phase to occupy the swapfile */
memset(mem1, 0x11, MEMSIZE_MTHP);
madvise(mem1, MEMSIZE_MTHP, MADV_PAGEOUT);
if (use_small_folio) {
memset(mem2, 0x11, MEMSIZE_SMALLFOLIO);
madvise(mem2, MEMSIZE_SMALLFOLIO, MADV_PAGEOUT);
}
/* iterations with newly created mTHP, swap-in, and swap-out */
for (i = 0; i < 100; ++i) {
unsigned long initial_swpout;
unsigned long initial_swpout_fallback;
unsigned long final_swpout;
unsigned long final_swpout_fallback;
unsigned long swpout_inc;
unsigned long swpout_fallback_inc;
double fallback_percentage;
initial_swpout = read_stat(SWPOUT_PATH);
initial_swpout_fallback = read_stat(SWPOUT_FALLBACK_PATH);
/*
* The following setup creates a 1:1 ratio of mTHP to small folios
* since large folio swap-in isn't supported yet. Once we support
* mTHP swap-in, we'll likely need to reduce MEMSIZE_MTHP and
* increase MEMSIZE_SMALLFOLIO to maintain the ratio.
*/
random_swapin(mem1, MEMSIZE_MTHP,
aligned_swapin ? ALIGNMENT_MTHP : ALIGNMENT_SMALLFOLIO,
TOTAL_DONTNEED_MTHP);
random_madvise_dontneed(mem1, MEMSIZE_MTHP, ALIGNMENT_MTHP,
TOTAL_DONTNEED_MTHP);
if (use_small_folio) {
random_swapin(mem2, MEMSIZE_SMALLFOLIO,
ALIGNMENT_SMALLFOLIO,
TOTAL_DONTNEED_SMALLFOLIO);
}
if (madvise(mem1, MEMSIZE_MTHP, MADV_PAGEOUT) != 0) {
perror("madvise pageout for mem1");
free(mem1);
if (mem2 != NULL)
free(mem2);
return EXIT_FAILURE;
}
if (use_small_folio) {
if (madvise(mem2, MEMSIZE_SMALLFOLIO, MADV_PAGEOUT) != 0) {
perror("madvise pageout for mem2");
free(mem1);
free(mem2);
return EXIT_FAILURE;
}
}
final_swpout = read_stat(SWPOUT_PATH);
final_swpout_fallback = read_stat(SWPOUT_FALLBACK_PATH);
swpout_inc = final_swpout - initial_swpout;
swpout_fallback_inc = final_swpout_fallback - initial_swpout_fallback;
fallback_percentage = (double)swpout_fallback_inc /
(swpout_fallback_inc + swpout_inc) * 100;
printf("Iteration %d: swpout inc: %lu, swpout fallback inc: %lu, Fallback percentage: %.2f%%\n",
i + 1, swpout_inc, swpout_fallback_inc, fallback_percentage);
}
free(mem1);
if (mem2 != NULL)
free(mem2);
return EXIT_SUCCESS;
}