/* Copyright (c) 2008-2012 Red Hat, Inc. This file is part of GlusterFS. This file is licensed to you under your choice of the GNU Lesser General Public License, version 3 or any later version (LGPLv3 or later), or the GNU General Public License, version 2 (GPLv2), in all cases as published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #define TWO_POWER(power) (2UL << (power)) #define RDD_INTEGER_VALUE ((TWO_POWER ((sizeof (int) * 8))) - 1) #ifndef UNIX_PATH_MAX #define UNIX_PATH_MAX 108 #endif #define UNIT_KB 1024ULL #define UNIT_MB UNIT_KB*1024ULL #define UNIT_GB UNIT_MB*1024ULL #define UNIT_TB UNIT_GB*1024ULL #define UNIT_PB UNIT_TB*1024ULL #define UNIT_KB_STRING "KB" #define UNIT_MB_STRING "MB" #define UNIT_GB_STRING "GB" #define UNIT_TB_STRING "TB" #define UNIT_PB_STRING "PB" struct rdd_file { char path[UNIX_PATH_MAX]; struct stat st; int fd; }; struct rdd_config { long iters; long max_ops_per_seq; size_t max_bs; size_t min_bs; int thread_count; pthread_t *threads; pthread_barrier_t barrier; pthread_mutex_t lock; struct rdd_file in_file; struct rdd_file out_file; ssize_t file_size; }; static struct rdd_config rdd_config; enum rdd_keys { RDD_MIN_BS_KEY = 1, RDD_MAX_BS_KEY, }; static error_t rdd_parse_opts (int key, char *arg, struct argp_state *_state) { switch (key) { case 'o': { int len = 0; len = strlen (arg); if (len > UNIX_PATH_MAX) { fprintf (stderr, "output file name too long (%s)\n", arg); return -1; } strncpy (rdd_config.out_file.path, arg, len); } break; case 'i': { int len = 0; len = strlen (arg); if (len > UNIX_PATH_MAX) { fprintf (stderr, "input file name too long (%s)\n", arg); return -1; } strncpy (rdd_config.in_file.path, arg, len); rdd_config.in_file.path[len] = '\0'; } break; case 'f': { char *tmp = NULL; unsigned long long fs = 0; if (string2bytesize (arg, &fs) == -1) { fprintf (stderr, "invalid argument for file size " "(%s)\n", arg); return -1; } rdd_config.file_size = fs; } break; case RDD_MIN_BS_KEY: { char *tmp = NULL; long bs = 0; bs = strtol (arg, &tmp, 10); if ((bs == LONG_MAX) || (bs == LONG_MIN) || (tmp && *tmp)) { fprintf (stderr, "invalid argument for minimum block" "size (%s)\n", arg); return -1; } rdd_config.min_bs = bs; } break; case RDD_MAX_BS_KEY: { char *tmp = NULL; long bs = 0; bs = strtol (arg, &tmp, 10); if ((bs == LONG_MAX) || (bs == LONG_MIN) || (tmp && *tmp)) { fprintf (stderr, "invalid argument for maximum block" "size (%s)\n", arg); return -1; } rdd_config.max_bs = bs; } break; case 'r': { char *tmp = NULL; long iters = 0; iters = strtol (arg, &tmp, 10); if ((iters == LONG_MAX) || (iters == LONG_MIN) || (tmp && *tmp)) { fprintf (stderr, "invalid argument for iterations" "(%s)\n", arg); return -1; } rdd_config.iters = iters; } break; case 'm': { char *tmp = NULL; long max_ops = 0; max_ops = strtol (arg, &tmp, 10); if ((max_ops == LONG_MAX) || (max_ops == LONG_MIN) || (tmp && *tmp)) { fprintf (stderr, "invalid argument for max-ops" "(%s)\n", arg); return -1; } rdd_config.max_ops_per_seq = max_ops; } break; case 't': { char *tmp = NULL; long threads = 0; threads = strtol (arg, &tmp, 10); if ((threads == LONG_MAX) || (threads == LONG_MIN) || (tmp && *tmp)) { fprintf (stderr, "invalid argument for thread count" "(%s)\n", arg); return -1; } rdd_config.thread_count = threads; } break; case ARGP_KEY_NO_ARGS: break; case ARGP_KEY_ARG: break; case ARGP_KEY_END: if (_state->argc == 1) { argp_usage (_state); } } return 0; } int string2bytesize (const char *str, unsigned long long *n) { unsigned long long value = 0ULL; char *tail = NULL; int old_errno = 0; const char *s = NULL; if (str == NULL || n == NULL) { errno = EINVAL; return -1; } for (s = str; *s != '\0'; s++) { if (isspace (*s)) { continue; } if (*s == '-') { return -1; } break; } old_errno = errno; errno = 0; value = strtoull (str, &tail, 10); if (errno == ERANGE || errno == EINVAL) { return -1; } if (errno == 0) { errno = old_errno; } if (tail[0] != '\0') { if (strcasecmp (tail, UNIT_KB_STRING) == 0) { value *= UNIT_KB; } else if (strcasecmp (tail, UNIT_MB_STRING) == 0) { value *= UNIT_MB; } else if (strcasecmp (tail, UNIT_GB_STRING) == 0) { value *= UNIT_GB; } else if (strcasecmp (tail, UNIT_TB_STRING) == 0) { value *= UNIT_TB; } else if (strcasecmp (tail, UNIT_PB_STRING) == 0) { value *= UNIT_PB; } else { return -1; } } *n = value; return 0; } static struct argp_option rdd_options[] = { {"if", 'i', "INPUT_FILE", 0, "input-file"}, {"of", 'o', "OUTPUT_FILE", 0, "output-file"}, {"threads", 't', "COUNT", 0, "number of threads to spawn (defaults to 2)"}, {"min-bs", RDD_MIN_BS_KEY, "MIN_BLOCK_SIZE", 0, "Minimum block size in bytes (defaults to 1024)"}, {"max-bs", RDD_MAX_BS_KEY, "MAX_BLOCK_SIZE", 0, "Maximum block size in bytes (defaults to 4096)"}, {"iters", 'r', "ITERS", 0, "Number of read-write sequences (defaults to 1000000)"}, {"max-ops", 'm', "MAXOPS", 0, "maximum number of read-writes to be performed in a sequence (defaults to 1)"}, {"file-size", 'f', "FILESIZE", 0, "the size of the file which will be created and upon it I/O will be done" " (defaults to 100MB"}, {0, 0, 0, 0, 0} }; static struct argp argp = { rdd_options, rdd_parse_opts, "", "random dd - tool to do a sequence of random block-sized continuous" "read writes starting at a random offset" }; static void rdd_default_config (void) { char *tmp_path = "rdd.in"; rdd_config.thread_count = 2; rdd_config.iters = 1000000; rdd_config.max_bs = 4096; rdd_config.min_bs = 1024; rdd_config.in_file.fd = rdd_config.out_file.fd = -1; rdd_config.max_ops_per_seq = 1; strncpy (rdd_config.in_file.path, tmp_path, strlen (tmp_path)); rdd_config.file_size = 104857600; return; } static char rdd_valid_config (void) { char ret = 1; int fd = -1; fd = open (rdd_config.in_file.path, O_RDONLY); if (fd == -1 && (errno != ENOENT)) { fprintf (stderr, "open: (%s)", strerror (errno)); ret = 0; goto out; } close (fd); if (rdd_config.min_bs > rdd_config.max_bs) { fprintf (stderr, "minimum blocksize %ld is greater than the " "maximum blocksize %ld", rdd_config.min_bs, rdd_config.max_bs); ret = 0; goto out; } if (strlen (rdd_config.out_file.path) == 0) { sprintf (rdd_config.out_file.path, "%s.rddout", rdd_config.in_file.path); } out: return ret; } static void * rdd_read_write (void *arg) { int i = 0, ret = 0; size_t bs = 0; off_t offset = 0; long rand = 0; long max_ops = 0; char *buf = NULL; buf = calloc (1, rdd_config.max_bs); if (!buf) { fprintf (stderr, "calloc failed (%s)\n", strerror (errno)); ret = -1; goto out; } for (i = 0; i < rdd_config.iters; i++) { pthread_mutex_lock (&rdd_config.lock); { int bytes = 0; rand = random (); if (rdd_config.min_bs == rdd_config.max_bs) { bs = rdd_config.max_bs; } else { bs = rdd_config.min_bs + (rand % (rdd_config.max_bs - rdd_config.min_bs)); } offset = rand % rdd_config.in_file.st.st_size; max_ops = rand % rdd_config.max_ops_per_seq; if (!max_ops) { max_ops ++; } ret = lseek (rdd_config.in_file.fd, offset, SEEK_SET); if (ret != offset) { fprintf (stderr, "lseek failed (%s)\n", strerror (errno)); ret = -1; goto unlock; } ret = lseek (rdd_config.out_file.fd, offset, SEEK_SET); if (ret != offset) { fprintf (stderr, "lseek failed (%s)\n", strerror (errno)); ret = -1; goto unlock; } while (max_ops--) { bytes = read (rdd_config.in_file.fd, buf, bs); if (!bytes) { break; } if (bytes == -1) { fprintf (stderr, "read failed (%s)\n", strerror (errno)); ret = -1; goto unlock; } if (write (rdd_config.out_file.fd, buf, bytes) != bytes) { fprintf (stderr, "write failed (%s)\n", strerror (errno)); ret = -1; goto unlock; } } } unlock: pthread_mutex_unlock (&rdd_config.lock); if (ret == -1) { goto out; } ret = 0; } out: free (buf); pthread_barrier_wait (&rdd_config.barrier); return NULL; } static void cleanup (void) { close (rdd_config.in_file.fd); close (rdd_config.out_file.fd); rdd_config.in_file.fd = rdd_config.out_file.fd = -1; } static int check_and_create (void) { int ret = -1; char buf[4096] = {0,}; struct stat stbuf = {0,}; int fd[2] = {-1,}; size_t total_size = -1; total_size = rdd_config.file_size; ret = stat (rdd_config.in_file.path, &stbuf); if (ret == -1 && (errno != ENOENT)) goto out; fd[1] = open (rdd_config.in_file.path, O_CREAT | O_WRONLY | O_TRUNC); if (fd[1] == -1) goto out; fd[0] = open ("/dev/urandom", O_RDONLY); if (fd[0] == -1) goto out; while (total_size > 0) { if (total_size >= 4096) { ret = read (fd[0], buf, 4096); if (ret == -1) goto out; ret = write (fd[1], buf, 4096); if (ret == -1) goto out; total_size = total_size - 4096; } else { ret = read (fd[0], buf, total_size); if (ret == -1) goto out; ret = write (fd[1], buf, total_size); if (ret == -1) goto out; total_size = total_size - total_size; } } ret = 0; out: if (fd[0] > 0) close (fd[0]); if (fd[1] > 0) close (fd[1]); return ret; } static int rdd_spawn_threads (void) { int i = 0, ret = -1, fd = -1; char buf[4096]; ret = check_and_create (); if (ret == -1) goto out; fd = open (rdd_config.in_file.path, O_RDONLY); if (fd < 0) { fprintf (stderr, "cannot open %s (%s)\n", rdd_config.in_file.path, strerror (errno)); ret = -1; goto out; } ret = fstat (fd, &rdd_config.in_file.st); if (ret != 0) { close (fd); fprintf (stderr, "cannot stat %s (%s)\n", rdd_config.in_file.path, strerror (errno)); ret = -1; goto out; } rdd_config.in_file.fd = fd; fd = open (rdd_config.out_file.path, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU | S_IROTH); if (fd < 0) { close (rdd_config.in_file.fd); rdd_config.in_file.fd = -1; fprintf (stderr, "cannot open %s (%s)\n", rdd_config.out_file.path, strerror (errno)); ret = -1; goto out; } rdd_config.out_file.fd = fd; while ((ret = read (rdd_config.in_file.fd, buf, 4096)) > 0) { if (write (rdd_config.out_file.fd, buf, ret) != ret) { fprintf (stderr, "write failed (%s)\n", strerror (errno)); cleanup (); ret = -1; goto out; } } rdd_config.threads = calloc (rdd_config.thread_count, sizeof (pthread_t)); if (rdd_config.threads == NULL) { fprintf (stderr, "calloc() failed (%s)\n", strerror (errno)); ret = -1; cleanup (); goto out; } ret = pthread_barrier_init (&rdd_config.barrier, NULL, rdd_config.thread_count + 1); if (ret != 0) { fprintf (stderr, "pthread_barrier_init() failed (%s)\n", strerror (ret)); free (rdd_config.threads); cleanup (); ret = -1; goto out; } ret = pthread_mutex_init (&rdd_config.lock, NULL); if (ret != 0) { fprintf (stderr, "pthread_mutex_init() failed (%s)\n", strerror (ret)); free (rdd_config.threads); pthread_barrier_destroy (&rdd_config.barrier); cleanup (); ret = -1; goto out; } for (i = 0; i < rdd_config.thread_count; i++) { ret = pthread_create (&rdd_config.threads[i], NULL, rdd_read_write, NULL); if (ret != 0) { fprintf (stderr, "pthread_create failed (%s)\n", strerror (errno)); exit (1); } } out: return ret; } static void rdd_wait_for_completion (void) { pthread_barrier_wait (&rdd_config.barrier); } int main (int argc, char *argv[]) { int ret = -1; rdd_default_config (); ret = argp_parse (&argp, argc, argv, 0, 0, NULL); if (ret != 0) { ret = -1; fprintf (stderr, "%s: argp_parse() failed\n", argv[0]); goto err; } if (!rdd_valid_config ()) { ret = -1; fprintf (stderr, "%s: configuration validation failed\n", argv[0]); goto err; } ret = rdd_spawn_threads (); if (ret != 0) { fprintf (stderr, "%s: spawning threads failed\n", argv[0]); goto err; } rdd_wait_for_completion (); err: return ret; }