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