| // 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 "utility.h" | |
| #include <lib/syslog/cpp/macros.h> | |
| #include <lib/zx/channel.h> | |
| #include <lib/zx/profile.h> | |
| #include <lib/zx/resource.h> | |
| #include <lib/zx/result.h> | |
| #include <zircon/syscalls/object.h> | |
| #include <zircon/types.h> | |
| #include <cstdint> | |
| #include <map> | |
| #include <mutex> | |
| #include <optional> | |
| #include <tuple> | |
| #include <re2/re2.h> | |
| #include "fuchsia/kernel/cpp/fidl.h" | |
| #include "lib/fdio/directory.h" | |
| // Represents ordering of CPU affinity masks. | |
| enum class MaskRelation { | |
| Less, | |
| Equal, | |
| Greater, | |
| }; | |
| // Compares the given affinity masks, defining a total order of masks. This | |
| // method is templatized to handle changes in the size of the affinity mask | |
| // array in zx_cpu_set_t. | |
| template <size_t Size> | |
| constexpr MaskRelation CompareMasks(const uint64_t (&a)[Size], const uint64_t (&b)[Size]) { | |
| for (size_t i = 0; i < Size; i++) { | |
| if (a[i] < b[i]) { | |
| return MaskRelation::Less; | |
| } | |
| if (a[i] > b[i]) { | |
| return MaskRelation::Greater; | |
| } | |
| } | |
| return MaskRelation::Equal; | |
| } | |
| // Provide comparisons needed for std::map. | |
| constexpr bool operator==(const zx_cpu_set_t& a, const zx_cpu_set_t& b) { | |
| return CompareMasks(a.mask, b.mask) == MaskRelation::Equal; | |
| } | |
| constexpr bool operator<(const zx_cpu_set_t& a, const zx_cpu_set_t& b) { | |
| return CompareMasks(a.mask, b.mask) == MaskRelation::Less; | |
| } | |
| std::chrono::nanoseconds ParseDurationString(const std::string& duration) { | |
| // Match one or more digits, optionally followed by time units m, s, ms, us, | |
| // or ns. | |
| static const re2::RE2 kReDuration{"^(\\d+)(m|s|ms|us|ns)?$"}; | |
| uint64_t scalar; | |
| std::string units; | |
| bool matched = re2::RE2::PartialMatch(duration, kReDuration, &scalar, &units); | |
| FX_CHECK(matched) << "String \"" << duration << "\" is not a valid duration!"; | |
| if (units == "") { | |
| return std::chrono::nanoseconds{scalar}; | |
| } else if (units == "m") { | |
| return std::chrono::minutes{scalar}; | |
| } else if (units == "s") { | |
| return std::chrono::seconds{scalar}; | |
| } else if (units == "ms") { | |
| return std::chrono::milliseconds{scalar}; | |
| } else if (units == "us") { | |
| return std::chrono::microseconds{scalar}; | |
| } else if (units == "ns") { | |
| return std::chrono::nanoseconds{scalar}; | |
| } else { | |
| FX_CHECK(false) << "String duration \"" << duration << "\" has unrecognized units \"" << units | |
| << "\"!"; | |
| __builtin_unreachable(); | |
| } | |
| } | |
| // Parses an expression of the form "cpu_num<+|-|*><positive integer>" and returns evaluated result | |
| // as an integer. | |
| size_t ParseInstancesString(const std::string& instances) { | |
| static const re2::RE2 kReInstances{"^cpu_num((\\+|\\*|-)(\\d+))?$"}; | |
| // submatch[0]: <operation><argument> | |
| // submatch[1]: <operation> | |
| // submatch[2]: <argument> | |
| std::string operation; | |
| size_t argument; | |
| FX_CHECK(re2::RE2::PartialMatch(instances, kReInstances, nullptr, &operation, &argument)) | |
| << "The expression string must be in the format cpu_num<+|-|*><positive integer>."; | |
| if (operation == "") { | |
| return ReadCpuCount(); | |
| } else { | |
| if (operation == "+") { | |
| return ReadCpuCount() + argument; | |
| } else if (operation == "-") { | |
| if (argument > ReadCpuCount()) { | |
| FX_LOGS(WARNING) << "Expression " << instances | |
| << " yields negative number. Instances set to 0"; | |
| return 0; | |
| } else { | |
| return ReadCpuCount() - argument; | |
| } | |
| } else if (operation == "*") { | |
| return ReadCpuCount() * argument; | |
| } else { | |
| __builtin_unreachable(); | |
| } | |
| } | |
| } | |
| zx::unowned_resource GetProfileResource() { | |
| static zx::resource profile_resource; | |
| static std::mutex mutex; | |
| std::lock_guard<std::mutex> guard{mutex}; | |
| if (profile_resource) { | |
| return zx::unowned_resource{profile_resource.get()}; | |
| } | |
| // Connect to the profile resource. | |
| zx::channel channel0, channel1; | |
| zx_status_t status; | |
| status = zx::channel::create(0u, &channel0, &channel1); | |
| FX_CHECK(status == ZX_OK); | |
| status = fdio_service_connect( | |
| (std::string("/svc/") + fuchsia::kernel::ProfileResource::Name_).c_str(), channel0.release()); | |
| FX_CHECK(status == ZX_OK); | |
| fuchsia::kernel::ProfileResource_SyncProxy proxy(std::move(channel1)); | |
| status = proxy.Get(&profile_resource); | |
| FX_CHECK(status == ZX_OK); | |
| return zx::unowned_resource{profile_resource.get()}; | |
| } | |
| // Returns an unowned handle to a profile for the specified priority. Maintains | |
| // an internal map of already requested profiles and returns the same handle for | |
| // multiple requests for the same priority. | |
| zx::unowned_profile GetProfile(int priority, std::optional<zx_cpu_set_t> affinity) { | |
| using Key = std::tuple<int, std::optional<zx_cpu_set_t>>; | |
| const Key key{priority, affinity}; | |
| // Maintains a map of profiles for each previously requested priority/affinity. | |
| static std::map<Key, zx::profile> profiles; | |
| static std::mutex mutex; | |
| std::lock_guard<std::mutex> guard{mutex}; | |
| // Return the existing handle if it's already in the map. | |
| auto search = profiles.find(key); | |
| if (search != profiles.end()) { | |
| return zx::unowned_profile{search->second.get()}; | |
| } | |
| // Create a priority profile. | |
| auto profile_resource = GetProfileResource(); | |
| zx_profile_info_t info = { | |
| .flags = ZX_PROFILE_INFO_FLAG_PRIORITY, | |
| .priority = priority, | |
| }; | |
| zx::profile profile; | |
| zx_status_t status = zx::profile::create(*profile_resource, 0u, &info, &profile); | |
| FX_CHECK(status == ZX_OK); | |
| // Add the new profile to the map for later retrieval. | |
| auto [iter, okay] = profiles.emplace(key, std::move(profile)); | |
| FX_CHECK(okay); | |
| return zx::unowned_profile{iter->second.get()}; | |
| } | |
| // Returns an unowned handle to a profile for the specified deadline parameters. | |
| // Maintains an internal map of already requested profiles and returns the same | |
| // handle for multiple request for the same deadline parameters. | |
| zx::unowned_profile GetProfile(zx::duration capacity, zx::duration deadline, zx::duration period, | |
| std::optional<zx_cpu_set_t> affinity) { | |
| using Key = std::tuple<zx_duration_t, zx_duration_t, zx_duration_t, std::optional<zx_cpu_set_t>>; | |
| const Key key{capacity.get(), deadline.get(), period.get(), affinity}; | |
| // Maintains a map of profiles for each previously requested deadline/affinity. | |
| static std::map<Key, zx::profile> profiles; | |
| static std::mutex mutex; | |
| std::lock_guard<std::mutex> guard{mutex}; | |
| // Return the existing handle if it's already in the map. | |
| auto search = profiles.find(key); | |
| if (search != profiles.end()) { | |
| return zx::unowned_profile{search->second.get()}; | |
| } | |
| // Create a deadline profile. | |
| auto profile_resource = GetProfileResource(); | |
| zx_profile_info_t info = { | |
| .flags = ZX_PROFILE_INFO_FLAG_DEADLINE, | |
| .deadline_params = | |
| zx_sched_deadline_params_t{ | |
| .capacity = capacity.get(), | |
| .relative_deadline = deadline.get(), | |
| .period = period.get(), | |
| }, | |
| }; | |
| zx::profile profile; | |
| zx_status_t status = zx::profile::create(*profile_resource, 0u, &info, &profile); | |
| FX_CHECK(status == ZX_OK); | |
| // Add the new profile to the map for later retrieval. | |
| auto [iter, okay] = profiles.emplace(key, std::move(profile)); | |
| FX_CHECK(okay); | |
| return zx::unowned_profile{iter->second.get()}; | |
| } | |
| zx::unowned_resource GetTracingResource() { | |
| static zx::resource tracing_resource; | |
| static std::mutex mutex; | |
| std::lock_guard<std::mutex> guard{mutex}; | |
| if (tracing_resource) { | |
| return zx::unowned_resource{tracing_resource.get()}; | |
| } | |
| // Connect to the debug resource. | |
| zx::channel channel0, channel1; | |
| zx_status_t status; | |
| status = zx::channel::create(0u, &channel0, &channel1); | |
| FX_CHECK(status == ZX_OK); | |
| status = fdio_service_connect( | |
| (std::string("/svc/") + fuchsia::kernel::TracingResource::Name_).c_str(), channel0.release()); | |
| FX_CHECK(status == ZX_OK); | |
| fuchsia::kernel::TracingResource_SyncProxy proxy(std::move(channel1)); | |
| status = proxy.Get(&tracing_resource); | |
| FX_CHECK(status == ZX_OK); | |
| return zx::unowned_resource{tracing_resource.get()}; | |
| } | |
| zx::unowned_resource GetInfoResource() { | |
| static zx::resource info_resource; | |
| static std::mutex mutex; | |
| std::lock_guard<std::mutex> guard{mutex}; | |
| if (info_resource) { | |
| return zx::unowned_resource{info_resource.get()}; | |
| } | |
| // Connect to the info resource. | |
| zx::channel channel0, channel1; | |
| zx_status_t status; | |
| status = zx::channel::create(0u, &channel0, &channel1); | |
| FX_CHECK(status == ZX_OK); | |
| status = fdio_service_connect( | |
| (std::string("/svc/") + fuchsia::kernel::InfoResource::Name_).c_str(), channel0.release()); | |
| FX_CHECK(status == ZX_OK); | |
| fuchsia::kernel::InfoResource_SyncProxy proxy(std::move(channel1)); | |
| status = proxy.Get(&info_resource); | |
| FX_CHECK(status == ZX_OK); | |
| return zx::unowned_resource{info_resource.get()}; | |
| } | |
| size_t ReadCpuCount() { | |
| size_t actual, available; | |
| const auto status = | |
| GetInfoResource()->get_info(ZX_INFO_CPU_STATS, nullptr, 0, &actual, &available); | |
| FX_CHECK(status == ZX_OK); | |
| return available; | |
| } | |
| void ReadCpuStats(zx_info_cpu_stats_t* stats_buffer, size_t record_count) { | |
| size_t actual, available; | |
| const auto buffer_size = sizeof(zx_info_cpu_stats_t) * record_count; | |
| const auto status = GetInfoResource()->get_info(ZX_INFO_CPU_STATS, stats_buffer, buffer_size, | |
| &actual, &available); | |
| FX_CHECK(status == ZX_OK); | |
| } |