| // 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 <fuchsia/device/schedule/work/test/llcpp/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/loop.h> |
| #include <lib/fidl-async/cpp/bind.h> |
| #include <lib/sync/completion.h> |
| #include <lib/zx/clock.h> |
| #include <lib/zx/time.h> |
| |
| #include <memory> |
| #include <thread> |
| #include <vector> |
| |
| #include <ddk/binding.h> |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/platform-defs.h> |
| #include <ddktl/device.h> |
| #include <ddktl/fidl.h> |
| #include <fbl/alloc_checker.h> |
| |
| namespace { |
| |
| using llcpp::fuchsia::device::schedule::work::test::LatencyHistogram; |
| using llcpp::fuchsia::device::schedule::work::test::OwnedChannelDevice; |
| using llcpp::fuchsia::device::schedule::work::test::TestDevice; |
| |
| class TestScheduleWorkDriver; |
| using DeviceType = ddk::Device<TestScheduleWorkDriver, ddk::UnbindableNew, ddk::Messageable>; |
| |
| class TestScheduleWorkDriver : public DeviceType, public TestDevice::Interface { |
| public: |
| struct WorkItemCtx { |
| zx::time start; |
| TestScheduleWorkDriver* parent; |
| }; |
| |
| TestScheduleWorkDriver(zx_device_t* parent) |
| : DeviceType(parent), loop_(&kAsyncLoopConfigNeverAttachToThread) { |
| loop_.StartThread("schedule-work-test-loop"); |
| } |
| |
| ~TestScheduleWorkDriver() { loop_.Shutdown(); } |
| |
| zx_status_t Bind(); |
| |
| void DdkUnbindNew(ddk::UnbindTxn txn) { txn.Reply(); } |
| void DdkRelease() { delete this; } |
| |
| void ScheduleWork(uint32_t batch_size, uint32_t num_work_items, |
| ScheduleWorkCompleter::Sync completer) override; |
| void ScheduleWorkDifferentThread(ScheduleWorkDifferentThreadCompleter::Sync completer) override; |
| void GetDoneEvent(GetDoneEventCompleter::Sync completer) override; |
| void ScheduledWorkRan(ScheduledWorkRanCompleter::Sync completer) override; |
| void GetChannel(zx::channel request, GetChannelCompleter::Sync completer) override; |
| |
| zx_status_t DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) { |
| DdkTransaction transaction(txn); |
| ::llcpp::fuchsia::device::schedule::work::test::TestDevice::Dispatch(this, msg, &transaction); |
| return transaction.Status(); |
| } |
| |
| static void DoWork(void* ctx) { |
| auto context = std::unique_ptr<WorkItemCtx>(static_cast<WorkItemCtx*>(ctx)); |
| context->parent->WorkItemCompletion(std::move(context)); |
| } |
| |
| private: |
| void WorkItemCompletion(std::unique_ptr<WorkItemCtx> work_item_ctx) { |
| work_items_ran_++; |
| |
| zx::duration duration = zx::clock::get_monotonic() - work_item_ctx->start; |
| if (duration < zx::nsec(100)) { |
| histogram_.buckets[0]++; |
| } else if (duration < zx::nsec(250)) { |
| histogram_.buckets[1]++; |
| } else if (duration < zx::nsec(500)) { |
| histogram_.buckets[2]++; |
| } else if (duration < zx::usec(1)) { |
| histogram_.buckets[3]++; |
| } else if (duration < zx::usec(2)) { |
| histogram_.buckets[4]++; |
| } else if (duration < zx::usec(4)) { |
| histogram_.buckets[5]++; |
| } else if (duration < zx::usec(7)) { |
| histogram_.buckets[6]++; |
| } else if (duration < zx::usec(15)) { |
| histogram_.buckets[7]++; |
| } else if (duration < zx::usec(30)) { |
| histogram_.buckets[8]++; |
| } else { |
| histogram_.buckets[9]++; |
| } |
| |
| if (work_items_ran_ == work_items_expected_) { |
| ZX_ASSERT(done_event_.signal(0, ZX_USER_SIGNAL_0) == ZX_OK); |
| } |
| |
| if (work_items_left_ > 0) { |
| work_item_ctx->start = zx::clock::get_monotonic(); |
| if (DdkScheduleWork(DoWork, work_item_ctx.get()) == ZX_OK) { |
| work_items_left_--; |
| work_item_ctx.release(); |
| } |
| } |
| } |
| |
| class Connection : public OwnedChannelDevice::Interface { |
| public: |
| struct WorkItemCtx { |
| zx::time start; |
| Connection* parent; |
| }; |
| |
| Connection(TestScheduleWorkDriver* parent) : parent_(parent) {} |
| |
| zx_status_t Connect(async_dispatcher_t* dispatcher, zx::channel request) { |
| return fidl::Bind(dispatcher, std::move(request), this); |
| } |
| |
| void ScheduleWork(uint32_t batch_size, uint32_t num_work_items, |
| ScheduleWorkCompleter::Sync completer) override; |
| |
| static void DoWork(void* ctx) { |
| auto context = std::unique_ptr<WorkItemCtx>(static_cast<WorkItemCtx*>(ctx)); |
| context->parent->WorkItemCompletion(std::move(context)); |
| } |
| |
| private: |
| void WorkItemCompletion(std::unique_ptr<WorkItemCtx> work_item_ctx) { |
| work_items_ran_++; |
| |
| zx::duration duration = zx::clock::get_monotonic() - work_item_ctx->start; |
| if (duration < zx::nsec(100)) { |
| histogram_.buckets[0]++; |
| } else if (duration < zx::nsec(250)) { |
| histogram_.buckets[1]++; |
| } else if (duration < zx::nsec(500)) { |
| histogram_.buckets[2]++; |
| } else if (duration < zx::usec(1)) { |
| histogram_.buckets[3]++; |
| } else if (duration < zx::usec(2)) { |
| histogram_.buckets[4]++; |
| } else if (duration < zx::usec(4)) { |
| histogram_.buckets[5]++; |
| } else if (duration < zx::usec(7)) { |
| histogram_.buckets[6]++; |
| } else if (duration < zx::usec(15)) { |
| histogram_.buckets[7]++; |
| } else if (duration < zx::usec(30)) { |
| histogram_.buckets[8]++; |
| } else { |
| histogram_.buckets[9]++; |
| } |
| if (work_items_ran_ == work_items_expected_) { |
| sync_completion_signal(&completion_); |
| } |
| |
| if (work_items_left_ > 0) { |
| work_item_ctx->start = zx::clock::get_monotonic(); |
| if (parent_->DdkScheduleWork(DoWork, work_item_ctx.get()) == ZX_OK) { |
| work_items_left_--; |
| work_item_ctx.release(); |
| } |
| } |
| } |
| |
| uint32_t work_items_left_ = 0; |
| uint32_t work_items_ran_ = 0; |
| uint32_t work_items_expected_ = 0; |
| LatencyHistogram histogram_; |
| TestScheduleWorkDriver* parent_; |
| sync_completion_t completion_; |
| }; |
| |
| async::Loop loop_; |
| zx::event done_event_; |
| std::vector<std::unique_ptr<Connection>> open_connections_; |
| uint32_t work_items_left_ = 0; |
| uint32_t work_items_ran_ = 0; |
| uint32_t work_items_expected_ = 0; |
| LatencyHistogram histogram_; |
| }; |
| |
| zx_status_t TestScheduleWorkDriver::Bind() { |
| if (auto status = zx::event::create(0, &done_event_); status != ZX_OK) { |
| return status; |
| } |
| return DdkAdd("schedule-work-test"); |
| } |
| |
| void TestScheduleWorkDriver::ScheduleWork(uint32_t batch_size, uint32_t num_work_items, |
| ScheduleWorkCompleter::Sync completer) { |
| batch_size = std::min(batch_size, num_work_items); |
| |
| work_items_left_ = num_work_items - batch_size; |
| work_items_expected_ = num_work_items; |
| |
| for (uint32_t i = 0; i < batch_size; i++) { |
| auto work_item_ctx = std::make_unique<WorkItemCtx>(); |
| work_item_ctx->start = zx::clock::get_monotonic(); |
| work_item_ctx->parent = this; |
| |
| auto status = DdkScheduleWork(DoWork, work_item_ctx.get()); |
| if (status != ZX_OK) { |
| completer.ReplyError(status); |
| return; |
| } |
| work_item_ctx.release(); |
| } |
| |
| completer.ReplySuccess(); |
| } |
| |
| void TestScheduleWorkDriver::ScheduleWorkDifferentThread( |
| ScheduleWorkDifferentThreadCompleter::Sync completer) { |
| work_items_left_ = 0; |
| work_items_expected_ = 1; |
| |
| zx_status_t status; |
| std::thread thread([this, &status]() { |
| auto work_item_ctx = std::make_unique<WorkItemCtx>(); |
| work_item_ctx->start = zx::clock::get_monotonic(); |
| work_item_ctx->parent = this; |
| |
| status = DdkScheduleWork(DoWork, work_item_ctx.get()); |
| if (status == ZX_OK) { |
| work_item_ctx.release(); |
| } |
| }); |
| thread.join(); |
| |
| if (status != ZX_OK) { |
| completer.ReplyError(status); |
| } else { |
| completer.ReplySuccess(); |
| } |
| } |
| |
| void TestScheduleWorkDriver::GetDoneEvent(GetDoneEventCompleter::Sync completer) { |
| zx::event dup; |
| zx_status_t status = done_event_.duplicate(ZX_RIGHT_WAIT | ZX_RIGHT_TRANSFER, &dup); |
| if (status != ZX_OK) { |
| completer.ReplyError(status); |
| } else { |
| completer.ReplySuccess(std::move(dup)); |
| } |
| } |
| |
| void TestScheduleWorkDriver::ScheduledWorkRan(ScheduledWorkRanCompleter::Sync completer) { |
| completer.Reply(work_items_ran_, histogram_); |
| |
| ZX_ASSERT(done_event_.signal(ZX_USER_SIGNAL_0, 0) == ZX_OK); |
| work_items_ran_ = 0; |
| histogram_ = {}; |
| } |
| |
| void TestScheduleWorkDriver::GetChannel(zx::channel request, GetChannelCompleter::Sync completer) { |
| auto connection = std::make_unique<Connection>(this); |
| auto status = connection->Connect(loop_.dispatcher(), std::move(request)); |
| if (status == ZX_OK) { |
| open_connections_.push_back(std::move(connection)); |
| completer.ReplySuccess(); |
| } else { |
| completer.ReplyError(status); |
| } |
| } |
| |
| void TestScheduleWorkDriver::Connection::ScheduleWork(uint32_t batch_size, uint32_t num_work_items, |
| ScheduleWorkCompleter::Sync completer) { |
| batch_size = std::min(batch_size, num_work_items); |
| |
| work_items_left_ = num_work_items - batch_size; |
| work_items_expected_ = num_work_items; |
| work_items_ran_ = 0; |
| |
| for (uint32_t i = 0; i < batch_size; i++) { |
| auto work_item_ctx = std::make_unique<WorkItemCtx>(); |
| work_item_ctx->start = zx::clock::get_monotonic(); |
| work_item_ctx->parent = this; |
| |
| auto status = parent_->DdkScheduleWork(DoWork, work_item_ctx.get()); |
| if (status != ZX_OK) { |
| completer.ReplyError(status); |
| return; |
| } |
| work_item_ctx.release(); |
| } |
| |
| if (batch_size > 0) { |
| sync_completion_wait(&completion_, ZX_TIME_INFINITE); |
| sync_completion_reset(&completion_); |
| } |
| |
| completer.ReplySuccess(histogram_); |
| histogram_ = {}; |
| } |
| |
| zx_status_t TestScheduleWorkBind(void* ctx, zx_device_t* device) { |
| fbl::AllocChecker ac; |
| auto dev = fbl::make_unique_checked<TestScheduleWorkDriver>(&ac, device); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| auto status = dev->Bind(); |
| if (status == ZX_OK) { |
| // devmgr is now in charge of the memory for dev |
| __UNUSED auto ptr = dev.release(); |
| } |
| return status; |
| } |
| |
| zx_driver_ops_t driver_ops = []() -> zx_driver_ops_t { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = TestScheduleWorkBind; |
| return ops; |
| }(); |
| |
| } // namespace |
| |
| // clang-format off |
| ZIRCON_DRIVER_BEGIN(TestScheduleWork, driver_ops, "zircon", "0.1", 2) |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_TEST), |
| BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_PID, PDEV_PID_SCHEDULE_WORK_TEST), |
| ZIRCON_DRIVER_END(TestScheduleWork) |
| // clang-format on |