blob: aa1115c95908c32c3975785669a4c18f85c99a37 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <atomic>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#include <lib/sync/completion.h>
#include <lib/zircon-internal/xorshiftrand.h>
#include <perftest/results.h>
#include <zircon/device/block.h>
#include <zircon/syscalls.h>
#include <zircon/time.h>
#include <zircon/types.h>
static uint64_t number(const char* str) {
char* end;
uint64_t n = strtoull(str, &end, 10);
uint64_t m = 1;
switch (*end) {
case 'G':
case 'g':
m = 1024*1024*1024;
break;
case 'M':
case 'm':
m = 1024*1024;
break;
case 'K':
case 'k':
m = 1024;
break;
}
return m * n;
}
static void bytes_per_second(uint64_t bytes, uint64_t nanos) {
double s = ((double)nanos) / ((double)1000000000);
double rate = ((double)bytes) / s;
const char* unit = "B";
if (rate > 1024*1024) {
unit = "MB";
rate /= 1024*1024;
} else if (rate > 1024) {
unit = "KB";
rate /= 1024;
}
fprintf(stderr, "%g %s/s\n", rate, unit);
}
static void ops_per_second(uint64_t count, uint64_t nanos) {
double s = ((double)nanos) / ((double)1000000000);
double rate = ((double)count) / s;
fprintf(stderr, "%g %s/s\n", rate, "ops");
}
typedef struct {
int fd;
zx_handle_t vmo;
zx_handle_t fifo;
reqid_t reqid;
vmoid_t vmoid;
size_t bufsz;
block_info_t info;
} blkdev_t;
static void blkdev_close(blkdev_t* blk) {
if (blk->fd >= 0) {
close(blk->fd);
}
zx_handle_close(blk->vmo);
zx_handle_close(blk->fifo);
memset(blk, 0, sizeof(blkdev_t));
blk->fd = -1;
}
static zx_status_t blkdev_open(int fd, const char* dev, size_t bufsz, blkdev_t* blk) {
memset(blk, 0, sizeof(blkdev_t));
blk->fd = fd;
blk->bufsz = bufsz;
zx_status_t r;
if (ioctl_block_get_info(fd, &blk->info) != sizeof(block_info_t)) {
fprintf(stderr, "error: cannot get block device info for '%s'\n", dev);
goto fail;
}
if (ioctl_block_get_fifos(fd, &blk->fifo) != sizeof(zx_handle_t)) {
fprintf(stderr, "error: cannot get fifo for '%s'\n", dev);
goto fail;
}
if ((r = zx_vmo_create(bufsz, 0, &blk->vmo)) != ZX_OK) {
fprintf(stderr, "error: out of memory %d\n", r);
goto fail;
}
zx_handle_t dup;
if ((r = zx_handle_duplicate(blk->vmo, ZX_RIGHT_SAME_RIGHTS, &dup)) != ZX_OK) {
fprintf(stderr, "error: cannot duplicate handle %d\n", r);
goto fail;
}
if (ioctl_block_attach_vmo(fd, &dup, &blk->vmoid) != sizeof(vmoid_t)) {
fprintf(stderr, "error: cannot attach vmo for '%s'\n", dev);
goto fail;
}
return ZX_OK;
fail:
blkdev_close(blk);
return ZX_ERR_INTERNAL;
}
typedef struct {
blkdev_t* blk;
size_t count;
size_t xfer;
uint64_t seed;
int max_pending;
bool write;
bool linear;
std::atomic<int> pending;
sync_completion_t signal;
} bio_random_args_t;
std::atomic<reqid_t> next_reqid(0);
static int bio_random_thread(void* arg) {
auto* a = reinterpret_cast<bio_random_args_t*>(arg);
size_t off = 0;
size_t count = a->count;
size_t xfer = a->xfer;
size_t blksize = a->blk->info.block_size;
size_t blkcount = ((count * xfer) / blksize) - (xfer / blksize);
rand64_t r64 = RAND63SEED(a->seed);
zx_handle_t fifo = a->blk->fifo;
size_t dev_off = 0;
while (count > 0) {
while (a->pending.load() == a->max_pending) {
sync_completion_wait(&a->signal, ZX_TIME_INFINITE);
sync_completion_reset(&a->signal);
}
block_fifo_request_t req = {};
req.reqid = next_reqid.fetch_add(1);
req.vmoid = a->blk->vmoid;
req.opcode = a->write ? BLOCKIO_WRITE : BLOCKIO_READ;
req.length = static_cast<uint32_t>(xfer);
req.vmo_offset = off;
if (a->linear) {
req.dev_offset = dev_off;
dev_off += xfer;
} else {
req.dev_offset = (rand64(&r64) % blkcount) * blksize;
}
off += xfer;
if ((off + xfer) > a->blk->bufsz) {
off = 0;
}
req.length /= static_cast<uint32_t>(blksize);
req.dev_offset /= blksize;
req.vmo_offset /= blksize;
#if 0
fprintf(stderr, "IO tid=%u vid=%u op=%x len=%zu vof=%zu dof=%zu\n",
req.reqid, req.vmoid, req.opcode, req.length, req.vmo_offset, req.dev_offset);
#endif
zx_status_t r = zx_fifo_write(fifo, sizeof(req), &req, 1, NULL);
if (r == ZX_ERR_SHOULD_WAIT) {
r = zx_object_wait_one(fifo, ZX_FIFO_WRITABLE | ZX_FIFO_PEER_CLOSED,
ZX_TIME_INFINITE, NULL);
if (r != ZX_OK) {
fprintf(stderr, "failed waiting for fifo\n");
zx_handle_close(fifo);
return -1;
}
continue;
} else if (r < 0) {
fprintf(stderr, "error: failed writing fifo\n");
zx_handle_close(fifo);
return -1;
}
a->pending.fetch_add(1);
count--;
}
return 0;
}
static zx_status_t bio_random(bio_random_args_t* a, uint64_t* _total, zx_duration_t* _res) {
thrd_t t;
int r;
size_t count = a->count;
zx_handle_t fifo = a->blk->fifo;
zx_time_t t0 = zx_clock_get_monotonic();
thrd_create(&t, bio_random_thread, a);
while (count > 0) {
block_fifo_response_t resp;
zx_status_t r = zx_fifo_read(fifo, sizeof(resp), &resp, 1, NULL);
if (r == ZX_ERR_SHOULD_WAIT) {
r = zx_object_wait_one(fifo, ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED,
ZX_TIME_INFINITE, NULL);
if (r != ZX_OK) {
fprintf(stderr, "failed waiting for fifo: %d\n", r);
goto fail;
}
continue;
} else if (r < 0) {
fprintf(stderr, "error: failed reading fifo: %d\n", r);
goto fail;
}
if (resp.status != ZX_OK) {
fprintf(stderr, "error: io txn failed %d (%zu remaining)\n",
resp.status, count);
goto fail;
}
count--;
if (a->pending.fetch_sub(1) == a->max_pending) {
sync_completion_signal(&a->signal);
}
}
zx_time_t t1;
t1 = zx_clock_get_monotonic();
fprintf(stderr, "waiting for thread to exit...\n");
thrd_join(t, &r);
*_res = zx_time_sub_time(t1, t0);
*_total = a->count * a->xfer;
return ZX_OK;
fail:
zx_handle_close(a->blk->fifo);
thrd_join(t, &r);
return ZX_ERR_IO;
}
void usage(void) {
fprintf(stderr, "usage: biotime <option>* <device>\n"
"\n"
"args: -bs <num> transfer block size (multiple of 4K)\n"
" -tt <num> total bytes to transfer\n"
" -mo <num> maximum outstanding ops (1..128)\n"
" -read test reading from the block device (default)\n"
" -write test writing to the block device\n"
" -live-dangerously required if using \"-write\"\n"
" -linear transfers in linear order (default)\n"
" -random random transfers across total range\n"
" -output-file <filename> destination file for "
"writing results in JSON format\n"
);
}
#define needparam() do { \
argc--; argv++; \
if (argc == 0) { \
fprintf(stderr, "error: option %s needs a parameter\n", argv[-1]); \
return -1; \
} } while (0)
#define nextarg() do { argc--; argv++; } while (0)
#define error(x...) do { fprintf(stderr, x); usage(); return -1; } while (0)
int main(int argc, char** argv) {
blkdev_t blk;
bool live_dangerously = false;
bio_random_args_t a = {};
a.blk = &blk;
a.xfer = 32768;
a.seed = 7891263897612ULL;
a.max_pending = 128;
a.write = false;
a.linear = true;
const char* output_file = nullptr;
size_t total = 0;
nextarg();
while (argc > 0) {
if (argv[0][0] != '-') {
break;
}
if (!strcmp(argv[0], "-bs")) {
needparam();
a.xfer = number(argv[0]);
if ((a.xfer == 0) || (a.xfer % 4096)) {
error("error: block size must be multiple of 4K\n");
}
} else if (!strcmp(argv[0], "-tt")) {
needparam();
total = number(argv[0]);
} else if (!strcmp(argv[0], "-mo")) {
needparam();
size_t n = number(argv[0]);
if ((n < 1) || (n > 128)) {
error("error: max pending must be between 1 and 128\n");
}
a.max_pending = static_cast<int>(n);
} else if (!strcmp(argv[0], "-read")) {
a.write = false;
} else if (!strcmp(argv[0], "-write")) {
a.write = true;
} else if (!strcmp(argv[0], "-live-dangerously")) {
live_dangerously = true;
} else if (!strcmp(argv[0], "-linear")) {
a.linear = true;
} else if (!strcmp(argv[0], "-random")) {
a.linear = false;
} else if (!strcmp(argv[0], "-output-file")) {
needparam();
output_file = argv[0];
} else if (!strcmp(argv[0], "-h")) {
usage();
return 0;
} else {
error("error: unknown option: %s\n", argv[0]);
}
nextarg();
}
if (argc == 0) {
error("error: no device specified\n");
}
if (argc > 1) {
error("error: unexpected arguments\n");
}
if (a.write && !live_dangerously) {
error("error: the option \"-live-dangerously\" is required when using"
" \"-write\"\n");
}
const char* device_filename = argv[0];
int fd;
if ((fd = open(device_filename, O_RDONLY)) < 0) {
fprintf(stderr, "error: cannot open '%s'\n", device_filename);
return -1;
}
if (blkdev_open(fd, device_filename, 8*1024*1024, &blk) != ZX_OK) {
return -1;
}
size_t devtotal = blk.info.block_count * blk.info.block_size;
// default to entire device
if ((total == 0) || (total > devtotal)) {
total = devtotal;
}
a.count = total / a.xfer;
zx_duration_t res = 0;
total = 0;
if (bio_random(&a, &total, &res) != ZX_OK) {
return -1;
}
fprintf(stderr, "%zu bytes in %zu ns: ", total, res);
bytes_per_second(total, res);
fprintf(stderr, "%zu ops in %zu ns: ", a.count, res);
ops_per_second(a.count, res);
if (output_file) {
perftest::ResultsSet results;
auto* test_case = results.AddTestCase(
"fuchsia.zircon", "BlockDeviceThroughput", "bytes/second");
double time_in_seconds = static_cast<double>(res) / 1e9;
test_case->AppendValue(static_cast<double>(total) / time_in_seconds);
if (!results.WriteJSONFile(output_file)) {
return 1;
}
}
return 0;
}