Add mmap tests.

Two test programs are added. The run time is about 1min on my el7
instance.

The test script finishes up with a read/write mmap test on offline
extents to verify the data wait paths in those functions.

One program will perform vfs read/write and mmap read/write calls on
the same file from across 5 threads (mounts) repeatedly.  The goal
is to assure there are no locking issues between read/write paths.

The second test program performs consistency checking on a file that is
repeatedly written/read using memory maps and normal reads and writes,
and the content is verified after every operation.

Signed-off-by: Auke Kok <auke.kok@versity.com>
This commit is contained in:
Auke Kok
2025-01-23 14:28:40 -05:00
parent 3788d67101
commit 311bf75902
7 changed files with 430 additions and 2 deletions
+181
View File
@@ -0,0 +1,181 @@
#define _GNU_SOURCE
/*
* mmap() stress test for scoutfs
*
* This test exercises the scoutfs kernel module's locking by
* repeatedly reading/writing using mmap and pread/write calls
* across 5 clients (mounts).
*
* Each thread operates on a single thread/client, and performs
* operations in a random order on the file.
*
* The goal is to assure that locking between _page_mkwrite vfs
* calls and the normal read/write paths do not cause deadlocks.
*
* There is no content validation performed. All that is done is
* assure that the programs continues without errors.
*/
#include <sys/types.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <sys/mman.h>
#include <pthread.h>
#include <errno.h>
static int size = 0;
static int count = 0; /* XXX make this duration instead */
struct thread_info {
int nr;
int fd;
};
static void *run_test_func(void *ptr)
{
void *buf = NULL;
char *addr = NULL;
struct thread_info *tinfo = ptr;
int c = 0;
int fd;
ssize_t read, written, ret;
int preads = 0, pwrites = 0, mreads = 0, mwrites = 0;
fd = tinfo->fd;
if (posix_memalign(&buf, 4096, size) != 0) {
perror("calloc");
exit(-1);
}
addr = mmap(NULL, size, PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap");
exit(-1);
}
usleep(100000); /* 0.1sec to allow all threads to start roughly at the same time */
for (;;) {
if (++c > count)
break;
switch (rand() % 4) {
case 0: /* pread */
preads++;
for (read = 0; read < size;) {
ret = pread(fd, buf, size - read, read);
if (ret < 0) {
perror("pwrite");
exit(-1);
}
read += ret;
}
break;
case 1: /* pwrite */
pwrites++;
memset(buf, (char)(c & 0xff), size);
for (written = 0; written < size;) {
ret = pwrite(fd, buf, size - written, written);
if (ret < 0) {
perror("pwrite");
exit(-1);
}
written += ret;
}
break;
case 2: /* mmap read */
mreads++;
memcpy(buf, addr, size); /* noerr */
break;
case 3: /* mmap write */
mwrites++;
memset(buf, (char)(c & 0xff), size);
memcpy(addr, buf, size); /* noerr */
break;
}
}
munmap(addr, size);
free(buf);
printf("thread %u complete: preads %u pwrites %u mreads %u mwrites %u\n", tinfo->nr,
mreads, mwrites, preads, pwrites);
return NULL;
}
int main(int argc, char **argv)
{
pthread_t thread[5];
struct thread_info tinfo[5];
int fd[5];
int ret;
int i;
if (argc != 8) {
fprintf(stderr, "%s requires 7 arguments - size count file1 file2 file3 file4 file5\n", argv[0]);
exit(-1);
}
size = atoi(argv[1]);
if (size <= 0) {
fprintf(stderr, "invalid size, must be greater than 0\n");
exit(-1);
}
count = atoi(argv[2]);
if (count < 0) {
fprintf(stderr, "invalid count, must be greater than 0\n");
exit(-1);
}
/* create and truncate one fd */
fd[0] = open(argv[3], O_RDWR | O_CREAT | O_TRUNC, 00644);
if (fd[0] < 0) {
perror("open");
exit(-1);
}
/* make it the test size */
if (posix_fallocate(fd[0], 0, size) != 0) {
perror("fallocate");
exit(-1);
}
/* now open the rest of the fds */
for (i = 1; i < 5; i++) {
fd[i] = open(argv[3+i], O_RDWR);
if (fd[i] < 0) {
perror("open");
exit(-1);
}
}
/* start threads */
for (i = 0; i < 5; i++) {
tinfo[i].fd = fd[i];
tinfo[i].nr = i;
ret = pthread_create(&thread[i], NULL, run_test_func, (void*)&tinfo[i]);
if (ret) {
perror("pthread_create");
exit(-1);
}
}
/* wait for complete */
for (i = 0; i < 5; i++)
pthread_join(thread[i], NULL);
for (i = 0; i < 5; i++)
close(fd[i]);
exit(0);
}
+159
View File
@@ -0,0 +1,159 @@
#define _GNU_SOURCE
/*
* mmap() content consistency checking for scoutfs
*
* This test program validates that content from memory mappings
* are consistent across clients, whether written/read with mmap or
* normal writes/reads.
*
* One side of (read/write) will always be memory mapped. It may
* be that both sides do memory mapped (33% of the time).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
static int count = 0;
static int size = 0;
static void run_test_func(int fd1, int fd2)
{
void *buf1 = NULL;
void *buf2 = NULL;
char *addr1 = NULL;
char *addr2 = NULL;
int c = 0;
ssize_t read, written, ret;
/* buffers for both sides to compare */
if (posix_memalign(&buf1, 4096, size) != 0) {
perror("calloc1");
exit(-1);
}
if (posix_memalign(&buf2, 4096, size) != 0) {
perror("calloc1");
exit(-1);
}
/* memory maps for both sides */
addr1 = mmap(NULL, size, PROT_WRITE | PROT_READ, MAP_SHARED, fd1, 0);
if (addr1 == MAP_FAILED) {
perror("mmap1");
exit(-1);
}
addr2 = mmap(NULL, size, PROT_WRITE | PROT_READ, MAP_SHARED, fd2, 0);
if (addr2 == MAP_FAILED) {
perror("mmap2");
exit(-1);
}
for (;;) {
if (++c > count) /* 10k iterations */
break;
/* put a pattern in buf1 */
memset(buf1, c & 0xff, size);
/* pwrite or mmap write from buf1 */
switch (c % 3) {
case 0: /* pwrite */
for (written = 0; written < size;) {
ret = pwrite(fd1, buf1, size - written, written);
if (ret < 0) {
perror("pwrite");
exit(-1);
}
written += ret;
}
break;
default: /* mmap write */
memcpy(addr1, buf1, size);
break;
}
/* pread or mmap read to buf2 */
switch (c % 3) {
case 2: /* pread */
for (read = 0; read < size;) {
ret = pread(fd2, buf2, size - read, read);
if (ret < 0) {
perror("pwrite");
exit(-1);
}
read += ret;
}
break;
default: /* mmap read */
memcpy(buf2, addr2, size);
break;
}
/* compare bufs */
if (memcmp(buf1, buf2, size) != 0) {
fprintf(stderr, "memcmp() failed\n");
exit(-1);
}
}
munmap(addr1, size);
munmap(addr2, size);
free(buf1);
free(buf2);
}
int main(int argc, char **argv)
{
int fd[1];
if (argc != 5) {
fprintf(stderr, "%s requires 4 arguments - size count file1 file2\n", argv[0]);
exit(-1);
}
size = atoi(argv[1]);
if (size <= 0) {
fprintf(stderr, "invalid size, must be greater than 0\n");
exit(-1);
}
count = atoi(argv[2]);
if (count < 3) {
fprintf(stderr, "invalid count, must be greater than 3\n");
exit(-1);
}
/* create and truncate one fd */
fd[0] = open(argv[3], O_RDWR | O_CREAT | O_TRUNC, 00644);
if (fd[0] < 0) {
perror("open");
exit(-1);
}
fd[1] = open(argv[4], O_RDWR , 00644);
if (fd[1] < 0) {
perror("open");
exit(-1);
}
/* make it the test size */
if (posix_fallocate(fd[0], 0, size) != 0) {
perror("fallocate");
exit(-1);
}
/* run the test function */
run_test_func(fd[0], fd[1]);
close(fd[0]);
close(fd[1]);
exit(0);
}