| // 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 <fcntl.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/profile.h> |
| #include <lib/zx/resource.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 "fuchsia/scheduler/cpp/fidl.h" |
| #include "lib/fdio/directory.h" |
| |
| namespace { |
| |
| // Returns a reference to a ProfileProvider proxy. Initializes the proxy on the |
| // first invocation, otherwise returns a reference to the existing proxy. |
| fuchsia::scheduler::ProfileProvider_SyncProxy& GetProfileProvider() { |
| static std::optional<fuchsia::scheduler::ProfileProvider_SyncProxy> provider; |
| static std::mutex mutex; |
| |
| std::lock_guard<std::mutex> guard{mutex}; |
| |
| if (provider.has_value()) { |
| return *provider; |
| } |
| |
| // Connect to the scheduler profile service. |
| 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::scheduler::ProfileProvider::Name_).c_str(), |
| channel0.release()); |
| FX_CHECK(status == ZX_OK); |
| |
| provider.emplace(std::move(channel1)); |
| return *provider; |
| } |
| |
| } // anonymous namespace |
| |
| // 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(); |
| } |
| } |
| } |
| |
| // 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()}; |
| } |
| |
| auto& provider = GetProfileProvider(); |
| |
| zx_status_t fidl_status; |
| zx::profile profile; |
| const auto status = provider.GetProfile(priority, "garnet/bin/loadbench", &fidl_status, &profile); |
| FX_CHECK(status == ZX_OK); |
| FX_CHECK(fidl_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()}; |
| } |
| |
| auto& provider = GetProfileProvider(); |
| |
| zx_status_t fidl_status; |
| zx::profile profile; |
| const auto status = provider.GetDeadlineProfile(capacity.get(), deadline.get(), period.get(), |
| "garnet/bin/loadbench", &fidl_status, &profile); |
| FX_CHECK(status == ZX_OK); |
| FX_CHECK(fidl_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 GetDebugResource() { |
| static zx::resource debug_resource; |
| static std::mutex mutex; |
| |
| std::lock_guard<std::mutex> guard{mutex}; |
| |
| if (debug_resource) { |
| return zx::unowned_resource{debug_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::DebugResource::Name_).c_str(), channel0.release()); |
| FX_CHECK(status == ZX_OK); |
| |
| fuchsia::kernel::DebugResource_SyncProxy proxy(std::move(channel1)); |
| status = proxy.Get(&debug_resource); |
| FX_CHECK(status == ZX_OK); |
| |
| return zx::unowned_resource{debug_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); |
| } |