blob: 5978c82831d5580fcabbaafde59cc63a27de673d [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 <fidl/fuchsia.hardware.block/cpp/wire.h>
#include <fuchsia/hardware/block/driver/c/banjo.h>
#include <lib/component/incoming/cpp/clone.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fit/defer.h>
#include <lib/zx/channel.h>
#include <lib/zx/clock.h>
#include <lib/zx/fifo.h>
#include <lib/zx/vmo.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zircon/syscalls.h>
#include <zircon/time.h>
#include <zircon/types.h>
#include "src/storage/lib/block_client/cpp/client.h"
#include "src/storage/lib/block_client/cpp/remote_block_device.h"
#include "src/storage/testing/ram_disk.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 zx::duration iotime_posix(int is_read, fbl::unique_fd fd, size_t total_io_size,
size_t buffer_size) {
std::unique_ptr<unsigned char[]> buffer(new unsigned char[buffer_size]);
if (buffer.get() == nullptr) {
fprintf(stderr, "error: out of memory\n");
return zx::duration::infinite();
}
const zx::time t0 = zx::clock::get_monotonic();
size_t bytes_remaining = total_io_size;
const char* fn_name = is_read ? "read" : "write";
while (bytes_remaining > 0) {
size_t transfer_size = std::min(buffer_size, bytes_remaining);
ssize_t r = is_read ? read(fd.get(), buffer.get(), transfer_size)
: write(fd.get(), buffer.get(), transfer_size);
if (r < 0) {
fprintf(stderr, "error: %s() error %d\n", fn_name, errno);
return zx::duration::infinite();
}
if (static_cast<size_t>(r) != transfer_size) {
fprintf(stderr, "error: %s() %zu of %zu bytes processed\n", fn_name, r, transfer_size);
return zx::duration::infinite();
}
bytes_remaining -= transfer_size;
}
const zx::time t1 = zx::clock::get_monotonic();
return t1 - t0;
}
static zx::duration iotime_block(int is_read,
fidl::UnownedClientEnd<fuchsia_hardware_block::Block> block_client,
size_t total_io_size, size_t buffer_size) {
if ((total_io_size % 4096) || (buffer_size % 4096)) {
fprintf(stderr, "error: total_io_size and buffer_size must be multiples of 4K\n");
return zx::duration::infinite();
}
std::unique_ptr<unsigned char[]> buffer(new unsigned char[buffer_size]);
if (buffer.get() == nullptr) {
fprintf(stderr, "error: out of memory\n");
return zx::duration::infinite();
}
size_t bytes_remaining = total_io_size;
const zx::time t0 = zx::clock::get_monotonic();
while (bytes_remaining > 0) {
size_t transfer_size = std::min(buffer_size, bytes_remaining);
if (is_read) {
zx_status_t status = block_client::SingleReadBytes(block_client, buffer.get(), transfer_size,
total_io_size - bytes_remaining);
if (status != ZX_OK) {
fprintf(stderr, "error: failed SingleReadBytes() call: %s\n", zx_status_get_string(status));
return zx::duration::infinite();
}
} else {
zx_status_t status = block_client::SingleWriteBytes(block_client, buffer.get(), transfer_size,
total_io_size - bytes_remaining);
if (status != ZX_OK) {
fprintf(stderr, "error: failed SingleWriteBytes() call: %s\n",
zx_status_get_string(status));
return zx::duration::infinite();
}
}
bytes_remaining -= transfer_size;
}
const zx::time t1 = zx::clock::get_monotonic();
return t1 - t0;
}
static zx::duration iotime_fifo(const char* dev, int is_read, fbl::unique_fd fd,
size_t total_io_size, size_t buffer_size) {
fdio_cpp::FdioCaller disk_connection(std::move(fd));
zx::result channel = disk_connection.take_as<fuchsia_hardware_block_volume::Volume>();
if (channel.is_error()) {
fprintf(stderr, "error: cannot take volume channel for '%s': %s\n", dev,
channel.status_string());
return zx::duration::infinite();
}
zx::result block_device = block_client::RemoteBlockDevice::Create(std::move(channel.value()));
if (block_device.is_error()) {
fprintf(stderr, "error: cannot create remote block device for '%s': %s\n", dev,
block_device.status_string());
return zx::duration::infinite();
}
fuchsia_hardware_block::wire::BlockInfo info;
if (zx_status_t status = block_device->BlockGetInfo(&info); status != ZX_OK) {
fprintf(stderr, "error: failed BlockGetInfo() call for '%s':%s\n", dev,
zx_status_get_string(status));
return zx::duration::infinite();
}
zx::vmo vmo;
if (zx_status_t status = zx::vmo::create(buffer_size, 0, &vmo); status != ZX_OK) {
fprintf(stderr, "error: out of memory: %s\n", zx_status_get_string(status));
return zx::duration::infinite();
}
storage::Vmoid vmoid;
if (zx_status_t status = block_device->BlockAttachVmo(vmo, &vmoid); status != ZX_OK) {
fprintf(stderr, "error: cannot attach vmo for '%s':%s\n", dev, zx_status_get_string(status));
return zx::duration::infinite();
}
auto cleanup = fit::defer([&]() { block_device->BlockDetachVmo(std::move(vmoid)); });
const uint8_t opcode = is_read ? BLOCK_OPCODE_READ : BLOCK_OPCODE_WRITE;
block_fifo_request_t request = {
.command = {.opcode = opcode, .flags = 0},
.reqid = 0,
.group = 0,
.vmoid = vmoid.get(),
.vmo_offset = 0,
};
size_t bytes_remaining = total_io_size;
const zx::time t0 = zx::clock::get_monotonic();
while (bytes_remaining > 0) {
size_t transfer_size = std::min(buffer_size, bytes_remaining);
request.length = static_cast<uint32_t>(transfer_size / info.block_size);
request.dev_offset = (total_io_size - bytes_remaining) / info.block_size;
if (zx_status_t status = block_device->FifoTransaction(&request, 1); status != ZX_OK) {
fprintf(stderr, "error: block_fifo_txn error %s\n", zx_status_get_string(status));
return zx::duration::infinite();
}
bytes_remaining -= transfer_size;
}
const zx::time t1 = zx::clock::get_monotonic();
return t1 - t0;
}
static int usage() {
fprintf(stderr,
"usage: iotime <read|write> <posix|block|fifo> <device|--ramdisk> <bytes> <bufsize>\n\n"
" <bytes> and <bufsize> must be a multiple of 4k for block mode\n"
" --ramdisk only supported for block mode\n");
return -1;
}
int main(int argc, char** argv) {
if (argc != 6) {
return usage();
}
const std::string io_type(argv[1]);
const std::string mode(argv[2]);
const std::string device(argv[3]);
const size_t total_io_size = number(argv[4]);
const size_t buffer_size = number(argv[5]);
const bool is_read = io_type == "read";
if (!is_read && io_type != "write") {
fprintf(stderr, "io type can only be 'read' or 'write'. got: %s\n", io_type.c_str());
return usage();
}
storage::RamDisk ramdisk;
fbl::unique_fd fd;
zx::duration io_duration;
if (device == "--ramdisk") {
if (mode != "block") {
fprintf(stderr, "ramdisk only supported for block\n");
return EXIT_FAILURE;
}
constexpr size_t kBlockSizeBytes = 512;
zx::result result = storage::RamDisk::Create(kBlockSizeBytes, total_io_size / kBlockSizeBytes);
if (result.is_error()) {
fprintf(stderr, "error: cannot create %zu-byte ramdisk: %s\n", total_io_size,
result.status_string());
return EXIT_FAILURE;
}
ramdisk = std::move(result.value());
zx_handle_t handle = ramdisk_get_block_interface(ramdisk.client());
fidl::UnownedClientEnd<fuchsia_hardware_block::Block> block(handle);
io_duration = iotime_block(is_read, block, total_io_size, buffer_size);
} else {
fd.reset(open(device.c_str(), is_read ? O_RDONLY : O_WRONLY));
if (fd.get() < 0) {
fprintf(stderr, "error: cannot open '%s': %s\n", device.c_str(), strerror(errno));
return EXIT_FAILURE;
}
if (mode == "posix") {
io_duration = iotime_posix(is_read, std::move(fd), total_io_size, buffer_size);
} else if (mode == "block") {
fdio_cpp::UnownedFdioCaller disk_connection(fd);
fidl::UnownedClientEnd channel = disk_connection.borrow_as<fuchsia_hardware_block::Block>();
io_duration = iotime_block(is_read, channel, total_io_size, buffer_size);
} else if (mode == "fifo") {
io_duration = iotime_fifo(device.c_str(), is_read, std::move(fd), total_io_size, buffer_size);
} else {
fprintf(stderr, "error: unsupported mode '%s'\n", mode.c_str());
return EXIT_FAILURE;
}
}
if (io_duration == zx::duration::infinite()) {
return EXIT_FAILURE;
}
double s = (static_cast<double>(io_duration.to_nsecs())) / (static_cast<double>(1000000000));
double rate = (static_cast<double>(total_io_size)) / s;
const char* unit = "B";
if (rate > 1024 * 1024) {
unit = "MB";
rate /= 1024 * 1024;
} else if (rate > 1024) {
unit = "KB";
rate /= 1024;
}
fprintf(stderr, "%s %zu bytes (%zu chunks) in %zu ns: %g %s/s\n", io_type.c_str(), total_io_size,
buffer_size, io_duration.to_nsecs(), rate, unit);
return EXIT_SUCCESS;
}