Skip to contentSkip to conversation

Fixes for compaction_test

PATCH Superseded Dev Jain 2 participants 7 messages May 15, 2024 – May 20, 2024

Subsystems (4)

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore

Dev Jain

author
#0 lore raw

cover Fixes for compaction_test

The compaction_test memory selftest introduces fragmentation in memory and then tries to allocate as many hugepages as possible. This series addresses some problems.

First off, correctly set the number of hugepages to zero before trying to set a large number of them.

Now, consider a situation in which, at the start of the test, a non-zero number of hugepages have been already set (while running the entire selftests/mm suite, or manually by the admin). The test operates on 80% of memory to avoid OOM-killer invocation, and because some memory is already blocked by hugepages, it would increase the chance of OOM-killing. Also, since mem_free used in check_compaction() is the value before we set nr_hugepages to zero, the chance that the compaction_index will be small is very high if the preset nr_hugepages was high, leading to a bogus test success.

This series applies on top of the stable 6.9 kernel.

Dev Jain (2):
selftests/mm: compaction_test: Fix incorrect write of zero to
nr_hugepages
selftests/mm: compaction_test: Fix trivial test success and reduce
probability of OOM-killer invocation

 tools/testing/selftests/mm/compaction_test.c | 70 ++++++++++++++------
 1 file changed, 50 insertions(+), 20 deletions(-)
Signature · 1 line
2.30.2
author
#1 lore raw

PATCH 1/2 selftests/mm: compaction_test: Fix incorrect write of zero to nr_hugepages

nr_hugepages is not set to zero because the file offset has not been reset after read(). Fix that using lseek().

Fixes bd67d5c15cc1 ("Test compaction of mlocked memory")

---
Merge dependency: https://lore.kernel.org/all/[email protected]/
Andrew, does it sound reasonable to have the fixes tag in the above
patch too, along with this series?

 tools/testing/selftests/mm/compaction_test.c | 2 ++
 1 file changed, 2 insertions(+)
tools/testing/selftests/mm/compaction_test.c +2 -0
index 533999b6c284..c5be395f8363 100644
@@ -107,6 +107,8 @@ int check_compaction(unsigned long mem_free, unsigned int hugepage_size)
 		goto close_fd;
 	}
 
+	lseek(fd, 0, SEEK_SET);
+
 	/* Start with the initial condition of 0 huge pages*/
 	if (write(fd, "0", sizeof(char)) != sizeof(char)) {
 		ksft_print_msg("Failed to write 0 to /proc/sys/vm/nr_hugepages: %s\n",
Signature · 1 line
2.30.2
maintainer
#2 lore raw

Re: [PATCH 1/2] selftests/mm: compaction_test: Fix incorrect write of zero to nr_hugepages

Quoted 3 lines · On Wed, 15 May 2024 15:06:32 +0530 Dev Jain <[email protected]> wrote:

nr_hugepages is not set to zero because the file offset has not been reset after read(). Fix that using lseek().

Please fully describe the runtime effects of this bug.

author
#3 lore raw

Re: [PATCH 1/2] selftests/mm: compaction_test: Fix incorrect write of zero to nr_hugepages

Quoted 6 lines · Andrew Morton
Quoted 3 lines · On Wed, 15 May 2024 15:06:32 +0530 Dev Jain <[email protected]> wrote:

nr_hugepages is not set to zero because the file offset has not been reset after read(). Fix that using lseek().

Please fully describe the runtime effects of this bug.

This is not a "bug", but a discrepancy; the following comment

by the author says "Start with the initial condition of 0 huge

pages", I am just ensuring that that is actually done. Although,

I am not sure about the utility of doing this in the first place,

since we are anyways trying to increase hugepages after that.

In the second patch, I have moved away this entire logic of

setting nr_hugepages to zero, to the place before we start

filling up memory; if you feel that this patch is unnecessary,

we may squash it.

author
#4 lore raw

PATCH 2/2 selftests/mm: compaction_test: Fix trivial test success and reduce probability of OOM-killer invocation

In reply to Dev Jain

Reset nr_hugepages to zero before the start of the test.

If a non-zero number of hugepages is already set before the start of the test, the following problems arise:

- The probability of the test getting OOM-killed increases.
Proof: The test wants to run on 80% of available memory to prevent OOM-killing (see original code comments). Let the value of mem_free at the start of the test, when nr_hugepages = 0, be x. In the other case, when nr_hugepages > 0, let the memory consumed by hugepages be y. In the former case, the test operates on 0.8 * x of memory. In the latter, the test operates on 0.8 * (x - y) of memory, with y already filled, hence, memory consumed is y + 0.8 * (x - y) = 0.8 * x + 0.2 * y > 0.8 * x. Q.E.D

- The probability of a bogus test success increases.
Proof: Let the memory consumed by hugepages be greater than 25% of x, with x and y defined as above. The definition of compaction_index is c_index = (x - y)/z where z is the memory consumed by hugepages after trying to increase them again. In check_compaction(), we set the number of hugepages to zero, and then increase them back; the probability that they will be set back to consume at least y amount of memory again is very high (since there is not much delay between the two attempts of changing nr_hugepages). Hence, z >= y > (x/4) (by the 25% assumption). Therefore,
c_index = (x - y)/z <= (x - y)/y = x/y - 1 < 4 - 1 = 3 hence, c_index can always be forced to be less than 3, thereby the test succeeding always. Q.E.D

NOTE: This patch depends on the previous one.

Fixes bd67d5c15cc1 ("Test compaction of mlocked memory")
---
 tools/testing/selftests/mm/compaction_test.c | 72 ++++++++++++++------
 1 file changed, 50 insertions(+), 22 deletions(-)
tools/testing/selftests/mm/compaction_test.c +50 -22
index c5be395f8363..2ae059989771 100644
@@ -82,12 +82,15 @@ int prereq(void)
 	return -1;
 }
 
-int check_compaction(unsigned long mem_free, unsigned int hugepage_size)
+int check_compaction(unsigned long mem_free, unsigned int hugepage_size,
+		     int initial_nr_hugepages)
 {
 	int fd, ret = -1;
 	int compaction_index = 0;
-	char initial_nr_hugepages[10] = {0};
 	char nr_hugepages[10] = {0};
+	char init_nr_hugepages[10] = {0};
+
+	sprintf(init_nr_hugepages, "%d", initial_nr_hugepages);
 
 	/* We want to test with 80% of available memory. Else, OOM killer comes
 	   in to play */
@@ -101,23 +104,6 @@ int check_compaction(unsigned long mem_free, unsigned int hugepage_size)
 		goto out;
 	}
 
-	if (read(fd, initial_nr_hugepages, sizeof(initial_nr_hugepages)) <= 0) {
-		ksft_print_msg("Failed to read from /proc/sys/vm/nr_hugepages: %s\n",
-			       strerror(errno));
-		goto close_fd;
-	}
-
-	lseek(fd, 0, SEEK_SET);
-
-	/* Start with the initial condition of 0 huge pages*/
-	if (write(fd, "0", sizeof(char)) != sizeof(char)) {
-		ksft_print_msg("Failed to write 0 to /proc/sys/vm/nr_hugepages: %s\n",
-			       strerror(errno));
-		goto close_fd;
-	}
-
-	lseek(fd, 0, SEEK_SET);
-
 	/* Request a large number of huge pages. The Kernel will allocate
 	   as much as it can */
 	if (write(fd, "100000", (6*sizeof(char))) != (6*sizeof(char))) {
@@ -140,8 +126,8 @@ int check_compaction(unsigned long mem_free, unsigned int hugepage_size)
 
 	lseek(fd, 0, SEEK_SET);
 
-	if (write(fd, initial_nr_hugepages, strlen(initial_nr_hugepages))
-	    != strlen(initial_nr_hugepages)) {
+	if (write(fd, init_nr_hugepages, strlen(init_nr_hugepages))
+	    != strlen(init_nr_hugepages)) {
 		ksft_print_msg("Failed to write value to /proc/sys/vm/nr_hugepages: %s\n",
 			       strerror(errno));
 		goto close_fd;
@@ -165,6 +151,42 @@ int check_compaction(unsigned long mem_free, unsigned int hugepage_size)
 	return ret;
 }
 
+int set_zero_hugepages(int *initial_nr_hugepages)
+{
+	int fd, ret = -1;
+	char nr_hugepages[10] = {0};
+
+	fd = open("/proc/sys/vm/nr_hugepages", O_RDWR | O_NONBLOCK);
+	if (fd < 0) {
+		ksft_print_msg("Failed to open /proc/sys/vm/nr_hugepages: %s\n",
+			       strerror(errno));
+		goto out;
+	}
+
+	if (read(fd, nr_hugepages, sizeof(nr_hugepages)) <= 0) {
+		ksft_print_msg("Failed to read from /proc/sys/vm/nr_hugepages: %s\n",
+			       strerror(errno));
+		goto close_fd;
+	}
+
+	lseek(fd, 0, SEEK_SET);
+
+	/* Start with the initial condition of 0 huge pages */
+	if (write(fd, "0", sizeof(char)) != sizeof(char)) {
+		ksft_print_msg("Failed to write 0 to /proc/sys/vm/nr_hugepages: %s\n",
+			       strerror(errno));
+		goto close_fd;
+	}
+
+	*initial_nr_hugepages = atoi(nr_hugepages);
+	ret = 0;
+
+ close_fd:
+	close(fd);
+
+ out:
+	return ret;
+}
 
 int main(int argc, char **argv)
 {
@@ -175,6 +197,7 @@ int main(int argc, char **argv)
 	unsigned long mem_free = 0;
 	unsigned long hugepage_size = 0;
 	long mem_fragmentable_MB = 0;
+	int initial_nr_hugepages;
 
 	ksft_print_header();
 
@@ -183,6 +206,10 @@ int main(int argc, char **argv)
 
 	ksft_set_plan(1);
 
+	/* start the test without hugepages reducing mem_free */
+	if (set_zero_hugepages(&initial_nr_hugepages))
+		return ksft_exit_fail();
+
 	lim.rlim_cur = RLIM_INFINITY;
 	lim.rlim_max = RLIM_INFINITY;
 	if (setrlimit(RLIMIT_MEMLOCK, &lim))
@@ -226,7 +253,8 @@ int main(int argc, char **argv)
 		entry = entry->next;
 	}
 
-	if (check_compaction(mem_free, hugepage_size) == 0)
+	if (check_compaction(mem_free, hugepage_size,
+			     initial_nr_hugepages) == 0)
 		return ksft_exit_pass();
 
 	return ksft_exit_fail();
Signature · 1 line
2.30.2
maintainer
#5 lore raw

Re: [PATCH 2/2] selftests/mm: compaction_test: Fix trivial test success and reduce probability of OOM-killer invocation

Quoted 31 lines · On Wed, 15 May 2024 15:06:33 +0530 Dev Jain <[email protected]> wrote:

Reset nr_hugepages to zero before the start of the test.

If a non-zero number of hugepages is already set before the start of the test, the following problems arise:

- The probability of the test getting OOM-killed increases.
Proof: The test wants to run on 80% of available memory to prevent OOM-killing (see original code comments). Let the value of mem_free at the start of the test, when nr_hugepages = 0, be x. In the other case, when nr_hugepages > 0, let the memory consumed by hugepages be y. In the former case, the test operates on 0.8 * x of memory. In the latter, the test operates on 0.8 * (x - y) of memory, with y already filled, hence, memory consumed is y + 0.8 * (x - y) = 0.8 * x + 0.2 * y > 0.8 * x. Q.E.D

- The probability of a bogus test success increases.
Proof: Let the memory consumed by hugepages be greater than 25% of x, with x and y defined as above. The definition of compaction_index is c_index = (x - y)/z where z is the memory consumed by hugepages after trying to increase them again. In check_compaction(), we set the number of hugepages to zero, and then increase them back; the probability that they will be set back to consume at least y amount of memory again is very high (since there is not much delay between the two attempts of changing nr_hugepages). Hence, z >= y > (x/4) (by the 25% assumption). Therefore,
c_index = (x - y)/z <= (x - y)/y = x/y - 1 < 4 - 1 = 3 hence, c_index can always be forced to be less than 3, thereby the test succeeding always. Q.E.D

NOTE: This patch depends on the previous one.

tools/testing/selftests/mm/compaction_test.c +0 -1
-int check_compaction(unsigned long mem_free, unsigned int hugepage_size)

tools/testing/selftests/mm/compaction_test.c int prereq(void) · line 93

Quoted 10 lines
tools/testing/selftests/mm/compaction_test.c +5 -1
+int check_compaction(unsigned long mem_free, unsigned int hugepage_size,
+		     int initial_nr_hugepages)
 {
 	int fd, ret = -1;
 	int compaction_index = 0;
-	char initial_nr_hugepages[10] = {0};
 	char nr_hugepages[10] = {0};
+	char init_nr_hugepages[10] = {0};
+
+	sprintf(init_nr_hugepages, "%d", initial_nr_hugepages);

Well, [10] isn't really large enough. "-1111111111" requires 12 chars, with the trailing \0. And I'd suggest an unsigned type and a %u - negative initial_nr_hugepages doesn't make a lot of sense.

tools/testing/selftests/mm/compaction_test.c int check_compaction(unsigned long mem_free, unsigned int hugepage_size) · line 157

Quoted 5 lines
tools/testing/selftests/mm/compaction_test.c +4 -0
+int set_zero_hugepages(int *initial_nr_hugepages)
+{
+	int fd, ret = -1;
+	char nr_hugepages[10] = {0};

Ditto?

Quoted 31 lines
tools/testing/selftests/mm/compaction_test.c +31 -0
+	fd = open("/proc/sys/vm/nr_hugepages", O_RDWR | O_NONBLOCK);
+	if (fd < 0) {
+		ksft_print_msg("Failed to open /proc/sys/vm/nr_hugepages: %s\n",
+			       strerror(errno));
+		goto out;
+	}
+
+	if (read(fd, nr_hugepages, sizeof(nr_hugepages)) <= 0) {
+		ksft_print_msg("Failed to read from /proc/sys/vm/nr_hugepages: %s\n",
+			       strerror(errno));
+		goto close_fd;
+	}
+
+	lseek(fd, 0, SEEK_SET);
+
+	/* Start with the initial condition of 0 huge pages */
+	if (write(fd, "0", sizeof(char)) != sizeof(char)) {
+		ksft_print_msg("Failed to write 0 to /proc/sys/vm/nr_hugepages: %s\n",
+			       strerror(errno));
+		goto close_fd;
+	}
+
+	*initial_nr_hugepages = atoi(nr_hugepages);
+	ret = 0;
+
+ close_fd:
+	close(fd);
+
+ out:
+	return ret;
+}
author
#6 lore raw

Re: [PATCH 2/2] selftests/mm: compaction_test: Fix trivial test success and reduce probability of OOM-killer invocation

Quoted 48 lines · Andrew Morton
Quoted 31 lines · On Wed, 15 May 2024 15:06:33 +0530 Dev Jain <[email protected]> wrote:

Reset nr_hugepages to zero before the start of the test.

If a non-zero number of hugepages is already set before the start of the test, the following problems arise:

- The probability of the test getting OOM-killed increases.
Proof: The test wants to run on 80% of available memory to prevent OOM-killing (see original code comments). Let the value of mem_free at the start of the test, when nr_hugepages = 0, be x. In the other case, when nr_hugepages > 0, let the memory consumed by hugepages be y. In the former case, the test operates on 0.8 * x of memory. In the latter, the test operates on 0.8 * (x - y) of memory, with y already filled, hence, memory consumed is y + 0.8 * (x - y) = 0.8 * x + 0.2 * y > 0.8 * x. Q.E.D

- The probability of a bogus test success increases.
Proof: Let the memory consumed by hugepages be greater than 25% of x, with x and y defined as above. The definition of compaction_index is c_index = (x - y)/z where z is the memory consumed by hugepages after trying to increase them again. In check_compaction(), we set the number of hugepages to zero, and then increase them back; the probability that they will be set back to consume at least y amount of memory again is very high (since there is not much delay between the two attempts of changing nr_hugepages). Hence, z >= y > (x/4) (by the 25% assumption). Therefore,
c_index = (x - y)/z <= (x - y)/y = x/y - 1 < 4 - 1 = 3 hence, c_index can always be forced to be less than 3, thereby the test succeeding always. Q.E.D

NOTE: This patch depends on the previous one.

tools/testing/selftests/mm/compaction_test.c +0 -1
-int check_compaction(unsigned long mem_free, unsigned int hugepage_size)

tools/testing/selftests/mm/compaction_test.c int prereq(void) · line 93

Quoted 10 lines
tools/testing/selftests/mm/compaction_test.c +5 -1
+int check_compaction(unsigned long mem_free, unsigned int hugepage_size,
+		     int initial_nr_hugepages)
  {
  	int fd, ret = -1;
  	int compaction_index = 0;
-	char initial_nr_hugepages[10] = {0};
  	char nr_hugepages[10] = {0};
+	char init_nr_hugepages[10] = {0};
+
+	sprintf(init_nr_hugepages, "%d", initial_nr_hugepages);

Well, [10] isn't really large enough. "-1111111111" requires 12 chars, with the trailing \0. And I'd suggest an unsigned type and a %u - negative initial_nr_hugepages doesn't make a lot of sense.

Quoted 1 line
Quoted 5 lines

tools/testing/selftests/mm/compaction_test.c int check_compaction(unsigned long mem_free, unsigned int hugepage_size) · line 157

Quoted 4 lines
tools/testing/selftests/mm/compaction_test.c +4 -0
+int set_zero_hugepages(int *initial_nr_hugepages)
+{
+	int fd, ret = -1;
+	char nr_hugepages[10] = {0};

Ditto?

Sure, makes sense. I'll just change that to 20 and make it unsigned.

Quoted 32 lines
Quoted 32 lines
tools/testing/selftests/mm/compaction_test.c +31 -0
+	fd = open("/proc/sys/vm/nr_hugepages", O_RDWR | O_NONBLOCK);
+	if (fd < 0) {
+		ksft_print_msg("Failed to open /proc/sys/vm/nr_hugepages: %s\n",
+			       strerror(errno));
+		goto out;
+	}
+
+	if (read(fd, nr_hugepages, sizeof(nr_hugepages)) <= 0) {
+		ksft_print_msg("Failed to read from /proc/sys/vm/nr_hugepages: %s\n",
+			       strerror(errno));
+		goto close_fd;
+	}
+
+	lseek(fd, 0, SEEK_SET);
+
+	/* Start with the initial condition of 0 huge pages */
+	if (write(fd, "0", sizeof(char)) != sizeof(char)) {
+		ksft_print_msg("Failed to write 0 to /proc/sys/vm/nr_hugepages: %s\n",
+			       strerror(errno));
+		goto close_fd;
+	}
+
+	*initial_nr_hugepages = atoi(nr_hugepages);
+	ret = 0;
+
+ close_fd:
+	close(fd);
+
+ out:
+	return ret;
+}