[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} {}