/*
Copyright (c) 2008-2012 Red Hat, Inc. <http://www.redhat.com>
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 <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <argp.h>
#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;
}