-
-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
35c7c72
commit 3678c2e
Showing
2 changed files
with
332 additions
and
1 deletion.
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,322 @@ | ||
// Copyright © Tavian Barnes <[email protected]> | ||
// SPDX-License-Identifier: 0BSD | ||
|
||
#include "atomic.h" | ||
#include "bfs.h" | ||
#include "bfstd.h" | ||
#include "diag.h" | ||
#include "ioq.h" | ||
#include "sighook.h" | ||
#include "xtime.h" | ||
|
||
#include <errno.h> | ||
#include <locale.h> | ||
#include <signal.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <time.h> | ||
#include <unistd.h> | ||
|
||
/** Which clock to use for benchmarking. */ | ||
static clockid_t clockid = CLOCK_REALTIME; | ||
|
||
/** Get a current time measurement. */ | ||
static void gettime(struct timespec *tp) { | ||
int ret = clock_gettime(clockid, tp); | ||
bfs_everify(ret == 0, "clock_gettime(%d)", (int)clockid); | ||
} | ||
|
||
/** | ||
* Time measurements. | ||
*/ | ||
struct times { | ||
/** The start time. */ | ||
struct timespec start; | ||
|
||
/** Total requests started. */ | ||
size_t pushed; | ||
/** Total requests finished. */ | ||
size_t popped; | ||
|
||
/** Number of timed requests (latency). */ | ||
size_t timed_reqs; | ||
/** The start time for the currently tracked request. */ | ||
struct timespec req_start; | ||
/** Whether a timed request is currently in flight. */ | ||
bool timing; | ||
|
||
/** Latency measurements. */ | ||
struct { | ||
struct timespec min; | ||
struct timespec max; | ||
struct timespec sum; | ||
} latency; | ||
}; | ||
|
||
/** Initialize a timer. */ | ||
static void times_init(struct times *times) { | ||
*times = (struct times) { | ||
.latency = { | ||
.min = { .tv_sec = 1000 }, | ||
}, | ||
}; | ||
gettime(×->start); | ||
} | ||
|
||
/** Start timing a single request. */ | ||
static void start_request(struct times *times) { | ||
gettime(×->req_start); | ||
times->timing = true; | ||
} | ||
|
||
/** Finish timing a request. */ | ||
static void finish_request(struct times *times) { | ||
struct timespec elapsed; | ||
gettime(&elapsed); | ||
timespec_sub(&elapsed, ×->req_start); | ||
|
||
timespec_min(×->latency.min, &elapsed); | ||
timespec_max(×->latency.max, &elapsed); | ||
timespec_add(×->latency.sum, &elapsed); | ||
|
||
bfs_assert(times->timing); | ||
times->timing = false; | ||
++times->timed_reqs; | ||
} | ||
|
||
/** Add times to the totals, and reset the lap times. */ | ||
static void times_lap(struct times *total, struct times *lap) { | ||
total->pushed += lap->pushed; | ||
total->popped += lap->popped; | ||
total->timed_reqs += lap->timed_reqs; | ||
|
||
timespec_min(&total->latency.min, &lap->latency.min); | ||
timespec_max(&total->latency.max, &lap->latency.max); | ||
timespec_add(&total->latency.sum, &lap->latency.sum); | ||
|
||
times_init(lap); | ||
} | ||
|
||
/** Print some times. */ | ||
static void times_print(const struct times *times, long seconds) { | ||
struct timespec elapsed; | ||
gettime(&elapsed); | ||
timespec_sub(&elapsed, ×->start); | ||
|
||
double fsec = timespec_ns(&elapsed) / 1.0e9; | ||
double iops = times->popped / fsec; | ||
double mean = timespec_ns(×->latency.sum) / times->timed_reqs; | ||
double min = timespec_ns(×->latency.min); | ||
double max = timespec_ns(×->latency.max); | ||
|
||
if (seconds > 0) { | ||
printf("%9ld", seconds); | ||
} else if (elapsed.tv_nsec >= 10 * 1000 * 1000) { | ||
printf("%9.2f", fsec); | ||
} else { | ||
printf("%9.0f", fsec); | ||
} | ||
|
||
printf(" │ %'17.0f │ %'15.0f ∈ [%'6.0f .. %'7.0f]\n", iops, mean, min, max); | ||
fflush(stdout); | ||
} | ||
|
||
/** Push an ioq request. */ | ||
static bool push(struct ioq *ioq, enum ioq_nop_type type, struct times *lap) { | ||
void *ptr = NULL; | ||
|
||
// Track latency for a small fraction of requests | ||
if (!lap->timing && (lap->pushed + 1) % 128 == 0) { | ||
start_request(lap); | ||
ptr = lap; | ||
} | ||
|
||
int ret = ioq_nop(ioq, type, ptr); | ||
if (ret != 0) { | ||
bfs_everify(errno == EAGAIN, "ioq_nop(%d)", (int)type); | ||
return false; | ||
} | ||
|
||
++lap->pushed; | ||
return true; | ||
} | ||
|
||
/** Pop an ioq request. */ | ||
static bool pop(struct ioq *ioq, struct times *lap, bool block) { | ||
struct ioq_ent *ent = ioq_pop(ioq, block); | ||
if (!ent) { | ||
return false; | ||
} | ||
|
||
if (ent->ptr) { | ||
finish_request(lap); | ||
} | ||
|
||
ioq_free(ioq, ent); | ||
++lap->popped; | ||
return true; | ||
} | ||
|
||
/** ^C flag. */ | ||
static atomic bool quit = false; | ||
|
||
/** ^C hook. */ | ||
static void ctrlc(int sig, siginfo_t *info, void *arg) { | ||
store(&quit, true, relaxed); | ||
} | ||
|
||
int main(int argc, char *argv[]) { | ||
// Use CLOCK_MONOTONIC if available | ||
#if defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0 | ||
if (sysoption(MONOTONIC_CLOCK) > 0) { | ||
clockid = CLOCK_MONOTONIC; | ||
} | ||
#endif | ||
|
||
// Enable thousands separators | ||
setlocale(LC_ALL, ""); | ||
|
||
// -d: queue depth | ||
long depth = 4096; | ||
// -j: threads | ||
long threads = 0; | ||
// -t: timeout | ||
double timeout = 5.0; | ||
// -L|-H: ioq_nop() type | ||
enum ioq_nop_type type = IOQ_NOP_LIGHT; | ||
|
||
const char *cmd = argc > 0 ? argv[0] : "ioq"; | ||
int c; | ||
while (c = getopt(argc, argv, ":d:j:t:LH"), c != -1) { | ||
switch (c) { | ||
case 'd': | ||
if (xstrtol(optarg, NULL, 10, &depth) != 0) { | ||
fprintf(stderr, "%s: Bad depth '%s': %s\n", cmd, optarg, errstr()); | ||
return EXIT_FAILURE; | ||
} | ||
break; | ||
case 'j': | ||
if (xstrtol(optarg, NULL, 10, &threads) != 0) { | ||
fprintf(stderr, "%s: Bad thread count '%s': %s\n", cmd, optarg, errstr()); | ||
return EXIT_FAILURE; | ||
} | ||
break; | ||
case 't': | ||
if (xstrtod(optarg, NULL, &timeout) != 0) { | ||
fprintf(stderr, "%s: Bad timeout '%s': %s\n", cmd, optarg, errstr()); | ||
return EXIT_FAILURE; | ||
} | ||
break; | ||
case 'L': | ||
type = IOQ_NOP_LIGHT; | ||
break; | ||
case 'H': | ||
type = IOQ_NOP_HEAVY; | ||
break; | ||
case ':': | ||
fprintf(stderr, "%s: Missing argument to -%c\n", cmd, optopt); | ||
return EXIT_FAILURE; | ||
case '?': | ||
fprintf(stderr, "%s: Unrecognized option -%c\n", cmd, optopt); | ||
return EXIT_FAILURE; | ||
} | ||
} | ||
|
||
if (threads <= 0) { | ||
threads = xsysconf(_SC_NPROCESSORS_ONLN); | ||
if (threads > 8) { | ||
threads = 8; | ||
} | ||
} | ||
if (threads < 2) { | ||
threads = 2; | ||
} | ||
--threads; | ||
|
||
// Listen for ^C to print the summary | ||
struct sighook *hook = sighook(SIGINT, ctrlc, NULL, SH_CONTINUE | SH_ONESHOT); | ||
|
||
printf("I/O queue benchmark (%s)\n\n", bfs_version); | ||
|
||
printf("[-d] depth: %ld\n", depth); | ||
printf("[-j] threads: %ld (including main)\n", threads + 1); | ||
if (type == IOQ_NOP_HEAVY) { | ||
printf("[-H] type: heavy (with syscalls)\n"); | ||
} else { | ||
printf("[-L] type: light (no syscalls)\n"); | ||
} | ||
printf("\n"); | ||
|
||
printf(" Time (s) │ Throughput (IO/s) │ Latency (ns/IO)\n"); | ||
printf("══════════╪═══════════════════╪═════════════════\n"); | ||
fflush(stdout); | ||
|
||
struct ioq *ioq = ioq_create(depth, threads); | ||
bfs_everify(ioq, "ioq_create(%ld, %ld)", depth, threads); | ||
|
||
// Pre-allocate all the requests | ||
while (ioq_capacity(ioq) > 0) { | ||
int ret = ioq_nop(ioq, type, NULL); | ||
bfs_everify(ret == 0, "ioq_nop(%d)", (int)type); | ||
} | ||
while (true) { | ||
struct ioq_ent *ent = ioq_pop(ioq, true); | ||
if (!ent) { | ||
break; | ||
} | ||
ioq_free(ioq, ent); | ||
} | ||
|
||
struct times total, lap; | ||
times_init(&total); | ||
lap = total; | ||
|
||
long seconds = 0; | ||
while (!load(&quit, relaxed)) { | ||
bool was_timing = lap.timing; | ||
|
||
for (int i = 0; i < 16; ++i) { | ||
bool block = ioq_capacity(ioq) == 0; | ||
if (!pop(ioq, &lap, block)) { | ||
break; | ||
} | ||
} | ||
|
||
if (was_timing && !lap.timing) { | ||
struct timespec elapsed; | ||
gettime(&elapsed); | ||
timespec_sub(&elapsed, &total.start); | ||
|
||
if (elapsed.tv_sec > seconds) { | ||
seconds = elapsed.tv_sec; | ||
times_print(&lap, seconds); | ||
times_lap(&total, &lap); | ||
} | ||
|
||
double ns = timespec_ns(&elapsed); | ||
if (timeout > 0 && ns >= timeout * 1.0e9) { | ||
break; | ||
} | ||
} | ||
|
||
for (int i = 0; i < 8; ++i) { | ||
if (!push(ioq, type, &lap)) { | ||
break; | ||
} | ||
} | ||
} | ||
|
||
while (pop(ioq, &lap, true)); | ||
times_lap(&total, &lap); | ||
|
||
if (load(&quit, relaxed)) { | ||
printf("\r────^C────┼───────────────────┼─────────────────\n"); | ||
} else { | ||
printf("──────────┼───────────────────┼─────────────────\n"); | ||
} | ||
times_print(&total, 0); | ||
|
||
ioq_destroy(ioq); | ||
sigunhook(hook); | ||
return 0; | ||
} |