Eric Holk | f1a2c0e | 2020-09-29 11:13:55 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2020 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #ifndef ART_LIBARTBASE_BASE_METRICS_H_ |
| 18 | #define ART_LIBARTBASE_BASE_METRICS_H_ |
| 19 | |
| 20 | #include <stdint.h> |
| 21 | |
| 22 | #include <array> |
Eric Holk | c4adf54 | 2020-10-02 13:46:28 -0700 | [diff] [blame] | 23 | #include <atomic> |
Eric Holk | f1a2c0e | 2020-09-29 11:13:55 -0700 | [diff] [blame] | 24 | #include <ostream> |
| 25 | #include <string_view> |
Eric Holk | d02435d | 2020-09-29 11:16:24 -0700 | [diff] [blame] | 26 | #include <vector> |
Eric Holk | f1a2c0e | 2020-09-29 11:13:55 -0700 | [diff] [blame] | 27 | |
Eric Holk | 1cd030f | 2020-09-30 11:42:34 -0700 | [diff] [blame^] | 28 | #include "android-base/logging.h" |
Eric Holk | f1a2c0e | 2020-09-29 11:13:55 -0700 | [diff] [blame] | 29 | #include "base/time_utils.h" |
| 30 | |
| 31 | #pragma clang diagnostic push |
| 32 | #pragma clang diagnostic error "-Wconversion" |
| 33 | |
| 34 | // COUNTER(counter_name) |
| 35 | #define ART_COUNTERS(COUNTER) COUNTER(ClassVerificationTotalTime) |
| 36 | // TODO: ClassVerificationTime serves as a mock for now. Implementation will come later. |
| 37 | |
Eric Holk | d02435d | 2020-09-29 11:16:24 -0700 | [diff] [blame] | 38 | // HISTOGRAM(counter_name, num_buckets, minimum_value, maximum_value) |
| 39 | // |
| 40 | // The num_buckets parameter affects memory usage for the histogram and data usage for exported |
| 41 | // metrics. It is recommended to keep this below 16. |
| 42 | // |
| 43 | // The minimum_value and maximum_value parameters are needed because we need to know what range the |
| 44 | // fixed number of buckets cover. We could keep track of the observed ranges and try to rescale the |
| 45 | // buckets or allocate new buckets, but this would make incrementing them more expensive than just |
| 46 | // some index arithmetic and an add. |
| 47 | // |
| 48 | // Values outside the range get clamped to the nearest bucket (basically, the two buckets on either |
| 49 | // side are infinitely long). If we see those buckets being way taller than the others, it means we |
| 50 | // should consider expanding the range. |
| 51 | #define ART_HISTOGRAMS(HISTOGRAM) HISTOGRAM(JitMethodCompileTime, 15, 0, 1'000'000) |
| 52 | // TODO: JitMethodCompileTime serves as a mock for now. Implementation will come later. |
| 53 | |
| 54 | // A lot of the metrics implementation code is generated by passing one-off macros into ART_COUNTERS |
| 55 | // and ART_HISTOGRAMS. This means metrics.h and metrics.cc are very #define-heavy, which can be |
| 56 | // challenging to read. The alternative was to require a lot of boilerplate code for each new metric |
| 57 | // added, all of which would need to be rewritten if the metrics implementation changed. Using |
| 58 | // macros lets us add new metrics by adding a single line to either ART_COUNTERS or ART_HISTOGRAMS, |
| 59 | // and modifying the implementation only requires changing the implementation once, instead of once |
| 60 | // per metric. |
| 61 | |
Eric Holk | f1a2c0e | 2020-09-29 11:13:55 -0700 | [diff] [blame] | 62 | namespace art { |
| 63 | namespace metrics { |
| 64 | |
| 65 | /** |
| 66 | * An enumeration of all ART counters and histograms. |
| 67 | */ |
| 68 | enum class DatumId { |
| 69 | #define ART_COUNTER(name) k##name, |
| 70 | ART_COUNTERS(ART_COUNTER) |
| 71 | #undef ART_COUNTER |
Eric Holk | d02435d | 2020-09-29 11:16:24 -0700 | [diff] [blame] | 72 | |
| 73 | #define ART_HISTOGRAM(name, num_buckets, low_value, high_value) k##name, |
| 74 | ART_HISTOGRAMS(ART_HISTOGRAM) |
| 75 | #undef ART_HISTOGRAM |
Eric Holk | f1a2c0e | 2020-09-29 11:13:55 -0700 | [diff] [blame] | 76 | }; |
| 77 | |
| 78 | struct SessionData { |
| 79 | const uint64_t session_id; |
| 80 | const std::string_view package_name; |
| 81 | // TODO: compiler filter / dexopt state |
| 82 | }; |
| 83 | |
| 84 | // MetricsBackends are used by a metrics reporter to write metrics to some external location. For |
| 85 | // example, a backend might write to logcat, or to a file, or to statsd. |
| 86 | class MetricsBackend { |
| 87 | public: |
| 88 | virtual ~MetricsBackend() {} |
| 89 | |
| 90 | protected: |
| 91 | // Begins an ART metrics session. |
| 92 | // |
| 93 | // This is called by the metrics reporter when the runtime is starting up. The session_data |
| 94 | // includes a session id which is used to correlate any metric reports with the same instance of |
| 95 | // the ART runtime. Additionally, session_data includes useful metadata such as the package name |
| 96 | // for this process. |
| 97 | virtual void BeginSession(const SessionData& session_data) = 0; |
| 98 | |
| 99 | // Marks the end of a metrics session. |
| 100 | // |
| 101 | // The metrics reporter will call this when metrics reported ends (e.g. when the runtime is |
| 102 | // shutting down). No further metrics will be reported for this session. Note that EndSession is |
| 103 | // not guaranteed to be called, since clean shutdowns for the runtime are quite rare in practice. |
| 104 | virtual void EndSession() = 0; |
| 105 | |
| 106 | // Called by the metrics reporter to give the current value of the counter with id counter_type. |
| 107 | // |
| 108 | // This will be called multiple times for each counter based on when the metrics reporter chooses |
| 109 | // to report metrics. For example, the metrics reporter may call this at shutdown or every N |
| 110 | // minutes. Counters are not reset in between invocations, so the value should represent the |
| 111 | // total count at the point this method is called. |
| 112 | virtual void ReportCounter(DatumId counter_type, uint64_t value) = 0; |
| 113 | |
Eric Holk | d02435d | 2020-09-29 11:16:24 -0700 | [diff] [blame] | 114 | // Called by the metrics reporter to report a histogram. |
| 115 | // |
| 116 | // This is called similarly to ReportCounter, but instead of receiving a single value, it receives |
| 117 | // a vector of the value in each bucket. Additionally, the function receives the lower and upper |
| 118 | // limit for the histogram. Note that these limits are the allowed limits, and not the observed |
| 119 | // range. Values below the lower limit will be counted in the first bucket, and values above the |
| 120 | // upper limit will be counted in the last bucket. Backends should store the minimum and maximum |
| 121 | // values to allow comparisons across module versions, since the minimum and maximum values may |
| 122 | // change over time. |
| 123 | virtual void ReportHistogram(DatumId histogram_type, |
| 124 | int64_t minimum_value, |
| 125 | int64_t maximum_value, |
| 126 | const std::vector<uint32_t>& buckets) = 0; |
| 127 | |
Eric Holk | f1a2c0e | 2020-09-29 11:13:55 -0700 | [diff] [blame] | 128 | friend class ArtMetrics; |
Eric Holk | d02435d | 2020-09-29 11:16:24 -0700 | [diff] [blame] | 129 | template <size_t num_buckets, int64_t low_value, int64_t high_value> |
| 130 | friend class MetricsHistogram; |
Eric Holk | f1a2c0e | 2020-09-29 11:13:55 -0700 | [diff] [blame] | 131 | }; |
| 132 | |
| 133 | class MetricsCounter { |
| 134 | public: |
Eric Holk | 1cd030f | 2020-09-30 11:42:34 -0700 | [diff] [blame^] | 135 | using value_t = uint64_t; |
| 136 | |
Eric Holk | d02435d | 2020-09-29 11:16:24 -0700 | [diff] [blame] | 137 | explicit constexpr MetricsCounter(uint64_t value = 0) : value_{value} { |
| 138 | // Ensure we do not have any unnecessary data in this class. |
| 139 | static_assert(sizeof(*this) == sizeof(uint64_t)); |
| 140 | } |
Eric Holk | f1a2c0e | 2020-09-29 11:13:55 -0700 | [diff] [blame] | 141 | |
Eric Holk | c4adf54 | 2020-10-02 13:46:28 -0700 | [diff] [blame] | 142 | void AddOne() { Add(1u); } |
Eric Holk | 1cd030f | 2020-09-30 11:42:34 -0700 | [diff] [blame^] | 143 | void Add(value_t value) { value_.fetch_add(value, std::memory_order::memory_order_relaxed); } |
Eric Holk | f1a2c0e | 2020-09-29 11:13:55 -0700 | [diff] [blame] | 144 | |
Eric Holk | 1cd030f | 2020-09-30 11:42:34 -0700 | [diff] [blame^] | 145 | value_t Value() const { return value_.load(std::memory_order::memory_order_relaxed); } |
Eric Holk | f1a2c0e | 2020-09-29 11:13:55 -0700 | [diff] [blame] | 146 | |
| 147 | private: |
Eric Holk | 1cd030f | 2020-09-30 11:42:34 -0700 | [diff] [blame^] | 148 | std::atomic<value_t> value_; |
| 149 | static_assert(std::atomic<value_t>::is_always_lock_free); |
Eric Holk | f1a2c0e | 2020-09-29 11:13:55 -0700 | [diff] [blame] | 150 | }; |
| 151 | |
Eric Holk | d02435d | 2020-09-29 11:16:24 -0700 | [diff] [blame] | 152 | template <size_t num_buckets_, int64_t minimum_value_, int64_t maximum_value_> |
| 153 | class MetricsHistogram { |
| 154 | static_assert(num_buckets_ >= 1); |
| 155 | static_assert(minimum_value_ < maximum_value_); |
| 156 | |
| 157 | public: |
Eric Holk | 1cd030f | 2020-09-30 11:42:34 -0700 | [diff] [blame^] | 158 | using value_t = int64_t; |
| 159 | |
Eric Holk | d02435d | 2020-09-29 11:16:24 -0700 | [diff] [blame] | 160 | constexpr MetricsHistogram() : buckets_{} { |
| 161 | // Ensure we do not have any unnecessary data in this class. |
| 162 | static_assert(sizeof(*this) == sizeof(uint32_t) * num_buckets_); |
| 163 | } |
| 164 | |
| 165 | void Add(int64_t value) { |
| 166 | const size_t i = FindBucketId(value); |
Eric Holk | c4adf54 | 2020-10-02 13:46:28 -0700 | [diff] [blame] | 167 | buckets_[i].fetch_add(1u, std::memory_order::memory_order_relaxed); |
Eric Holk | d02435d | 2020-09-29 11:16:24 -0700 | [diff] [blame] | 168 | } |
| 169 | |
| 170 | protected: |
| 171 | std::vector<uint32_t> GetBuckets() const { |
Eric Holk | c4adf54 | 2020-10-02 13:46:28 -0700 | [diff] [blame] | 172 | // The loads from buckets_ will all be memory_order_seq_cst, which means they will be acquire |
| 173 | // loads. This is a stricter memory order than is needed, but this should not be a |
| 174 | // performance-critical section of code. |
Eric Holk | d02435d | 2020-09-29 11:16:24 -0700 | [diff] [blame] | 175 | return std::vector<uint32_t>{buckets_.begin(), buckets_.end()}; |
| 176 | } |
| 177 | |
| 178 | private: |
| 179 | inline constexpr size_t FindBucketId(int64_t value) const { |
| 180 | // Values below the minimum are clamped into the first bucket. |
| 181 | if (value <= minimum_value_) { |
| 182 | return 0; |
| 183 | } |
| 184 | // Values above the maximum are clamped into the last bucket. |
| 185 | if (value >= maximum_value_) { |
| 186 | return num_buckets_ - 1; |
| 187 | } |
| 188 | // Otherise, linearly interpolate the value into the right bucket |
| 189 | constexpr size_t bucket_width = maximum_value_ - minimum_value_; |
| 190 | return static_cast<size_t>(value - minimum_value_) * num_buckets_ / bucket_width; |
| 191 | } |
| 192 | |
Eric Holk | c4adf54 | 2020-10-02 13:46:28 -0700 | [diff] [blame] | 193 | std::array<std::atomic<uint32_t>, num_buckets_> buckets_; |
Eric Holk | d02435d | 2020-09-29 11:16:24 -0700 | [diff] [blame] | 194 | |
| 195 | friend class ArtMetrics; |
Eric Holk | c4adf54 | 2020-10-02 13:46:28 -0700 | [diff] [blame] | 196 | static_assert(std::atomic<uint32_t>::is_always_lock_free); |
Eric Holk | d02435d | 2020-09-29 11:16:24 -0700 | [diff] [blame] | 197 | }; |
| 198 | |
Eric Holk | f1a2c0e | 2020-09-29 11:13:55 -0700 | [diff] [blame] | 199 | /** |
Eric Holk | 1cd030f | 2020-09-30 11:42:34 -0700 | [diff] [blame^] | 200 | * AutoTimer simplifies time-based metrics collection. |
| 201 | * |
| 202 | * Several modes are supported. In the default case, the timer starts immediately and stops when it |
| 203 | * goes out of scope. Example: |
| 204 | * |
| 205 | * { |
| 206 | * AutoTimer timer{metric}; |
| 207 | * DoStuff(); |
| 208 | * // timer stops and updates metric automatically here. |
| 209 | * } |
| 210 | * |
| 211 | * You can also stop the timer early: |
| 212 | * |
| 213 | * timer.Stop(); |
| 214 | * |
| 215 | * Finally, you can choose to not automatically start the timer at the beginning by passing false as |
| 216 | * the second argument to the constructor: |
| 217 | * |
| 218 | * AutoTimer timer{metric, false}; |
| 219 | * DoNotTimeThis(); |
| 220 | * timer.Start(); |
| 221 | * TimeThis(); |
| 222 | * |
| 223 | * Manually started timers will still automatically stop in the destructor, but they can be manually |
| 224 | * stopped as well. |
| 225 | * |
| 226 | * Note that AutoTimer makes calls to MicroTime(), so this may not be suitable on critical paths, or |
| 227 | * in cases where the counter needs to be started and stopped on different threads. |
| 228 | */ |
| 229 | template <typename Metric> |
| 230 | class AutoTimer { |
| 231 | public: |
| 232 | explicit AutoTimer(Metric* metric, bool autostart = true) |
| 233 | : running_{false}, start_time_microseconds_{}, metric_{metric} { |
| 234 | if (autostart) { |
| 235 | Start(); |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | ~AutoTimer() { |
| 240 | if (running_) { |
| 241 | Stop(); |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | void Start() { |
| 246 | DCHECK(!running_); |
| 247 | running_ = true; |
| 248 | start_time_microseconds_ = MicroTime(); |
| 249 | } |
| 250 | |
| 251 | void Stop() { |
| 252 | DCHECK(running_); |
| 253 | uint64_t stop_time_microseconds = MicroTime(); |
| 254 | running_ = false; |
| 255 | |
| 256 | metric_->Add( |
| 257 | static_cast<typename Metric::value_t>(stop_time_microseconds - start_time_microseconds_)); |
| 258 | } |
| 259 | |
| 260 | private: |
| 261 | bool running_; |
| 262 | uint64_t start_time_microseconds_; |
| 263 | Metric* metric_; |
| 264 | }; |
| 265 | |
| 266 | /** |
Eric Holk | f1a2c0e | 2020-09-29 11:13:55 -0700 | [diff] [blame] | 267 | * This struct contains all of the metrics that ART reports. |
| 268 | */ |
| 269 | class ArtMetrics { |
| 270 | public: |
| 271 | ArtMetrics(); |
| 272 | |
| 273 | void ReportAllMetrics(MetricsBackend* backend) const; |
| 274 | |
Eric Holk | d02435d | 2020-09-29 11:16:24 -0700 | [diff] [blame] | 275 | #define ART_COUNTER(name) \ |
| 276 | MetricsCounter* name() { return &name##_; } \ |
| 277 | const MetricsCounter* name() const { return &name##_; } |
Eric Holk | f1a2c0e | 2020-09-29 11:13:55 -0700 | [diff] [blame] | 278 | ART_COUNTERS(ART_COUNTER) |
| 279 | #undef ART_COUNTER |
| 280 | |
Eric Holk | d02435d | 2020-09-29 11:16:24 -0700 | [diff] [blame] | 281 | #define ART_HISTOGRAM(name, num_buckets, low_value, high_value) \ |
| 282 | MetricsHistogram<num_buckets, low_value, high_value>* name() { return &name##_; } \ |
| 283 | const MetricsHistogram<num_buckets, low_value, high_value>* name() const { return &name##_; } |
| 284 | ART_HISTOGRAMS(ART_HISTOGRAM) |
| 285 | #undef ART_HISTOGRAM |
| 286 | |
Eric Holk | f1a2c0e | 2020-09-29 11:13:55 -0700 | [diff] [blame] | 287 | private: |
| 288 | // This field is only included to allow us expand the ART_COUNTERS and ART_HISTOGRAMS macro in |
| 289 | // the initializer list in ArtMetrics::ArtMetrics. See metrics.cc for how it's used. |
| 290 | // |
| 291 | // It's declared as a zero-length array so it has no runtime space impact. |
| 292 | #pragma clang diagnostic push |
| 293 | #pragma clang diagnostic ignored "-Wunused-private-field" |
| 294 | int unused_[0]; |
| 295 | #pragma clang diagnostic pop // -Wunused-private-field |
Eric Holk | d02435d | 2020-09-29 11:16:24 -0700 | [diff] [blame] | 296 | |
| 297 | #define ART_COUNTER(name) MetricsCounter name##_; |
| 298 | ART_COUNTERS(ART_COUNTER) |
| 299 | #undef ART_COUNTER |
| 300 | |
| 301 | #define ART_HISTOGRAM(name, num_buckets, low_value, high_value) \ |
| 302 | MetricsHistogram<num_buckets, low_value, high_value> name##_; |
| 303 | ART_HISTOGRAMS(ART_HISTOGRAM) |
| 304 | #undef ART_HISTOGRAM |
Eric Holk | f1a2c0e | 2020-09-29 11:13:55 -0700 | [diff] [blame] | 305 | }; |
| 306 | |
| 307 | // Returns a human readable name for the given DatumId. |
| 308 | std::string DatumName(DatumId datum); |
| 309 | |
| 310 | } // namespace metrics |
| 311 | } // namespace art |
| 312 | |
| 313 | #pragma clang diagnostic pop // -Wconversion |
| 314 | |
| 315 | #endif // ART_LIBARTBASE_BASE_METRICS_H_ |