| /* |
| * 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 <optional> |
| #include <ostream> |
| #include <string_view> |
| #include <thread> |
| #include <vector> |
| |
| #include "android-base/logging.h" |
| #include "base/message_queue.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 { |
| |
| class Runtime; |
| struct RuntimeArgumentMap; |
| |
| 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); |
| |
| // Defines the set of options for how metrics reporting happens. |
| struct ReportingConfig { |
| static ReportingConfig FromRuntimeArguments(const RuntimeArgumentMap& args); |
| |
| // Causes metrics to be written to the log, which makes them show up in logcat. |
| bool dump_to_logcat{false}; |
| |
| // If set, provides a file name to enable metrics logging to a file. |
| std::optional<std::string> dump_to_file; |
| |
| // Indicates whether to report the final state of metrics on shutdown. |
| // |
| // Note that reporting only happens if some output, such as logcat, is enabled. |
| bool report_metrics_on_shutdown{true}; |
| |
| // If set, metrics will be reported every time this many seconds elapses. |
| std::optional<unsigned int> periodic_report_seconds; |
| |
| // Returns whether any options are set that enables metrics reporting. |
| constexpr bool ReportingEnabled() const { return dump_to_logcat || dump_to_file.has_value(); } |
| |
| // Returns whether any options are set that requires a background reporting thread. |
| constexpr bool BackgroundReportingEnabled() const { |
| return ReportingEnabled() && periodic_report_seconds.has_value(); |
| } |
| }; |
| |
| // 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, Runtime* runtime); |
| |
| ~MetricsReporter(); |
| |
| // Creates and runs the background reporting thread. |
| void MaybeStartBackgroundThread(); |
| |
| // Sends a request to the background thread to shutdown. |
| void MaybeStopBackgroundThread(); |
| |
| static constexpr const char* kBackgroundThreadName = "Metrics Background Reporting Thread"; |
| |
| private: |
| MetricsReporter(ReportingConfig config, Runtime* runtime); |
| |
| // The background reporting thread main loop. |
| void BackgroundThreadRun(); |
| |
| // Calls messages_.SetTimeout if needed. |
| void MaybeResetTimeout(); |
| |
| // Outputs the current state of the metrics to the destination set by config_. |
| void ReportMetrics() const; |
| |
| const ReportingConfig config_; |
| Runtime* runtime_; |
| |
| std::optional<std::thread> thread_; |
| |
| // A message indicating that the reporting thread should shut down. |
| struct ShutdownRequestedMessage {}; |
| |
| MessageQueue<ShutdownRequestedMessage> messages_; |
| }; |
| |
| |
| } // namespace metrics |
| } // namespace art |
| |
| #pragma clang diagnostic pop // -Wconversion |
| |
| #endif // ART_RUNTIME_METRICS_METRICS_H_ |