| // 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 <regex> | |
| #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(), 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, 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()); | |
| } | |
| } | |
| int 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 { | |
| 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; | |
| } |