1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) 2012-2017 Red Hat, Inc.
4 */
5
6 /*\
7 * [Description]
8 *
9 * Detect heavy swapping during first time swap use.
10 *
11 * This case is used for testing kernel commit:
12 * 50a15981a1fa ("[S390] reference bit testing for unmapped pages")
13 *
14 * The upstream commit fixed a issue on s390/x platform that heavy
15 * swapping might occur in some condition, however since the patch
16 * was quite general, this testcase will be run on all supported
17 * platforms to ensure no regression been introduced.
18 *
19 * Details of the kernel fix:
20 *
21 * On x86 a page without a mapper is by definition not referenced / old.
22 * The s390 architecture keeps the reference bit in the storage key and
23 * the current code will check the storage key for page without a mapper.
24 * This leads to an interesting effect: the first time an s390 system
25 * needs to write pages to swap it only finds referenced pages. This
26 * causes a lot of pages to get added and written to the swap device.
27 * To avoid this behaviour change page_referenced to query the storage
28 * key only if there is a mapper of the page.
29 *
30 * [Algorithm]
31 *
32 * Try to allocate memory which size is slightly larger than current
33 * available memory. After allocation done, continue loop for a while
34 * and calculate the used swap size. The used swap size should be small
35 * enough, else it indicates that heavy swapping is occurred unexpectedly.
36 */
37
38 #include <sys/types.h>
39 #include <sys/wait.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 #include "tst_safe_stdio.h"
44 #include "mem.h"
45
46 /* allow swapping 1 * phy_mem in maximum */
47 #define COE_DELTA 1
48 /* will try to alloc 1.3 * phy_mem */
49 #define COE_SLIGHT_OVER 0.3
50 #define MEM_SIZE 1024 * 1024
51
52 static void init_meminfo(void);
53 static void do_alloc(int allow_raise);
54 static void check_swapping(void);
55
56 static long mem_available_init;
57 static long swap_free_init;
58 static long mem_over;
59 static long mem_over_max;
60 static pid_t pid;
61 static unsigned int start_runtime;
62
test_swapping(void)63 static void test_swapping(void)
64 {
65 FILE *file;
66 char line[PATH_MAX];
67
68 start_runtime = tst_remaining_runtime();
69
70 file = SAFE_FOPEN("/proc/swaps", "r");
71 while (fgets(line, sizeof(line), file)) {
72 if (strstr(line, "/dev/zram")) {
73 SAFE_FCLOSE(file);
74 tst_brk(TCONF, "zram-swap is being used!");
75 }
76 }
77 SAFE_FCLOSE(file);
78
79 init_meminfo();
80
81 switch (pid = SAFE_FORK()) {
82 case 0:
83 TST_PRINT_MEMINFO();
84 do_alloc(0);
85 TST_PRINT_MEMINFO();
86 do_alloc(1);
87 exit(0);
88 default:
89 check_swapping();
90 }
91 }
92
init_meminfo(void)93 static void init_meminfo(void)
94 {
95 swap_free_init = SAFE_READ_MEMINFO("SwapFree:");
96 mem_available_init = tst_available_mem();
97 mem_over = mem_available_init * COE_SLIGHT_OVER;
98 mem_over_max = mem_available_init * COE_DELTA;
99
100 if (swap_free_init < mem_over_max)
101 tst_brk(TCONF, "Not enough swap space to test: swap_free_init(%ldkB) < mem_over_max(%ldkB)",
102 swap_free_init, mem_over_max);
103 }
104
memset_blocks(char * ptr,int mem_count,int sleep_time_ms)105 static void memset_blocks(char *ptr, int mem_count, int sleep_time_ms) {
106 for (int i = 0; i < mem_count / 1024; i++) {
107 memset(ptr + (i * MEM_SIZE), 1, MEM_SIZE);
108 usleep(sleep_time_ms * 1000);
109 }
110 }
111
do_alloc(int allow_raise)112 static void do_alloc(int allow_raise)
113 {
114 long mem_count;
115 void *s;
116
117 if (allow_raise == 1)
118 tst_res(TINFO, "available physical memory: %ld MB",
119 mem_available_init / 1024);
120
121 mem_count = mem_available_init + mem_over;
122
123 if (allow_raise == 1)
124 tst_res(TINFO, "try to allocate: %ld MB", mem_count / 1024);
125 s = SAFE_MALLOC(mem_count * 1024);
126 memset_blocks(s, mem_count, 1);
127
128 if ((allow_raise == 1) && (raise(SIGSTOP) == -1)) {
129 tst_res(TINFO, "memory allocated: %ld MB", mem_count / 1024);
130 tst_brk(TBROK | TERRNO, "kill");
131 }
132
133 free(s);
134 }
135
check_swapping(void)136 static void check_swapping(void)
137 {
138 int status;
139 long swap_free_now, swapped;
140
141 /* wait child stop */
142 SAFE_WAITPID(pid, &status, WUNTRACED);
143 if (!WIFSTOPPED(status))
144 tst_brk(TBROK, "child was not stopped.");
145
146 /* Still occupying memory, loop for a while */
147 while (tst_remaining_runtime() > start_runtime/2) {
148 swap_free_now = SAFE_READ_MEMINFO("SwapFree:");
149 sleep(1);
150 long diff = labs(swap_free_now - SAFE_READ_MEMINFO("SwapFree:"));
151
152 if (diff < 10)
153 break;
154
155 tst_res(TINFO, "SwapFree difference %li", diff);
156 }
157
158 swapped = SAFE_READ_PROC_STATUS(pid, "VmSwap:");
159 if (swapped > mem_over_max) {
160 TST_PRINT_MEMINFO();
161 kill(pid, SIGCONT);
162 tst_brk(TFAIL, "heavy swapping detected: %ld MB swapped",
163 swapped / 1024);
164 }
165
166 tst_res(TPASS, "no heavy swapping detected, %ld MB swapped.",
167 swapped / 1024);
168 kill(pid, SIGCONT);
169 /* wait child exit */
170 SAFE_WAITPID(pid, &status, 0);
171 }
172
173 static struct tst_test test = {
174 .needs_root = 1,
175 .forks_child = 1,
176 .min_mem_avail = 10,
177 .max_runtime = 600,
178 .test_all = test_swapping,
179 .skip_in_compat = 1,
180 .needs_kconfigs = (const char *[]) {
181 "CONFIG_SWAP=y",
182 NULL
183 },
184 .tags = (const struct tst_tag[]) {
185 {"linux-git", "50a15981a1fa"},
186 {}
187 }
188 };
189