| // Copyright 2019 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 <fidl/fuchsia.hardware.goldfish/cpp/wire.h> |
| #include <lib/component/incoming/cpp/protocol.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/time.h> |
| #include <lib/zx/vmo.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <zircon/assert.h> |
| #include <zircon/syscalls.h> |
| |
| #include <memory> |
| |
| namespace { |
| |
| // Lines of text for each result are prefixed with this. |
| constexpr const char* kTestOutputPrefix = " - "; |
| |
| // The number of warm up iterations prior to test runs. |
| constexpr unsigned kWarmUpIterations = 5; |
| |
| // The number of test runs to do. |
| constexpr unsigned kNumTestRuns = 10; |
| |
| // Kilobyte. |
| constexpr unsigned kKb = 1024; |
| |
| // Megabyte. |
| constexpr unsigned kMb = kKb * kKb; |
| |
| unsigned SizeValue(unsigned size) { |
| if (size >= kMb) { |
| return size / kMb; |
| } |
| if (size >= kKb) { |
| return size / kKb; |
| } |
| return size; |
| } |
| |
| const char* SizeSuffix(unsigned size) { |
| if (size >= kMb) { |
| return "MiB"; |
| } |
| if (size >= kKb) { |
| return "KiB"; |
| } |
| return "B"; |
| } |
| |
| // Measures how long it takes to run some number of iterations of a closure. |
| // Returns a value in microseconds. |
| template <typename T> |
| float Measure(unsigned iterations, const T& closure) { |
| zx_ticks_t start = zx_ticks_get(); |
| for (unsigned i = 0; i < iterations; ++i) { |
| closure(); |
| } |
| zx_ticks_t stop = zx_ticks_get(); |
| return (static_cast<float>(stop - start) * 1000000.f / static_cast<float>(zx_ticks_per_second())); |
| } |
| |
| // Runs a closure repeatedly and prints its timing. |
| template <typename T> |
| void RunAndMeasure(const char* test_name, unsigned iterations, const T& closure) { |
| printf("\n* %s ...\n", test_name); |
| |
| float warm_up_time = Measure(kWarmUpIterations, closure); |
| |
| printf("%swarm-up: %u iterations in %.3f us, %.3f us per iteration\n", kTestOutputPrefix, |
| kWarmUpIterations, warm_up_time, warm_up_time / kWarmUpIterations); |
| |
| float run_times[kNumTestRuns]; |
| for (unsigned i = 0; i < kNumTestRuns; ++i) { |
| run_times[i] = Measure(iterations, closure); |
| zx::nanosleep(zx::deadline_after(zx::msec(10))); |
| } |
| |
| float min = 0, max = 0; |
| float cumulative = 0; |
| for (const auto rt : run_times) { |
| if (min == 0 || min > rt) { |
| min = rt; |
| } |
| if (max == 0 || max < rt) { |
| max = rt; |
| } |
| cumulative += rt; |
| } |
| float average = cumulative / kNumTestRuns; |
| |
| printf("%srun: %u test runs, %u iterations per run\n", kTestOutputPrefix, kNumTestRuns, |
| iterations); |
| printf("%stotal (usec): min: %.3f, max: %.3f, ave: %.3f\n", kTestOutputPrefix, min, max, average); |
| printf("%sper-iteration (usec): min: %.3f\n", |
| // The static cast is to avoid a "may change value" warning. |
| kTestOutputPrefix, min / static_cast<float>(iterations)); |
| } |
| |
| void RunPingPongBenchmark(fidl::WireSyncClient<fuchsia_hardware_goldfish::Pipe>& pipe, |
| unsigned size, unsigned iterations, bool skip_if_out_of_memory) { |
| { |
| auto result = pipe->SetBufferSize(size); |
| ZX_ASSERT(result.ok()); |
| |
| if (skip_if_out_of_memory && result.value().res == ZX_ERR_NO_MEMORY) { |
| fprintf(stderr, |
| "Failed to allocate memory (ZX_ERR_NO_MEMORY). " |
| "buffer size: %u (bytes). Test skipped.\n", |
| size); |
| return; |
| } |
| |
| ZX_ASSERT(result.value().res == ZX_OK); |
| } |
| |
| zx::vmo vmo; |
| { |
| auto result = pipe->GetBuffer(); |
| ZX_ASSERT(result.ok() && result.value().res == ZX_OK); |
| vmo = std::move(result.value().vmo); |
| } |
| |
| { |
| auto buffer = std::make_unique<uint8_t[]>(size); |
| uint8_t* data = buffer.get(); |
| memset(data, 0xff, size); |
| vmo.write(data, 0, size); |
| } |
| |
| char test_name[64]; |
| snprintf(test_name, sizeof(test_name), "pingpong, %u%s", SizeValue(size), SizeSuffix(size)); |
| |
| RunAndMeasure(test_name, iterations, [&pipe, size] { |
| auto result = pipe->DoCall(size, 0, size, 0); |
| // For the test purpose we expect the buffer is small enough |
| // so that we can finish in one write-read round trip. |
| ZX_ASSERT(result.ok() && result.value().res == ZX_OK); |
| ZX_ASSERT(result.value().actual == 2 * size); |
| }); |
| } |
| |
| } // namespace |
| |
| int main(int argc, char** argv) { |
| // TODO(https://fxbug.dev/42065067): Stop hardcoding the 000 in this path. |
| zx::result controller = |
| component::Connect<fuchsia_hardware_goldfish::Controller>("/dev/class/goldfish-pipe/000"); |
| ZX_ASSERT_MSG(controller.is_ok(), "%s", controller.status_string()); |
| |
| auto [pipe_device_client, pipe_device_server] = |
| fidl::Endpoints<fuchsia_hardware_goldfish::PipeDevice>::Create(); |
| { |
| fidl::Status status = |
| fidl::WireCall(controller.value())->OpenSession(std::move(pipe_device_server)); |
| ZX_ASSERT_MSG(status.ok(), "%s", status.status_string()); |
| } |
| |
| fidl::WireSyncClient pipe_device(std::move(pipe_device_client)); |
| |
| auto [pipe_client, pipe_server] = fidl::Endpoints<fuchsia_hardware_goldfish::Pipe>::Create(); |
| { |
| fidl::Status status = pipe_device->OpenPipe(std::move(pipe_server)); |
| ZX_ASSERT_MSG(status.ok(), "%s", status.status_string()); |
| } |
| |
| fidl::WireSyncClient pipe(std::move(pipe_client)); |
| |
| zx::vmo vmo; |
| |
| { |
| auto result = pipe->GetBuffer(); |
| ZX_ASSERT(result.ok() && result.value().res == ZX_OK); |
| vmo = std::move(result.value().vmo); |
| } |
| |
| // Connect to pingpong service. |
| constexpr char kPipeName[] = "pipe:pingpong"; |
| size_t bytes = strlen(kPipeName) + 1; |
| ZX_ASSERT(vmo.write(kPipeName, 0, bytes) == ZX_OK); |
| |
| { |
| auto result = pipe->Write(bytes, 0); |
| ZX_ASSERT(result.ok() && result.value().res == ZX_OK); |
| ZX_ASSERT(result.value().actual == bytes); |
| } |
| |
| if (argc > 1) { |
| for (int i = 1; (i + 1) < argc; i += 2) { |
| unsigned size = atoi(argv[i]); |
| unsigned iterations = atoi(argv[i + 1]); |
| |
| RunPingPongBenchmark(pipe, size, iterations, /* skip_if_out_of_memory */ false); |
| } |
| } else { |
| RunPingPongBenchmark(pipe, ZX_PAGE_SIZE, 500, /* skip_if_out_of_memory */ false); |
| |
| // In some cases the system might not be able to allocate a contiguous |
| // memory space of 1MB due to out of memory. In that case we should just |
| // skip the test. |
| RunPingPongBenchmark(pipe, kMb, 5, /* skip_if_out_of_memory */ true); |
| } |
| |
| printf("\nGoldfish benchmarks completed.\n"); |
| |
| return 0; |
| } |