direct-io-test.c
text/x-csrc
/*
* A simple test of forward/backward sequential scans with direct I/O.
*
* Build like this:
*
* gcc -O2 -Werror -o direct-io-test direct-io-test.c
*
* Use like this:
*
* ./direct-io-test /PATH/TO/FILE SIZE TIME_LIMIT
*
* with size in gigabytes (e.g. 32 means 32GB). The file will be created
* and populated with random data. Then it runs a number of test with
* different block sizes (1KB to 8MB) and directions. Can take a lot of
* time (1h or more, depending on the storage device).
*/
#define _GNU_SOURCE
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/time.h>
/*
* generate a file with size_gb size, fill it with random data
*/
void
generate_file(char *path, int size_gb)
{
/* longs to generate / write at once */
#define BUFF_SIZE 1024
long *buff;
size_t buff_bytes = BUFF_SIZE * sizeof(long);
long size = (size_gb * 1024L * 1024L * 1024L);
long nwritten = 0;
int fd = open(path, (O_WRONLY | O_CREAT | O_DIRECT), (S_IRUSR | S_IRUSR));
long path_memalign = pathconf(path,_PC_REC_XFER_ALIGN);
if (posix_memalign((void **) &buff, path_memalign, buff_bytes) != 0)
{
printf("posix_memalign failed (bytes %u memalign %ld)\n", buff_bytes, path_memalign);
fflush(NULL);
abort();
}
while (nwritten < size)
{
for (int i = 0; i < BUFF_SIZE; i++)
{
/* combine two 48-bit values into a 64-bit one */
buff[i] = ((lrand48() << 16) ^ lrand48());
}
write(fd, buff, buff_bytes);
nwritten += buff_bytes;
}
free(buff);
fsync(fd);
close(fd);
}
/*
* Run forward/backward sequential scans, starting from 8MB blocks, and
* going down to 1KB blocks. For each block test forward/backward scan.
*/
void
test_direct_io(char *path, int size_gb, int run, int run_limit)
{
int bs; /* block size (in KB) */
int fd = open(path, (O_RDONLY | O_DIRECT), 0);
long size_kbs = (size_gb * 1024L * 1024L);
long size = (size_kbs * 1024L);
long path_memalign = pathconf(path,_PC_REC_XFER_ALIGN);
/* print header */
printf("%16s%8s%8s%16s%8s%16s\n", "direction", "run", "block", "usec", "sec", "KB/s");
fflush(NULL);
/* start from largest block, go down to 1KB */
bs = 8192;
while (bs > 0)
{
char *buff = NULL;
long block_bytes = (bs * 1024L);
long nblocks = (size / block_bytes); /* total */
long nblocks_read;
#define MIN_CHECK_DISTANCE 1000
long nblocks_check;
off_t pos;
/* timing */
struct timeval ts,
te;
long usecs;
double secs;
posix_memalign((void **) &buff, path_memalign, block_bytes);
/* forward direction */
gettimeofday(&ts, NULL);
/* random starting position in the file (multiple of block size) */
pos = (lrand48() % nblocks) * block_bytes;
nblocks_read = 0;
nblocks_check = MIN_CHECK_DISTANCE;
while (nblocks_read < nblocks)
{
size_t r = pread(fd, buff, block_bytes, pos);
if (r == -1)
{
printf("pread failed %ld %m\n", r);
fflush(NULL);
abort();
}
else if (r < block_bytes) /* incomplete read, just retry */
continue;
pos += block_bytes;
if (pos >= size)
pos = 0;
nblocks_read++;
/* maybe check time limit */
if (nblocks_check == nblocks_read)
{
long nblocks_expected;
gettimeofday(&te, NULL);
usecs = (te.tv_sec - ts.tv_sec) * 1000000L + (te.tv_usec - ts.tv_usec);
secs = (double) usecs / 1000000L;
/* run exceeded limit */
if (secs > run_limit)
break;
/* how many blocks we expect to read in the limit */
nblocks_expected = run_limit * (nblocks_read / secs);
nblocks_check = (nblocks_expected + nblocks_read) / 2;
if (nblocks_check < nblocks_read + MIN_CHECK_DISTANCE)
nblocks_check = nblocks_read + MIN_CHECK_DISTANCE;
}
}
gettimeofday(&te, NULL);
usecs = (te.tv_sec - ts.tv_sec) * 1000000L + (te.tv_usec - ts.tv_usec);
secs = (double) usecs / 1000000L;
/* print info about the forward scan */
printf("%16s%8d%8d%16ld%8.2f%16.2f\n", "forward", run, bs, usecs, secs,
(nblocks_read * block_bytes / 1024L) / secs);
fflush(NULL);
/* backward direction */
gettimeofday(&ts, NULL);
/* random starting position in the file (multiple of block size) */
pos = (lrand48() % nblocks) * block_bytes;
nblocks_read = 0;
nblocks_check = MIN_CHECK_DISTANCE;
while (nblocks_read < nblocks) /* number of blocks remaining */
{
size_t r = pread(fd, buff, block_bytes, pos);
if (r == -1)
{
printf("pread failed %ld %m\n", r);
fflush(NULL);
abort();
}
else if (r < block_bytes) /* incomplete read, just retry */
{
printf("retry write\n");
fflush(NULL);
continue;
}
pos -= block_bytes;
if (pos < 0)
pos = size - block_bytes;
nblocks_read++;
/* maybe check time limit */
if (nblocks_check == nblocks_read)
{
long nblocks_expected;
gettimeofday(&te, NULL);
usecs = (te.tv_sec - ts.tv_sec) * 1000000L + (te.tv_usec - ts.tv_usec);
secs = (double) usecs / 1000000L;
/* run exceeded limit */
if (secs > run_limit)
break;
/* how many blocks we expect to read in the limit */
nblocks_expected = run_limit * (nblocks_read / secs);
nblocks_check = (nblocks_expected + nblocks_read) / 2;
if (nblocks_check < nblocks_read + MIN_CHECK_DISTANCE)
nblocks_check = nblocks_read + MIN_CHECK_DISTANCE;
}
}
gettimeofday(&te, NULL);
usecs = (te.tv_sec - ts.tv_sec) * 1000000L + (te.tv_usec - ts.tv_usec);
secs = (double) usecs / 1000000L;
/* print info about the backward scan */
printf("%16s%8d%8d%16ld%8.2f%16.2f\n", "backward", run, bs, usecs, secs,
(nblocks_read * block_bytes / 1024L) / secs);
fflush(NULL);
free(buff);
/* proceed to smaller block */
bs /= 2;
}
close(fd);
}
int
main(int argc, char **argv)
{
char *path = argv[1];
int size_gb = atoi(argv[2]);
int runs = atoi(argv[3]);
int limit = atoi(argv[4]);
printf("path = %s\n", path);
printf("size = %d GB\n", size_gb);
printf("runs = %d\n", runs);
printf("limit = %d\n", limit);
fflush(NULL);
generate_file(path, size_gb);
printf("file generated\n");
fflush(NULL);
/* */
for (int r = 1; r <= runs; r++)
test_direct_io(path, size_gb, r, limit);
return 0;
}