| // Copyright 2021 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 "zircon/system/ulib/profile/config.h" |
| |
| #include <fcntl.h> |
| #include <lib/fit/result.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zx/profile.h> |
| #include <lib/zx/time.h> |
| #include <zircon/assert.h> |
| #include <zircon/syscalls/profile.h> |
| |
| #include <algorithm> |
| #include <limits> |
| #include <sstream> |
| #include <string> |
| #include <string_view> |
| |
| #include <fbl/unique_fd.h> |
| #include <rapidjson/document.h> |
| #include <rapidjson/error/en.h> |
| #include <re2/re2.h> |
| #include <src/lib/files/directory.h> |
| #include <src/lib/files/file.h> |
| |
| using fuchsia_scheduler::Parameter; |
| using fuchsia_scheduler::ParameterValue; |
| using zircon_profile::Profile; |
| using zircon_profile::ProfileScope; |
| using zircon_profile::Role; |
| |
| namespace { |
| |
| constexpr char kConfigFileExtension[] = ".profiles"; |
| |
| std::string ToString(const std::vector<Parameter> params) { |
| std::ostringstream stream; |
| stream << "{ "; |
| for (auto param : params) { |
| stream << param.key() << ": "; |
| switch (param.value().Which()) { |
| case ParameterValue::Tag::kIntValue: |
| stream << std::to_string(param.value().int_value().value()); |
| break; |
| case ParameterValue::Tag::kFloatValue: |
| stream << std::to_string(param.value().float_value().value()); |
| break; |
| case ParameterValue::Tag::kStringValue: |
| stream << param.value().string_value().value(); |
| break; |
| default: |
| FX_SLOG(WARNING, "invalid output parameter format"); |
| } |
| stream << ", "; |
| } |
| stream << " }"; |
| return stream.str(); |
| } |
| |
| std::string ToString(const zx_profile_info_t& info) { |
| std::ostringstream stream; |
| |
| stream << "{ "; |
| |
| if (info.flags & ZX_PROFILE_INFO_FLAG_PRIORITY) { |
| stream << "\"priority\": " << info.priority << ", "; |
| } |
| if (info.flags & ZX_PROFILE_INFO_FLAG_DEADLINE) { |
| stream << "\"capacity\": " << info.deadline_params.capacity |
| << ", \"deadline\": " << info.deadline_params.relative_deadline |
| << ", \"period\": " << info.deadline_params.period << ", "; |
| } |
| if (info.flags & ZX_PROFILE_INFO_FLAG_CPU_MASK) { |
| stream << "\"affinity\": " << info.cpu_affinity_mask.mask[0] << " (0x" << std::hex |
| << info.cpu_affinity_mask.mask[0] << "), " << std::dec; |
| } |
| |
| stream << "}"; |
| return stream.str(); |
| } |
| |
| std::string ToString(ProfileScope scope) { |
| switch (scope) { |
| case ProfileScope::Bringup: |
| return "bringup"; |
| case ProfileScope::Core: |
| return "core"; |
| case ProfileScope::Product: |
| return "product"; |
| default: |
| return "none"; |
| } |
| } |
| // 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; |
| }; |
| |
| // Utility to build a fit::result<std::string, ?> in the error state using stream operators. |
| // |
| // Example: |
| // |
| // return Error() << "Failed to open file " << filename << "!"; |
| // |
| class Error { |
| public: |
| Error() = default; |
| explicit Error(const std::string& initial) : stream_{initial} {} |
| |
| // Forwards the stream operator argument to the underlying ostream. |
| template <typename T> |
| Error& operator<<(const T& value) { |
| stream_ << value; |
| return *this; |
| } |
| |
| // Implicit conversion to fit::result<std::string, ?> in the error state with accumulated string |
| // as the error value. |
| template <typename... Vs> |
| operator fit::result<std::string, Vs...>() const { |
| return fit::error(stream_.str()); |
| } |
| |
| operator fit::error<std::string>() const { return fit::error(stream_.str()); } |
| |
| private: |
| std::ostringstream stream_; |
| }; |
| |
| fit::result<std::string, zx::duration> ParseDurationString(const std::string& duration) { |
| // Match one or more digits, optionally followed by time units ms, us, or ns. |
| static const re2::RE2 kReDuration{"^(\\d+)(ms|us|ns)?$"}; |
| |
| int64_t scalar; |
| std::string units; |
| const bool matched = re2::RE2::PartialMatch(duration, kReDuration, &scalar, &units); |
| if (!matched) { |
| return Error() << "String \"" << duration << "\" is not a valid duration!"; |
| } |
| |
| if (units.empty() || units == "ns") { |
| return fit::ok(zx::nsec(scalar)); |
| } |
| if (units == "ms") { |
| return fit::ok(zx::msec(scalar)); |
| } |
| if (units == "us") { |
| return fit::ok(zx::usec(scalar)); |
| } |
| return Error() << "String duration \"" << duration << "\" has unrecognized units \"" << units |
| << "\"!"; |
| } |
| |
| fit::result<std::string, zx::duration> ParseDuration(const rapidjson::Value& object) { |
| if (object.IsInt()) { |
| return fit::ok(zx::nsec(object.GetInt())); |
| } |
| if (object.IsString()) { |
| return ParseDurationString(object.GetString()); |
| } |
| return fit::error("Duration must be an integer or duration string!"); |
| } |
| |
| struct TextPosition { |
| int32_t line; |
| int32_t column; |
| }; |
| |
| TextPosition GetLineAndColumnForOffset(const std::string& input, size_t offset) { |
| if (offset == 0) { |
| // Errors at position 0 are assumed to be related to the whole file. |
| return {.line = 0, .column = 0}; |
| } |
| |
| TextPosition position = {.line = 1, .column = 1}; |
| for (size_t i = 0; i < input.size() && i < offset; i++) { |
| if (input[i] == '\n') { |
| position.line += 1; |
| position.column = 1; |
| } else { |
| position.column += 1; |
| } |
| } |
| |
| return position; |
| } |
| |
| std::string GetErrorMessage(const rapidjson::Document& document, const std::string& file_data) { |
| const auto [line, column] = GetLineAndColumnForOffset(file_data, document.GetErrorOffset()); |
| std::ostringstream stream; |
| stream << line << ":" << column << ": " << GetParseError_En(document.GetParseError()); |
| return stream.str(); |
| } |
| |
| template <typename... Context> |
| auto GetMember(const char* name, const rapidjson::Value& object, Context&&... context) |
| -> fit::result<std::string, decltype(std::cref(object[name]))> { |
| if (!object.IsObject()) { |
| return (Error() << ... << std::forward<Context>(context)) << " must be a JSON object!"; |
| } |
| if (!object.HasMember(name)) { |
| return (Error() << ... << std::forward<Context>(context)) |
| << " must have a \"" << name << "\" member!"; |
| } |
| return fit::ok(std::cref(object[name])); |
| } |
| |
| template <typename... Context> |
| fit::result<std::string, int> GetInt(const char* name, const rapidjson::Value& object, |
| Context&&... context) { |
| auto result = GetMember(name, object, std::forward<Context>(context)...); |
| if (result.is_error()) { |
| return result.take_error(); |
| } |
| if (!result->get().IsInt()) { |
| return (Error() << ... << std::forward<Context>(context)) |
| << " member \"" << name << "\" must be an integer!"; |
| } |
| return fit::ok(result->get().GetInt()); |
| } |
| |
| template <typename... Context> |
| fit::result<std::string, const char*> GetString(const char* name, const rapidjson::Value& object, |
| Context&&... context) { |
| auto result = GetMember(name, object, std::forward<Context>(context)...); |
| if (result.is_error()) { |
| return result.take_error(); |
| } |
| if (!result->get().IsString()) { |
| return (Error() << ... << std::forward<Context>(context)) |
| << " member \"" << name << "\" must be a string!"; |
| } |
| return fit::ok(result->get().GetString()); |
| } |
| |
| template <typename... Context> |
| auto GetArray(const char* name, const rapidjson::Value& object, Context&&... context) { |
| auto result = GetMember(name, object, std::forward<Context>(context)...); |
| if (result.is_ok() && !result->get().IsArray()) { |
| return (Error() << ... << std::forward<Context>(context)) |
| << " member \"" << name << "\" must be an array!"; |
| } |
| return result; |
| } |
| |
| template <typename... Context> |
| auto GetObject(const char* name, const rapidjson::Value& object, Context&&... context) { |
| auto result = GetMember(name, object, std::forward<Context>(context)...); |
| if (result.is_ok() && !result->get().IsObject()) { |
| return (Error() << ... << std::forward<Context>(context)) |
| << " member \"" << name << "\" must be a JSON object!"; |
| } |
| return result; |
| } |
| |
| template <typename... Context> |
| fit::result<std::string, unsigned int> GetUint(const char* name, const rapidjson::Value& object, |
| Context&&... context) { |
| auto result = GetMember(name, object, std::forward<Context>(context)...); |
| if (result.is_error()) { |
| return result.take_error(); |
| } |
| if (!result->get().IsUint()) { |
| return (Error() << ... << std::forward<Context>(context)).rdbuf() |
| << " member \"" << name << "\" must be an unsigned integer!"; |
| } |
| return fit::ok(result->get().GetUint()); |
| } |
| |
| std::optional<zx_profile_info_t> ParseThreadProfile(const std::string& filename, |
| const char* profile_name, |
| const rapidjson::Value::Member& profile) { |
| const bool has_priority = profile.value.HasMember("priority"); |
| const bool has_capacity = profile.value.HasMember("capacity"); |
| const bool has_deadline = profile.value.HasMember("deadline"); |
| const bool has_period = profile.value.HasMember("period"); |
| const bool has_affinity = profile.value.HasMember("affinity"); |
| |
| const bool has_complete_deadline = has_capacity && has_deadline && has_period; |
| const bool has_some_deadline = has_capacity || has_deadline || has_period; |
| |
| zx_profile_info_t info{}; |
| if (has_priority && !has_some_deadline) { |
| auto result = GetInt("priority", profile.value, "Profile ", profile_name); |
| if (result.is_ok()) { |
| info.flags = ZX_PROFILE_INFO_FLAG_PRIORITY; |
| info.priority = std::clamp<int32_t>(result.value(), ZX_PRIORITY_LOWEST, ZX_PRIORITY_HIGHEST); |
| } else { |
| FX_SLOG(WARNING, result.error_value().c_str(), FX_KV("profile_name", profile_name), |
| FX_KV("tag", "ProfileProvider")); |
| return std::nullopt; |
| } |
| } else if (!has_priority && has_complete_deadline) { |
| auto capacity_result = ParseDuration(profile.value["capacity"]); |
| if (capacity_result.is_error()) { |
| FX_SLOG(WARNING, capacity_result.error_value().c_str(), FX_KV("profile_name", profile_name), |
| FX_KV("tag", "ProfileProvider")); |
| return std::nullopt; |
| } |
| auto deadline_result = ParseDuration(profile.value["deadline"]); |
| if (deadline_result.is_error()) { |
| FX_SLOG(WARNING, deadline_result.error_value().c_str(), FX_KV("profile_name", profile_name), |
| FX_KV("tag", "ProfileProvider")); |
| return std::nullopt; |
| } |
| auto period_result = ParseDuration(profile.value["period"]); |
| if (period_result.is_error()) { |
| FX_SLOG(WARNING, period_result.error_value().c_str(), FX_KV("profile_name", profile_name), |
| FX_KV("tag", "ProfileProvider")); |
| return std::nullopt; |
| } |
| info.flags = ZX_PROFILE_INFO_FLAG_DEADLINE; |
| info.deadline_params = zx_sched_deadline_params_t{.capacity = capacity_result->get(), |
| .relative_deadline = deadline_result->get(), |
| .period = period_result->get()}; |
| } else if (has_priority && has_some_deadline) { |
| FX_SLOG(WARNING, "Priority and deadline parameters are mutually exclusive!", |
| FX_KV("filename", filename), FX_KV("profile_name", profile_name), |
| FX_KV("tag", "ProfileProvider")); |
| return std::nullopt; |
| } else if (!has_priority && !has_complete_deadline && has_some_deadline) { |
| FX_SLOG(WARNING, "Deadline profiles must specify \"capacity\", \"deadline\", and \"period\"!", |
| FX_KV("filename", filename), FX_KV("profile_name", profile_name), |
| FX_KV("tag", "ProfileProvider")); |
| return std::nullopt; |
| } |
| |
| if (has_affinity) { |
| const auto& affinity_member = profile.value["affinity"]; |
| const bool is_uint = affinity_member.IsUint64(); |
| const bool is_array = affinity_member.IsArray(); |
| |
| info.flags |= ZX_PROFILE_INFO_FLAG_CPU_MASK; |
| |
| if (is_uint) { |
| static_assert(std::numeric_limits<uint64_t>::digits <= ZX_CPU_SET_BITS_PER_WORD); |
| info.cpu_affinity_mask.mask[0] = affinity_member.GetUint64(); |
| } else if (is_array) { |
| size_t element_count = 0; |
| bool failed = false; |
| |
| for (const auto& value : IterateValues(affinity_member)) { |
| if (!value.IsUint()) { |
| FX_SLOG(WARNING, |
| "Array element of profile member \"affinity\" must be an " |
| "unsigned integer!", |
| FX_KV("element_count", element_count), FX_KV("filename", filename), |
| FX_KV("profile_name", profile_name), FX_KV("tag", "ProfileProvider")); |
| failed = true; |
| break; |
| } |
| |
| element_count++; |
| const size_t cpu_number = value.GetUint(); |
| |
| if (cpu_number >= ZX_CPU_SET_MAX_CPUS) { |
| FX_SLOG(WARNING, "Profile member \"affinity\" must be an integer < ZX_CPU_SET_MAX_CPUS", |
| FX_KV("filename", filename), FX_KV("profile_name", profile_name), |
| FX_KV("ZX_CPU_SET_MAX_CPUS", ZX_CPU_SET_MAX_CPUS), |
| FX_KV("tag", "ProfileProvider")); |
| failed = true; |
| break; |
| } |
| |
| info.cpu_affinity_mask.mask[cpu_number / ZX_CPU_SET_BITS_PER_WORD] |= |
| uint64_t{1} << (cpu_number % ZX_CPU_SET_BITS_PER_WORD); |
| } |
| if (failed) { |
| return std::nullopt; |
| } |
| } else { |
| FX_SLOG(WARNING, "Profile member \"affinity\" must be a uint64 or an array of CPU indices!", |
| FX_KV("filename", filename), FX_KV("profile_name", profile_name), |
| FX_KV("tag", "ProfileProvider")); |
| return std::nullopt; |
| } |
| } // if (has_affinity) |
| |
| return info; |
| } |
| |
| std::optional<zx_profile_info_t> ParseMemoryProfile(const std::string& filename, |
| const char* profile_name, |
| const rapidjson::Value::Member& profile) { |
| const bool has_priority = profile.value.HasMember("priority"); |
| |
| zx_profile_info_t info{}; |
| if (has_priority) { |
| auto result = GetInt("priority", profile.value, "Profile ", profile_name); |
| if (result.is_ok()) { |
| info.flags = ZX_PROFILE_INFO_FLAG_MEMORY_PRIORITY; |
| info.priority = std::clamp<int32_t>(result.value(), ZX_PRIORITY_LOWEST, ZX_PRIORITY_HIGHEST); |
| } else { |
| FX_SLOG(WARNING, result.error_value().c_str(), FX_KV("profile_name", profile_name), |
| FX_KV("tag", "ProfileProvider")); |
| return std::nullopt; |
| } |
| } |
| return info; |
| } |
| |
| using SingleProfileParser = std::optional<zx_profile_info_t>( |
| const std::string& filename, const char* profile_name, const rapidjson::Value::Member& profile); |
| |
| void ParseProfiles(const std::string& filename, const rapidjson::Document& document, |
| const char* type_name, SingleProfileParser parser, |
| zircon_profile::ProfileMap* profiles) { |
| if (!document.IsObject()) { |
| FX_SLOG(WARNING, "The profile config document must be a JSON object!", |
| FX_KV("filename", filename), FX_KV("tag", "ProfileProvider")); |
| return; |
| } |
| |
| if (!document.HasMember(type_name)) { |
| return; |
| } |
| const rapidjson::Value& profile_member = document[type_name]; |
| |
| ProfileScope scope = ProfileScope::None; |
| if (document.HasMember("scope")) { |
| auto result = GetString("scope", document); |
| if (result.is_ok()) { |
| if (!strcmp(*result, "bringup")) { |
| scope = ProfileScope::Bringup; |
| } else if (!strcmp(*result, "core")) { |
| scope = ProfileScope::Core; |
| } else if (!strcmp(*result, "product")) { |
| scope = ProfileScope::Product; |
| } else { |
| FX_SLOG(WARNING, "Invalid role scope, defaulting to none!", FX_KV("filename", filename), |
| FX_KV("scope", *result), FX_KV("tag", "ProfileProvider")); |
| } |
| } |
| } else { |
| FX_SLOG(WARNING, "Missing role scope, defaulting to none!", FX_KV("filename", filename)); |
| } |
| |
| for (const auto& profile : IterateMembers(profile_member)) { |
| const char* profile_name = profile.name.GetString(); |
| if (!profile.value.IsObject()) { |
| FX_SLOG(WARNING, "Profile value must be a JSON object!", FX_KV("filename", filename), |
| FX_KV("type", type_name), FX_KV("profile", profile_name), |
| FX_KV("tag", "ProfileProvider")); |
| continue; |
| } |
| |
| fit::result role = Role::Create(profile_name); |
| if (role.is_error()) { |
| FX_SLOG(WARNING, "Failed to create role from JSON object!", FX_KV("filename", filename), |
| FX_KV("type", type_name), FX_KV("profile", profile_name), |
| FX_KV("tag", "ProfileProvider")); |
| continue; |
| } |
| |
| auto result = parser(filename, profile_name, profile); |
| if (!result.has_value()) { |
| continue; |
| } |
| |
| zx_profile_info_t& info = *result; |
| |
| if (info.flags == 0) { |
| FX_SLOG(WARNING, "Ignoring empty profile.", FX_KV("filename", filename), |
| FX_KV("type", type_name), FX_KV("profile_name", profile_name), |
| FX_KV("tag", "ProfileProvider")); |
| continue; |
| } |
| |
| std::vector<Parameter> output_parameters = {}; |
| if (profile.value.HasMember("output_parameters")) { |
| const auto& output_param_member = profile.value["output_parameters"]; |
| if (!output_param_member.IsObject()) { |
| FX_SLOG(WARNING, "Output parameters must be a JSON object!", FX_KV("filename", filename), |
| FX_KV("type", type_name), FX_KV("profile", profile_name), |
| FX_KV("tag", "ProfileProvider")); |
| continue; |
| } |
| for (const auto& m : IterateMembers(output_param_member)) { |
| if (m.value.IsInt()) { |
| output_parameters.push_back( |
| Parameter{m.name.GetString(), ParameterValue::WithIntValue(m.value.GetInt())}); |
| } else if (m.value.IsDouble()) { |
| output_parameters.push_back( |
| Parameter{m.name.GetString(), ParameterValue::WithFloatValue(m.value.GetDouble())}); |
| } else if (m.value.IsString()) { |
| output_parameters.push_back( |
| Parameter{m.name.GetString(), ParameterValue::WithStringValue(m.value.GetString())}); |
| } else { |
| FX_SLOG(WARNING, "Output parameter value must be a float, integer, or string!", |
| FX_KV("parameter_name", m.name.GetString()), FX_KV("filename", filename), |
| FX_KV("type", type_name), FX_KV("profile", profile_name), |
| FX_KV("tag", "ProfileProvider")); |
| } |
| } |
| } |
| |
| Profile p(scope, info, output_parameters); |
| const auto [iter, added] = profiles->insert(std::pair{std::move(role.value()), std::move(p)}); |
| if (!added) { |
| const ProfileScope existing_scope = iter->second.scope; |
| if (existing_scope >= scope) { |
| FX_SLOG(WARNING, "Profile already exists at scope.", FX_KV("filename", filename), |
| FX_KV("profile_name", profile_name), |
| FX_KV("existing_scope", ToString(existing_scope)), FX_KV("type", type_name), |
| FX_KV("profile_name", profile_name), FX_KV("scope", ToString(scope)), |
| FX_KV("tag", "ProfileProvider")); |
| continue; |
| } |
| if (iter->second.scope < scope) { |
| FX_SLOG(INFO, "Profile overridden at scope.", FX_KV("filename", filename), |
| FX_KV("type", type_name), FX_KV("profile_name", profile_name), |
| FX_KV("scope", ToString(scope)), FX_KV("profile_name", profile_name), |
| FX_KV("tag", "ProfileProvider")); |
| iter->second = Profile(scope, info, std::move(output_parameters)); |
| } |
| } |
| } // for (const auto& profile : IterateMembers(document)) |
| } |
| |
| } // anonymous namespace |
| |
| namespace zircon_profile { |
| |
| fit::result<std::string, ConfiguredProfiles> LoadConfigs(const std::string& config_path) { |
| fbl::unique_fd dir_fd(openat(AT_FDCWD, config_path.c_str(), O_RDONLY | O_DIRECTORY)); |
| if (!dir_fd.is_valid()) { |
| // A non-existent directory is not an error. |
| FX_SLOG(WARNING, "Failed to open config dir.", FX_KV("config_path", config_path), |
| FX_KV("error", strerror(errno)), FX_KV("tag", "ProfileProvider")); |
| return fit::ok(ConfiguredProfiles{}); |
| } |
| |
| std::vector<std::string> dir_entries; |
| if (!files::ReadDirContentsAt(dir_fd.get(), ".", &dir_entries)) { |
| return Error() << "Could not read directory contents from path " << config_path << " error " |
| << strerror(errno); |
| } |
| |
| const auto filename_predicate = [](const std::string& filename) { |
| const auto pos = filename.rfind(kConfigFileExtension); |
| return pos != std::string::npos && pos == (filename.size() - std::strlen(kConfigFileExtension)); |
| }; |
| |
| ConfiguredProfiles profiles; |
| |
| // Define fuchsia.default at builtin scope to prevent overrides from config files. |
| { |
| zx_profile_info_t info{}; |
| info.flags = ZX_PROFILE_INFO_FLAG_PRIORITY; |
| info.priority = ZX_PRIORITY_DEFAULT; |
| fit::result default_role_result = Role::Create("fuchsia.default"); |
| ZX_DEBUG_ASSERT(default_role_result.is_ok()); |
| auto [iter, added] = profiles.thread.emplace(std::move(default_role_result.value()), |
| Profile{ProfileScope::Builtin, info}); |
| ZX_DEBUG_ASSERT(added); |
| } |
| { |
| zx_profile_info_t info{}; |
| info.flags = ZX_PROFILE_INFO_FLAG_MEMORY_PRIORITY; |
| info.priority = ZX_PRIORITY_DEFAULT; |
| fit::result default_role_result = Role::Create("fuchsia.default"); |
| ZX_DEBUG_ASSERT(default_role_result.is_ok()); |
| auto [iter, added] = profiles.memory.emplace(std::move(default_role_result.value()), |
| Profile{ProfileScope::Builtin, info}); |
| ZX_DEBUG_ASSERT(added); |
| } |
| |
| for (const auto& entry : dir_entries) { |
| if (!files::IsFileAt(dir_fd.get(), entry) || !filename_predicate(entry)) { |
| continue; |
| } |
| |
| FX_SLOG(INFO, "Loading config.", FX_KV("config_path", entry), FX_KV("tag", "ProfileProvider")); |
| |
| std::string data; |
| if (!files::ReadFileToStringAt(dir_fd.get(), entry, &data)) { |
| FX_SLOG(WARNING, "Failed to read file.", FX_KV("config_path", entry), |
| FX_KV("error", strerror(errno)), FX_KV("tag", "ProfileProvider")); |
| continue; |
| } |
| |
| rapidjson::Document document; |
| const auto kFlags = rapidjson::kParseCommentsFlag | rapidjson::kParseTrailingCommasFlag | |
| rapidjson::kParseIterativeFlag; |
| document.Parse<kFlags>(data); |
| |
| if (document.HasParseError()) { |
| FX_SLOG(WARNING, "Failed to parse config.", FX_KV("config_path", entry), |
| FX_KV("error", GetErrorMessage(document, data)), FX_KV("tag", "ProfileProvider")); |
| continue; |
| } |
| |
| ParseProfiles(entry, document, "profiles", ParseThreadProfile, &profiles.thread); |
| ParseProfiles(entry, document, "memory", ParseMemoryProfile, &profiles.memory); |
| } |
| |
| auto log_profiles = [](ProfileMap& profiles) { |
| for (const auto& [key, value] : profiles) { |
| FX_SLOG(DEBUG, "Loaded profile.", FX_KV("key", key.name()), |
| FX_KV("scope", ToString(value.scope)), FX_KV("info", ToString(value.info)), |
| FX_KV("tag", "ProfileProvider"), |
| FX_KV("output_parameters", ToString(value.output_parameters))); |
| } |
| }; |
| FX_SLOG(DEBUG, "Loaded thread profiles:"); |
| log_profiles(profiles.thread); |
| FX_SLOG(DEBUG, "Defined memory profiles:"); |
| log_profiles(profiles.memory); |
| |
| return fit::ok(std::move(profiles)); |
| } |
| |
| fit::result<zx_status_t, Role> Role::Create(std::string_view name, |
| std::vector<Parameter> selectors) { |
| // Validate the name. It should have no selectors embedded in it. |
| Role role; |
| if (!re2::RE2::FullMatch(name, kReRoleName, &role.name_)) { |
| FX_SLOG(WARNING, "Bad role name.", FX_KV("role_name", name), FX_KV("tag", "RoleManager")); |
| return fit::error(ZX_ERR_INVALID_ARGS); |
| } |
| for (auto selector : selectors) { |
| role.selectors_.insert(std::pair{selector.key(), selector.value()}); |
| } |
| return fit::ok(std::move(role)); |
| } |
| |
| // ToLong attempts to convert the given string into a long and populates *out with the result. |
| // Returns true if str is indeed a long, false otherwise. |
| bool ToLong(std::string str, long* out) { |
| char* end = nullptr; |
| long value = std::strtol(str.c_str(), &end, 10); |
| if (end == str.c_str() || *end != '\0' || value == LONG_MAX || value == LONG_MIN) { |
| return false; |
| } |
| *out = value; |
| return true; |
| } |
| |
| // ToDouble attempts to convert the given string into a long and populates *out with the result. |
| // Returns true if str is indeed a double, false otherwise. |
| // Note that this will return true for any whole number, so it's important that the caller checks |
| // if the number is a long using ToLong before calling this function. |
| bool ToDouble(std::string str, double* out) { |
| char* end = nullptr; |
| double value = std::strtod(str.c_str(), &end); |
| if (end == str.c_str() || *end != '\0' || value == HUGE_VAL) { |
| return false; |
| } |
| *out = value; |
| return true; |
| } |
| |
| fit::result<zx_status_t, Role> Role::Create(std::string_view name_with_selectors, |
| bool ignore_selectors) { |
| Role role; |
| std::string selectors; |
| if (!re2::RE2::FullMatch(name_with_selectors, kReRoleParts, &role.name_, &selectors)) { |
| FX_SLOG(WARNING, "Bad selector.", FX_KV("role_selector", name_with_selectors), |
| FX_KV("tag", "RoleManager")); |
| return fit::error(ZX_ERR_INVALID_ARGS); |
| } |
| |
| // If the caller wants to us to ignore the selectors, exit early. |
| if (ignore_selectors) { |
| return fit::ok(std::move(role)); |
| } |
| |
| // Parse the selectors. |
| re2::StringPiece input{selectors}; |
| std::string key, raw_value; |
| while (re2::RE2::Consume(&input, kReSelector, &key, &raw_value)) { |
| ParameterValue value = ParameterValue::WithStringValue(raw_value); |
| long l; |
| double d; |
| if (ToLong(raw_value, &l)) { |
| value = ParameterValue::WithIntValue(l); |
| } else if (ToDouble(raw_value, &d)) { |
| value = ParameterValue::WithFloatValue(d); |
| } |
| const auto [iter, added] = role.selectors_.emplace(key, value); |
| if (!added) { |
| FX_SLOG(WARNING, "Duplicate key in selector.", FX_KV("key", key), FX_KV("value", raw_value), |
| FX_KV("role_selector", name_with_selectors), FX_KV("tag", "RoleManager")); |
| } |
| } |
| return fit::ok(std::move(role)); |
| } |
| |
| bool Role::HasSelector(std::string selector) const { |
| auto search = selectors_.find(selector); |
| return search != selectors_.end(); |
| } |
| |
| fit::result<fit::failed, MediaRole> Role::ToMediaRole() const { |
| const auto realm_iter = selectors_.find("realm"); |
| if (realm_iter == selectors_.end() || realm_iter->second.string_value().value() != "media") { |
| return fit::failed{}; |
| } |
| |
| const auto capacity_iter = selectors_.find("capacity"); |
| const auto deadline_iter = selectors_.find("deadline"); |
| if (capacity_iter == selectors_.end() || deadline_iter == selectors_.end()) { |
| return fit::failed{}; |
| } |
| |
| if (capacity_iter->second.Which() != ParameterValue::Tag::kIntValue) { |
| FX_SLOG(WARNING, "Media role has invalid capacity selector.", FX_KV("role_name", name_), |
| FX_KV("tag", "ProfileProvider")); |
| return fit::failed{}; |
| } |
| int64_t capacity = capacity_iter->second.int_value().value(); |
| |
| if (deadline_iter->second.Which() != ParameterValue::Tag::kIntValue) { |
| FX_SLOG(WARNING, "Media role has invalid deadline selector.", FX_KV("role_name", name_), |
| FX_KV("tag", "ProfileProvider")); |
| return fit::failed{}; |
| } |
| int64_t deadline = deadline_iter->second.int_value().value(); |
| |
| return fit::ok(MediaRole{.capacity = capacity, .deadline = deadline}); |
| } |
| |
| bool Role::operator==(const Role& other) const { |
| // The role names must be the same. |
| if (name_ != other.name_) { |
| return false; |
| } |
| // The other role must have the exact same selectors as this one. |
| if (selectors_.size() != other.selectors_.size()) { |
| return false; |
| } |
| for (auto selector : selectors_) { |
| auto it = other.selectors_.find(selector.first); |
| if (it == other.selectors_.end()) { |
| return false; |
| } |
| if (selector.second.Which() != it->second.Which()) { |
| return false; |
| } |
| switch (selector.second.Which()) { |
| case ParameterValue::Tag::kIntValue: |
| if (selector.second.int_value().value() != it->second.int_value().value()) { |
| return false; |
| } |
| break; |
| case ParameterValue::Tag::kFloatValue: |
| if (selector.second.float_value().value() != it->second.float_value().value()) { |
| return false; |
| } |
| break; |
| case ParameterValue::Tag::kStringValue: |
| if (selector.second.string_value().value() != it->second.string_value().value()) { |
| return false; |
| } |
| break; |
| default: |
| // We should never hit this case. |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| } // namespace zircon_profile |