[metrics] Add MetricsAccumulator
MetricsAccumulator metrics are a generalization of counters that allow
a custom function to be supplied that specifies how to combine
incoming values. This allows for the definition of things like minimum
and maximum metrics.
Test: libartbase_gtests
Bug: 170149255
Change-Id: Ibc928a5d0e5cad3036e5829be9a535bc4a685ae1
diff --git a/libartbase/base/metrics/README.md b/libartbase/base/metrics/README.md
index 93c13b7..ade5852 100644
--- a/libartbase/base/metrics/README.md
+++ b/libartbase/base/metrics/README.md
@@ -13,10 +13,33 @@
METRIC(MyCounter, MetricsCounter)
+Counters store a single value that can be added to. This is useful for counting
+events, counting the total amount of time spent in a section of code, and other
+uses.
+
+### Accumulators
+
+ METRIC(MyAccumulator, MetricsAccumulator, type, accumulator_function)
+
+Example:
+
+ METRIC(MaximumTestMetric, MetricsAccumulator, int64_t, std::max<int64_t>)
+
+Accumulators are a generalization of counters that takes an accumulator
+function that is used to combine the new value with the old value. Common
+choices are the min and max function. To be valid, the accumulator function
+must be monotonic in its first argument. That is, if
+`x_new == accumulator_function(x_old, y)` then `x_new ⪯ x_old` for some
+ordering relation `⪯` (e.g. less-than-or-equal or greater-than-or-equal).
+
### Histograms
METRIC(MyHistogram, MetricsHistogram, num_buckets, minimum_value, maximum_value)
+Histograms divide a range into several buckets and count how many times a value
+falls within each bucket. They are useful for seeing the overall distribution
+for different events.
+
The `num_buckets` parameter affects memory usage for the histogram and data
usage for exported metrics. It is recommended to keep this below 16. The
`minimum_value` and `maximum_value` parameters are needed because we need to
diff --git a/libartbase/base/metrics/metrics.h b/libartbase/base/metrics/metrics.h
index e61e982..acfd20c 100644
--- a/libartbase/base/metrics/metrics.h
+++ b/libartbase/base/metrics/metrics.h
@@ -177,6 +177,8 @@
friend class MetricsCounter;
template <DatumId histogram_type, size_t num_buckets, int64_t low_value, int64_t high_value>
friend class MetricsHistogram;
+ template <DatumId datum_id, typename T, const T& AccumulatorFunction(const T&, const T&)>
+ friend class MetricsAccumulator;
friend class ArtMetrics;
};
@@ -281,6 +283,48 @@
friend class ArtMetrics;
};
+template <DatumId datum_id, typename T, const T& AccumulatorFunction(const T&, const T&)>
+class MetricsAccumulator final : MetricsBase<T> {
+ public:
+ explicit constexpr MetricsAccumulator(T value = 0) : value_{value} {
+ // Ensure we do not have any unnecessary data in this class.
+ // Adding intptr_t to accommodate vtable, and rounding up to incorporate
+ // padding.
+ static_assert(RoundUp(sizeof(*this), sizeof(uint64_t)) ==
+ RoundUp(sizeof(intptr_t) + sizeof(T), sizeof(uint64_t)));
+ }
+
+ void Add(T value) {
+ T current = value_.load(std::memory_order::memory_order_relaxed);
+ T new_value;
+ do {
+ new_value = AccumulatorFunction(current, value);
+ // If the value didn't change, don't bother storing it.
+ if (current == new_value) {
+ break;
+ }
+ } while (!value_.compare_exchange_weak(
+ current, new_value, std::memory_order::memory_order_relaxed));
+ }
+
+ // Report the metric as a counter, since this has only a single value.
+ void Report(MetricsBackend* backend) const {
+ backend->ReportCounter(datum_id, static_cast<uint64_t>(Value()));
+ }
+
+ protected:
+ void Reset() {
+ value_ = 0;
+ }
+
+ private:
+ T Value() const { return value_.load(std::memory_order::memory_order_relaxed); }
+
+ std::atomic<T> value_;
+
+ friend class ArtMetrics;
+};
+
// A backend that writes metrics in a human-readable format to a string.
//
// This is used as a base for LogBackend and FileBackend.
diff --git a/libartbase/base/metrics/metrics_test.cc b/libartbase/base/metrics/metrics_test.cc
index 7b26e43..eae9367 100644
--- a/libartbase/base/metrics/metrics_test.cc
+++ b/libartbase/base/metrics/metrics_test.cc
@@ -90,6 +90,26 @@
EXPECT_GT(CounterValue(test_counter), 0u);
}
+TEST_F(MetricsTest, AccumulatorMetric) {
+ MetricsAccumulator<DatumId::kClassLoadingTotalTime, uint64_t, std::max> accumulator;
+
+ std::vector<std::thread> threads;
+
+ constexpr uint64_t kMaxValue = 100;
+
+ for (uint64_t i = 0; i <= kMaxValue; i++) {
+ threads.emplace_back(std::thread{[&accumulator, i]() {
+ accumulator.Add(i);
+ }});
+ }
+
+ for (auto& thread : threads) {
+ thread.join();
+ }
+
+ EXPECT_EQ(CounterValue(accumulator), kMaxValue);
+}
+
TEST_F(MetricsTest, DatumName) {
EXPECT_EQ("ClassVerificationTotalTime", DatumName(DatumId::kClassVerificationTotalTime));
}
diff --git a/libartbase/base/metrics/metrics_test.h b/libartbase/base/metrics/metrics_test.h
index 4a7dad8..48fd517 100644
--- a/libartbase/base/metrics/metrics_test.h
+++ b/libartbase/base/metrics/metrics_test.h
@@ -48,8 +48,8 @@
void EndReport() override {}
};
-template <DatumId counter_type>
-uint64_t CounterValue(const MetricsCounter<counter_type>& counter) {
+template <typename MetricType>
+uint64_t CounterValue(const MetricType& counter) {
uint64_t counter_value{0};
struct CounterBackend : public TestBackendBase {
explicit CounterBackend(uint64_t* counter_value) : counter_value_{counter_value} {}