Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

selftests/cgroup: add test for zswap incompressible pages

Add test_zswap_incompressible() to verify that the zswap_incomp memcg stat
correctly tracks incompressible pages.

The test allocates memory filled with random data from /dev/urandom, which
cannot be effectively compressed by zswap. When this data is swapped out
to zswap, it should be stored as-is and tracked by the zswap_incomp
counter.

The test verifies that:
1. Pages are swapped out to zswap (zswpout increases)
2. Incompressible pages are tracked (zswap_incomp increases)

test:
dd if=/dev/zero of=/swapfile bs=1M count=2048
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo Y > /sys/module/zswap/parameters/enabled

./test_zswap
TAP version 13
1..8
ok 1 test_zswap_usage
ok 2 test_swapin_nozswap
ok 3 test_zswapin
ok 4 test_zswap_writeback_enabled
ok 5 test_zswap_writeback_disabled
ok 6 test_no_kmem_bypass
ok 7 test_no_invasive_cgroup_shrink
ok 8 test_zswap_incompressible
Totals: pass:8 fail:0 xfail:0 xpass:0 skip:0 error:0

Link: https://lkml.kernel.org/r/20260213071827.5688-3-jiayuan.chen@linux.dev
Signed-off-by: Jiayuan Chen <jiayuan.chen@shopee.com>
Acked-by: Shakeel Butt <shakeel.butt@linux.dev>
Acked-by: Nhat Pham <nphamcs@gmail.com>
Reviewed-by: SeongJae Park <sj@kernel.org>
Cc: Chengming Zhou <chengming.zhou@linux.dev>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Michal Hocko <mhocko@kernel.org>
Cc: Michal Koutný <mkoutny@suse.com>
Cc: Muchun Song <muchun.song@linux.dev>
Cc: Roman Gushchin <roman.gushchin@linux.dev>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Tejun Heo <tj@kernel.org>
Cc: Yosry Ahmed <yosry.ahmed@linux.dev>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

authored by

Jiayuan Chen and committed by
Andrew Morton
4e89004e 5ad41a38

+136
+136
tools/testing/selftests/cgroup/test_zswap.c
··· 5 5 #include <unistd.h> 6 6 #include <stdio.h> 7 7 #include <signal.h> 8 + #include <errno.h> 9 + #include <fcntl.h> 8 10 #include <sys/sysinfo.h> 9 11 #include <string.h> 10 12 #include <sys/wait.h> ··· 576 574 return ret; 577 575 } 578 576 577 + struct incomp_child_args { 578 + size_t size; 579 + int pipefd[2]; 580 + int madvise_ret; 581 + int madvise_errno; 582 + }; 583 + 584 + static int allocate_random_and_wait(const char *cgroup, void *arg) 585 + { 586 + struct incomp_child_args *values = arg; 587 + size_t size = values->size; 588 + char *mem; 589 + int fd; 590 + ssize_t n; 591 + 592 + close(values->pipefd[0]); 593 + 594 + mem = mmap(NULL, size, PROT_READ | PROT_WRITE, 595 + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 596 + if (mem == MAP_FAILED) 597 + return -1; 598 + 599 + /* Fill with random data from /dev/urandom - incompressible */ 600 + fd = open("/dev/urandom", O_RDONLY); 601 + if (fd < 0) { 602 + munmap(mem, size); 603 + return -1; 604 + } 605 + 606 + for (size_t i = 0; i < size; ) { 607 + n = read(fd, mem + i, size - i); 608 + if (n <= 0) 609 + break; 610 + i += n; 611 + } 612 + close(fd); 613 + 614 + /* Touch all pages to ensure they're faulted in */ 615 + for (size_t i = 0; i < size; i += PAGE_SIZE) 616 + mem[i] = mem[i]; 617 + 618 + /* Use MADV_PAGEOUT to push pages into zswap */ 619 + values->madvise_ret = madvise(mem, size, MADV_PAGEOUT); 620 + values->madvise_errno = errno; 621 + 622 + /* Notify parent that allocation and pageout are done */ 623 + write(values->pipefd[1], "x", 1); 624 + close(values->pipefd[1]); 625 + 626 + /* Keep memory alive for parent to check stats */ 627 + pause(); 628 + munmap(mem, size); 629 + return 0; 630 + } 631 + 632 + static long get_zswap_incomp(const char *cgroup) 633 + { 634 + return cg_read_key_long(cgroup, "memory.stat", "zswap_incomp "); 635 + } 636 + 637 + /* 638 + * Test that incompressible pages (random data) are tracked by zswap_incomp. 639 + * 640 + * The child process allocates random data within memory.max, then uses 641 + * MADV_PAGEOUT to push pages into zswap. The parent waits on a pipe for 642 + * the child to finish, then checks the zswap_incomp stat before the child 643 + * exits (zswap_incomp is a gauge that decreases on free). 644 + */ 645 + static int test_zswap_incompressible(const char *root) 646 + { 647 + int ret = KSFT_FAIL; 648 + struct incomp_child_args *values; 649 + char *test_group; 650 + long zswap_incomp; 651 + pid_t child_pid; 652 + int child_status; 653 + char buf; 654 + 655 + values = mmap(0, sizeof(struct incomp_child_args), PROT_READ | 656 + PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); 657 + if (values == MAP_FAILED) 658 + return KSFT_FAIL; 659 + 660 + if (pipe(values->pipefd)) { 661 + munmap(values, sizeof(struct incomp_child_args)); 662 + return KSFT_FAIL; 663 + } 664 + 665 + test_group = cg_name(root, "zswap_incompressible_test"); 666 + if (!test_group) 667 + goto out; 668 + if (cg_create(test_group)) 669 + goto out; 670 + if (cg_write(test_group, "memory.max", "32M")) 671 + goto out; 672 + 673 + values->size = MB(4); 674 + child_pid = cg_run_nowait(test_group, allocate_random_and_wait, values); 675 + if (child_pid < 0) 676 + goto out; 677 + 678 + close(values->pipefd[1]); 679 + 680 + /* Wait for child to finish allocating and pageout */ 681 + read(values->pipefd[0], &buf, 1); 682 + close(values->pipefd[0]); 683 + 684 + zswap_incomp = get_zswap_incomp(test_group); 685 + if (zswap_incomp <= 0) { 686 + long zswpout = get_zswpout(test_group); 687 + long zswapped = cg_read_key_long(test_group, "memory.stat", "zswapped "); 688 + long zswap_b = cg_read_key_long(test_group, "memory.stat", "zswap "); 689 + 690 + ksft_print_msg("zswap_incomp not increased: %ld\n", zswap_incomp); 691 + ksft_print_msg("debug: zswpout=%ld zswapped=%ld zswap_b=%ld\n", 692 + zswpout, zswapped, zswap_b); 693 + ksft_print_msg("debug: madvise ret=%d errno=%d\n", 694 + values->madvise_ret, values->madvise_errno); 695 + goto out_kill; 696 + } 697 + 698 + ret = KSFT_PASS; 699 + 700 + out_kill: 701 + kill(child_pid, SIGTERM); 702 + waitpid(child_pid, &child_status, 0); 703 + out: 704 + cg_destroy(test_group); 705 + free(test_group); 706 + munmap(values, sizeof(struct incomp_child_args)); 707 + return ret; 708 + } 709 + 579 710 #define T(x) { x, #x } 580 711 struct zswap_test { 581 712 int (*fn)(const char *root); ··· 721 586 T(test_zswap_writeback_disabled), 722 587 T(test_no_kmem_bypass), 723 588 T(test_no_invasive_cgroup_shrink), 589 + T(test_zswap_incompressible), 724 590 }; 725 591 #undef T 726 592