| // 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. | |
| #ifndef SRC_TESTING_LOADBENCH_WORKER_H_ | |
| #define SRC_TESTING_LOADBENCH_WORKER_H_ | |
| #include <lib/sync/completion.h> | |
| #include <lib/syslog/cpp/macros.h> | |
| #include <lib/zx/profile.h> | |
| #include <lib/zx/thread.h> | |
| #include <lib/zx/time.h> | |
| #include <lib/zx/timer.h> | |
| #include <zircon/time.h> | |
| #include <atomic> | |
| #include <chrono> | |
| #include <condition_variable> | |
| #include <iostream> | |
| #include <memory> | |
| #include <mutex> | |
| #include <optional> | |
| #include <string> | |
| #include <thread> | |
| #include <utility> | |
| #include <vector> | |
| #include "action.h" | |
| #include "object.h" | |
| #include "utility.h" | |
| #include "workload.h" | |
| class Worker { | |
| public: | |
| Worker(Worker&&) = delete; | |
| Worker& operator=(Worker&&) = delete; | |
| Worker(const Worker&) = delete; | |
| Worker& operator=(const Worker&) = delete; | |
| static std::pair<std::thread, std::unique_ptr<Worker>> Create(WorkerConfig config) { | |
| std::unique_ptr<Worker> worker{ | |
| new Worker{std::move(config.actions), config.name, config.group, config.priority}}; | |
| return {std::thread{&Worker::Run, worker.get()}, std::move(worker)}; | |
| } | |
| // Sleeps the worker for the given duration. Returns early if the termination | |
| // flag is set. | |
| void Sleep(std::chrono::nanoseconds duration_ns) { | |
| sync_completion_wait(&terminate_completion_, duration_ns.count()); | |
| } | |
| // Spins the worker for the given duration. Returns early if the termination | |
| // flag is set. | |
| void Spin(std::chrono::nanoseconds duration_ns) { | |
| const auto end_time = std::chrono::steady_clock::now() + duration_ns; | |
| while (std::chrono::steady_clock::now() < end_time && !should_terminate()) { | |
| spin_iterations_.fetch_add(1, std::memory_order_relaxed); | |
| } | |
| } | |
| // Yields the worker. | |
| void Yield() { zx::nanosleep(zx::time{0}); } | |
| void SetProfile(const zx::unowned_profile& profile) { | |
| const auto status = zx::thread::self()->set_profile(*profile, 0); | |
| FX_CHECK(status == ZX_OK); | |
| } | |
| void Exit() { early_exit_ = true; } | |
| void Dump() { | |
| static std::mutex output_lock; | |
| std::lock_guard<std::mutex> guard{output_lock}; | |
| std::cout << "Thread " << id_ << ": group=" << group() << " name=" << name() << std::endl; | |
| std::cout << " Spin iterations: " << spin_iterations() << std::endl; | |
| std::cout << " Total runtime: " << double_seconds{total_runtime()}.count() << " s" | |
| << std::endl; | |
| } | |
| static void WaitForAllReady(size_t count) { | |
| using std::chrono_literals::operator""s; | |
| constexpr auto kTimeoutSeconds = 5s; | |
| NullLock lock; | |
| ready_condition_.wait_for(lock, kTimeoutSeconds, [count] { return ready_count() == count; }); | |
| FX_CHECK(ready_count() == count) << "ready_count=" << ready_count() << " count=" << count; | |
| } | |
| static void StartAll() { sync_completion_signal(&start_completion_); } | |
| static void TerminateAll() { | |
| sync_completion_signal(&terminate_completion_); | |
| // Exit any indefinite port_wait syscalls. | |
| const auto status = PortObject::GetTerminateEvent()->signal(0, PortObject::kTerminateSignal); | |
| FX_CHECK(status == ZX_OK) << "Failed to send signal to terminate event: " << status; | |
| } | |
| std::chrono::nanoseconds total_runtime() const { | |
| return total_runtime_end_ - total_runtime_begin_; | |
| } | |
| uint64_t spin_iterations() const { return spin_iterations_.load(); } | |
| const std::string& name() const { return name_; } | |
| const std::string& group() const { return group_; } | |
| private: | |
| Worker(std::vector<std::unique_ptr<Action>> actions, const std::string& name, | |
| const std::string& group, WorkerConfig::PriorityType priority) | |
| : id_{thread_counter_++}, | |
| actions_{std::move(actions)}, | |
| name_{name}, | |
| group_{group}, | |
| priority_{priority} {} | |
| void Run() { | |
| if (std::holds_alternative<int>(priority_)) { | |
| auto profile = GetProfile(std::get<int>(priority_)); | |
| const auto status = zx::thread::self()->set_profile(*profile, 0); | |
| FX_CHECK(status == ZX_OK) << "Failed to set worker " << id_ << " to priority " | |
| << std::get<int>(priority_) << "!"; | |
| } else if (std::holds_alternative<WorkerConfig::DeadlineParams>(priority_)) { | |
| const auto params = std::get<WorkerConfig::DeadlineParams>(priority_); | |
| auto profile = GetProfile(params.capacity, params.deadline, params.period); | |
| const auto status = zx::thread::self()->set_profile(*profile, 0); | |
| FX_CHECK(status == ZX_OK) << "Failed to set worker " << id_ | |
| << " to {capacity=" << params.capacity.get() | |
| << ", deadline=" << params.deadline.get() | |
| << ", period=" << params.period.get() << "}!"; | |
| } | |
| // Setup the actions on this worker. | |
| for (auto& action : actions_) { | |
| action->Setup(this); | |
| } | |
| // Signal that the worker is ready and wait for the benchmark to kick off. | |
| { | |
| ready_count_++; | |
| ready_condition_.notify_one(); | |
| const auto status = sync_completion_wait(&start_completion_, ZX_TIME_INFINITE); | |
| FX_CHECK(status == ZX_OK) << "Failed to wait for start condition: status=" << status; | |
| } | |
| zx_info_thread_stats_t thread_stats{}; | |
| zx::thread::self()->get_info(ZX_INFO_THREAD_STATS, &thread_stats, sizeof(thread_stats), nullptr, | |
| nullptr); | |
| total_runtime_begin_ = std::chrono::nanoseconds{thread_stats.total_runtime}; | |
| while (!should_terminate() && !early_exit_) { | |
| for (auto& action : actions_) { | |
| if (should_terminate() || early_exit_) { | |
| break; | |
| } | |
| action->Perform(this); | |
| } | |
| } | |
| zx::thread::self()->get_info(ZX_INFO_THREAD_STATS, &thread_stats, sizeof(thread_stats), nullptr, | |
| nullptr); | |
| total_runtime_end_ = std::chrono::nanoseconds{thread_stats.total_runtime}; | |
| } | |
| struct NullLock { | |
| void lock() {} | |
| void unlock() {} | |
| }; | |
| int id_; | |
| std::vector<std::unique_ptr<Action>> actions_; | |
| std::string name_; | |
| std::string group_; | |
| WorkerConfig::PriorityType priority_; | |
| bool early_exit_{false}; | |
| std::atomic<uint64_t> spin_iterations_{0}; | |
| std::chrono::nanoseconds total_runtime_begin_; | |
| std::chrono::nanoseconds total_runtime_end_; | |
| static bool should_terminate() { return sync_completion_signaled(&terminate_completion_); } | |
| static size_t ready_count() { return ready_count_.load(); } | |
| inline static std::atomic<int> thread_counter_{0}; | |
| inline static sync_completion terminate_completion_; | |
| inline static sync_completion start_completion_; | |
| inline static std::condition_variable_any ready_condition_{}; | |
| inline static std::atomic<size_t> ready_count_{0}; | |
| }; | |
| #endif // SRC_TESTING_LOADBENCH_WORKER_H_ |