| // 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 "workload.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zircon-internal/ktrace.h> |
| #include <lib/zx/port.h> |
| #include <lib/zx/profile.h> |
| #include <lib/zx/time.h> |
| #include <lib/zx/timer.h> |
| #include <zircon/syscalls/port.h> |
| |
| #include <chrono> |
| #include <memory> |
| #include <optional> |
| #include <sstream> |
| #include <utility> |
| #include <vector> |
| |
| #include "action.h" |
| #include "object.h" |
| #include "random.h" |
| #include "rapidjson/document.h" |
| #include "rapidjson/error/en.h" |
| #include "src/lib/files/file.h" |
| #include "utility.h" |
| #include "worker.h" |
| |
| struct SequenceAction : ActionBase<SequenceAction, ActionDefaultCopyable::False> { |
| explicit SequenceAction(std::vector<std::unique_ptr<Action>> actions) |
| : actions{std::move(actions)} {} |
| |
| void Perform(Worker* worker) override { |
| for (auto& action : actions) { |
| action->Perform(worker); |
| } |
| } |
| |
| std::unique_ptr<Action> Copy() const override { |
| std::vector<std::unique_ptr<Action>> actions_copy; |
| for (const auto& action : actions) { |
| actions_copy.emplace_back(action->Copy()); |
| } |
| return std::make_unique<SequenceAction>(std::move(actions_copy)); |
| } |
| |
| std::vector<std::unique_ptr<Action>> actions; |
| }; |
| |
| struct SleepDurationAction : ActionBase<SleepDurationAction> { |
| explicit SleepDurationAction(std::chrono::nanoseconds duration_ns) : duration_ns{duration_ns} {} |
| |
| void Perform(Worker* worker) override { worker->Sleep(duration_ns); } |
| |
| const std::chrono::nanoseconds duration_ns; |
| }; |
| |
| struct SleepUniformAction : ActionBase<SleepUniformAction> { |
| SleepUniformAction(std::chrono::nanoseconds min_ns, std::chrono::nanoseconds max_ns) |
| : min_ns{min_ns}, max_ns{max_ns} {} |
| |
| void Perform(Worker* worker) override { |
| const std::chrono::nanoseconds duration_ns{random.GetUniform(min_ns.count(), max_ns.count())}; |
| worker->Sleep(duration_ns); |
| } |
| |
| const std::chrono::nanoseconds min_ns; |
| const std::chrono::nanoseconds max_ns; |
| Random random; |
| }; |
| |
| struct SpinDurationAction : ActionBase<SpinDurationAction> { |
| explicit SpinDurationAction(std::chrono::nanoseconds duration_ns) : duration_ns{duration_ns} {} |
| |
| void Perform(Worker* worker) override { worker->Spin(duration_ns); } |
| |
| const std::chrono::nanoseconds duration_ns; |
| }; |
| |
| struct SpinUniformAction : ActionBase<SpinUniformAction> { |
| SpinUniformAction(std::chrono::nanoseconds min_ns, std::chrono::nanoseconds max_ns) |
| : min_ns{min_ns}, max_ns{max_ns} {} |
| |
| void Perform(Worker* worker) override { |
| const std::chrono::nanoseconds duration_ns{random.GetUniform(min_ns.count(), max_ns.count())}; |
| worker->Spin(duration_ns); |
| } |
| |
| const std::chrono::nanoseconds min_ns; |
| const std::chrono::nanoseconds max_ns; |
| Random random; |
| }; |
| |
| struct YieldAction : ActionBase<YieldAction> { |
| void Perform(Worker* worker) override { worker->Yield(); } |
| }; |
| |
| struct ExitAction : ActionBase<ExitAction> { |
| void Perform(Worker* worker) override { worker->Exit(); } |
| }; |
| |
| struct SetProfileAction : ActionBase<SetProfileAction> { |
| SetProfileAction(zx::unowned_profile profile, bool once) |
| : profile{std::move(profile)}, once{once} {} |
| |
| void Perform(Worker* worker) override { |
| if (!once || !completed) { |
| completed = true; |
| worker->SetProfile(profile); |
| } |
| } |
| |
| zx::unowned_profile profile; |
| const bool once; |
| bool completed{false}; |
| }; |
| |
| struct SetTimerAction : ActionBase<SetTimerAction> { |
| SetTimerAction(TimerObject timer, std::chrono::nanoseconds relative_deadline_ns, |
| std::chrono::nanoseconds timer_slack_ns) |
| : timer{timer}, relative_deadline_ns{relative_deadline_ns}, timer_slack_ns{timer_slack_ns} {} |
| |
| void Perform(Worker*) override { |
| const zx_status_t status = |
| timer->set(zx::deadline_after(zx::duration{relative_deadline_ns.count()}), |
| zx::duration{timer_slack_ns.count()}); |
| FX_CHECK(status == ZX_OK); |
| } |
| |
| TimerObject timer; |
| const std::chrono::nanoseconds relative_deadline_ns; |
| const std::chrono::nanoseconds timer_slack_ns; |
| }; |
| |
| struct ChannelWriteAction : ActionBase<ChannelWriteAction> { |
| ChannelWriteAction(ChannelObject channel, size_t side, size_t bytes) |
| : channel{channel}, |
| endpoint{side == 0 ? channel->first : channel->second}, |
| bytes{bytes}, |
| buffer(bytes) {} |
| |
| void Perform(Worker*) override { |
| const auto status = |
| endpoint->write(0, buffer.data(), static_cast<uint32_t>(buffer.size()), nullptr, 0); |
| FX_CHECK(status == ZX_OK); |
| } |
| |
| ChannelObject channel; |
| zx::unowned_channel endpoint; |
| size_t bytes; |
| std::vector<uint8_t> buffer; |
| }; |
| |
| struct ChannelReadAction : ActionBase<ChannelReadAction> { |
| ChannelReadAction(ChannelObject channel, size_t side) |
| : channel{channel}, |
| endpoint{side == 0 ? channel->first : channel->second}, |
| buffer(64 * 1024) {} |
| |
| void Perform(Worker*) override { |
| uint32_t actual_bytes; |
| uint32_t actual_handles; |
| const auto status = |
| endpoint->read(0, buffer.data(), nullptr, static_cast<uint32_t>(buffer.size()), 0, |
| &actual_bytes, &actual_handles); |
| FX_CHECK(status == ZX_OK) << "Failed to read channel: " << status; |
| } |
| |
| ChannelObject channel; |
| zx::unowned_channel endpoint; |
| std::vector<uint8_t> buffer; |
| }; |
| |
| struct WaitOneAction : ActionBase<WaitOneAction> { |
| WaitOneAction(zx::unowned_handle handle, zx_signals_t signals, |
| std::optional<std::chrono::nanoseconds> relative_deadline_ns = std::nullopt) |
| : handle{handle}, signals{signals}, relative_deadline_ns{relative_deadline_ns} {} |
| |
| void Perform(Worker*) { |
| const auto absolute_deadline = |
| relative_deadline_ns ? zx::deadline_after(zx::duration{relative_deadline_ns->count()}) |
| : zx::time::infinite(); |
| const auto status = handle->wait_one(signals, absolute_deadline, nullptr); |
| FX_CHECK(status == ZX_OK) << "Failed to signal object: " << status; |
| } |
| |
| zx::unowned_handle handle; |
| zx_signals_t signals; |
| std::optional<std::chrono::nanoseconds> relative_deadline_ns; |
| }; |
| |
| struct WaitAsyncAction : ActionBase<WaitAsyncAction> { |
| WaitAsyncAction(zx::unowned_port port, zx::unowned_handle handle, zx_signals_t signals) |
| : port{port}, handle{handle}, signals{signals} {} |
| |
| void Perform(Worker*) override { |
| const auto status = handle->wait_async(*port, 0, signals, 0); |
| FX_CHECK(status == ZX_OK) << "Faild to wait async: " << status; |
| } |
| |
| zx::unowned_port port; |
| zx::unowned_handle handle; |
| zx_signals_t signals; |
| }; |
| |
| struct PortWaitAction : ActionBase<PortWaitAction, ActionDefaultCopyable::False> { |
| explicit PortWaitAction( |
| PortObject port, std::optional<std::chrono::nanoseconds> relative_deadline_ns = std::nullopt) |
| : port{port}, relative_deadline_ns{relative_deadline_ns} { |
| RegisterTerminateEvent(); |
| } |
| |
| void RegisterTerminateEvent() { |
| const auto status = PortObject::GetTerminateEvent()->wait_async( |
| *port.object(), 0, PortObject::kTerminateSignal, 0); |
| FX_CHECK(status == ZX_OK) << "Failed to wait async on terminate event: " << status; |
| } |
| |
| void Perform(Worker*) override { |
| zx_port_packet_t packet; |
| const auto absolute_deadline = |
| relative_deadline_ns ? zx::deadline_after(zx::duration{relative_deadline_ns->count()}) |
| : zx::time::infinite(); |
| const auto status = port->wait(absolute_deadline, &packet); |
| |
| // TODO(eieio): Add option to fail or not on timeout. |
| FX_CHECK(status == ZX_OK || status == ZX_ERR_TIMED_OUT) << "Failed to port wait: " << status; |
| } |
| |
| std::unique_ptr<Action> Copy() const override { |
| auto copy = std::make_unique<PortWaitAction>(*this); |
| // Register an additional packet for every copy. |
| copy->RegisterTerminateEvent(); |
| return copy; |
| } |
| |
| PortObject port; |
| const std::optional<std::chrono::nanoseconds> relative_deadline_ns; |
| }; |
| |
| struct ObjectSignalAction : ActionBase<ObjectSignalAction> { |
| ObjectSignalAction(zx::unowned_handle handle, zx_signals_t clear_mask, zx_signals_t set_mask) |
| : handle{handle}, clear_mask{clear_mask}, set_mask{set_mask} {} |
| |
| void Perform(Worker*) { |
| const auto status = handle->signal(clear_mask, set_mask); |
| FX_CHECK(status == ZX_OK) << "Failed to signal object: " << status; |
| } |
| |
| zx::unowned_handle handle; |
| zx_signals_t clear_mask; |
| zx_signals_t set_mask; |
| }; |
| |
| // Proxies an iterator over the members of the given value node. As of this |
| // writing, the version of rapidjson in third_party does not support range-based |
| // for loops. This adapter provides the missing functionality. |
| struct IterateMembers { |
| explicit IterateMembers(const rapidjson::Value& value) |
| : begin_iterator{value.MemberBegin()}, end_iterator{value.MemberEnd()} {} |
| |
| auto begin() { return begin_iterator; } |
| auto end() { return end_iterator; } |
| |
| rapidjson::Value::ConstMemberIterator begin_iterator; |
| rapidjson::Value::ConstMemberIterator end_iterator; |
| }; |
| |
| // Proxies an iterator over the values of the given array node. Provides missing |
| // functionality similar to the iterator above. |
| struct IterateValues { |
| explicit IterateValues(const rapidjson::Value& value) |
| : begin_iterator{value.Begin()}, end_iterator{value.End()} {} |
| |
| auto begin() { return begin_iterator; } |
| auto end() { return end_iterator; } |
| |
| rapidjson::Value::ConstValueIterator begin_iterator; |
| rapidjson::Value::ConstValueIterator end_iterator; |
| }; |
| |
| template <typename... Context> |
| const auto& GetMember(const char* name, const rapidjson::Value& object, Context&&... context) { |
| FX_CHECK(object.IsObject()) |
| << (std::ostringstream{} << ... << std::forward<Context>(context)).rdbuf() |
| << " must be a JSON object!"; |
| FX_CHECK(object.HasMember(name)) |
| << (std::ostringstream{} << ... << std::forward<Context>(context)).rdbuf() |
| << " must have a \"" << name << "\" member!"; |
| return object[name]; |
| } |
| |
| template <typename... Context> |
| auto GetInt(const char* name, const rapidjson::Value& object, Context&&... context) { |
| const auto& member = GetMember(name, object, std::forward<Context>(context)...); |
| FX_CHECK(member.IsInt()) |
| << (std::ostringstream{} << ... << std::forward<Context>(context)).rdbuf() << " member \"" |
| << name << "\" must be an integer!"; |
| return member.GetInt(); |
| } |
| |
| template <typename... Context> |
| const auto* GetString(const char* name, const rapidjson::Value& object, Context&&... context) { |
| const auto& member = GetMember(name, object, std::forward<Context>(context)...); |
| FX_CHECK(member.IsString()) |
| << (std::ostringstream{} << ... << std::forward<Context>(context)).rdbuf() << " member \"" |
| << name << "\" must be a string!"; |
| return member.GetString(); |
| } |
| |
| template <typename... Context> |
| const auto& GetArray(const char* name, const rapidjson::Value& object, Context&&... context) { |
| const auto& member = GetMember(name, object, std::forward<Context>(context)...); |
| FX_CHECK(member.IsArray()) |
| << (std::ostringstream{} << ... << std::forward<Context>(context)).rdbuf() << " member \"" |
| << name << "\" must be an array!"; |
| return member; |
| } |
| |
| template <typename... Context> |
| const auto& GetObject(const char* name, const rapidjson::Value& object, Context&&... context) { |
| const auto& member = GetMember(name, object, std::forward<Context>(context)...); |
| FX_CHECK(member.IsObject()) |
| << (std::ostringstream{} << ... << std::forward<Context>(context)).rdbuf() << " member \"" |
| << name << "\" must be a JSON object!"; |
| return member; |
| } |
| |
| template <typename... Context> |
| auto GetUint(const char* name, const rapidjson::Value& object, Context&&... context) { |
| const auto& member = GetMember(name, object, std::forward<Context>(context)...); |
| FX_CHECK(member.IsUint()) |
| << (std::ostringstream{} << ... << std::forward<Context>(context)).rdbuf() << " member \"" |
| << name << "\" must be an unsigned integer!"; |
| return member.GetUint(); |
| } |
| |
| void Workload::ParseObject(const std::string& name, const rapidjson::Value& object) { |
| const std::string type_string = GetString("type", object, "Named object \"", name, "\""); |
| if (type_string == "timer") { |
| Add(name, TimerObject::Create()); |
| } else if (type_string == "port") { |
| Add(name, PortObject::Create()); |
| } else if (type_string == "channel") { |
| Add(name, ChannelObject::Create()); |
| } else if (type_string == "event") { |
| Add(name, EventObject::Create()); |
| } else { |
| FX_CHECK(false) << "Object \"" << name << "\" has unknown type \"" << type_string << "\"!"; |
| } |
| } |
| |
| Workload::Duration Workload::ParseDuration(const rapidjson::Value& object) { |
| if (object.IsInt()) { |
| return {std::chrono::nanoseconds{object.GetInt()}}; |
| } else if (object.IsString()) { |
| return {ParseDurationString(object.GetString())}; |
| } else { |
| FX_CHECK(false) << "Duration must be an integer or string!"; |
| __builtin_unreachable(); |
| } |
| } |
| |
| Workload::Uniform Workload::ParseUniform(const rapidjson::Value& object) { |
| auto [min_ns] = ParseDuration(GetMember("min", object, "Uniform object")); |
| auto [max_ns] = ParseDuration(GetMember("max", object, "Uniform object")); |
| |
| return {min_ns, max_ns}; |
| } |
| |
| std::variant<Workload::Duration, Workload::Uniform> Workload::ParseInterval( |
| const rapidjson::Value& object, AcceptNamedIntervalFlag accept_named_interval) { |
| FX_CHECK(object.IsObject()) << "Interval must be a JSON object!"; |
| |
| const bool has_duration = object.HasMember("duration"); |
| const bool has_uniform = object.HasMember("uniform"); |
| const bool has_interval = object.HasMember("interval"); |
| |
| FX_CHECK(accept_named_interval || !has_interval) |
| << "Timespec \"interval\" is not supported in this context!"; |
| |
| FX_CHECK((has_duration + has_uniform + has_interval) == 1) |
| << "Interval must have exactly one timespec: either \"uniform\" or " |
| "\"duration\"" |
| << (accept_named_interval ? " or \"interval\"" : "") << "!"; |
| |
| if (has_duration) { |
| return {ParseDuration(object["duration"])}; |
| } else if (has_uniform) { |
| return {ParseUniform(object["uniform"])}; |
| } else if (has_interval) { |
| const auto* interval_name_string = GetString("interval", object, "Interval"); |
| auto search = intervals_.find(interval_name_string); |
| FX_CHECK(search != intervals_.end()) |
| << "Undefined named interval \"" << interval_name_string << "\"!"; |
| return search->second; |
| } else { |
| __builtin_unreachable(); |
| } |
| } |
| |
| void Workload::ParseNamedInterval(const std::string& name, const rapidjson::Value& object) { |
| FX_CHECK(object.IsObject()) << "Named interval must be a JSON object!"; |
| |
| const auto result = ParseInterval(object, RejectNamedInterval); |
| auto [iter, okay] = intervals_.emplace(name, result); |
| FX_CHECK(okay) << "Named interval \"" << name << "\" defined more than once!"; |
| } |
| |
| zx::unowned_handle Workload::ParseTargetObjectAndGetHandle(const std::string& name, |
| const rapidjson::Value& object, |
| const std::string& context) { |
| Object& target = Get(name); |
| switch (target.type()) { |
| case TimerObject::Type: |
| return zx::unowned_handle{static_cast<TimerObject&>(target)->get()}; |
| case ChannelObject::Type: { |
| const size_t side = GetInt("side", object, context); |
| FX_CHECK(side == 0 || side == 1) |
| << "Wait async action member \"side\" must be an integer value 0 or 1!"; |
| auto [first, second] = static_cast<ChannelObject&>(target).bind(); |
| return zx::unowned_handle{side == 0 ? first->get() : second->get()}; |
| } |
| case EventObject::Type: |
| return zx::unowned_handle{static_cast<EventObject&>(target)->get()}; |
| case PortObject::Type: |
| return zx::unowned_handle{static_cast<PortObject&>(target)->get()}; |
| default: |
| FX_CHECK(false) << "Unknown object type: " << target.type(); |
| __builtin_unreachable(); |
| } |
| } |
| |
| std::unique_ptr<Action> Workload::ParseAction(const rapidjson::Value& action) { |
| const std::string action_string = GetString("action", action, "Action"); |
| if (action_string == "spin") { |
| const auto result = ParseInterval(action, AcceptNamedInterval); |
| if (std::holds_alternative<Duration>(result)) { |
| const auto [duration_ns] = std::get<Duration>(result); |
| return SpinDurationAction::Create(duration_ns); |
| } else /*if (std::holds_alternative<Uniform>(result))*/ { |
| const auto [min_ns, max_ns] = std::get<Uniform>(result); |
| return SpinUniformAction::Create(min_ns, max_ns); |
| } |
| } else if (action_string == "sleep") { |
| const auto result = ParseInterval(action, AcceptNamedInterval); |
| if (std::holds_alternative<Duration>(result)) { |
| const auto [duration_ns] = std::get<Duration>(result); |
| return SleepDurationAction::Create(duration_ns); |
| } else /*if (std::holds_alternative<Uniform>(result))*/ { |
| const auto [min_ns, max_ns] = std::get<Uniform>(result); |
| return SleepUniformAction::Create(min_ns, max_ns); |
| } |
| } else if (action_string == "yield") { |
| return YieldAction::Create(); |
| } else if (action_string == "write") { |
| const auto* channel_name = GetString("channel", action, "Write action"); |
| const size_t side = GetInt("side", action, "Write action"); |
| const size_t bytes = GetInt("bytes", action, "Write action"); |
| return ChannelWriteAction::Create(Get<ChannelObject>(channel_name), side, bytes); |
| } else if (action_string == "read") { |
| const auto* channel_name = GetString("channel", action, "Read action"); |
| const size_t side = GetInt("side", action, "Read action"); |
| return ChannelReadAction::Create(Get<ChannelObject>(channel_name), side); |
| } else if (action_string == "behavior") { |
| const auto* behavior_name = GetString("name", action, "Behavior action"); |
| auto search = behaviors_.find(behavior_name); |
| FX_CHECK(search != behaviors_.end()) << "Unknown named behavior \"" << behavior_name << "\"!"; |
| return search->second->Copy(); |
| } else if (action_string == "wait_async") { |
| const auto* context = "Wait async action"; |
| const auto* port_name = GetString("port", action, context); |
| const auto* object_name = GetString("object", action, context); |
| const auto signals = GetInt("signals", action, context); |
| |
| auto& port_object = Get<PortObject>(port_name); |
| zx::unowned_handle handle = ParseTargetObjectAndGetHandle(object_name, action, context); |
| |
| return WaitAsyncAction::Create(zx::unowned_port{port_object->get()}, std::move(handle), |
| signals); |
| } else if (action_string == "wait_one") { |
| const auto* context = "Wait one action"; |
| const auto* object_name = GetString("object", action, context); |
| const auto signals = GetInt("signals", action, context); |
| |
| std::optional<std::chrono::nanoseconds> relative_deadline_ns; |
| if (action.HasMember("deadline")) { |
| relative_deadline_ns = ParseDuration(action["deadline"]).value; |
| } |
| |
| zx::unowned_handle handle = ParseTargetObjectAndGetHandle(object_name, action, context); |
| |
| return WaitOneAction::Create(std::move(handle), signals, relative_deadline_ns); |
| } else if (action_string == "port_wait") { |
| const auto* port_name = GetString("port", action, "Port wait action"); |
| |
| std::optional<std::chrono::nanoseconds> relative_deadline_ns; |
| if (action.HasMember("deadline")) { |
| relative_deadline_ns = ParseDuration(action["deadline"]).value; |
| } |
| |
| return PortWaitAction::Create(Get<PortObject>(port_name), relative_deadline_ns); |
| } else if (action_string == "signal") { |
| const auto* context = "Signal action"; |
| const auto* object_name = GetString("object", action, context); |
| const auto clear_mask = GetInt("clear", action, context); |
| const auto set_mask = GetInt("set", action, context); |
| |
| zx::unowned_handle handle = ParseTargetObjectAndGetHandle(object_name, action, context); |
| |
| return ObjectSignalAction::Create(std::move(handle), clear_mask, set_mask); |
| } else if (action_string == "timer_set") { |
| const auto* timer_name = GetString("timer", action, "Timer set action"); |
| const auto relative_deadline_ns = ParseDuration(action["deadline"]).value; |
| const auto timer_slack_ns = action.HasMember("slack") ? ParseDuration(action["slack"]).value |
| : std::chrono::nanoseconds{0}; |
| |
| auto& timer_object = Get<TimerObject>(timer_name); |
| |
| return SetTimerAction::Create(timer_object, relative_deadline_ns, timer_slack_ns); |
| } else if (action_string == "exit") { |
| return ExitAction::Create(); |
| } else { |
| FX_CHECK(false) << "Unknown action \"" << action_string << "\"!"; |
| __builtin_unreachable(); |
| } |
| } |
| |
| void Workload::ParseNamedBehavior(const std::string& name, const rapidjson::Value& behavior) { |
| if (behavior.IsObject()) { |
| auto [iter, okay] = behaviors_.emplace(name, ParseAction(behavior)); |
| FX_CHECK(okay) << "Behavior \"" << name << "\" defined more than once!"; |
| } else if (behavior.IsArray()) { |
| std::vector<std::unique_ptr<Action>> actions; |
| for (const auto& action : IterateValues(behavior)) { |
| actions.emplace_back(ParseAction(action)); |
| } |
| |
| auto [iter, okay] = behaviors_.emplace(name, SequenceAction::Create(std::move(actions))); |
| FX_CHECK(okay) << "Behavior \"" << name << "\" defined more than once!"; |
| } else { |
| FX_CHECK(false) << "Behavior \"" << name << "\" must be a JSON object or array!"; |
| } |
| } |
| |
| void Workload::ParseWorker(const rapidjson::Value& worker) { |
| FX_CHECK(worker.IsObject()) << "Worker must be a JSON object!"; |
| |
| WorkerConfig config; |
| |
| if (worker.HasMember("name")) { |
| config.name = GetString("name", worker, "Worker"); |
| } |
| |
| if (worker.HasMember("group")) { |
| config.group = GetString("group", worker, "Worker"); |
| } |
| |
| if (worker.HasMember("priority")) { |
| const auto& priority_member = worker["priority"]; |
| const bool is_int = priority_member.IsInt(); |
| const bool is_object = priority_member.IsObject(); |
| FX_CHECK(is_int || is_object) |
| << "Worker member \"priority\" must either be an integer or a JSON object!"; |
| |
| if (is_int) { |
| config.priority = GetInt("priority", worker, "Worker"); |
| } else if (is_object) { |
| const bool has_capacity = priority_member.HasMember("capacity"); |
| const bool has_deadline = priority_member.HasMember("deadline"); |
| const bool has_period = priority_member.HasMember("period"); |
| FX_CHECK(has_capacity && has_deadline && has_period) |
| << "Worker member \"priority\" must have members \"capacity\", \"deadline\", and " |
| "\"period\"!"; |
| |
| const zx::duration capacity{ParseDuration(priority_member["capacity"]).value.count()}; |
| const zx::duration deadline{ParseDuration(priority_member["deadline"]).value.count()}; |
| const zx::duration period{ParseDuration(priority_member["period"]).value.count()}; |
| |
| config.priority = WorkerConfig::DeadlineParams{capacity, deadline, period}; |
| } |
| } |
| |
| if (worker.HasMember("actions")) { |
| const auto& actions_member = worker["actions"]; |
| const bool is_array = actions_member.IsArray(); |
| const bool is_string = actions_member.IsString(); |
| FX_CHECK(is_array || is_string) |
| << "Worker member \"actions\" must either be a string or a JSON object!"; |
| |
| if (is_array) { |
| const auto& actions = GetArray("actions", worker, "Worker"); |
| for (const auto& action : IterateValues(actions)) { |
| config.actions.emplace_back(ParseAction(action)); |
| } |
| } else if (is_string) { |
| const auto* behavior_name = GetString("actions", worker, "Worker"); |
| auto search = behaviors_.find(behavior_name); |
| FX_CHECK(search != behaviors_.end()) << "Unknown named behavior \"" << behavior_name << "\"!"; |
| config.actions.emplace_back(search->second->Copy()); |
| } |
| } |
| |
| int64_t instances = 1; |
| if (worker.HasMember("instances")) { |
| const auto& instances_member = worker["instances"]; |
| const bool is_integer = instances_member.IsInt(); |
| const bool is_string = instances_member.IsString(); |
| const bool is_object = instances_member.IsObject(); |
| FX_CHECK(is_integer || is_string || is_object) |
| << "Worker member \"instances\" must either be an integer, string or a JSON object!"; |
| |
| if (is_integer) { |
| instances = GetInt("instances", worker, "Worker"); |
| } else if (is_string) { |
| instances = ParseInstancesString(GetString("instances", worker, "Worker")); |
| } else if (is_object) { |
| FX_CHECK(false) << "Worker member \"instances\" expressions are not yet implemented!"; |
| } |
| } |
| |
| if (instances <= 0) { |
| FX_LOGS(WARNING) << "Worker configured with instances=" << instances << "!"; |
| } |
| |
| for (int i = 0; i < instances; i++) { |
| workers_.emplace_back(config); |
| } |
| } |
| |
| void Workload::ParseTracing(const rapidjson::Value& tracing) { |
| FX_CHECK(tracing.IsObject()) << "Tracing configuration must be a JSON object!"; |
| |
| TracingConfig config; |
| |
| if (tracing.HasMember("group mask")) { |
| const auto& group_mask = GetMember("group mask", tracing, "Tracing"); |
| |
| if (group_mask.IsUint()) { |
| config.group_mask = GetUint("group mask", tracing, "Tracing"); |
| } else if (group_mask.IsString()) { |
| const std::string group_mask_str = GetString("group mask", tracing, "Tracing"); |
| |
| if (group_mask_str == "KTRACE_GRP_ALL") { |
| config.group_mask = KTRACE_GRP_ALL; |
| } else if (group_mask_str == "KTRACE_GRP_META") { |
| config.group_mask = KTRACE_GRP_META; |
| } else if (group_mask_str == "KTRACE_GRP_LIFECYCLE") { |
| config.group_mask = KTRACE_GRP_LIFECYCLE; |
| } else if (group_mask_str == "KTRACE_GRP_SCHEDULER") { |
| config.group_mask = KTRACE_GRP_SCHEDULER; |
| } else if (group_mask_str == "KTRACE_GRP_TASKS") { |
| config.group_mask = KTRACE_GRP_TASKS; |
| } else if (group_mask_str == "KTRACE_GRP_IPC") { |
| config.group_mask = KTRACE_GRP_IPC; |
| } else if (group_mask_str == "KTRACE_GRP_IRQ") { |
| config.group_mask = KTRACE_GRP_IRQ; |
| } else if (group_mask_str == "KTRACE_GRP_PROBE") { |
| config.group_mask = KTRACE_GRP_PROBE; |
| } else if (group_mask_str == "KTRACE_GRP_ARCH") { |
| config.group_mask = KTRACE_GRP_ARCH; |
| } else if (group_mask_str == "KTRACE_GRP_SYSCALL") { |
| config.group_mask = KTRACE_GRP_SYSCALL; |
| } else if (group_mask_str == "KTRACE_GRP_VM") { |
| config.group_mask = KTRACE_GRP_VM; |
| } else { |
| FX_LOGS(WARNING) << "Tracing enabled with unknown group mask, mask set to all groups."; |
| config.group_mask = KTRACE_GRP_ALL; |
| } |
| } else { |
| FX_CHECK(false) << "Tracing group mask must be an unsigned integer or string!"; |
| __builtin_unreachable(); |
| } |
| } else /*Set default tracing group mask*/ { |
| FX_LOGS(WARNING) << "Tracing enabled with no group mask specified, mask set to all groups."; |
| config.group_mask = KTRACE_GRP_ALL; |
| } |
| |
| if (tracing.HasMember("filepath")) { |
| config.filepath = GetString("filepath", tracing, "Tracing"); |
| } |
| |
| if (tracing.HasMember("string ref")) { |
| config.trace_string_ref = GetString("string ref", tracing, "Tracing"); |
| } |
| |
| tracing_ = config; |
| } |
| |
| void GetLineAndColumnForOffset(const std::string& input, size_t offset, int32_t* output_line, |
| int32_t* output_column) { |
| if (offset == 0) { |
| // Errors at position 0 are assumed to be related to the whole file. |
| *output_line = 0; |
| *output_column = 0; |
| return; |
| } |
| *output_line = 1; |
| *output_column = 1; |
| for (size_t i = 0; i < input.size() && i < offset; i++) { |
| if (input[i] == '\n') { |
| *output_line += 1; |
| *output_column = 1; |
| } else { |
| *output_column += 1; |
| } |
| } |
| } |
| |
| std::string GetErrorMessage(const rapidjson::Document& document, const std::string& file_data) { |
| int32_t line; |
| int32_t column; |
| GetLineAndColumnForOffset(file_data, document.GetErrorOffset(), &line, &column); |
| |
| std::ostringstream stream; |
| stream << "at " << line << ":" << column << ": " << GetParseError_En(document.GetParseError()); |
| return stream.str(); |
| } |
| |
| Workload Workload::Load(const std::string& path) { |
| std::string file_data; |
| FX_CHECK(files::ReadFileToString(path, &file_data)) |
| << "Failed to read workload config file \"" << path << "\"!"; |
| |
| rapidjson::Document document; |
| |
| const auto kFlags = rapidjson::kParseCommentsFlag | rapidjson::kParseTrailingCommasFlag; |
| document.Parse<kFlags>(file_data); |
| |
| FX_CHECK(!document.HasParseError()) << "Error parsing workload config file \"" << path << "\" " |
| << GetErrorMessage(document, file_data) << "!"; |
| FX_CHECK(document.IsObject()) << "Document must be a JSON object!"; |
| |
| Workload workload; |
| |
| // Handle workload name. |
| if (document.HasMember("name")) { |
| workload.name_ = GetString("name", document, "Workload"); |
| } |
| |
| // Handle global config. |
| if (document.HasMember("config")) { |
| const auto& config = GetObject("config", document, "Workload"); |
| if (config.HasMember("priority")) { |
| workload.priority_ = GetInt("priority", config, "Workload config"); |
| } |
| |
| if (config.HasMember("interval")) { |
| const auto& interval = config["interval"]; |
| auto [duration_ns] = workload.ParseDuration(interval); |
| workload.interval_ = duration_ns; |
| } |
| } |
| |
| // Handle named intervals. |
| if (document.HasMember("intervals")) { |
| const auto& intervals = GetObject("intervals", document, "Workload"); |
| for (const auto& interval : IterateMembers(intervals)) { |
| workload.ParseNamedInterval(interval.name.GetString(), interval.value); |
| } |
| } |
| |
| // Handle global objects. |
| if (document.HasMember("objects")) { |
| const auto& objects = GetObject("objects", document, "Workload"); |
| for (const auto& object : IterateMembers(objects)) { |
| workload.ParseObject(object.name.GetString(), object.value); |
| } |
| } |
| |
| // Handle named actions. |
| if (document.HasMember("behaviors")) { |
| const auto& behaviors = GetObject("behaviors", document, "Workload"); |
| for (const auto& behavior : IterateMembers(behaviors)) { |
| workload.ParseNamedBehavior(behavior.name.GetString(), behavior.value); |
| } |
| } |
| |
| // Handle workers. |
| if (document.HasMember("workers")) { |
| const auto& workers = GetArray("workers", document, "Workload"); |
| for (const auto& worker : IterateValues(workers)) { |
| workload.ParseWorker(worker); |
| } |
| } |
| |
| // Handle tracing. |
| if (document.HasMember("tracing")) { |
| const auto& tracing = GetObject("tracing", document, "Workload"); |
| workload.ParseTracing(tracing); |
| } |
| |
| return workload; |
| } |