1 /*
<lambda>null2 * Copyright 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 package androidx.benchmark
18
19 import android.os.Debug
20
21 /**
22 * Microbenchmark metric.
23 *
24 * Note that the API is designed around low overhead, even in the case of multiple submetrics (such
25 * as cpu perf event counters) that must be started/stopped together for efficiency.
26 *
27 * This class may be initialized on a different thread from where measurement occurs, but all
28 * `capture` methods must be invoked from the same thread.
29 *
30 * @sample androidx.benchmark.samples.metricCaptureMultiMetricSample
31 */
32 @ExperimentalBenchmarkConfigApi
33 abstract class MetricCapture(
34 /**
35 * List of names of metrics produced by this MetricCapture.
36 *
37 * The length of this list defines how many metrics will be produced by [captureStart] and
38 * [captureStop].
39 */
40 val names: List<String>
41 ) {
42 /**
43 * Starts collecting data for a run.
44 *
45 * Called at the start of each run.
46 *
47 * @param timeNs Current time, just before starting metrics. Can be used directly to drive a
48 * timing metric produced.
49 */
50 abstract fun captureStart(timeNs: Long)
51
52 /**
53 * Mark the end of a run, and store offset metrics in the output array, per sub metric.
54 *
55 * To output values, store them in the output array offset by both the parameter offset, and
56 * their submetric index.
57 *
58 * @param timeNs Time of metric capture start, in monotonic time ([java.lang.System.nanoTime])
59 * @param output LongArray sized to hold all simultaneous sub metric outputs, use `offset` as
60 * the initial position in `output` to start writing submetrics.
61 * @param offset Offset into the output array to start writing sub metrics.
62 */
63 abstract fun captureStop(timeNs: Long, output: LongArray, offset: Int)
64
65 /** Pause data collection. */
66 abstract fun capturePaused()
67
68 /** Resume data collection */
69 abstract fun captureResumed()
70
71 override fun equals(other: Any?): Boolean {
72 return (other is MetricCapture && other.names == this.names)
73 }
74
75 override fun hashCode(): Int {
76 return names.hashCode() // This is the only true state retained, and hashCode must match ==
77 }
78 }
79
80 /**
81 * Time metric, which reports time in nanos, based on the time passed to [captureStop].
82 *
83 * Reports elapsed time with the label from `name`, which defaults to `timeNs`.
84 *
85 * @param name Metric name of the measured time, defaults to `timeNs`.
86 */
87 @ExperimentalBenchmarkConfigApi
88 class TimeCapture @JvmOverloads constructor(name: String = "timeNs") :
89 MetricCapture(names = listOf(name)) {
90 private var currentStarted = 0L
91 private var currentPausedStarted = 0L
92 private var currentTotalPaused = 0L
93
captureStartnull94 override fun captureStart(timeNs: Long) {
95 currentTotalPaused = 0
96 currentStarted = timeNs
97 }
98
captureStopnull99 override fun captureStop(timeNs: Long, output: LongArray, offset: Int) {
100 output[offset] = timeNs - currentStarted - currentTotalPaused
101 }
102
capturePausednull103 override fun capturePaused() {
104 currentPausedStarted = System.nanoTime()
105 }
106
captureResumednull107 override fun captureResumed() {
108 currentTotalPaused += System.nanoTime() - currentPausedStarted
109 }
110 }
111
112 @Suppress("DEPRECATION")
113 internal class AllocationCountCapture : MetricCapture(names = listOf("allocationCount")) {
114 private var currentPausedStarted = 0
115 private var currentTotalPaused = 0
116
captureStartnull117 override fun captureStart(timeNs: Long) {
118 currentTotalPaused = 0
119 Debug.startAllocCounting()
120 }
121
captureStopnull122 override fun captureStop(timeNs: Long, output: LongArray, offset: Int) {
123 Debug.stopAllocCounting()
124 output[offset] = (Debug.getGlobalAllocCount() - currentTotalPaused).toLong()
125 }
126
capturePausednull127 override fun capturePaused() {
128 // Note - can't start/stop allocation counting to pause/resume, since that would clear
129 // the current counter (and is likely more disruptive than just querying count)
130 currentPausedStarted = Debug.getGlobalAllocCount()
131 }
132
captureResumednull133 override fun captureResumed() {
134 currentTotalPaused += Debug.getGlobalAllocCount() - currentPausedStarted
135 }
136 }
137
138 @Suppress
139 internal class CpuEventCounterCapture(
140 private val cpuEventCounter: CpuEventCounter,
141 private val events: List<CpuEventCounter.Event>
<lambda>null142 ) : MetricCapture(events.map { it.outputName }) {
143 constructor(
144 cpuEventCounter: CpuEventCounter,
145 mask: Int
<lambda>null146 ) : this(cpuEventCounter, CpuEventCounter.Event.values().filter { it.flag.and(mask) != 0 })
147
148 private val values = CpuEventCounter.Values()
149 private val flags = events.getFlags()
150
captureStartnull151 override fun captureStart(timeNs: Long) {
152 // must be called on measure thread, so we wait until after init (which can be separate)
153 cpuEventCounter.resetEvents(flags)
154 cpuEventCounter.start()
155 }
156
captureStopnull157 override fun captureStop(timeNs: Long, output: LongArray, offset: Int) {
158 cpuEventCounter.stop()
159 cpuEventCounter.read(values)
160 events.forEachIndexed { index, event -> output[offset + index] = values.getValue(event) }
161 }
162
capturePausednull163 override fun capturePaused() {
164 cpuEventCounter.stop()
165 }
166
captureResumednull167 override fun captureResumed() {
168 cpuEventCounter.start()
169 }
170 }
171