1 /*
2  * Copyright 2021 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 package androidx.metrics.performance
17 
18 /**
19  * This class stores duration data for a single frame.
20  *
21  * @property frameStartNanos The time at which this frame began (in nanoseconds)
22  * @property frameDurationUiNanos The time spent in the UI portion of this frame (in nanoseconds).
23  *   This is essentially the time spent on the UI thread to draw this frame, but does not include
24  *   any time spent on the RenderThread.
25  * @property isJank Whether this frame was determined to be janky, meaning that its duration exceeds
26  *   the duration determined by the system to indicate jank (@see
27  *   [JankStats.jankHeuristicMultiplier]).
28  * @property states The UI/app state during this frame. This is the information set by the app, or
29  *   by other library code, that can be analyzed later to determine the UI state that was current
30  *   when jank occurred.
31  * @see JankStats.jankHeuristicMultiplier
32  * @see PerformanceMetricsState.putState
33  */
34 open class FrameData(
35     frameStartNanos: Long,
36     frameDurationUiNanos: Long,
37     isJank: Boolean,
38     val states: List<StateInfo>
39 ) {
40     /**
41      * These backing fields are used to enable mutation of an existing FrameData object, to avoid
42      * allocating a new object on every frame for sending out to listeners.
43      */
44     var frameStartNanos = frameStartNanos
45         private set
46 
47     var frameDurationUiNanos = frameDurationUiNanos
48         private set
49 
50     var isJank = isJank
51         private set
52 
53     /**
54      * Utility method which makes a copy of the items in this object (including copying the items in
55      * `states` into a new List). This is used internally to create a copy to pass along to
56      * listeners to avoid having a reference to the internally-mutable FrameData object.
57      */
copynull58     open fun copy(): FrameData {
59         return FrameData(frameStartNanos, frameDurationUiNanos, isJank, ArrayList(states))
60     }
61 
62     /**
63      * Utility method for updating the internal values in this object. Externally, this object is
64      * immutable. Internally, we need the ability to update the values so that we can reuse it for a
65      * non-allocating listener model, to avoid having to re-allocate a new FrameData (and its states
66      * List). Note that the states object is not being updated here; internal can already use a
67      * Mutable list to update the contents of that list; they do not need to update this object with
68      * a new List, since any usage of FrameData to avoid allocations should not be creating a new
69      * state List anyway.
70      */
updatenull71     internal fun update(frameStartNanos: Long, frameDurationUiNanos: Long, isJank: Boolean) {
72         this.frameStartNanos = frameStartNanos
73         this.frameDurationUiNanos = frameDurationUiNanos
74         this.isJank = isJank
75     }
76 
equalsnull77     override fun equals(other: Any?): Boolean {
78         if (this === other) return true
79         if (javaClass != other?.javaClass) return false
80 
81         other as FrameData
82 
83         if (frameStartNanos != other.frameStartNanos) return false
84         if (frameDurationUiNanos != other.frameDurationUiNanos) return false
85         if (isJank != other.isJank) return false
86         if (states != other.states) return false
87 
88         return true
89     }
90 
hashCodenull91     override fun hashCode(): Int {
92         var result = frameStartNanos.hashCode()
93         result = 31 * result + frameDurationUiNanos.hashCode()
94         result = 31 * result + isJank.hashCode()
95         result = 31 * result + states.hashCode()
96         return result
97     }
98 
toStringnull99     override fun toString(): String {
100         return "FrameData(frameStartNanos=$frameStartNanos, " +
101             "frameDurationUiNanos=$frameDurationUiNanos, " +
102             "isJank=$isJank, " +
103             "states=$states)"
104     }
105 }
106 
107 /**
108  * This class contains information about application state.
109  *
110  * @property key An arbitrary name used for this state, used as a key for storing the state value.
111  * @property value The value of this state.
112  */
113 class StateInfo(val key: String, val value: String) {
114 
equalsnull115     override fun equals(other: Any?): Boolean {
116         if (this === other) return true
117         if (javaClass != other?.javaClass) return false
118 
119         other as StateInfo
120 
121         if (key != other.key) return false
122         if (value != other.value) return false
123 
124         return true
125     }
126 
hashCodenull127     override fun hashCode(): Int {
128         var result = key.hashCode()
129         result = 31 * result + value.hashCode()
130         return result
131     }
132 
toStringnull133     override fun toString(): String {
134         return "$key: $value"
135     }
136 
137     /**
138      * This internal componion is used to manage a pool of reusable StateInfo objects. Rather than
139      * creating a new StateInfo object very time, the library requests an object for the given
140      * stateName/state pair. In general, requests will be common using the same pairs, thus reuse
141      * will be high and an object from the pool will be returned. When reuse is not necessary, a new
142      * StateInfo object will be created, added to the pool, and returned.
143      */
144     internal companion object {
145         val pool = mutableMapOf<String, MutableMap<String, StateInfo>>()
146 
getStateInfonull147         fun getStateInfo(stateName: String, state: String): StateInfo {
148             synchronized(pool) {
149                 var poolItem = pool.get(stateName)
150                 var stateInfo = poolItem?.get(state)
151                 if (stateInfo != null) return stateInfo
152                 else {
153                     stateInfo = StateInfo(stateName, state)
154                     if (poolItem != null) {
155                         poolItem.put(state, stateInfo)
156                     } else {
157                         poolItem = mutableMapOf(state to stateInfo)
158                         pool.put(stateName, poolItem)
159                     }
160                     return stateInfo
161                 }
162             }
163         }
164     }
165 }
166