/* * syncstone - measure the performance of sequential writes * * $dotat: syncstone/syncstone.c,v 1.5 2006/10/02 20:51:19 fanf2 Exp $ */ #include #include #include #include #include #include #include #include #include #include #include static const char *progname; /* misc options */ static bool do_sync = false; static bool do_append = false; static bool open_sync = false; static bool open_append = false; #ifdef O_DIRECT static bool open_direct = false; #endif /* IO accounting */ #define MIN_MAX_BLOCK 65536 #define MAX_FILES 10000 static void *block; static size_t min_block = 1024; static size_t max_block = 1024; static size_t num_blocks = 102400; #define NAMELEN 20 static int files = 1; static int min_fd; static int max_fd; /* stats collection */ #define MAX_TRIALS 50 static int trials = 10; static int pre_trials = 1; static int trial_index; static double trial_time[MAX_TRIALS]; static double trial_writes[MAX_TRIALS]; static double trial_written[MAX_TRIALS]; static double trial_frequency[MAX_TRIALS]; static double trial_bandwidth[MAX_TRIALS]; /* IO */ static void init_block(void) { size_t bytes; size_t count; u_int32_t *a; size_t i; bytes = max_block > MIN_MAX_BLOCK ? max_block : MIN_MAX_BLOCK; count = bytes / sizeof(u_int32_t) + 1; bytes = count * sizeof(u_int32_t); block = malloc(bytes); if(block == NULL) err(EX_OSERR, "malloc"); /* don't cheat with zero pages */ for(a = block, i = 0; i < count; i++) a[i] = random(); } static void write_err(int fd, size_t bytes) { assert(bytes >= min_block); assert(bytes <= MIN_MAX_BLOCK || bytes <= max_block); if(write(fd, block, bytes) < 0) err(EX_IOERR, "write %d", fd); } static void seek_start(int fd) { if(lseek(fd, 0, SEEK_SET) < 0) err(EX_IOERR, "lseek start %d", fd); } static void empty_file(int fd) { if(ftruncate(fd, 0) < 0) err(EX_IOERR, "ftruncate %d", fd); } static void reset_file(int fd) { if(do_append || do_append) empty_file(fd); seek_start(fd); } static void reset_files(void) { int fd; for(fd = min_fd; fd < max_fd; fd++) reset_file(fd); } static int open_file(void) { char name[NAMELEN]; int fd; int flags = O_RDWR | O_CREAT | O_EXCL; if(open_sync) flags |= O_SYNC; if(open_append) flags |= O_APPEND; #ifdef O_DIRECT if(open_direct) flags |= O_DIRECT; #endif fd = open("tmp", flags, 0666); if(fd < 0) err(EX_CANTCREAT, "open tmp"); assert(snprintf(name, sizeof(name), "%d", fd) < NAMELEN); if(rename("tmp", name) < 0) err(EX_CANTCREAT, "rename tmp -> %s", name); if(!do_append && !open_append) { size_t i; for(i = 0; i < num_blocks; i++) write_err(fd, max_block); seek_start(fd); } return(fd); } static void open_files(void) { int fd; min_fd = 3; max_fd = 3 + files; for(fd = min_fd; fd < max_fd; fd++) { close(fd); assert(fd == open_file()); } } static void close_file(int fd) { char name[NAMELEN]; assert(snprintf(name, sizeof(name), "%d", fd) < NAMELEN); if(unlink(name) < 0) err(EX_IOERR, "unlink %s", name); if(close(fd) < 0) err(EX_IOERR, "close %d", fd); } static void close_files(void) { int fd; for(fd = min_fd; fd < max_fd; fd++) close_file(fd); } /* statistics */ static double mean(double a[], size_t n) { double sum; size_t i; for(sum = 0.0, i = 0; i < n; i++) sum += a[i]; return(sum / n); } static double stddev(double a[], size_t n) { double sum2, mu, t; size_t i; mu = mean(a, n); /* sum of squares of differences from the mean */ for(sum2 = 0.0, i = 0; i < n; i++) { t = a[i] - mu; sum2 += t * t; } /* bias-corrected sample standard deviation */ return(sqrt(sum2 / (n - 1.0))); } static int double_cmp(const void *pa, const void *pb) { double a, b; a = *(const double *)pa; b = *(const double *)pb; return(a < b ? -1 : a > b ? +1 : 0); } static double quantile(double a[], size_t n, double p) { double lower, frac; int i; qsort(a, n, sizeof(double), double_cmp); assert(p >= 0.0 && p <= 1.0); /* we want a range of 0 ... trials-1 */ p *= n - 1.0; lower = floor(p); frac = p - lower; i = (int)lower; if(frac == 0.0) /* avoid out-of-bounds i */ return(a[i]); else return(a[i] + (a[i+1] - a[i]) * frac); } static void printstats(const char *tag, double a[], size_t n) { if(tag == NULL) { printf(" " " mean " " 10th %%ile " " median " " 90th %%ile " " stddev\n"); } else { printf("%12s %e %e %e %e %e\n", tag, mean(a, n), quantile(a, n, 0.1), quantile(a, n, 0.5), quantile(a, n, 0.9), stddev(a, n)); } } /* details of the trials */ #define RAND(min, max) \ ((((max) - (min)) ? random() % ((max) - (min)) : 0) + (min)) static void write_block(void) { int fd = RAND(min_fd, max_fd); size_t bytes = RAND(min_block, max_block); write_err(fd, bytes); if(do_sync && fsync(fd) < 0) err(EX_IOERR, "fsync %d", fd); trial_written[trial_index] += bytes; trial_writes[trial_index] += 1; } static double double_time(void) { struct timeval tv; if(gettimeofday(&tv, NULL) < 0) err(EX_SOFTWARE, "gettimeofday"); return(tv.tv_sec + tv.tv_usec / 1000000.0); } static void run_trial(void) { double start = double_time(); trial_time[trial_index] = 0; trial_writes[trial_index] = 0; trial_written[trial_index] = 0; trial_frequency[trial_index] = 0; trial_bandwidth[trial_index] = 0; while(trial_writes[trial_index] < num_blocks) write_block(); trial_time[trial_index] = double_time() - start; trial_frequency[trial_index] = trial_writes[trial_index] / trial_time[trial_index]; trial_bandwidth[trial_index] = trial_written[trial_index] / trial_time[trial_index]; printf("time %e writes %e written %e frequency %e bandwidth %e\n", trial_time[trial_index], trial_writes[trial_index], trial_written[trial_index], trial_frequency[trial_index], trial_bandwidth[trial_index]); reset_files(); } /* main */ static void usage(void) { fprintf(stderr, "usage: %s [-aAsSD] [-b ] [-B ] [-f ] [-t ] [-T ] [-w ]\n" " -a append to files (instead of overwiting them)\n" " -A open files with O_APPEND\n" #ifdef O_DIRECT " -D open files with O_DIRECT\n" #endif " -s call fsync() after write()\n" " -S open files with O_SYNC\n" " -b minimum write size (default n = %lu bytes)\n" " -B maximum write size (default n = %lu bytes)\n" " -f number of files (default n = %d)\n" " -t number of preload trials (default n = %d)\n" " -T number of counted trials (default n = %d)\n" " -w number of writes (default n = %lu)\n" , progname , min_block, max_block, files, pre_trials, trials, num_blocks ); exit(EX_USAGE); } int main(int argc, char *argv[]) { int c; progname = strrchr(argv[0], '/'); if(!progname) progname = argv[0]; else progname += 1; while((c = getopt(argc, argv, "AaB:b:Df:sST:t:w:")) != -1) switch(c) { case('A'): open_append = true; break; case('a'): do_append = true; break; case('B'): max_block = atoi(optarg); break; case('b'): min_block = atoi(optarg); break; #ifdef O_DIRECT case('D'): open_direct = true; break; #endif case('f'): files = atoi(optarg); if(files < 0 || files >= MAX_FILES) usage(); break; case('s'): do_sync = true; break; case('S'): open_sync = true; break; case('T'): trials = atoi(optarg); if(trials < 0 || trials >= MAX_FILES) usage(); break; case('t'): pre_trials = atoi(optarg); break; case('w'): num_blocks = atoi(optarg); break; default: usage(); } argc -= optind; argv += optind; if(argc != 0) usage(); init_block(); open_files(); for(trial_index = 0; trial_index < pre_trials; trial_index++) { printf("starting pre-loader %d ... ", trial_index); fflush(stdout); run_trial(); } for(trial_index = 0; trial_index < trials; trial_index++) { printf("starting trial %d ... ", trial_index); fflush(stdout); run_trial(); } printf("\n"); printstats(NULL, NULL, 0); printstats("time", trial_time, trials); printstats("writes", trial_writes, trials); printstats("written", trial_written, trials); printstats("frequency", trial_frequency, trials); printstats("bandwidth", trial_bandwidth, trials); printf("\n"); close_files(); exit(0); }