| // 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; |
| } |