| // Copyright 2023 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| #pragma once |
| |
| #include <cstddef> |
| #include <cstdint> |
| |
| #include "pw_metric/metric.h" |
| |
| namespace pw::allocator { |
| |
| /// Declares the names of metrics used by `pw::allocator::Metrics`. |
| /// |
| /// Only the names of declared metrics may be passed to |
| /// ``PW_ALLOCATOR_METRICS_ENABLE`` as part of a metrics struct definition. |
| /// |
| /// This macro generates trait types that are used by ``Metrics`` to |
| /// conditionally include metric-related code. |
| /// |
| /// Note: if enabling ``peak_allocated_bytes` or `cumulative_allocated_bytes`, |
| /// `allocated_bytes` should also be enabled. |
| #define PW_ALLOCATOR_METRICS_DECLARE(metric_name) \ |
| template <typename MetricsType, typename = void> \ |
| struct has_##metric_name : std::false_type {}; \ |
| template <typename MetricsType> \ |
| struct has_##metric_name<MetricsType, \ |
| std::void_t<decltype(MetricsType::metric_name)>> \ |
| : std::true_type {} |
| |
| // Tracks the current, peak, and cumulative number of bytes requested to be |
| // allocated, respectively. |
| PW_ALLOCATOR_METRICS_DECLARE(requested_bytes); |
| PW_ALLOCATOR_METRICS_DECLARE(peak_requested_bytes); |
| PW_ALLOCATOR_METRICS_DECLARE(cumulative_requested_bytes); |
| |
| // Tracks the current, peak, and cumulative number of bytes actually allocated, |
| // respectively. |
| PW_ALLOCATOR_METRICS_DECLARE(allocated_bytes); |
| PW_ALLOCATOR_METRICS_DECLARE(peak_allocated_bytes); |
| PW_ALLOCATOR_METRICS_DECLARE(cumulative_allocated_bytes); |
| |
| // Tracks the number of successful calls to each interface method. |
| PW_ALLOCATOR_METRICS_DECLARE(num_allocations); |
| PW_ALLOCATOR_METRICS_DECLARE(num_deallocations); |
| PW_ALLOCATOR_METRICS_DECLARE(num_resizes); |
| PW_ALLOCATOR_METRICS_DECLARE(num_reallocations); |
| |
| // Tracks the number of interface calls that failed, and the number of bytes |
| // requested in those calls. |
| PW_ALLOCATOR_METRICS_DECLARE(num_failures); |
| PW_ALLOCATOR_METRICS_DECLARE(unfulfilled_bytes); |
| |
| #undef PW_ALLOCATOR_METRICS_DECLARE |
| |
| /// Enables a metric for in a metrics struct. |
| /// |
| /// The ``pw::allocator::TrackingAllocator`` template takes a struct that |
| /// enables zero or more of the metrics enumerated by |
| /// ``PW_ALLOCATOR_METRICS_DECLARE```. |
| /// |
| /// This struct may be one of ``AllMetrics`` or ``NoMetrics``, or may be a |
| /// custom struct that selects a subset of metrics. |
| /// |
| /// Note that this must be fully-qualified since the metric struct may be |
| /// defined in any namespace. |
| /// |
| /// Example: |
| /// @code{.cpp} |
| /// struct MyMetrics { |
| /// PW_ALLOCATOR_METRICS_ENABLE(allocated_bytes); |
| /// PW_ALLOCATOR_METRICS_ENABLE(peak_allocated_bytes); |
| /// PW_ALLOCATOR_METRICS_ENABLE(num_failures); |
| /// }; |
| /// @endcode |
| #define PW_ALLOCATOR_METRICS_ENABLE(metric_name) \ |
| static_assert(!::pw::allocator::has_##metric_name<void>::value); \ |
| PW_METRIC(metric_name, #metric_name, 0U) |
| |
| /// A predefined metric struct that enables no allocator metrics. |
| struct NoMetrics {}; |
| |
| namespace internal { |
| |
| /// A metrics type that enables all metrics for testing. |
| /// |
| /// Warning! Do not use in production code. If metrics are added to it later, |
| /// code using this struct may unexpected grow in code size, memory usage, |
| /// and/or performance overhead. |
| struct AllMetrics { |
| PW_ALLOCATOR_METRICS_ENABLE(requested_bytes); |
| PW_ALLOCATOR_METRICS_ENABLE(peak_requested_bytes); |
| PW_ALLOCATOR_METRICS_ENABLE(cumulative_requested_bytes); |
| PW_ALLOCATOR_METRICS_ENABLE(allocated_bytes); |
| PW_ALLOCATOR_METRICS_ENABLE(peak_allocated_bytes); |
| PW_ALLOCATOR_METRICS_ENABLE(cumulative_allocated_bytes); |
| PW_ALLOCATOR_METRICS_ENABLE(num_allocations); |
| PW_ALLOCATOR_METRICS_ENABLE(num_deallocations); |
| PW_ALLOCATOR_METRICS_ENABLE(num_resizes); |
| PW_ALLOCATOR_METRICS_ENABLE(num_reallocations); |
| PW_ALLOCATOR_METRICS_ENABLE(num_failures); |
| PW_ALLOCATOR_METRICS_ENABLE(unfulfilled_bytes); |
| }; |
| |
| /// Encapsulates the metrics struct for ``pw::allocator::TrackingAllocator``. |
| /// |
| /// This class uses the type traits from ``PW_ALLOCATOR_METRICS_DECLARE`` to |
| /// conditionally include or exclude code to update metrics based on calls to |
| /// the ``pw::Allocator`` API. This minimizes code size without adding |
| /// additional conditions to be evaluated at runtime. |
| /// |
| /// @tparam MetricsType The struct defining which metrics are enabled. |
| template <typename MetricsType> |
| class Metrics final { |
| public: |
| Metrics(metric::Token token); |
| ~Metrics() = default; |
| |
| const metric::Group& group() const { return group_; } |
| metric::Group& group() { return group_; } |
| |
| const MetricsType& metrics() const { return metrics_; } |
| |
| /// Updates how much memory was requested and successfully allocated. |
| /// |
| /// This will update the current, peak, and cumulative amounts of memory |
| /// requests that were satisfied by an allocator. |
| /// |
| /// @param increase How much memory was requested to be allocated. |
| /// @param decrease How much memory was requested to be freed. |
| void ModifyRequested(size_t increase, size_t decrease); |
| |
| /// Updates how much memory is allocated. |
| /// |
| /// This will update the current, peak, and cumulative amounts of memory that |
| /// has been actually allocated or freed. This method acts as if it frees |
| /// memory before allocating. If a routine suchas `Reallocate` allocates |
| /// before freeing, the update should be separated into two calls, e.g. |
| /// |
| /// @code{.cpp} |
| /// ModifyAllocated(increase, 0); |
| /// ModifyAllocated(0, decrease); |
| /// @endcode |
| /// |
| /// @param increase How much memory was allocated. |
| /// @param decrease How much memory was freed. |
| void ModifyAllocated(size_t increase, size_t decrease); |
| |
| /// Records that a call to `Allocate` was made. |
| void IncrementAllocations(); |
| |
| /// Records that a call to `Deallocate` was made. |
| void IncrementDeallocations(); |
| |
| /// Records that a call to `Resize` was made. |
| void IncrementResizes(); |
| |
| /// Records that a call to `Reallocate` was made. |
| void IncrementReallocations(); |
| |
| /// Records that a call to `Allocate`, `Resize`, or `Reallocate` failed. |
| /// |
| /// This may indicated that memory becoming exhausted and/or highly |
| /// fragmented. |
| /// |
| /// @param requested How much memory was requested in the failed |
| /// call. |
| void RecordFailure(size_t requested); |
| |
| private: |
| metric::Group group_; |
| MetricsType metrics_; |
| }; |
| |
| // Helper method for converting `size_t`s to `uint32_t`s. |
| inline uint32_t ClampU32(size_t size) { |
| return static_cast<uint32_t>(std::min( |
| size, static_cast<size_t>(std::numeric_limits<uint32_t>::max()))); |
| } |
| |
| // Template method implementation. |
| |
| template <typename MetricsType> |
| Metrics<MetricsType>::Metrics(metric::Token token) : group_(token) { |
| if constexpr (has_requested_bytes<MetricsType>::value) { |
| group_.Add(metrics_.requested_bytes); |
| } |
| if constexpr (has_peak_requested_bytes<MetricsType>::value) { |
| group_.Add(metrics_.peak_requested_bytes); |
| } |
| if constexpr (has_cumulative_requested_bytes<MetricsType>::value) { |
| group_.Add(metrics_.cumulative_requested_bytes); |
| } |
| if constexpr (has_allocated_bytes<MetricsType>::value) { |
| group_.Add(metrics_.allocated_bytes); |
| } |
| if constexpr (has_peak_allocated_bytes<MetricsType>::value) { |
| group_.Add(metrics_.peak_allocated_bytes); |
| } |
| if constexpr (has_cumulative_allocated_bytes<MetricsType>::value) { |
| group_.Add(metrics_.cumulative_allocated_bytes); |
| } |
| if constexpr (has_num_allocations<MetricsType>::value) { |
| group_.Add(metrics_.num_allocations); |
| } |
| if constexpr (has_num_deallocations<MetricsType>::value) { |
| group_.Add(metrics_.num_deallocations); |
| } |
| if constexpr (has_num_resizes<MetricsType>::value) { |
| group_.Add(metrics_.num_resizes); |
| } |
| if constexpr (has_num_reallocations<MetricsType>::value) { |
| group_.Add(metrics_.num_reallocations); |
| } |
| if constexpr (has_num_failures<MetricsType>::value) { |
| group_.Add(metrics_.num_failures); |
| } |
| if constexpr (has_unfulfilled_bytes<MetricsType>::value) { |
| group_.Add(metrics_.unfulfilled_bytes); |
| } |
| } |
| |
| template <typename MetricsType> |
| void Metrics<MetricsType>::ModifyRequested(size_t increase, size_t decrease) { |
| if constexpr (has_requested_bytes<MetricsType>::value) { |
| metrics_.requested_bytes.Increment(internal::ClampU32(increase)); |
| metrics_.requested_bytes.Decrement(internal::ClampU32(decrease)); |
| if constexpr (has_peak_requested_bytes<MetricsType>::value) { |
| uint32_t requested_bytes = metrics_.requested_bytes.value(); |
| if (metrics_.peak_requested_bytes.value() < requested_bytes) { |
| metrics_.peak_requested_bytes.Set(requested_bytes); |
| } |
| } |
| if constexpr (has_cumulative_requested_bytes<MetricsType>::value) { |
| if (increase > decrease) { |
| metrics_.cumulative_requested_bytes.Increment( |
| internal::ClampU32(increase - decrease)); |
| } |
| } |
| } |
| } |
| |
| template <typename MetricsType> |
| void Metrics<MetricsType>::ModifyAllocated(size_t increase, size_t decrease) { |
| if constexpr (has_allocated_bytes<MetricsType>::value) { |
| metrics_.allocated_bytes.Increment(internal::ClampU32(increase)); |
| metrics_.allocated_bytes.Decrement(internal::ClampU32(decrease)); |
| if constexpr (has_peak_allocated_bytes<MetricsType>::value) { |
| uint32_t allocated_bytes = metrics_.allocated_bytes.value(); |
| if (metrics_.peak_allocated_bytes.value() < allocated_bytes) { |
| metrics_.peak_allocated_bytes.Set(allocated_bytes); |
| } |
| } |
| if constexpr (has_cumulative_allocated_bytes<MetricsType>::value) { |
| if (increase > decrease) { |
| metrics_.cumulative_allocated_bytes.Increment( |
| internal::ClampU32(increase - decrease)); |
| } |
| } |
| } |
| } |
| |
| template <typename MetricsType> |
| void Metrics<MetricsType>::IncrementAllocations() { |
| if constexpr (has_num_allocations<MetricsType>::value) { |
| metrics_.num_allocations.Increment(); |
| } |
| } |
| |
| template <typename MetricsType> |
| void Metrics<MetricsType>::IncrementDeallocations() { |
| if constexpr (has_num_deallocations<MetricsType>::value) { |
| metrics_.num_deallocations.Increment(); |
| } |
| } |
| |
| template <typename MetricsType> |
| void Metrics<MetricsType>::IncrementResizes() { |
| if constexpr (has_num_resizes<MetricsType>::value) { |
| metrics_.num_resizes.Increment(); |
| } |
| } |
| |
| template <typename MetricsType> |
| void Metrics<MetricsType>::IncrementReallocations() { |
| if constexpr (has_num_reallocations<MetricsType>::value) { |
| metrics_.num_reallocations.Increment(); |
| } |
| } |
| |
| template <typename MetricsType> |
| void Metrics<MetricsType>::RecordFailure(size_t requested) { |
| if constexpr (has_num_failures<MetricsType>::value) { |
| metrics_.num_failures.Increment(); |
| } |
| if constexpr (has_unfulfilled_bytes<MetricsType>::value) { |
| metrics_.unfulfilled_bytes.Increment(internal::ClampU32(requested)); |
| } |
| } |
| |
| } // namespace internal |
| } // namespace pw::allocator |