#include #include #include #include #include #include #include #include #include #include static int test_fd; static char *buf; static char *fname; static int openflags = O_RDWR; /* * Just creates a random file, overwriting the file in a random number of loops * and fsyncing between each loop. */ static int test_one(int *max_blocks) { int loops = (random() % 20) + 5; lseek(test_fd, 0, SEEK_SET); while (loops--) { int character = (random() % 126) + 33; /* printable character */ int blocks = (random() % 100) + 1; if (blocks > *max_blocks) *max_blocks = blocks; lseek(test_fd, 0, SEEK_SET); memset(buf, character, 4096); if (fsync(test_fd)) { fprintf(stderr, "Fsync failed, test results will be " "invalid: %d\n", errno); return 1; } while (blocks--) { if (write(test_fd, buf, 4096) < 4096) { fprintf(stderr, "Short write %d\n", errno); return 1; } } } return 0; } /* * Preallocate a randomly sized file and then overwrite the entire thing and * then fsync. */ static int test_two(int *max_blocks) { int blocks = (random() % 1024) + 1; int character = (random() % 126) + 33; *max_blocks = blocks; if (fallocate(test_fd, 0, 0, blocks * 4096)) { fprintf(stderr, "Error fallocating %d (%s)\n", errno, strerror(errno)); return 1; } lseek(test_fd, 0, SEEK_SET); memset(buf, character, 4096); while (blocks--) { if (write(test_fd, buf, 4096) < 4096) { fprintf(stderr, "Short write %d\n", errno); return 1; } } return 0; } static void drop_all_caches() { char value[] = "3\n"; int fd; if ((fd = open("/proc/sys/vm/drop_caches", O_WRONLY)) < 0) { fprintf(stderr, "Error opening drop caches: %d\n", errno); return; } write(fd, value, sizeof(value)-1); close(fd); } /* * Randomly write inside of a file, either creating a sparse file or prealloc * the file and randomly write within it, depending on the prealloc flag */ static int test_three(int *max_blocks, int prealloc, int rand_fsync, int do_sync, int drop_caches) { int size = (random() % 2048) + 4; int blocks = size / 2; int sync_block = blocks / 2; int rand_sync_interval = (random() % blocks) + 1; int character = (random() % 126) + 33; if (prealloc && fallocate(test_fd, 0, 0, size * 4096)) { fprintf(stderr, "Error fallocating %d (%s)\n", errno, strerror(errno)); return 1; } if (prealloc) *max_blocks = size; memset(buf, character, 4096); while (blocks--) { int block = (random() % size); if ((block + 1) > *max_blocks) *max_blocks = block + 1; if (rand_fsync && !(blocks % rand_sync_interval)) { if (fsync(test_fd)) { fprintf(stderr, "Fsync failed, test results " "will be invalid: %d\n", errno); return 1; } } /* Force a transaction commit in between just for fun */ if (blocks == sync_block && (do_sync || drop_caches)) { if (do_sync) sync(); else sync_file_range(test_fd, 0, 0, SYNC_FILE_RANGE_WRITE| SYNC_FILE_RANGE_WAIT_AFTER); if (drop_caches) { close(test_fd); drop_all_caches(); test_fd = open(fname, openflags); if (test_fd < 0) { test_fd = 0; fprintf(stderr, "Error re-opening file: %d\n", errno); return 1; } } } if (pwrite(test_fd, buf, 4096, block * 4096) < 4096) { fprintf(stderr, "Short write %d\n", errno); return 1; } } return 0; } static void timeval_subtract(struct timeval *result,struct timeval *x, struct timeval *y) { if (x->tv_usec < y->tv_usec) { int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1; y->tv_usec -= 1000000 * nsec; y->tv_sec += nsec; } if (x->tv_usec - y->tv_usec > 1000000) { int nsec = (x->tv_usec - y->tv_usec) / 1000000; y->tv_usec += 1000000 * nsec; y->tv_sec -= nsec; } result->tv_sec = x->tv_sec - y->tv_sec; result->tv_usec = x->tv_usec - y->tv_usec; } static int test_four(int *max_blocks) { size_t size = 2621440; /* 10 gigabytes */ size_t blocks = size / 2; size_t sync_block = blocks / 8; /* fsync 8 times */ int character = (random() % 126) + 33; struct timeval start, end, diff; memset(buf, character, 4096); while (blocks--) { off_t block = (random() % size); if ((block + 1) > *max_blocks) *max_blocks = block + 1; if ((blocks % sync_block) == 0) { if (gettimeofday(&start, NULL)) { fprintf(stderr, "Error getting time: %d\n", errno); return 1; } if (fsync(test_fd)) { fprintf(stderr, "Fsync failed, test results " "will be invalid: %d\n", errno); return 1; } if (gettimeofday(&end, NULL)) { fprintf(stderr, "Error getting time: %d\n", errno); return 1; } timeval_subtract(&diff, &end, &start); printf("Fsync time was %ds and %dus\n", (int)diff.tv_sec, (int)diff.tv_usec); } if (pwrite(test_fd, buf, 4096, block * 4096) < 4096) { fprintf(stderr, "Short write %d\n", errno); return 1; } } return 0; } static int test_five() { int character = (random() % 126) + 33; int runs = (random() % 100) + 1; int i; memset(buf, character, 3072); for (i = 0; i < runs; i++) { ssize_t write_size = (random() % 3072) + 1; if (pwrite(test_fd, buf, write_size, 0) < write_size) { fprintf(stderr, "Short write %d\n", errno); return 1; } if ((i % 8) == 0) { if (fsync(test_fd)) { fprintf(stderr, "Fsync failed, test results " "will be invalid: %d\n", errno); return 1; } } } return 0; } /* * Reproducer for something like this * * [data][prealloc][data] * * and then in the [prealloc] section we have * * [ pre ][pre][ pre ] * [d][pp][dd][ppp][d][ppp][d] * * where each letter represents on block of either data or prealloc. * * This explains all the weirdly specific numbers. */ static int test_six() { int character = (random() % 126) + 33; int i; memset(buf, character, 4096); /* Write on either side of the file, leaving a hole in the middle */ for (i = 0; i < 10; i++) { if (pwrite(test_fd, buf, 4096, i * 4096) < 4096) { fprintf(stderr, "Short write %d\n", errno); return 1; } } if (fsync(test_fd)) { fprintf(stderr, "Fsync failed %d\n", errno); return 1; } /* * The test fs I had the prealloc extent was 13 4k blocks long so I'm * just using that to give myself the best chances of reproducing. */ for (i = 23; i < 33; i++) { if (pwrite(test_fd, buf, 4096, i * 4096) < 4096) { fprintf(stderr, "Short write %d\n", errno); return 1; } } if (fsync(test_fd)) { fprintf(stderr, "Fsync failed %d\n", errno); return 1; } if (fallocate(test_fd, 0, 10 * 4096, 4 * 4096)) { fprintf(stderr, "Error fallocating %d\n", errno); return 1; } if (fallocate(test_fd, 0, 14 * 4096, 5 * 4096)) { fprintf(stderr, "Error fallocating %d\n", errno); return 1; } if (fallocate(test_fd, 0, 19 * 4096, 4 * 4096)) { fprintf(stderr, "Error fallocating %d\n", errno); return 1; } if (pwrite(test_fd, buf, 4096, 10 * 4096) < 4096) { fprintf(stderr, "Short write %d\n", errno); return 1; } if (fsync(test_fd)) { fprintf(stderr, "Fsync failed %d\n", errno); return 1; } for (i = 13; i < 15; i++) { if (pwrite(test_fd, buf, 4096, i * 4096) < 4096) { fprintf(stderr, "Short write %d\n", errno); return 1; } } if (fsync(test_fd)) { fprintf(stderr, "Fsync failed %d\n", errno); return 1; } if (pwrite(test_fd, buf, 4096, 18 * 4096) < 4096) { fprintf(stderr, "Short write %d\n", errno); return 1; } if (fsync(test_fd)) { fprintf(stderr, "Fsync failed %d\n", errno); return 1; } if (pwrite(test_fd, buf, 4096, 22 * 4096) < 4096) { fprintf(stderr, "Short write %d\n", errno); return 1; } if (fsync(test_fd)) { fprintf(stderr, "Fsync failed %d\n", errno); return 1; } return 0; } static void usage() { printf("Usage fsync-tester [-s ] [-r] [-d] -t \n"); printf(" -s seed : seed for teh random map generator (defaults to reading /dev/urandom)\n"); printf(" -r : don't reboot the box immediately\n"); printf(" -d : use O_DIRECT\n"); printf(" -t test : test nr to run, required\n"); exit(1); } int main(int argc, char **argv) { int opt; int fd; int max_blocks = 0; char *endptr; unsigned int seed = 123; int reboot = 0; int direct_io = 0; long int test = 1; long int tmp; int ret = 0; if (argc < 2) usage(); fd = open("/dev/urandom", O_RDONLY); if (fd >= 0) { read(fd, &seed, sizeof(seed)); close(fd); } while ((opt = getopt(argc, argv, "s:rdt:")) != -1) { switch (opt) { case 's': tmp = strtol(optarg, &endptr, 10); if (tmp == LONG_MAX || endptr == optarg) usage(); seed = tmp; break; case 'r': reboot = 1; break; case 'd': direct_io = 1; break; case 't': test = strtol(optarg, &endptr, 10); if (test == LONG_MAX || endptr == optarg) usage(); break; default: usage(); } } if (optind >= argc) usage(); /* * test 19 is for smaller than blocksize writes to test btrfs's inline * extent fsyncing, so direct_io doesn't make sense and in fact doesn't * work for other file systems, so just disable direct io for this test. */ if (test == 19) direct_io = 0; fname = argv[optind]; if (!fname) usage(); printf("Random seed is %u\n", seed); srandom(seed); if (direct_io) { openflags |= O_DIRECT; ret = posix_memalign((void **)&buf, getpagesize(), 4096); if (ret) { fprintf(stderr, "Error allocating buf: %d\n", ret); return 1; } } else { buf = malloc(4096); if (!buf) { fprintf(stderr, "Error allocating buf: %d\n", errno); return 1; } } test_fd = open(fname, openflags | O_CREAT | O_TRUNC, 0644); if (test_fd < 0) { fprintf(stderr, "Error opening file %d (%s)\n", errno, strerror(errno)); return 1; } switch (test) { case 1: ret = test_one(&max_blocks); break; case 2: ret = test_two(&max_blocks); break; case 3: ret = test_three(&max_blocks, 0, 0, 0, 0); break; case 4: ret = test_three(&max_blocks, 1, 0, 0, 0); break; case 5: ret = test_three(&max_blocks, 0, 1, 0, 0); break; case 6: ret = test_three(&max_blocks, 1, 1, 0, 0); break; case 7: ret = test_three(&max_blocks, 0, 0, 1, 0); break; case 8: ret = test_three(&max_blocks, 1, 0, 1, 0); break; case 9: ret = test_three(&max_blocks, 0, 1, 1, 0); break; case 10: ret = test_three(&max_blocks, 1, 1, 1, 0); break; case 11: ret = test_three(&max_blocks, 0, 0, 0, 1); break; case 12: ret = test_three(&max_blocks, 0, 1, 0, 1); break; case 13: ret = test_three(&max_blocks, 0, 0, 1, 1); break; case 14: ret = test_three(&max_blocks, 0, 1, 1, 1); break; case 15: ret = test_three(&max_blocks, 1, 0, 0, 1); break; case 16: ret = test_three(&max_blocks, 1, 1, 0, 1); break; case 17: ret = test_three(&max_blocks, 1, 0, 1, 1); break; case 18: ret = test_three(&max_blocks, 1, 1, 1, 1); break; case 19: ret = test_five(); break; case 20: ret = test_six(); break; case 21: /* * This is just a perf test, keep moving it down so it's always * the last test option. */ reboot = 0; ret = test_four(&max_blocks); goto out; default: usage(); } if (ret) goto out; if (fsync(test_fd)) { fprintf(stderr, "Fsync failed, test results will be invalid: " "%d\n", errno); return 1; } if (reboot) system("reboot -fn"); out: free(buf); close(test_fd); return ret; }