root/tools/mm/thp_swap_allocator_test.c
// 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 <v-songbaohua@oppo.com>
 */

#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_SMALLFOLIO);
                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;
}