Skip to content

Instantly share code, notes, and snippets.

@iamahuman
Created March 8, 2021 14:43
Show Gist options
  • Save iamahuman/21d593e659539a18e09d2b450bee30e0 to your computer and use it in GitHub Desktop.
Save iamahuman/21d593e659539a18e09d2b450bee30e0 to your computer and use it in GitHub Desktop.
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifndef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 64
#endif
#define _LARGEFILE64_SOURCE
#ifndef _REENTRANT
#define _REENTRANT
#endif
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fs.h>
#define BLOCKSIZE 512
#define DEFAULT_TIMEOUT 30
static int blockdev_fd;
static int timeout = DEFAULT_TIMEOUT;
static int threads = 1;
static pthread_barrier_t barrier;
static volatile sig_atomic_t global_stop = 0;
static struct timespec start, end;
static const char *devname;
static ssize_t (*randacc_fn)(void *, off64_t);
static unsigned int cbsize;
struct controlblock {
unsigned char buffer[BLOCKSIZE];
volatile int stop;
int count;
off64_t maxoffset;
off64_t minoffset;
unsigned long long seed;
unsigned long long numbytes;
pthread_t tid;
};
static void *cbs;
#define GETCB(i) ((struct controlblock *)((unsigned char *)cbs + cbsize * (i)))
static void sighandle(int sig) {
(void)sig;
global_stop = 1;
}
static void perror_die(const char *string) {
perror(string);
exit(EXIT_FAILURE);
}
#ifdef __GNUC__
#define unlikely(cond) (__builtin_expect((cond), 0))
#else
#define unlikely(cond) (cond)
#endif
#define handle(msg, cond) (unlikely(cond) ? perror_die(msg) : (void)0)
static unsigned long long lcg(unsigned long long state) {
state *= 6364136223846793005ULL;
state += 1442695040888963407ULL;
return state;
}
static ssize_t randacc_pread(void *buffer, off64_t off) {
return pread(blockdev_fd, buffer, BLOCKSIZE, off);
}
static ssize_t randacc_fadvise(void *buffer, off64_t off) {
return posix_fadvise(blockdev_fd, off, BLOCKSIZE, POSIX_FADV_WILLNEED);
}
static void *threadfn(void *arg) {
struct controlblock *cb = (struct controlblock *)arg;
ssize_t retval;
unsigned long long state = cb->seed;
unsigned long long numbytes = cb->numbytes;
int count = 0;
off64_t maxoffset = 0;
off64_t minoffset = (1ULL << 63) - 1;
off64_t offset;
pthread_barrier_wait(&barrier);
while (!__atomic_load_n(&cb->stop, __ATOMIC_RELAXED)) {
state = lcg(state);
offset = state % numbytes;
offset &= -BLOCKSIZE; // do blocksize aligned seeks
retval = (*randacc_fn)(cb->buffer, offset);
handle("block access", retval == -1);
count++;
if (offset > maxoffset) {
maxoffset = offset;
} else if (offset < minoffset) {
minoffset = offset;
}
}
cb->count = count;
cb->maxoffset = maxoffset;
cb->minoffset = minoffset;
return NULL;
}
static void setup_args(int argc, char **argv) {
int open_flags = O_RDONLY | O_CLOEXEC | O_NOCTTY;
if (argc > 1 && strcmp(argv[1], "--fadvise") == 0) {
argc--, argv++;
randacc_fn = randacc_fadvise;
} else {
randacc_fn = randacc_pread;
#ifdef O_DIRECT
open_flags |= O_DIRECT;
#endif
}
if (!(argc == 2 || argc == 3 || argc == 4)) {
printf("Usage: %s [--fadvise] device [threads] [seconds]\n", argv[0]);
exit(1);
}
if (argc > 2) {
threads = atoi(argv[2]);
}
if (argc > 3) {
timeout = atoi(argv[3]);
}
devname = argv[1];
blockdev_fd = open(devname, open_flags);
handle("open", blockdev_fd < 0);
}
static void setup_tasks(unsigned long long numbytes) {
int i;
unsigned long long initseed;
cbsize = sysconf(_SC_PAGESIZE);
if (cbsize < sizeof(struct controlblock))
cbsize = sizeof(struct controlblock);
cbs = mmap(NULL, cbsize * threads, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
handle("mmap", cbs == MAP_FAILED);
initseed = time(NULL) ^ ((unsigned long long)getpid() << 32);
for (i = 0; i < threads; i++) {
struct controlblock *cb = GETCB(i);
cb->stop = 0;
cb->seed = initseed ^ ((unsigned long long)i << 24);
cb->numbytes = numbytes;
}
}
static void start_tasks(void) {
int i, retval;
retval = pthread_barrier_init(&barrier, NULL, threads + 1);
handle("pthread_barrier_init", retval && (errno = retval));
for (i = 0; i < threads; i++) {
struct controlblock *cb = GETCB(i);
retval = pthread_create(&cb->tid, NULL, &threadfn, cb);
handle("pthread_create", retval && (errno = retval));
}
signal(SIGINT, sighandle);
retval = pthread_barrier_wait(&barrier);
handle("pthread_barrier_wait",
retval && retval != PTHREAD_BARRIER_SERIAL_THREAD &&
(errno = retval));
}
static void wait_until_timeout(void) {
int retval;
struct timespec curr, next;
next = start;
for (;;) {
retval = clock_gettime(CLOCK_MONOTONIC, &curr);
handle("clock_gettime", retval == -1);
if (curr.tv_sec > end.tv_sec ||
(curr.tv_sec == end.tv_sec && curr.tv_nsec >= end.tv_nsec))
break;
next.tv_sec += 1;
do {
retval = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL);
} while (retval == EINTR && !global_stop);
handle("clock_nanosleep", retval != EINTR && (errno = retval));
if (global_stop) {
break;
}
write(STDERR_FILENO, ".", 1);
}
}
static void stop_threads(void) {
int i;
for (i = 0; i < threads; i++) {
struct controlblock *cb = GETCB(i);
__atomic_store_n(&cb->stop, 1, __ATOMIC_RELAXED);
}
}
static void join_threads(void) {
int i;
for (i = 0; i < threads; i++) {
struct controlblock *cb = GETCB(i);
pthread_join(cb->tid, NULL);
}
}
static void report(void) {
int i;
int count = 0;
off64_t maxoffset = 0;
off64_t minoffset = (1ULL << 63) - 1;
for (i = 0; i < threads; i++) {
struct controlblock *cb = GETCB(i);
count += cb->count;
if (cb->maxoffset > maxoffset) {
maxoffset = cb->maxoffset;
}
if (cb->minoffset < minoffset) {
minoffset = cb->minoffset;
}
}
if (!count) {
return;
}
printf("\nResults: %d seeks/second, "
"%.3f ms random access time (%llu < offsets < %llu)\n",
count / timeout, 1000.0 * timeout / count,
(unsigned long long)minoffset,
(unsigned long long)maxoffset);
}
int main(int argc, char **argv) {
int retval;
unsigned int physical_sector_size = 0;
size_t logical_sector_size = 0;
unsigned long long numblocks, numbytes;
printf("Seeker v3.0+iamahuman@github, 2009-06-17, "
"http://www.linuxinsight.com/how_fast_is_your_disk.html\n");
setup_args(argc, argv);
retval = ioctl(blockdev_fd, BLKGETSIZE64, &numbytes);
handle("ioctl(BLKGETSIZE64)", retval == -1);
retval = ioctl(blockdev_fd, BLKBSZGET, &logical_sector_size);
handle("ioctl(BLKBSZGET)", retval == -1 && logical_sector_size > 0);
retval = ioctl(blockdev_fd, BLKSSZGET, &physical_sector_size);
handle("ioctl(BLKSSZGET)", retval == -1 && physical_sector_size > 0);
numblocks = numbytes/BLOCKSIZE;
printf("Benchmarking %s [%llu blocks, %llu bytes, %llu GB, %llu MB, %llu GiB, %llu MiB]\n",
devname, numblocks,
numbytes, numbytes>>30, numbytes>>20,
numbytes/1000000000ULL, numbytes / 1000000ULL);
printf("[%zu logical sector size, %u physical sector size]\n",
logical_sector_size, physical_sector_size);
printf("[%d threads]\n", threads);
fflush(stdout);
setup_tasks(numbytes);
start_tasks();
retval = clock_gettime(CLOCK_MONOTONIC, &start);
handle("clock_gettime", retval == -1);
end = start;
end.tv_sec += timeout;
printf("Wait %d seconds", timeout);
fflush(stdout);
wait_until_timeout();
stop_threads();
join_threads();
report();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment