[metrics] Add timer conveniences

This adds convenience timers for each of the metrics. The timer's can be
backed by a Counter, in which case the Counter will store cumulative
time elapsed, or by a Histogram, in which case the timer will increment
a bucket based on the time between a particular start/stop pair.

Timers can either be manually started and stopped, or can be used
automatically in an RAII manner.

Bug: 170149255
Test: m test-art-host-gtest-art_libartbase_tests
Change-Id: I4fc77e35f3e7b67c12fc004b72bf4fe177dc3151
diff --git a/libartbase/base/metrics.h b/libartbase/base/metrics.h
index 51ea66f..d403834 100644
--- a/libartbase/base/metrics.h
+++ b/libartbase/base/metrics.h
@@ -25,6 +25,7 @@
 #include <string_view>
 #include <vector>
 
+#include "android-base/logging.h"
 #include "base/time_utils.h"
 
 #pragma clang diagnostic push
@@ -131,19 +132,21 @@
 
 class MetricsCounter {
  public:
+  using value_t = uint64_t;
+
   explicit constexpr MetricsCounter(uint64_t value = 0) : value_{value} {
     // Ensure we do not have any unnecessary data in this class.
     static_assert(sizeof(*this) == sizeof(uint64_t));
   }
 
   void AddOne() { Add(1u); }
-  void Add(uint64_t value) { value_.fetch_add(value, std::memory_order::memory_order_relaxed); }
+  void Add(value_t value) { value_.fetch_add(value, std::memory_order::memory_order_relaxed); }
 
-  uint64_t Value() const { return value_.load(std::memory_order::memory_order_relaxed); }
+  value_t Value() const { return value_.load(std::memory_order::memory_order_relaxed); }
 
  private:
-  std::atomic<uint64_t> value_;
-  static_assert(std::atomic<uint64_t>::is_always_lock_free);
+  std::atomic<value_t> value_;
+  static_assert(std::atomic<value_t>::is_always_lock_free);
 };
 
 template <size_t num_buckets_, int64_t minimum_value_, int64_t maximum_value_>
@@ -152,6 +155,8 @@
   static_assert(minimum_value_ < maximum_value_);
 
  public:
+  using value_t = int64_t;
+
   constexpr MetricsHistogram() : buckets_{} {
     // Ensure we do not have any unnecessary data in this class.
     static_assert(sizeof(*this) == sizeof(uint32_t) * num_buckets_);
@@ -192,6 +197,73 @@
 };
 
 /**
+ * AutoTimer simplifies time-based metrics collection.
+ *
+ * Several modes are supported. In the default case, the timer starts immediately and stops when it
+ * goes out of scope. Example:
+ *
+ *     {
+ *       AutoTimer timer{metric};
+ *       DoStuff();
+ *       // timer stops and updates metric automatically here.
+ *     }
+ *
+ * You can also stop the timer early:
+ *
+ *     timer.Stop();
+ *
+ * Finally, you can choose to not automatically start the timer at the beginning by passing false as
+ * the second argument to the constructor:
+ *
+ *     AutoTimer timer{metric, false};
+ *     DoNotTimeThis();
+ *     timer.Start();
+ *     TimeThis();
+ *
+ * Manually started timers will still automatically stop in the destructor, but they can be manually
+ * stopped as well.
+ *
+ * Note that AutoTimer makes calls to MicroTime(), so this may not be suitable on critical paths, or
+ * in cases where the counter needs to be started and stopped on different threads.
+ */
+template <typename Metric>
+class AutoTimer {
+ public:
+  explicit AutoTimer(Metric* metric, bool autostart = true)
+      : running_{false}, start_time_microseconds_{}, metric_{metric} {
+    if (autostart) {
+      Start();
+    }
+  }
+
+  ~AutoTimer() {
+    if (running_) {
+      Stop();
+    }
+  }
+
+  void Start() {
+    DCHECK(!running_);
+    running_ = true;
+    start_time_microseconds_ = MicroTime();
+  }
+
+  void Stop() {
+    DCHECK(running_);
+    uint64_t stop_time_microseconds = MicroTime();
+    running_ = false;
+
+    metric_->Add(
+        static_cast<typename Metric::value_t>(stop_time_microseconds - start_time_microseconds_));
+  }
+
+ private:
+  bool running_;
+  uint64_t start_time_microseconds_;
+  Metric* metric_;
+};
+
+/**
  * This struct contains all of the metrics that ART reports.
  */
 class ArtMetrics {
diff --git a/libartbase/base/metrics_test.cc b/libartbase/base/metrics_test.cc
index 54306d6..67ad313 100644
--- a/libartbase/base/metrics_test.cc
+++ b/libartbase/base/metrics_test.cc
@@ -67,6 +67,53 @@
   EXPECT_EQ(6u, test_counter.Value());
 }
 
+TEST_F(MetricsTest, CounterTimer) {
+  MetricsCounter test_counter;
+  {
+    AutoTimer timer{&test_counter};
+    // Sleep for 2µs so the counter will be greater than 0.
+    NanoSleep(2'000);
+  }
+  EXPECT_GT(test_counter.Value(), 0u);
+}
+
+TEST_F(MetricsTest, CounterTimerExplicitStop) {
+  MetricsCounter test_counter;
+  AutoTimer timer{&test_counter};
+  // Sleep for 2µs so the counter will be greater than 0.
+  NanoSleep(2'000);
+  timer.Stop();
+  EXPECT_GT(test_counter.Value(), 0u);
+}
+
+TEST_F(MetricsTest, CounterTimerExplicitStart) {
+  MetricsCounter test_counter;
+  {
+    AutoTimer timer{&test_counter, /*autostart=*/false};
+    // Sleep for 2µs so the counter will be greater than 0.
+    NanoSleep(2'000);
+  }
+  EXPECT_EQ(test_counter.Value(), 0u);
+
+  {
+    AutoTimer timer{&test_counter, /*autostart=*/false};
+    timer.Start();
+    // Sleep for 2µs so the counter will be greater than 0.
+    NanoSleep(2'000);
+  }
+  EXPECT_GT(test_counter.Value(), 0u);
+}
+
+TEST_F(MetricsTest, CounterTimerExplicitStartStop) {
+  MetricsCounter test_counter;
+  AutoTimer timer{&test_counter, /*autostart=*/false};
+  // Sleep for 2µs so the counter will be greater than 0.
+  timer.Start();
+  NanoSleep(2'000);
+  timer.Stop();
+  EXPECT_GT(test_counter.Value(), 0u);
+}
+
 TEST_F(MetricsTest, DatumName) {
   EXPECT_EQ("ClassVerificationTotalTime", DatumName(DatumId::kClassVerificationTotalTime));
 }
@@ -171,6 +218,17 @@
   metrics.ReportAllMetrics(&backend);
 }
 
+TEST_F(MetricsTest, HistogramTimer) {
+  TestMetricsHistogram<1, 0, 100> test_histogram;
+  {
+    AutoTimer timer{&test_histogram};
+    // Sleep for 2µs so the counter will be greater than 0.
+    NanoSleep(2'000);
+  }
+
+  EXPECT_GT(test_histogram.GetBucketsForTest()[0], 0u);
+}
+
 }  // namespace metrics
 }  // namespace art