blob: 6e584825a2375e9bd9f369b90bdd96ef8175867c [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 <errno.h>
#include <fcntl.h>
#include <fuchsia/hardware/block/c/fidl.h>
#include <lib/fit/defer.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/sync/completion.h>
#include <lib/zircon-internal/xorshiftrand.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#include <zircon/device/block.h>
#include <zircon/syscalls.h>
#include <zircon/time.h>
#include <zircon/types.h>
#include <atomic>
#include <perftest/results.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;
fuchsia_hardware_block_VmoId vmoid;
size_t bufsz;
fuchsia_hardware_block_BlockInfo 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;
auto cleanup = fit::defer([blk]() { blkdev_close(blk); });
fdio_cpp::UnownedFdioCaller caller(fd);
zx_status_t status;
zx_status_t io_status =
fuchsia_hardware_block_BlockGetInfo(caller.borrow_channel(), &status, &blk->info);
if (io_status != ZX_OK || status != ZX_OK) {
fprintf(stderr, "error: cannot get block device info for '%s'\n", dev);
return ZX_ERR_INTERNAL;
}
io_status = fuchsia_hardware_block_BlockGetFifo(caller.borrow_channel(), &status, &blk->fifo);
if (io_status != ZX_OK || status != ZX_OK) {
fprintf(stderr, "error: cannot get fifo for '%s'\n", dev);
return ZX_ERR_INTERNAL;
}
if ((status = zx_vmo_create(bufsz, 0, &blk->vmo)) != ZX_OK) {
fprintf(stderr, "error: out of memory %d\n", status);
return status;
}
zx_handle_t dup;
if ((status = zx_handle_duplicate(blk->vmo, ZX_RIGHT_SAME_RIGHTS, &dup)) != ZX_OK) {
fprintf(stderr, "error: cannot duplicate handle %d\n", status);
return status;
}
io_status =
fuchsia_hardware_block_BlockAttachVmo(caller.borrow_channel(), dup, &status, &blk->vmoid);
if (io_status != ZX_OK || status != ZX_OK) {
fprintf(stderr, "error: cannot attach vmo for '%s'\n", dev);
return ZX_ERR_INTERNAL;
}
cleanup.cancel();
return ZX_OK;
}
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.id;
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.id, 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);
auto cleanup = fit::defer([&a, &t]() {
int r;
zx_handle_close(a->blk->fifo);
thrd_join(t, &r);
});
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);
return r;
}
continue;
} else if (r < 0) {
fprintf(stderr, "error: failed reading fifo: %d\n", r);
return r;
}
if (resp.status != ZX_OK) {
fprintf(stderr, "error: io txn failed %d (%zu remaining)\n", resp.status, count);
return resp.status;
}
count--;
if (a->pending.fetch_sub(1) == a->max_pending) {
sync_completion_signal(&a->signal);
}
}
cleanup.cancel();
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;
}
void usage(void) {
fprintf(stderr,
"usage: biotime <option>* <device>\n"
"\n"
"args: -bs <num> transfer block size (multiple of 4K)\n"
" -total-bytes-to-transfer <num> total amount to read or write\n"
" -iter <num-iterations> total number of iterations (0 stands for infinite)\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 = {};
bool opt_write = false;
bool opt_linear = true;
int opt_max_pending = 128;
size_t opt_xfer_size = 32768;
uint64_t opt_num_iter = 1;
bool loop_forever = false;
a.blk = &blk;
a.seed = 7891263897612ULL;
const char* output_file = nullptr;
size_t total = 0;
nextarg();
while (argc > 0) {
if (argv[0][0] != '-') {
break;
}
if (!strcmp(argv[0], "-bs")) {
needparam();
opt_xfer_size = number(argv[0]);
if ((opt_xfer_size == 0) || (opt_xfer_size % 4096)) {
error("error: block size must be multiple of 4K\n");
}
} else if (!strcmp(argv[0], "-total-bytes-to-transfer")) {
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");
}
opt_max_pending = static_cast<int>(n);
} else if (!strcmp(argv[0], "-read")) {
opt_write = false;
} else if (!strcmp(argv[0], "-write")) {
opt_write = true;
} else if (!strcmp(argv[0], "-live-dangerously")) {
live_dangerously = true;
} else if (!strcmp(argv[0], "-linear")) {
opt_linear = true;
} else if (!strcmp(argv[0], "-random")) {
opt_linear = false;
} else if (!strcmp(argv[0], "-output-file")) {
needparam();
output_file = argv[0];
} else if (!strcmp(argv[0], "-h")) {
usage();
return 0;
} else if (!strcmp(argv[0], "-iter")) {
needparam();
opt_num_iter = strtoull(argv[0], NULL, 10);
if (opt_num_iter == 0)
loop_forever = true;
} 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];
do {
int fd;
a.xfer = opt_xfer_size;
a.max_pending = opt_max_pending;
a.write = opt_write;
a.linear = opt_linear;
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;
}
}
blkdev_close(&blk);
} while (loop_forever || (--opt_num_iter > 0));
return 0;
}