summaryrefslogtreecommitdiff
path: root/runtime/metrics/metrics.h
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/metrics/metrics.h')
-rw-r--r--runtime/metrics/metrics.h369
1 files changed, 369 insertions, 0 deletions
diff --git a/runtime/metrics/metrics.h b/runtime/metrics/metrics.h
new file mode 100644
index 0000000000..7156d576e0
--- /dev/null
+++ b/runtime/metrics/metrics.h
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+#ifndef ART_RUNTIME_METRICS_METRICS_H_
+#define ART_RUNTIME_METRICS_METRICS_H_
+
+#include <stdint.h>
+
+#include <array>
+#include <atomic>
+#include <ostream>
+#include <string_view>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "base/time_utils.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wconversion"
+
+// COUNTER(counter_name)
+#define ART_COUNTERS(COUNTER) COUNTER(ClassVerificationTotalTime)
+
+// 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)
+
+// 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 {
+
+/**
+ * An enumeration of all ART counters and histograms.
+ */
+enum class DatumId {
+#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 {
+ const uint64_t session_id;
+ const std::string_view package_name;
+ // TODO: compiler filter / dexopt state
+};
+
+// MetricsBackends are used by a metrics reporter to write metrics to some external location. For
+// example, a backend might write to logcat, or to a file, or to statsd.
+class MetricsBackend {
+ public:
+ virtual ~MetricsBackend() {}
+
+ protected:
+ // Begins an ART metrics session.
+ //
+ // This is called by the metrics reporter when the runtime is starting up. The session_data
+ // includes a session id which is used to correlate any metric reports with the same instance of
+ // the ART runtime. Additionally, session_data includes useful metadata such as the package name
+ // for this process.
+ virtual void BeginSession(const SessionData& session_data) = 0;
+
+ // Marks the end of a metrics session.
+ //
+ // The metrics reporter will call this when metrics reported ends (e.g. when the runtime is
+ // shutting down). No further metrics will be reported for this session. Note that EndSession is
+ // not guaranteed to be called, since clean shutdowns for the runtime are quite rare in practice.
+ virtual void EndSession() = 0;
+
+ // Called by the metrics reporter to give the current value of the counter with id counter_type.
+ //
+ // This will be called multiple times for each counter based on when the metrics reporter chooses
+ // to report metrics. For example, the metrics reporter may call this at shutdown or every N
+ // minutes. Counters are not reset in between invocations, so the value should represent the
+ // 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;
+
+ template <DatumId counter_type>
+ friend class MetricsCounter;
+ template <DatumId histogram_type, size_t num_buckets, int64_t low_value, int64_t high_value>
+ friend class MetricsHistogram;
+};
+
+template <DatumId counter_type>
+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(value_t value) { value_.fetch_add(value, std::memory_order::memory_order_relaxed); }
+
+ void Report(MetricsBackend* backend) const { backend->ReportCounter(counter_type, Value()); }
+
+ private:
+ value_t Value() const { return value_.load(std::memory_order::memory_order_relaxed); }
+
+ std::atomic<value_t> value_;
+ static_assert(std::atomic<value_t>::is_always_lock_free);
+};
+
+template <DatumId histogram_type_,
+ 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:
+ using value_t = uint32_t;
+
+ 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].fetch_add(1u, std::memory_order::memory_order_relaxed);
+ }
+
+ void Report(MetricsBackend* backend) const {
+ backend->ReportHistogram(histogram_type_, minimum_value_, maximum_value_, GetBuckets());
+ }
+
+ 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::vector<value_t> GetBuckets() const {
+ // The loads from buckets_ will all be memory_order_seq_cst, which means they will be acquire
+ // loads. This is a stricter memory order than is needed, but this should not be a
+ // performance-critical section of code.
+ return std::vector<value_t>{buckets_.begin(), buckets_.end()};
+ }
+
+ std::array<std::atomic<value_t>, num_buckets_> buckets_;
+ static_assert(std::atomic<value_t>::is_always_lock_free);
+};
+
+// A backend that writes metrics in a human-readable format to an std::ostream.
+class StreamBackend : public MetricsBackend {
+ public:
+ explicit StreamBackend(std::ostream& os);
+
+ void BeginSession(const SessionData& session_data) override;
+ void EndSession() override;
+
+ void ReportCounter(DatumId counter_type, uint64_t value) override;
+
+ void ReportHistogram(DatumId histogram_type,
+ int64_t low_value,
+ int64_t high_value,
+ const std::vector<uint32_t>& buckets) override;
+
+ private:
+ std::ostream& os_;
+};
+
+/**
+ * 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();
+ }
+
+ // Stops a running timer. Returns the time elapsed since starting the timer in microseconds.
+ uint64_t Stop() {
+ DCHECK(running_);
+ uint64_t stop_time_microseconds = MicroTime();
+ running_ = false;
+
+ uint64_t elapsed_time = stop_time_microseconds - start_time_microseconds_;
+ metric_->Add(static_cast<typename Metric::value_t>(elapsed_time));
+ return elapsed_time;
+ }
+
+ private:
+ bool running_;
+ uint64_t start_time_microseconds_;
+ Metric* metric_;
+};
+
+/**
+ * This struct contains all of the metrics that ART reports.
+ */
+class ArtMetrics {
+ public:
+ ArtMetrics();
+
+ void ReportAllMetrics(MetricsBackend* backend) const;
+ void DumpForSigQuit(std::ostream& os) const;
+
+#define ART_COUNTER(name) \
+ MetricsCounter<DatumId::k##name>* name() { return &name##_; } \
+ const MetricsCounter<DatumId::k##name>* name() const { return &name##_; }
+ ART_COUNTERS(ART_COUNTER)
+#undef ART_COUNTER
+
+#define ART_HISTOGRAM(name, num_buckets, low_value, high_value) \
+ MetricsHistogram<DatumId::k##name, num_buckets, low_value, high_value>* name() { \
+ return &name##_; \
+ } \
+ const MetricsHistogram<DatumId::k##name, 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.
+ //
+ // It's declared as a zero-length array so it has no runtime space impact.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-private-field"
+ int unused_[0];
+#pragma clang diagnostic pop // -Wunused-private-field
+
+#define ART_COUNTER(name) MetricsCounter<DatumId::k##name> name##_;
+ ART_COUNTERS(ART_COUNTER)
+#undef ART_COUNTER
+
+#define ART_HISTOGRAM(name, num_buckets, low_value, high_value) \
+ MetricsHistogram<DatumId::k##name, num_buckets, low_value, high_value> name##_;
+ ART_HISTOGRAMS(ART_HISTOGRAM)
+#undef ART_HISTOGRAM
+};
+
+// Returns a human readable name for the given DatumId.
+std::string DatumName(DatumId datum);
+
+struct ReportingConfig {
+ bool dump_to_logcat;
+ // TODO(eholk): this will grow to support other configurations, such as logging to a file, or
+ // statsd. There will also be options for reporting after a period of time, or at certain events.
+};
+
+// MetricsReporter handles periodically reporting ART metrics.
+class MetricsReporter {
+ public:
+ // Creates a MetricsReporter instance that matches the options selected in ReportingConfig.
+ static std::unique_ptr<MetricsReporter> Create(ReportingConfig config, const ArtMetrics* metrics);
+
+ ~MetricsReporter();
+
+ private:
+ explicit MetricsReporter(ReportingConfig config, const ArtMetrics* metrics);
+
+ ReportingConfig config_;
+ const ArtMetrics* metrics_;
+};
+
+
+} // namespace metrics
+} // namespace art
+
+#pragma clang diagnostic pop // -Wconversion
+
+#endif // ART_RUNTIME_METRICS_METRICS_H_