Created
March 8, 2021 14:43
-
-
Save iamahuman/21d593e659539a18e09d2b450bee30e0 to your computer and use it in GitHub Desktop.
Enhanced version of http://www.linuxinsight.com/how_fast_is_your_disk.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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