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_METRICS_H_
18 #define ART_LIBARTBASE_BASE_METRICS_METRICS_H_
19
20 #include <stdint.h>
21
22 #include <array>
23 #include <atomic>
24 #include <optional>
25 #include <sstream>
26 #include <string_view>
27 #include <thread>
28 #include <vector>
29
30 #include "android-base/logging.h"
31 #include "base/bit_utils.h"
32 #include "base/time_utils.h"
33
34 #pragma clang diagnostic push
35 #pragma clang diagnostic error "-Wconversion"
36
37 // See README.md in this directory for how to define metrics.
38 #define ART_METRICS(METRIC) \
39 METRIC(ClassLoadingTotalTime, MetricsCounter) \
40 METRIC(ClassVerificationTotalTime, MetricsCounter) \
41 METRIC(ClassVerificationCount, MetricsCounter) \
42 METRIC(WorldStopTimeDuringGCAvg, MetricsAverage) \
43 METRIC(YoungGcCount, MetricsCounter) \
44 METRIC(FullGcCount, MetricsCounter) \
45 METRIC(TotalBytesAllocated, MetricsCounter) \
46 METRIC(TotalGcCollectionTime, MetricsCounter) \
47 METRIC(YoungGcThroughputAvg, MetricsAverage) \
48 METRIC(FullGcThroughputAvg, MetricsAverage) \
49 METRIC(YoungGcTracingThroughputAvg, MetricsAverage) \
50 METRIC(FullGcTracingThroughputAvg, MetricsAverage) \
51 METRIC(JitMethodCompileTotalTime, MetricsCounter) \
52 METRIC(JitMethodCompileCount, MetricsCounter) \
53 METRIC(YoungGcCollectionTime, MetricsHistogram, 15, 0, 60'000) \
54 METRIC(FullGcCollectionTime, MetricsHistogram, 15, 0, 60'000) \
55 METRIC(YoungGcThroughput, MetricsHistogram, 15, 0, 10'000) \
56 METRIC(FullGcThroughput, MetricsHistogram, 15, 0, 10'000) \
57 METRIC(YoungGcTracingThroughput, MetricsHistogram, 15, 0, 10'000) \
58 METRIC(FullGcTracingThroughput, MetricsHistogram, 15, 0, 10'000)
59
60 // A lot of the metrics implementation code is generated by passing one-off macros into ART_COUNTERS
61 // and ART_HISTOGRAMS. This means metrics.h and metrics.cc are very #define-heavy, which can be
62 // challenging to read. The alternative was to require a lot of boilerplate code for each new metric
63 // added, all of which would need to be rewritten if the metrics implementation changed. Using
64 // macros lets us add new metrics by adding a single line to either ART_COUNTERS or ART_HISTOGRAMS,
65 // and modifying the implementation only requires changing the implementation once, instead of once
66 // per metric.
67
68 namespace art {
69
70 class Runtime;
71 struct RuntimeArgumentMap;
72
73 namespace metrics {
74
75 /**
76 * An enumeration of all ART counters and histograms.
77 */
78 enum class DatumId {
79 #define METRIC(name, type, ...) k##name,
80 ART_METRICS(METRIC)
81 #undef METRIC
82 };
83
84 // Names come from PackageManagerServiceCompilerMapping.java
85 #define REASON_NAME_LIST(V) \
86 V(kError, "error") \
87 V(kUnknown, "unknown") \
88 V(kFirstBoot, "first-boot") \
89 V(kBootAfterOTA, "boot-after-ota") \
90 V(kPostBoot, "post-boot") \
91 V(kInstall, "install") \
92 V(kInstallFast, "install-fast") \
93 V(kInstallBulk, "install-bulk") \
94 V(kInstallBulkSecondary, "install-bulk-secondary") \
95 V(kInstallBulkDowngraded, "install-bulk-downgraded") \
96 V(kInstallBulkSecondaryDowngraded, "install-bulk-secondary-downgraded") \
97 V(kBgDexopt, "bg-dexopt") \
98 V(kABOTA, "ab-ota") \
99 V(kInactive, "inactive") \
100 V(kShared, "shared") \
101 V(kInstallWithDexMetadata, "install-with-dex-metadata") \
102 V(kPrebuilt, "prebuilt") \
103 V(kCmdLine, "cmdline")
104
105 // We log compilation reasons as part of the metadata we report. Since elsewhere compilation reasons
106 // are specified as a string, we define them as an enum here which indicates the reasons that we
107 // support.
108 enum class CompilationReason {
109 #define REASON(kind, name) kind,
110 REASON_NAME_LIST(REASON)
111 #undef REASON
112 };
113
114 #define REASON_NAME(kind, kind_name) \
115 case CompilationReason::kind: return kind_name;
116 #define REASON_FROM_NAME(kind, kind_name) \
117 if (name == kind_name) { return CompilationReason::kind; }
118
CompilationReasonName(CompilationReason reason)119 constexpr const char* CompilationReasonName(CompilationReason reason) {
120 switch (reason) {
121 REASON_NAME_LIST(REASON_NAME)
122 }
123 }
124
CompilationReasonFromName(std::string_view name)125 constexpr CompilationReason CompilationReasonFromName(std::string_view name) {
126 REASON_NAME_LIST(REASON_FROM_NAME)
127 return CompilationReason::kError;
128 }
129
130 #undef REASON_NAME
131 #undef ReasonFromName
132
133 #define COMPILER_FILTER_REPORTING_LIST(V) \
134 V(kError, "error") /* Error (invalid value) condition */ \
135 V(kUnknown, "unknown") /* Unknown (not set) condition */ \
136 V(kAssumeVerified, "assume-verified") /* Standard compiler filters */ \
137 V(kExtract, "extract") \
138 V(kVerify, "verify") \
139 V(kSpaceProfile, "space-profile") \
140 V(kSpace, "space") \
141 V(kSpeedProfile, "speed-profile") \
142 V(kSpeed, "speed") \
143 V(kEverythingProfile, "everything-profile") \
144 V(kEverything, "everything") \
145 V(kRunFromApk, "run-from-apk") /* Augmented compiler filters as produces by OatFileAssistant#GetOptimizationStatus */ \
146 V(kRunFromApkFallback, "run-from-apk-fallback")
147
148 // Augmented compiler filter enum, used in the reporting infra.
149 enum class CompilerFilterReporting {
150 #define FILTER(kind, name) kind,
151 COMPILER_FILTER_REPORTING_LIST(FILTER)
152 #undef FILTER
153 };
154
155 #define FILTER_NAME(kind, kind_name) \
156 case CompilerFilterReporting::kind: return kind_name;
157 #define FILTER_FROM_NAME(kind, kind_name) \
158 if (name == kind_name) { return CompilerFilterReporting::kind; }
159
CompilerFilterReportingName(CompilerFilterReporting filter)160 constexpr const char* CompilerFilterReportingName(CompilerFilterReporting filter) {
161 switch (filter) {
162 COMPILER_FILTER_REPORTING_LIST(FILTER_NAME)
163 }
164 }
165
CompilerFilterReportingFromName(std::string_view name)166 constexpr CompilerFilterReporting CompilerFilterReportingFromName(std::string_view name) {
167 COMPILER_FILTER_REPORTING_LIST(FILTER_FROM_NAME)
168 return CompilerFilterReporting::kError;
169 }
170
171 #undef FILTER_NAME
172 #undef FILTER_FROM_NAME
173
174 // SessionData contains metadata about a metrics session (basically the lifetime of an ART process).
175 // This information should not change for the lifetime of the session.
176 struct SessionData {
177 static SessionData CreateDefault();
178
179 static constexpr int64_t kInvalidSessionId = -1;
180 static constexpr int32_t kInvalidUserId = -1;
181
182 int64_t session_id;
183 int32_t uid;
184 CompilationReason compilation_reason;
185 CompilerFilterReporting compiler_filter;
186 };
187
188 // MetricsBackends are used by a metrics reporter to write metrics to some external location. For
189 // example, a backend might write to logcat, or to a file, or to statsd.
190 class MetricsBackend {
191 public:
~MetricsBackend()192 virtual ~MetricsBackend() {}
193
194 // Begins an ART metrics session.
195 //
196 // This is called by the metrics reporter when the runtime is starting up. The session_data
197 // includes a session id which is used to correlate any metric reports with the same instance of
198 // the ART runtime. Additionally, session_data includes useful metadata such as the package name
199 // for this process.
200 //
201 // It may also be called whenever there is an update to the session metadata (e.g. optimization
202 // state).
203 virtual void BeginOrUpdateSession(const SessionData& session_data) = 0;
204
205 protected:
206 // Called by the metrics reporter to indicate that a new metrics report is starting.
207 virtual void BeginReport(uint64_t timestamp_since_start_ms) = 0;
208
209 // Called by the metrics reporter to give the current value of the counter with id counter_type.
210 //
211 // This will be called multiple times for each counter based on when the metrics reporter chooses
212 // to report metrics. For example, the metrics reporter may call this at shutdown or every N
213 // minutes. Counters are not reset in between invocations, so the value should represent the
214 // total count at the point this method is called.
215 virtual void ReportCounter(DatumId counter_type, uint64_t value) = 0;
216
217 // Called by the metrics reporter to report a histogram.
218 //
219 // This is called similarly to ReportCounter, but instead of receiving a single value, it receives
220 // a vector of the value in each bucket. Additionally, the function receives the lower and upper
221 // limit for the histogram. Note that these limits are the allowed limits, and not the observed
222 // range. Values below the lower limit will be counted in the first bucket, and values above the
223 // upper limit will be counted in the last bucket. Backends should store the minimum and maximum
224 // values to allow comparisons across module versions, since the minimum and maximum values may
225 // change over time.
226 virtual void ReportHistogram(DatumId histogram_type,
227 int64_t minimum_value,
228 int64_t maximum_value,
229 const std::vector<uint32_t>& buckets) = 0;
230
231 // Called by the metrics reporter to indicate that the current metrics report is complete.
232 virtual void EndReport() = 0;
233
234 template <DatumId counter_type, typename T>
235 friend class MetricsCounter;
236 template <DatumId histogram_type, size_t num_buckets, int64_t low_value, int64_t high_value>
237 friend class MetricsHistogram;
238 template <DatumId datum_id, typename T, const T& AccumulatorFunction(const T&, const T&)>
239 friend class MetricsAccumulator;
240 template <DatumId datum_id, typename T>
241 friend class MetricsAverage;
242 friend class ArtMetrics;
243 };
244
245 template <typename value_t>
246 class MetricsBase {
247 public:
248 virtual void Add(value_t value) = 0;
~MetricsBase()249 virtual ~MetricsBase() { }
250 };
251
252 template <DatumId counter_type, typename T = uint64_t>
253 class MetricsCounter : public MetricsBase<T> {
254 public:
255 using value_t = T;
256 explicit constexpr MetricsCounter(uint64_t value = 0) : value_{value} {
257 // Ensure we do not have any unnecessary data in this class.
258 // Adding intptr_t to accommodate vtable, and rounding up to incorporate
259 // padding.
260 static_assert(RoundUp(sizeof(*this), sizeof(uint64_t))
261 == RoundUp(sizeof(intptr_t) + sizeof(value_t), sizeof(uint64_t)));
262 }
263
AddOne()264 void AddOne() { Add(1u); }
Add(value_t value)265 void Add(value_t value) { value_.fetch_add(value, std::memory_order::memory_order_relaxed); }
266
Report(MetricsBackend * backend)267 void Report(MetricsBackend* backend) const { backend->ReportCounter(counter_type, Value()); }
268
269 protected:
Reset()270 void Reset() {
271 value_ = 0;
272 }
273
Value()274 value_t Value() const { return value_.load(std::memory_order::memory_order_relaxed); }
275
276 private:
277 std::atomic<value_t> value_;
278 static_assert(std::atomic<value_t>::is_always_lock_free);
279
280 friend class ArtMetrics;
281 };
282
283 template <DatumId datum_id, typename T = uint64_t>
284 class MetricsAverage final : public MetricsCounter<datum_id, T> {
285 public:
286 using value_t = T;
287 using count_t = T;
288 explicit constexpr MetricsAverage(uint64_t value = 0, uint64_t count = 0) :
289 MetricsCounter<datum_id, value_t>(value), count_(count) {
290 // Ensure we do not have any unnecessary data in this class.
291 // Adding intptr_t to accommodate vtable, and rounding up to incorporate
292 // padding.
293 static_assert(RoundUp(sizeof(*this), sizeof(uint64_t))
294 == RoundUp(sizeof(intptr_t) + sizeof(value_t) + sizeof(count_t),
295 sizeof(uint64_t)));
296 }
297
298 // We use release memory-order here and then acquire in Report() to ensure
299 // that at least the non-racy reads/writes to this metric are consistent. This
300 // doesn't guarantee the atomicity of the change to both fields, but that
301 // may not be desired because:
302 // 1. The metric eventually becomes consistent.
303 // 2. For sufficiently large count_, a few data points which are off shouldn't
304 // make a huge difference to the reporter.
Add(value_t value)305 void Add(value_t value) {
306 MetricsCounter<datum_id, value_t>::Add(value);
307 count_.fetch_add(1, std::memory_order::memory_order_release);
308 }
309
Report(MetricsBackend * backend)310 void Report(MetricsBackend* backend) const {
311 count_t count = count_.load(std::memory_order::memory_order_acquire);
312 backend->ReportCounter(datum_id,
313 // Avoid divide-by-0.
314 count != 0 ? MetricsCounter<datum_id, value_t>::Value() / count : 0);
315 }
316
317 protected:
Reset()318 void Reset() {
319 count_ = 0;
320 MetricsCounter<datum_id, value_t>::Reset();
321 }
322
323 private:
324 std::atomic<count_t> count_;
325 static_assert(std::atomic<count_t>::is_always_lock_free);
326
327 friend class ArtMetrics;
328 };
329
330 template <DatumId histogram_type_,
331 size_t num_buckets_,
332 int64_t minimum_value_,
333 int64_t maximum_value_>
334 class MetricsHistogram final : public MetricsBase<int64_t> {
335 static_assert(num_buckets_ >= 1);
336 static_assert(minimum_value_ < maximum_value_);
337
338 public:
339 using value_t = uint32_t;
340
MetricsHistogram()341 constexpr MetricsHistogram() : buckets_{} {
342 // Ensure we do not have any unnecessary data in this class.
343 // Adding intptr_t to accommodate vtable, and rounding up to incorporate
344 // padding.
345 static_assert(RoundUp(sizeof(*this), sizeof(uint64_t))
346 == RoundUp(sizeof(intptr_t) + sizeof(value_t) * num_buckets_, sizeof(uint64_t)));
347 }
348
Add(int64_t value)349 void Add(int64_t value) {
350 const size_t i = FindBucketId(value);
351 buckets_[i].fetch_add(1u, std::memory_order::memory_order_relaxed);
352 }
353
Report(MetricsBackend * backend)354 void Report(MetricsBackend* backend) const {
355 backend->ReportHistogram(histogram_type_, minimum_value_, maximum_value_, GetBuckets());
356 }
357
358 protected:
Reset()359 void Reset() {
360 for (auto& bucket : buckets_) {
361 bucket = 0;
362 }
363 }
364
365 private:
FindBucketId(int64_t value)366 inline constexpr size_t FindBucketId(int64_t value) const {
367 // Values below the minimum are clamped into the first bucket.
368 if (value <= minimum_value_) {
369 return 0;
370 }
371 // Values above the maximum are clamped into the last bucket.
372 if (value >= maximum_value_) {
373 return num_buckets_ - 1;
374 }
375 // Otherise, linearly interpolate the value into the right bucket
376 constexpr size_t bucket_width = maximum_value_ - minimum_value_;
377 return static_cast<size_t>(value - minimum_value_) * num_buckets_ / bucket_width;
378 }
379
GetBuckets()380 std::vector<value_t> GetBuckets() const {
381 // The loads from buckets_ will all be memory_order_seq_cst, which means they will be acquire
382 // loads. This is a stricter memory order than is needed, but this should not be a
383 // performance-critical section of code.
384 return std::vector<value_t>{buckets_.begin(), buckets_.end()};
385 }
386
387 std::array<std::atomic<value_t>, num_buckets_> buckets_;
388 static_assert(std::atomic<value_t>::is_always_lock_free);
389
390 friend class ArtMetrics;
391 };
392
393 template <DatumId datum_id, typename T, const T& AccumulatorFunction(const T&, const T&)>
394 class MetricsAccumulator final : MetricsBase<T> {
395 public:
396 explicit constexpr MetricsAccumulator(T value = 0) : value_{value} {
397 // Ensure we do not have any unnecessary data in this class.
398 // Adding intptr_t to accommodate vtable, and rounding up to incorporate
399 // padding.
400 static_assert(RoundUp(sizeof(*this), sizeof(uint64_t)) ==
401 RoundUp(sizeof(intptr_t) + sizeof(T), sizeof(uint64_t)));
402 }
403
Add(T value)404 void Add(T value) {
405 T current = value_.load(std::memory_order::memory_order_relaxed);
406 T new_value;
407 do {
408 new_value = AccumulatorFunction(current, value);
409 // If the value didn't change, don't bother storing it.
410 if (current == new_value) {
411 break;
412 }
413 } while (!value_.compare_exchange_weak(
414 current, new_value, std::memory_order::memory_order_relaxed));
415 }
416
417 // Report the metric as a counter, since this has only a single value.
Report(MetricsBackend * backend)418 void Report(MetricsBackend* backend) const {
419 backend->ReportCounter(datum_id, static_cast<uint64_t>(Value()));
420 }
421
422 protected:
Reset()423 void Reset() {
424 value_ = 0;
425 }
426
427 private:
Value()428 T Value() const { return value_.load(std::memory_order::memory_order_relaxed); }
429
430 std::atomic<T> value_;
431
432 friend class ArtMetrics;
433 };
434
435 // A backend that writes metrics in a human-readable format to a string.
436 //
437 // This is used as a base for LogBackend and FileBackend.
438 class StringBackend : public MetricsBackend {
439 public:
440 StringBackend();
441
442 void BeginOrUpdateSession(const SessionData& session_data) override;
443
444 void BeginReport(uint64_t timestamp_millis) override;
445
446 void ReportCounter(DatumId counter_type, uint64_t value) override;
447
448 void ReportHistogram(DatumId histogram_type,
449 int64_t low_value,
450 int64_t high_value,
451 const std::vector<uint32_t>& buckets) override;
452
453 void EndReport() override;
454
455 std::string GetAndResetBuffer();
456
457 private:
458 std::ostringstream os_;
459 std::optional<SessionData> session_data_;
460 };
461
462 // A backend that writes metrics in human-readable format to the log (i.e. logcat).
463 class LogBackend : public StringBackend {
464 public:
465 explicit LogBackend(android::base::LogSeverity level);
466
467 void BeginReport(uint64_t timestamp_millis) override;
468 void EndReport() override;
469
470 private:
471 android::base::LogSeverity level_;
472 };
473
474 // A backend that writes metrics to a file.
475 //
476 // These are currently written in the same human-readable format used by StringBackend and
477 // LogBackend, but we will probably want a more machine-readable format in the future.
478 class FileBackend : public StringBackend {
479 public:
480 explicit FileBackend(const std::string& filename);
481
482 void BeginReport(uint64_t timestamp_millis) override;
483 void EndReport() override;
484
485 private:
486 std::string filename_;
487 };
488
489 /**
490 * AutoTimer simplifies time-based metrics collection.
491 *
492 * Several modes are supported. In the default case, the timer starts immediately and stops when it
493 * goes out of scope. Example:
494 *
495 * {
496 * AutoTimer timer{metric};
497 * DoStuff();
498 * // timer stops and updates metric automatically here.
499 * }
500 *
501 * You can also stop the timer early:
502 *
503 * timer.Stop();
504 *
505 * Finally, you can choose to not automatically start the timer at the beginning by passing false as
506 * the second argument to the constructor:
507 *
508 * AutoTimer timer{metric, false};
509 * DoNotTimeThis();
510 * timer.Start();
511 * TimeThis();
512 *
513 * Manually started timers will still automatically stop in the destructor, but they can be manually
514 * stopped as well.
515 *
516 * Note that AutoTimer makes calls to MicroTime(), so this may not be suitable on critical paths, or
517 * in cases where the counter needs to be started and stopped on different threads.
518 */
519 template <typename Metric>
520 class AutoTimer {
521 public:
522 explicit AutoTimer(Metric* metric, bool autostart = true)
523 : running_{false}, start_time_microseconds_{}, metric_{metric} {
524 if (autostart) {
525 Start();
526 }
527 }
528
~AutoTimer()529 ~AutoTimer() {
530 if (running_) {
531 Stop();
532 }
533 }
534
Start()535 void Start() {
536 DCHECK(!running_);
537 running_ = true;
538 start_time_microseconds_ = MicroTime();
539 }
540
541 // Stops a running timer. Returns the time elapsed since starting the timer in microseconds.
Stop()542 uint64_t Stop() {
543 DCHECK(running_);
544 uint64_t stop_time_microseconds = MicroTime();
545 running_ = false;
546
547 uint64_t elapsed_time = stop_time_microseconds - start_time_microseconds_;
548 metric_->Add(static_cast<typename Metric::value_t>(elapsed_time));
549 return elapsed_time;
550 }
551
552 private:
553 bool running_;
554 uint64_t start_time_microseconds_;
555 Metric* metric_;
556 };
557
558 /**
559 * This struct contains all of the metrics that ART reports.
560 */
561 class ArtMetrics {
562 public:
563 ArtMetrics();
564
565 void ReportAllMetrics(MetricsBackend* backend) const;
566 void DumpForSigQuit(std::ostream& os) const;
567
568 // Resets all metrics to their initial value. This is intended to be used after forking from the
569 // zygote so we don't attribute parent values to the child process.
570 void Reset();
571
572 #define METRIC_ACCESSORS(name, Kind, ...) \
573 Kind<DatumId::k##name, ##__VA_ARGS__>* name() { return &name##_; } \
574 const Kind<DatumId::k##name, ##__VA_ARGS__>* name() const { return &name##_; }
575 ART_METRICS(METRIC_ACCESSORS)
576 #undef METRIC_ACCESSORS
577
578 private:
579 uint64_t beginning_timestamp_;
580
581 #define METRIC(name, Kind, ...) Kind<DatumId::k##name, ##__VA_ARGS__> name##_;
582 ART_METRICS(METRIC)
583 #undef METRIC
584 };
585
586 // Returns a human readable name for the given DatumId.
587 std::string DatumName(DatumId datum);
588
589 // We also log the thread type for metrics so we can distinguish things that block the UI thread
590 // from things that happen on the background thread. This enum keeps track of what thread types we
591 // support.
592 enum class ThreadType {
593 kMain,
594 kBackground,
595 };
596
597 } // namespace metrics
598 } // namespace art
599
600 #pragma clang diagnostic pop // -Wconversion
601
602 #endif // ART_LIBARTBASE_BASE_METRICS_METRICS_H_
603