[metrics] Define Histogram
Adds a definition of the Histogram metric type. This CL also declares
the JitMethodCompileTime histogram, but collection for this metric is
not yet included.
The ArtMetrics class is also refactored so that metrics are not exposed
directly as fields, but instead through accessor functions.
Finally, this CL includes a test case for ArtMetrics::ReportAllMetrics
that demonstrates how the set of metrics are reported to a backend.
Bug: 170149255
Test: m test-art-host-gtest-art_libartbase_tests
Change-Id: I5041588609d9c386ce053c16713c9e9bb5210e3a
diff --git a/libartbase/base/metrics.h b/libartbase/base/metrics.h
index 735b366..d4d00b9 100644
--- a/libartbase/base/metrics.h
+++ b/libartbase/base/metrics.h
@@ -22,6 +22,7 @@
#include <array>
#include <ostream>
#include <string_view>
+#include <vector>
#include "base/time_utils.h"
@@ -32,6 +33,30 @@
#define ART_COUNTERS(COUNTER) COUNTER(ClassVerificationTotalTime)
// TODO: ClassVerificationTime serves as a mock for now. Implementation will come later.
+// HISTOGRAM(counter_name, num_buckets, minimum_value, maximum_value)
+//
+// 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 know what range the
+// fixed number of buckets cover. We could keep track of the observed ranges and try to rescale the
+// buckets or allocate new buckets, but this would make incrementing them more expensive than just
+// some index arithmetic and an add.
+//
+// Values outside the range get clamped to the nearest bucket (basically, the two buckets on either
+// side are infinitely long). If we see those buckets being way taller than the others, it means we
+// should consider expanding the range.
+#define ART_HISTOGRAMS(HISTOGRAM) HISTOGRAM(JitMethodCompileTime, 15, 0, 1'000'000)
+// TODO: JitMethodCompileTime serves as a mock for now. Implementation will come later.
+
+// A lot of the metrics implementation code is generated by passing one-off macros into ART_COUNTERS
+// and ART_HISTOGRAMS. This means metrics.h and metrics.cc are very #define-heavy, which can be
+// challenging to read. The alternative was to require a lot of boilerplate code for each new metric
+// added, all of which would need to be rewritten if the metrics implementation changed. Using
+// macros lets us add new metrics by adding a single line to either ART_COUNTERS or ART_HISTOGRAMS,
+// and modifying the implementation only requires changing the implementation once, instead of once
+// per metric.
+
namespace art {
namespace metrics {
@@ -42,6 +67,10 @@
#define ART_COUNTER(name) k##name,
ART_COUNTERS(ART_COUNTER)
#undef ART_COUNTER
+
+#define ART_HISTOGRAM(name, num_buckets, low_value, high_value) k##name,
+ ART_HISTOGRAMS(ART_HISTOGRAM)
+#undef ART_HISTOGRAM
};
struct SessionData {
@@ -80,12 +109,31 @@
// total count at the point this method is called.
virtual void ReportCounter(DatumId counter_type, uint64_t value) = 0;
+ // Called by the metrics reporter to report a histogram.
+ //
+ // This is called similarly to ReportCounter, but instead of receiving a single value, it receives
+ // a vector of the value in each bucket. Additionally, the function receives the lower and upper
+ // limit for the histogram. Note that these limits are the allowed limits, and not the observed
+ // range. Values below the lower limit will be counted in the first bucket, and values above the
+ // upper limit will be counted in the last bucket. Backends should store the minimum and maximum
+ // values to allow comparisons across module versions, since the minimum and maximum values may
+ // change over time.
+ virtual void ReportHistogram(DatumId histogram_type,
+ int64_t minimum_value,
+ int64_t maximum_value,
+ const std::vector<uint32_t>& buckets) = 0;
+
friend class ArtMetrics;
+ template <size_t num_buckets, int64_t low_value, int64_t high_value>
+ friend class MetricsHistogram;
};
class MetricsCounter {
public:
- explicit constexpr MetricsCounter(uint64_t value = 0) : value_{value} {}
+ 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() { value_++; }
void Add(uint64_t value) { value_ += value; }
@@ -96,6 +144,47 @@
uint64_t value_;
};
+template <size_t num_buckets_, int64_t minimum_value_, int64_t maximum_value_>
+class MetricsHistogram {
+ static_assert(num_buckets_ >= 1);
+ static_assert(minimum_value_ < maximum_value_);
+
+ public:
+ constexpr MetricsHistogram() : buckets_{} {
+ // Ensure we do not have any unnecessary data in this class.
+ static_assert(sizeof(*this) == sizeof(uint32_t) * num_buckets_);
+ }
+
+ void Add(int64_t value) {
+ const size_t i = FindBucketId(value);
+ buckets_[i]++;
+ }
+
+ protected:
+ std::vector<uint32_t> GetBuckets() const {
+ return std::vector<uint32_t>{buckets_.begin(), buckets_.end()};
+ }
+
+ private:
+ inline constexpr size_t FindBucketId(int64_t value) const {
+ // Values below the minimum are clamped into the first bucket.
+ if (value <= minimum_value_) {
+ return 0;
+ }
+ // Values above the maximum are clamped into the last bucket.
+ if (value >= maximum_value_) {
+ return num_buckets_ - 1;
+ }
+ // Otherise, linearly interpolate the value into the right bucket
+ constexpr size_t bucket_width = maximum_value_ - minimum_value_;
+ return static_cast<size_t>(value - minimum_value_) * num_buckets_ / bucket_width;
+ }
+
+ std::array<uint32_t, num_buckets_> buckets_;
+
+ friend class ArtMetrics;
+};
+
/**
* This struct contains all of the metrics that ART reports.
*/
@@ -105,10 +194,18 @@
void ReportAllMetrics(MetricsBackend* backend) const;
-#define ART_COUNTER(name) MetricsCounter name;
+#define ART_COUNTER(name) \
+ MetricsCounter* name() { return &name##_; } \
+ const MetricsCounter* name() const { return &name##_; }
ART_COUNTERS(ART_COUNTER)
#undef ART_COUNTER
+#define ART_HISTOGRAM(name, num_buckets, low_value, high_value) \
+ MetricsHistogram<num_buckets, low_value, high_value>* name() { return &name##_; } \
+ const MetricsHistogram<num_buckets, low_value, high_value>* name() const { return &name##_; }
+ ART_HISTOGRAMS(ART_HISTOGRAM)
+#undef ART_HISTOGRAM
+
private:
// This field is only included to allow us expand the ART_COUNTERS and ART_HISTOGRAMS macro in
// the initializer list in ArtMetrics::ArtMetrics. See metrics.cc for how it's used.
@@ -118,6 +215,15 @@
#pragma clang diagnostic ignored "-Wunused-private-field"
int unused_[0];
#pragma clang diagnostic pop // -Wunused-private-field
+
+#define ART_COUNTER(name) MetricsCounter name##_;
+ ART_COUNTERS(ART_COUNTER)
+#undef ART_COUNTER
+
+#define ART_HISTOGRAM(name, num_buckets, low_value, high_value) \
+ MetricsHistogram<num_buckets, low_value, high_value> name##_;
+ ART_HISTOGRAMS(ART_HISTOGRAM)
+#undef ART_HISTOGRAM
};
// Returns a human readable name for the given DatumId.