From 32a4e0d84d5b068dd9c8628e0e7f1dd0e2305b27 Mon Sep 17 00:00:00 2001 From: Ben Gras Date: Fri, 12 Apr 2013 20:54:42 +0000 Subject: [PATCH] many new tests . test70: regression test for m_out vfs race condition The following tests use testcache.c to generate test i/o patterns, generate random write data and verify the reads. . test71: blackbox full-stack test of FS operation, testing using the regular VFS interface crazy i/o patterns with various working set sizes, triggering only primary cache, also secondary cache, and finally disk i/o and verifying contents all the time . test72: unit test of libminixfs, implementing functions it needs from -lsys and -lblockdriver and the client in order to simulate a working cache client and backend environment. . test73: blackbox test of secondary vm cache in isolation Change-Id: I1287e9753182b8719e634917ad158e3c1e079ceb --- distrib/sets/lists/minix/mi | 6 + include/minix/libminixfs.h | 1 + lib/libminixfs/cache.c | 7 +- servers/vm/region.c | 6 +- test/Makefile | 12 +- test/common.c | 48 ++++++ test/common.h | 4 +- test/run | 4 +- test/test70.c | 84 ++++++++++ test/test71.c | 131 +++++++++++++++ test/test72.c | 306 ++++++++++++++++++++++++++++++++++++ test/test73.c | 89 +++++++++++ test/testcache.c | 217 +++++++++++++++++++++++++ test/testcache.h | 18 +++ test/testvm.c | 171 ++++++++++++++++++++ test/testvm.conf | 11 ++ test/testvm.h | 8 + 17 files changed, 1115 insertions(+), 8 deletions(-) create mode 100644 test/test70.c create mode 100644 test/test71.c create mode 100644 test/test72.c create mode 100644 test/test73.c create mode 100644 test/testcache.c create mode 100644 test/testcache.h create mode 100644 test/testvm.c create mode 100644 test/testvm.conf create mode 100644 test/testvm.h diff --git a/distrib/sets/lists/minix/mi b/distrib/sets/lists/minix/mi index 1a702bff3..0a77844c2 100644 --- a/distrib/sets/lists/minix/mi +++ b/distrib/sets/lists/minix/mi @@ -4623,12 +4623,18 @@ ./usr/tests/minix-posix/test67 minix-sys ./usr/tests/minix-posix/test68 minix-sys ./usr/tests/minix-posix/test69 minix-sys +./usr/tests/minix-posix/test70 minix-sys +./usr/tests/minix-posix/test71 minix-sys +./usr/tests/minix-posix/test72 minix-sys +./usr/tests/minix-posix/test73 minix-sys ./usr/tests/minix-posix/test7 minix-sys ./usr/tests/minix-posix/test8 minix-sys ./usr/tests/minix-posix/test9 minix-sys ./usr/tests/minix-posix/testinterp minix-sys ./usr/tests/minix-posix/testsh1 minix-sys ./usr/tests/minix-posix/testsh2 minix-sys +./usr/tests/minix-posix/testvm minix-sys +./usr/tests/minix-posix/testvm.conf minix-sys ./usr/tmp minix-sys ./usr/var/db minix-sys ./usr/var/db/pkg minix-sys diff --git a/include/minix/libminixfs.h b/include/minix/libminixfs.h index d68ef5e79..bbeda8877 100644 --- a/include/minix/libminixfs.h +++ b/include/minix/libminixfs.h @@ -45,6 +45,7 @@ struct buf *lmfs_get_block(dev_t dev, block_t block,int only_search); void lmfs_invalidate(dev_t device); void lmfs_put_block(struct buf *bp, int block_type); void lmfs_rw_scattered(dev_t, struct buf **, int, int); +void lmfs_setquiet(int q); /* calls that libminixfs does into fs */ void fs_blockstats(u32_t *blocks, u32_t *free, u32_t *used); diff --git a/lib/libminixfs/cache.c b/lib/libminixfs/cache.c index 2772db091..aa7999260 100644 --- a/lib/libminixfs/cache.c +++ b/lib/libminixfs/cache.c @@ -43,6 +43,11 @@ static int fs_block_size = 1024; /* raw i/o block size */ static int rdwt_err; +static int quiet = 0; + +void +lmfs_setquiet(int q) { quiet = q; } + u32_t fs_bufs_heuristic(int minbufs, u32_t btotal, u32_t bfree, int blocksize, dev_t majordev) { @@ -66,7 +71,7 @@ u32_t fs_bufs_heuristic(int minbufs, u32_t btotal, u32_t bfree, */ if((vm_info_stats(&vsi) != OK)) { bufs = 1024; - printf("fslib: heuristic info fail: default to %d bufs\n", bufs); + if(!quiet) printf("fslib: heuristic info fail: default to %d bufs\n", bufs); return bufs; } diff --git a/servers/vm/region.c b/servers/vm/region.c index 5f46a0fa4..1733eafa7 100644 --- a/servers/vm/region.c +++ b/servers/vm/region.c @@ -1876,7 +1876,7 @@ int do_yieldblockgetblock(message *m) endpoint_t caller = m->m_source; struct vmproc *vmp; yielded_t *yb = NULL; - int r = ESRCH; + int r = OK; int pages; if(vm_isokendpt(caller, &n) != OK) @@ -1901,11 +1901,11 @@ int do_yieldblockgetblock(message *m) if(cmp64(yieldid, VM_BLOCKID_NONE) != 0) { /* A block was given to yield. */ - yieldblock(vmp, yieldid, (vir_bytes) m->VMYBGB_VADDR, &yb, + r = yieldblock(vmp, yieldid, (vir_bytes) m->VMYBGB_VADDR, &yb, pages); } - if(cmp64(getid, VM_BLOCKID_NONE) != 0) { + if(r == OK && cmp64(getid, VM_BLOCKID_NONE) != 0) { /* A block was given to get. */ r = getblock(vmp, getid, (vir_bytes) m->VMYBGB_VADDR, pages); } diff --git a/test/Makefile b/test/Makefile index ceea1188e..baeb47c9a 100644 --- a/test/Makefile +++ b/test/Makefile @@ -31,12 +31,22 @@ LDFLAGS.mod= -shared # make shared object # Some have an extra file OBJS.test57= test57loop.o +# Cache testing programs +OBJS.test71+= testcache.o +OBJS.test72+= testcache.o +LDADD.test72+= -lminixfs +PROGS += testvm +OBJS.testvm+= testcache.o +LDADD.testvm+= -lsys -ltimers -lminlib -static + +FILES += testvm.conf + # Tests to compile, For every architecture MINIX_TESTS= \ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \ 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \ 41 42 43 44 45 46 48 49 50 52 53 54 55 56 58 59 60 \ -61 64 65 66 67 68 69 +61 64 65 66 67 68 69 70 71 72 73 .if ${MACHINE_ARCH} == "i386" MINIX_TESTS+= \ diff --git a/test/common.c b/test/common.c index 3ccb6700b..ca6725843 100644 --- a/test/common.c +++ b/test/common.c @@ -14,6 +14,8 @@ int common_test_nr = -1, errct = 0, subtest; +int quietflag = 1; + /* provide a default max_error symbol as Max_error with a value * of 5. The test program can override it wit its own max_error * symbol if it wants that this code will then use instead. @@ -130,3 +132,49 @@ void quit() exit(1); } } + +void +printprogress(char *msg, int i, int max) +{ + int use_i = i + 1; + static time_t start_time, prev_time; + static int prev_i; + time_t now; + + if(quietflag) return; + + time(&now); + if(prev_i >= i) start_time = now; + + if(now > start_time && prev_time < now) { + double i_per_sec = i / (now - start_time); + int remain_secs; + + remain_secs = (int)((max-i) / i_per_sec); + + fprintf(stderr, "%-35s %7d/%7d %3d%% ETA %3ds\r", msg, + use_i, (max), use_i*100/(max), remain_secs); + fflush(stderr); + } + + if(use_i >= max) { + fprintf(stderr, "%-35s done \n", msg); + } + + prev_i = i; + prev_time = now; +} + +void getmem(u32_t *total, u32_t *free, u32_t *cached) +{ + u32_t pagesize, largest; + FILE *f = fopen("/proc/meminfo", "r"); + if(!f) return; + if(fscanf(f, "%u %u %u %u %u", &pagesize, total, free, + &largest, cached) != 5) { + fprintf(stderr, "fscanf of meminfo failed\n"); + exit(1); + } + fclose(f); +} + diff --git a/test/common.h b/test/common.h index d98bf30be..69e3237f5 100644 --- a/test/common.h +++ b/test/common.h @@ -10,6 +10,8 @@ #define e(errn) e_f(__FILE__, __LINE__, (errn)) #define em(errn,msg) do { fprintf(stderr, "%s\n", msg); e(errn); } while(0) +#define BIGVARNAME "BIGTEST" + void printprogress(char *msg, int i, int max); void cleanup(void); int does_fs_truncate(void); @@ -19,6 +21,6 @@ void quit(void); void rm_rf_dir(int test_nr); void rm_rf_ppdir(int test_nr); void start(int test_nr); +void getmem(u32_t *total, u32_t *free, u32_t *cached); extern int common_test_nr, errct, subtest; - diff --git a/test/run b/test/run index 9bf5c94cb..7abad9d53 100755 --- a/test/run +++ b/test/run @@ -19,12 +19,12 @@ badones= # list of tests that failed # Tests which require setuid setuids="test11 test33 test43 test44 test46 test56 test60 test61 test65 \ - test69" + test69 test73" alltests=" 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \ 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \ 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 \ - 61 62 63 64 65 66 67 68 69\ + 61 62 63 64 65 66 67 68 69 70 71 72 73 \ sh1.sh sh2.sh interp.sh" tests_no=`expr 0` diff --git a/test/test70.c b/test/test70.c new file mode 100644 index 000000000..c4c584d4c --- /dev/null +++ b/test/test70.c @@ -0,0 +1,84 @@ +/* Test 70 - regression test for m_out vfs race condition. + * + * Regression test for vfs overwriting m_out fields by competing threads. + * lseek() uses one of these fields too so this test performs concurrent + * lseek()s to trigger this situation. + * + * The program consists of 2 processes, each seeking to different ranges in + * a test file. The bug would return the wrong value in one of the messaeg + * fields so the lseek() return value would be wrong sometimes. + * + * The first instance seeks from 0 to SEEKWINDOW, the other instance seeks + * from SEEKWINDOW to SEEKWINDOW+SEEKWINDOW. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +#define SEEKWINDOW 1000 + +static int +doseeks(int seekbase) +{ + char template[30] = "tempfile.XXXXXXXX"; + int iteration, fd = mkstemp(template); + int limit = seekbase + SEEKWINDOW; + + /* make a temporary file, unlink it so it's always gone + * afterwards, and make it the size we need. + */ + if(fd < 0) { perror("mkstemp"); e(2); return 1; } + if(unlink(template) < 0) { perror("unlink"); e(3); return 1; } + if(ftruncate(fd, limit) < 0) { perror("ftruncate"); e(4); return 1; } + + /* try lseek() lots of times with different arguments and make + * sure we get the right return value back, while this happens + * in a concurrent process too. + */ +#define ITERATIONS 50000 + for(iteration = 0; iteration < ITERATIONS; iteration++) { + int o; + for(o = seekbase; o < limit; o++) { + int r; + if((r=lseek(fd, o, SEEK_SET)) != o) { + if(r < 0) perror("lseek"); + fprintf(stderr, "%d/%d %d != %d\n", + iteration, ITERATIONS, r, o); + e(5); + return 1; + } + } + } + + return 0; +} + +int +main() +{ + start(70); + pid_t f; + int result; + + if((f=fork()) < 0) { e(1); quit(); } + + if(f == 0) { exit(doseeks(0)); } + + if(doseeks(SEEKWINDOW)) { e(10); } + + if (waitpid(f, &result, 0) == -1) e(11); + if (WEXITSTATUS(result) != 0) e(12); + + quit(); + + return(-1); /* impossible */ +} + diff --git a/test/test71.c b/test/test71.c new file mode 100644 index 000000000..94b7b4b42 --- /dev/null +++ b/test/test71.c @@ -0,0 +1,131 @@ +/* Test 71 - full hierachy storage test. + * + * Black box test of storage: test consistent file contents + * under various working sets and access patterns. + * + * Using varying working set sizes, exercise various cache + * layers separately. + * + * There is a 'smoke test' mode, suitable for running interactively, + * and a 'regression test' (big) mode, meant for batch invocation only + * as it takes very long. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "testcache.h" + +/* we want to flexibly split this test over multiple files + * - for big working sets we might run over the 2GB MFS file limit + * - we might want to test the FS being able to handle lots of + * files / unusual metadata situations + */ +#define MBPERFILE 100 +#define MB (1024*1024) +#define MAXFILES ((u64_t) MAXBLOCKS * MAXBLOCKSIZE / MB / MBPERFILE + 1) + +static int fds[MAXFILES]; + +static void +get_fd_offset(int b, int blocksize, u64_t *file_offset, int *fd) +{ + u64_t offset = (u64_t) b * blocksize; + int filenumber; + + filenumber = offset / MB / MBPERFILE; + + assert(filenumber >= 0 && filenumber < MAXFILES); + assert(fds[filenumber] > 0); + + *fd = fds[filenumber]; + *file_offset = offset - (filenumber * MBPERFILE * MB); +} + +int +dowriteblock(int b, int blocksize, u32_t seed, char *data) +{ + u64_t offset; + int fd; + + get_fd_offset(b, blocksize, &offset, &fd); + + if(pwrite(fd, data, blocksize, offset) < blocksize) { + perror("pwrite"); + return -1; + } + + return blocksize; +} + +int +readblock(int b, int blocksize, u32_t seed, char *data) +{ + u64_t offset; + int fd; + + get_fd_offset(b, blocksize, &offset, &fd); + + if(pread(fd, data, blocksize, offset) < blocksize) { + perror("pread"); + return -1; + } + + return blocksize; +} + +void testend(void) { } + +int +main(int argc, char *argv[]) +{ + int f, big = !!getenv(BIGVARNAME), iter = 2; + + start(71); + + cachequiet(!big); + if(big) iter = 3; + + for(f = 0; f < MAXFILES; f++) { + char tempfilename[] = "cachetest.XXXXXXXX"; + fds[f] = mkstemp(tempfilename); + if(fds[f] < 0) { perror("mkstemp"); e(20); return 1; } + assert(fds[f] > 0); + } + + /* Try various combinations working set sizes + * and block sizes in order to specifically + * target the primary cache, then primary+secondary + * cache, then primary+secondary cache+secondary + * cache eviction. + */ + + if(dotest(PAGE_SIZE, 100, iter)) e(5); + if(dotest(PAGE_SIZE*2, 100, iter)) e(2); + if(dotest(PAGE_SIZE*3, 100, iter)) e(3); + if(dotest(PAGE_SIZE, 20000, iter)) e(5); + + if(big) { + u32_t totalmem, freemem, cachedmem; + if(dotest(PAGE_SIZE, 150000, iter)) e(5); + getmem(&totalmem, &freemem, &cachedmem); + if(dotest(PAGE_SIZE, totalmem*1.5, iter)) e(6); + } + + for(f = 0; f < MAXFILES; f++) { + assert(fds[f] > 0); + close(fds[f]); + } + + quit(); + + return 0; +} + diff --git a/test/test72.c b/test/test72.c new file mode 100644 index 000000000..f376497ca --- /dev/null +++ b/test/test72.c @@ -0,0 +1,306 @@ +/* Test 72 - libminixfs unit test. + * + * Exercise the caching functionality of libminixfs in isolation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int max_error = 0; + +#include "common.h" +#include "testcache.h" + +#define MYMAJOR 40 /* doesn't really matter, shouldn't be NO_DEV though */ + +#define MYDEV makedev(MYMAJOR, 1) + +static int curblocksize = -1; + +static char *writtenblocks[MAXBLOCKS]; + +/* Some functions used by testcache.c */ + +int +dowriteblock(int b, int blocksize, u32_t seed, char *data) +{ + struct buf *bp; + + assert(blocksize == curblocksize); + + if(!(bp = lmfs_get_block(MYDEV, b, NORMAL))) { + e(30); + return 0; + } + + memcpy(bp->data, data, blocksize); + + lmfs_markdirty(bp); + + lmfs_put_block(bp, FULL_DATA_BLOCK); + + return blocksize; +} + +int +readblock(int b, int blocksize, u32_t seed, char *data) +{ + struct buf *bp; + + assert(blocksize == curblocksize); + + if(!(bp = lmfs_get_block(MYDEV, b, NORMAL))) { + e(30); + return 0; + } + + memcpy(data, bp->data, blocksize); + + lmfs_put_block(bp, FULL_DATA_BLOCK); + + return blocksize; +} + +void testend(void) +{ + int i; + for(i = 0; i < MAXBLOCKS; i++) { + if(writtenblocks[i]) { + free(writtenblocks[i]); + writtenblocks[i] = NULL; + } + } +} + +/* Fake some libminixfs client functions */ + +int +fs_sync(void) +{ + return 0; +} + +void +fs_blockstats(u32_t *total, u32_t *free, u32_t *used) +{ + *total = *free = *used = 0; +} + +static void allocate(int b) +{ + assert(curblocksize > 0); + assert(!writtenblocks[b]); + if(!(writtenblocks[b] = calloc(1, curblocksize))) { + fprintf(stderr, "out of memory allocating block %d\n", b); + exit(1); + } +} + +/* Fake some libblockdriver functions */ +ssize_t +bdev_gather(dev_t dev, u64_t pos, iovec_t *vec, int count, int flags) +{ + int i, block; + ssize_t tot = 0; + assert(dev == MYDEV); + assert(curblocksize > 0); + assert(!(pos % curblocksize)); + block = pos / curblocksize; + for(i = 0; i < count; i++) { + int subblocks; + char *data = (char *) vec[i].iov_addr; + assert(vec[i].iov_size > 0); + assert(!(vec[i].iov_size % curblocksize)); + subblocks = vec[i].iov_size / curblocksize; + while(subblocks > 0) { + assert(block > 0); + assert(block < MAXBLOCKS); + if(!writtenblocks[block]) { + allocate(block); + } + memcpy(data, writtenblocks[block], curblocksize); + block++; + subblocks--; + data += curblocksize; + tot += curblocksize; + } + } + + return tot; +} + +ssize_t +bdev_scatter(dev_t dev, u64_t pos, iovec_t *vec, int count, int flags) +{ + int i, block; + ssize_t tot = 0; + assert(dev == MYDEV); + assert(curblocksize > 0); + assert(!(pos % curblocksize)); + block = pos / curblocksize; + for(i = 0; i < count; i++) { + int subblocks; + char *data = (char *) vec[i].iov_addr; + assert(vec[i].iov_size > 0); + assert(!(vec[i].iov_size % curblocksize)); + subblocks = vec[i].iov_size / curblocksize; + while(subblocks > 0) { + assert(block >= 0); + assert(block < MAXBLOCKS); + if(!writtenblocks[block]) { + allocate(block); + } + memcpy(writtenblocks[block], data, curblocksize); + block++; + subblocks--; + data += curblocksize; + tot += curblocksize; + } + } + + return tot; +} + +ssize_t +bdev_read(dev_t dev, u64_t pos, char *data, size_t count, int flags) +{ + int block; + ssize_t tot = 0; + int subblocks; + + assert(dev == MYDEV); + assert(curblocksize > 0); + assert(!(pos % curblocksize)); + assert(count > 0); + assert(!(count % curblocksize)); + + block = pos / curblocksize; + subblocks = count / curblocksize; + while(subblocks > 0) { + assert(block >= 0); + assert(block < MAXBLOCKS); + if(!writtenblocks[block]) { + allocate(block); + } + memcpy(data, writtenblocks[block], curblocksize); + block++; + subblocks--; + data += curblocksize; + tot += curblocksize; + } + + return tot; +} + +/* Fake some libsys functions */ + +__dead void +panic(const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + vfprintf(stderr, fmt, va); + va_end(va); + + exit(1); +} + +int vm_forgetblock(u64_t id) +{ + return ENOSYS; +} + +int vm_yield_block_get_block(u64_t yieldid, u64_t getid, void *mem, + vir_bytes len) +{ + return ENOSYS; +} + +void vm_forgetblocks(void) +{ + return; +} + +int +vm_info_stats(struct vm_stats_info *vsi) +{ + return ENOSYS; +} + +void +util_stacktrace(void) +{ + fprintf(stderr, "fake stacktrace\n"); +} + +void *alloc_contig(size_t len, int flags, phys_bytes *phys) +{ + return malloc(len); +} + +int free_contig(void *addr, size_t len) +{ + free(addr); + return 0; +} + +u32_t sqrt_approx(u32_t v) +{ + return (u32_t) sqrt(v); +} + +int +main(int argc, char *argv[]) +{ + int wss, cs, n = 0, p; + +#define ITER 3 +#define BLOCKS 200 + + start(72); + + lmfs_setquiet(1); + + /* Can the cache handle differently sized blocks? */ + + for(p = 1; p <= 3; p++) { + curblocksize = PAGE_SIZE*p; + lmfs_set_blocksize(curblocksize, MYMAJOR); + lmfs_buf_pool(BLOCKS); + if(dotest(curblocksize, BLOCKS, ITER)) e(n); + n++; + } + + /* Can the cache handle various combinations of the working set + * being larger and smaller than the cache? + */ + for(wss = 2; wss <= 3; wss++) { + int wsblocks = 10*wss*wss*wss*wss*wss; + for(cs = wsblocks/4; cs <= wsblocks*3; cs *= 1.5) { + curblocksize = PAGE_SIZE; + lmfs_set_blocksize(curblocksize, MYMAJOR); + lmfs_buf_pool(cs); + if(dotest(curblocksize, wsblocks, ITER)) e(n); + n++; + } + } + + quit(); + + return 0; +} + diff --git a/test/test73.c b/test/test73.c new file mode 100644 index 000000000..3c5c7f4b1 --- /dev/null +++ b/test/test73.c @@ -0,0 +1,89 @@ +/* Test 73 - VM secondary cache blackbox test. + * + * Blackbox test of the VM secondary cache in isolation, implemented + * in testvm.c, started as a service by this test program. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "testvm.h" + +int max_error = 0; + +#include "common.h" +#include "testcache.h" + +int +main(int argc, char *argv[]) +{ + char pipefn[30], cwd[400], cmdline[400]; + int pipefd; + static struct info i; + ssize_t r; + int big = 0; + +#define ITER 3 +#define BLOCKS 200 + + start(73); + + unlink(pipefn); + + /* 'big' as a substring indicates to testvm that it's ok to + * run a long test + */ + if(getenv(BIGVARNAME)) big = 1; + + if(big) strcpy(pipefn, "pipe_testvm_big"); + else strcpy(pipefn, "pipe_testvm"); + + umask(0); + if(mkfifo(pipefn, 0666) < 0) { e(1); exit(1); } + if(!getcwd(cwd, sizeof(cwd))) { e(2); exit(1); } + + /* stop residual testvm service if any */ + snprintf(cmdline, sizeof(cmdline), "%s down testvm >/dev/null 2>&1", + _PATH_SERVICE); + if(system(cmdline) < 0) { e(9); exit(1); } + + /* start the testvm service */ + snprintf(cmdline, sizeof(cmdline), + "%s up /%s/../testvm -script /etc/rs.single " + "-args /%s/%s -config %s/../testvm.conf", + _PATH_SERVICE, cwd, cwd, pipefn, cwd); + if(system(cmdline) < 0) { e(10); exit(1); } + + /* don't hang forever if the open or read block */ + alarm(big ? 6000 : 600); + + if((pipefd=open(pipefn, O_RDONLY)) < 0) { e(3); exit(1); } + + if((r=read(pipefd, &i, sizeof(i))) != sizeof(i)) { + printf("read returned %d\n", r); + e(12); + exit(1); + } + + if(i.result != 0) { e(i.result); } + + quit(); + + return 0; +} + diff --git a/test/testcache.c b/test/testcache.c new file mode 100644 index 000000000..b25141a27 --- /dev/null +++ b/test/testcache.c @@ -0,0 +1,217 @@ +/* A general i/o consistency test library. It performs i/o + * using functions provided by the client (readblock, dowriteblock) + * with a working set size specified by the client. It checks that + * blocks that were written have the same contents when later read, + * using different access patterns. The assumption is the various + * cache layers so exercised are forced into many different states + * (reordering, eviction, etc), hopefully triggering bugs if present. + * + * Entry point: dotest() + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "testcache.h" +#include "common.h" + +extern int quietflag; + +static void +genblock(int b, char *blockdata, int blocksize, u32_t seed) +{ + u32_t *p = (u32_t *) blockdata, + *plimit = (u32_t *) (blockdata + blocksize), + i = 0; + + srandom(seed ^ b); + + for(p = (u32_t *) blockdata; p < plimit; p++) { + i++; + *p = random(); + } +} + +static int +checkblock(int b, int blocksize, u32_t seed) +{ + static char data[MAXBLOCKSIZE], expected_data[MAXBLOCKSIZE]; + int r; + + genblock(b, expected_data, blocksize, seed); + + r = readblock(b, blocksize, seed, data); + + if(r == OK_BLOCK_GONE) { return 0; } + + if(r != blocksize) { + fprintf(stderr, "readblock failed\n"); + return 1; + } + + if(memcmp(expected_data, data, blocksize)) { + fprintf(stderr, "comparison of %d failed\n", b); + return 1; + } + + return 0; +} + +static int +writeblock(int b, int blocksize, u32_t seed) +{ + static char data[MAXBLOCKSIZE]; + + genblock(b, data, blocksize, seed); + + if(dowriteblock(b, blocksize, seed, data) != blocksize) { + fprintf(stderr, "writeblock of %d failed\n", b); + return 0; + } + + return blocksize; +} + +static int * +makepermutation(int nblocks, int *permutation) +{ + int b; + + assert(nblocks > 0 && nblocks <= MAXBLOCKS); + + for(b = 0; b < nblocks; b++) permutation[b] = b; + + for(b = 0; b < nblocks-1; b++) { + int s, other = b + random() % (nblocks - b - 1); + assert(other >= b && other < nblocks); + s = permutation[other]; + permutation[other] = permutation[b]; + permutation[b] = s; + } + + return permutation; +} + +static int +checkblocks(int nblocks, int blocksize, u32_t seed) +{ + int b; + int nrandom = nblocks * 3; + static int perm1[MAXBLOCKS]; + + if(!quietflag) { fprintf(stderr, "\nverifying "); fflush(stderr); } + + makepermutation(nblocks, perm1); + + assert(nblocks > 0 && nblocks <= MAXBLOCKS); + assert(blocksize > 0 && blocksize <= MAXBLOCKSIZE); + + for(b = 0; b < nblocks; b++) { + if(checkblock(b, blocksize, seed)) { return 1; } + } + + for(b = 0; b < nrandom; b++) { + if(checkblock(random() % nblocks, blocksize, seed)) { return 1; } + } + + for(b = 0; b < nblocks; b++) { + if(checkblock(b, blocksize, seed)) { return 1; } + } + + for(b = 0; b < nblocks; b++) { + if(checkblock(perm1[b], blocksize, seed)) { return 1; } + } + + if(!quietflag) { fprintf(stderr, "done\n"); } + + return 0; +} + +int +dotest(int blocksize, int nblocks, int iterations) +{ + int b, i; + int nrandom = nblocks * iterations; + static int perm1[MAXBLOCKS], perm2[MAXBLOCKS]; + static int newblock[MAXBLOCKS]; + u32_t seed = random(), newseed; + int mb; + + assert(nblocks > 0 && nblocks <= MAXBLOCKS); + + mb = (int) ((u64_t) blocksize * nblocks / 1024 / 1024); + + if(!quietflag) { fprintf(stderr, "test: %d * %d = %dMB\n", blocksize, nblocks, mb); } + + for(b = 0; b < nblocks; b++) { + if(writeblock(b, blocksize, seed) < blocksize) { return 1; } + if(checkblock(b, blocksize, seed)) { return 1; } + printprogress("writing sequential", b, nblocks); + } + + if(checkblocks(nblocks, blocksize, seed)) { return 1; } + + makepermutation(nblocks, perm1); + + for(b = 0; b < nblocks; b++) { + if(writeblock(perm1[b], blocksize, seed) < blocksize) { return 1; } + if(checkblock(perm1[b], blocksize, seed)) { return 1; } + printprogress("writing permutation", b, nblocks); + } + + if(checkblocks(nblocks, blocksize, seed)) { return 1; } + + for(i = 0; i < iterations; i++) { + makepermutation(nblocks, perm1); + makepermutation(nblocks, perm2); + memset(newblock, 0, sizeof(newblock)); + + newseed = random(); + + if(!quietflag) { fprintf(stderr, "iteration %d/%d\n", i, iterations); } + + for(b = 0; b < nblocks; b++) { + int wr = perm1[b], check = perm2[b]; + if(writeblock(wr, blocksize, newseed) < blocksize) { return 1; } + newblock[wr] = 1; + if(checkblock(check, blocksize, newblock[check] ? newseed : seed)) { return 1; } + printprogress("interleaved permutation read, write", b, nblocks); + } + + seed = newseed; + + if(checkblocks(nblocks, blocksize, seed)) { return 1; } + } + + newseed = random(); + + memset(newblock, 0, sizeof(newblock)); + + for(b = 0; b < nrandom; b++) { + int wr = random() % nblocks, check = random() % nblocks; + if(writeblock(wr, blocksize, newseed) < blocksize) { return 1; } + newblock[wr] = 1; + if(checkblock(check, blocksize, + newblock[check] ? newseed : seed)) { return 1; } + printprogress("1 random verify, 1 random write", b, nrandom); + } + + seed = newseed; + + if(!quietflag) { fprintf(stderr, "\n"); } + testend(); + + return 0; +} + +void cachequiet(int quiet) +{ + quietflag = quiet; +} + diff --git a/test/testcache.h b/test/testcache.h new file mode 100644 index 000000000..de735f6ae --- /dev/null +++ b/test/testcache.h @@ -0,0 +1,18 @@ + +/* Common definitions and declarations for the testcache code + * and the testcache clients. + */ + +#include + +#define MAXBLOCKS 1500000 + +#define MAXBLOCKSIZE (4*PAGE_SIZE) + +int dowriteblock(int b, int blocksize, u32_t seed, char *block); +int readblock(int b, int blocksize, u32_t seed, char *block); +void testend(void); +int dotest(int blocksize, int nblocks, int iterations); +void cachequiet(int quiet); + +#define OK_BLOCK_GONE -999 diff --git a/test/testvm.c b/test/testvm.c new file mode 100644 index 000000000..dacf31a16 --- /dev/null +++ b/test/testvm.c @@ -0,0 +1,171 @@ +/* testvm - service-started code that goes with test73.o + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "testvm.h" +#include "common.h" +#include "testcache.h" + +static char *pipefilename = NULL, *progname; +int pipefd = -1; + +int memfd; + +static char *bdata = NULL; + +int dowriteblock(int b, int blocksize, u32_t seed, char *block) +{ + int r; + + r=vm_yield_block_get_block(VM_BLOCKID_NONE, b, bdata, blocksize); + + if(r != OK && r != ESRCH) { + printf("dowriteblock: vm_yield_block_get_block get %d\n", r); + exit(1); + } + + memcpy(bdata, block, blocksize); + + r=vm_yield_block_get_block(b, VM_BLOCKID_NONE, bdata, blocksize); + + if(r != OK) { + printf("dowriteblock: vm_yield_block_get_block yield %d\n", r); + exit(1); + } + + return blocksize; +} + +int readblock(int b, int blocksize, u32_t seed, char *block) +{ + int r; + + r=vm_yield_block_get_block(VM_BLOCKID_NONE, b, bdata, blocksize); + if(r == ESRCH) { + return OK_BLOCK_GONE; + } + if(r != OK) { + printf("readblock: vm_yield_block_get_block get %d\n", r); + exit(1); + } + + memcpy(block, bdata, blocksize); + r=vm_yield_block_get_block(b, VM_BLOCKID_NONE, bdata, blocksize); + if(r != OK) { + printf("readblock: vm_yield_block_get_block yield %d\n", r); + exit(1); + } + + return blocksize; +} + +void testend(void) { vm_forgetblocks(); } + +static void +writepipe(struct info *i) +{ + if(write(pipefd, i, sizeof(*i)) != sizeof(*i)) { + printf("%s: pipe write failed\n", progname); + exit(1); + } +} + +static int +testinit(void) +{ + struct stat st; + int attempts = 0; + + for(attempts = 0; attempts < 5 && pipefd < 0; attempts++) { + if(attempts > 0) sleep(1); + pipefd = open(pipefilename, O_WRONLY | O_NONBLOCK); + } + + if(pipefd < 0) { + printf("%s: could not open pipe %s, errno %d\n", + progname, pipefilename, errno); + exit(1); + } + + if(fstat(pipefd, &st) < 0) { + printf("%s: could not fstat pipe %s\n", progname, pipefilename); + exit(1); + } + + if(!(st.st_mode & I_NAMED_PIPE)) { + printf("%s: file %s is not a pipe\n", progname, pipefilename); + exit(1); + } + + return OK; +} + +static int +sef_cb_init(int type, sef_init_info_t *UNUSED(info)) +{ + return OK; +} + +static void +init(void) +{ + /* SEF init */ + sef_setcb_init_fresh(sef_cb_init); + sef_setcb_init_lu(sef_cb_init); + sef_setcb_init_restart(sef_cb_init); + + sef_startup(); +} + + + +int +main(int argc, char *argv[]) +{ + struct info info; + int big; + u32_t totalmem, freemem, cachedmem; + + progname = argv[0]; + + if(argc < 2) { printf("no args\n"); return 1; } + + pipefilename=argv[1]; + + big = !!strstr(pipefilename, "big"); + + init(); + + info.result = time(NULL); + + if(testinit() != OK) { printf("%s: testinit failed\n", progname); return 1; } + + cachequiet(!big); + + if(!(bdata = alloc_contig(PAGE_SIZE, 0, NULL))) { + printf("could not allocate block\n"); + exit(1); + } + + if(dotest(PAGE_SIZE, 10, 3)) { e(11); exit(1); } + if(dotest(PAGE_SIZE, 1000, 3)) { e(11); exit(1); } + if(dotest(PAGE_SIZE, 50000, 3)) { e(11); exit(1); } + if(big) { + getmem(&totalmem, &freemem, &cachedmem); + if(dotest(PAGE_SIZE, totalmem*1.5, 3)) { e(11); exit(1); } + } + + info.result = 0; + + writepipe(&info); + + return 0; +} + diff --git a/test/testvm.conf b/test/testvm.conf new file mode 100644 index 000000000..d2c6907b7 --- /dev/null +++ b/test/testvm.conf @@ -0,0 +1,11 @@ +service testvm { + ipc ALL; # All system ipc targets allowed + system BASIC; # Only basic kernel calls allowed + vm BASIC; + io NONE; # No I/O range allowed + irq NONE; # No IRQ allowed + sigmgr rs; # Signal manager is RS + scheduler sched; # Scheduler is sched + priority 5; # priority queue 5 + quantum 500; # default server quantum +}; diff --git a/test/testvm.h b/test/testvm.h new file mode 100644 index 000000000..c47a7b045 --- /dev/null +++ b/test/testvm.h @@ -0,0 +1,8 @@ + +struct info { + /* info to testvm */ + int big; /* do BIG mode */ + + /* info from testvm */ + int result; /* error code */ +};