• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2022 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 com.android.systemui.log.table
18 
19 import com.android.systemui.kairos.BuildScope
20 import com.android.systemui.kairos.ExperimentalKairosApi
21 import com.android.systemui.kairos.State
22 import com.android.systemui.kairos.changes
23 import com.android.systemui.kairos.effect
24 import com.android.systemui.util.kotlin.pairwiseBy
25 import kotlinx.coroutines.flow.Flow
26 
27 /**
28  * An interface that enables logging the difference between values in table format.
29  *
30  * Many objects that we want to log are data-y objects with a collection of fields. When logging
31  * these objects, we want to log each field separately. This allows ABT (Android Bug Tool) to easily
32  * highlight changes in individual fields.
33  *
34  * See [TableLogBuffer].
35  */
36 interface Diffable<T> {
37     /**
38      * Finds the differences between [prevVal] and this object and logs those diffs to [row].
39      *
40      * Each implementer should determine which individual fields have changed between [prevVal] and
41      * this object, and only log the fields that have actually changed. This helps save buffer
42      * space.
43      *
44      * For example, if:
45      * - prevVal = Object(val1=100, val2=200, val3=300)
46      * - this = Object(val1=100, val2=200, val3=333)
47      *
48      * Then only the val3 change should be logged.
49      */
50     fun logDiffs(prevVal: T, row: TableRowLogger)
51 
52     /**
53      * Logs all the relevant fields of this object to [row].
54      *
55      * As opposed to [logDiffs], this method should log *all* fields.
56      *
57      * Implementation is optional. This method will only be used with [logDiffsForTable] in order to
58      * fully log the initial value of the flow.
59      */
60     fun logFull(row: TableRowLogger) {}
61 }
62 
63 /**
64  * Each time the flow is updated with a new value, logs the differences between the previous value
65  * and the new value to the given [tableLogBuffer].
66  *
67  * The new value's [Diffable.logDiffs] method will be used to log the differences to the table.
68  *
69  * @param columnPrefix a prefix that will be applied to every column name that gets logged.
70  */
logDiffsForTablenull71 fun <T : Diffable<T>> Flow<T>.logDiffsForTable(
72     tableLogBuffer: TableLogBuffer,
73     columnPrefix: String = "",
74     initialValue: T,
75 ): Flow<T> {
76     // Fully log the initial value to the table.
77     val getInitialValue = {
78         tableLogBuffer.logChange(columnPrefix, isInitial = true) { row ->
79             initialValue.logFull(row)
80         }
81         initialValue
82     }
83     return this.pairwiseBy(getInitialValue) { prevVal: T, newVal: T ->
84         tableLogBuffer.logDiffs(columnPrefix, prevVal, newVal)
85         newVal
86     }
87 }
88 
89 // Here and below: Various Flow<SomeType> extension functions that are effectively equivalent to the
90 // above [logDiffsForTable] method.
91 
92 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
logDiffsForTablenull93 fun Flow<Boolean>.logDiffsForTable(
94     tableLogBuffer: TableLogBuffer,
95     columnPrefix: String = "",
96     columnName: String,
97     initialValue: Boolean,
98 ): Flow<Boolean> {
99     val initialValueFun = {
100         tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
101         initialValue
102     }
103     return this.pairwiseBy(initialValueFun) { prevVal: Boolean, newVal: Boolean ->
104         if (prevVal != newVal) {
105             tableLogBuffer.logChange(columnPrefix, columnName, newVal)
106         }
107         newVal
108     }
109 }
110 
111 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
logDiffsForTablenull112 fun Flow<Int>.logDiffsForTable(
113     tableLogBuffer: TableLogBuffer,
114     columnPrefix: String = "",
115     columnName: String,
116     initialValue: Int,
117 ): Flow<Int> {
118     val initialValueFun = {
119         tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
120         initialValue
121     }
122     return this.pairwiseBy(initialValueFun) { prevVal: Int, newVal: Int ->
123         if (prevVal != newVal) {
124             tableLogBuffer.logChange(columnPrefix, columnName, newVal)
125         }
126         newVal
127     }
128 }
129 
130 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
logDiffsForTablenull131 fun Flow<Int?>.logDiffsForTable(
132     tableLogBuffer: TableLogBuffer,
133     columnPrefix: String = "",
134     columnName: String,
135     initialValue: Int?,
136 ): Flow<Int?> {
137     val initialValueFun = {
138         tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
139         initialValue
140     }
141     return this.pairwiseBy(initialValueFun) { prevVal: Int?, newVal: Int? ->
142         if (prevVal != newVal) {
143             tableLogBuffer.logChange(columnPrefix, columnName, newVal)
144         }
145         newVal
146     }
147 }
148 
149 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
logDiffsForTablenull150 fun Flow<String?>.logDiffsForTable(
151     tableLogBuffer: TableLogBuffer,
152     columnPrefix: String = "",
153     columnName: String,
154     initialValue: String?,
155 ): Flow<String?> {
156     val initialValueFun = {
157         tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
158         initialValue
159     }
160     return this.pairwiseBy(initialValueFun) { prevVal: String?, newVal: String? ->
161         if (prevVal != newVal) {
162             tableLogBuffer.logChange(columnPrefix, columnName, newVal)
163         }
164         newVal
165     }
166 }
167 
168 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
logDiffsForTablenull169 fun <T> Flow<List<T>>.logDiffsForTable(
170     tableLogBuffer: TableLogBuffer,
171     columnPrefix: String = "",
172     columnName: String,
173     initialValue: List<T>,
174 ): Flow<List<T>> {
175     val initialValueFun = {
176         tableLogBuffer.logChange(
177             columnPrefix,
178             columnName,
179             initialValue.toString(),
180             isInitial = true,
181         )
182         initialValue
183     }
184     return this.pairwiseBy(initialValueFun) { prevVal: List<T>, newVal: List<T> ->
185         if (prevVal != newVal) {
186             // TODO(b/267761156): Can we log list changes without using toString?
187             tableLogBuffer.logChange(columnPrefix, columnName, newVal.toString())
188         }
189         newVal
190     }
191 }
192 
193 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
194 @ExperimentalKairosApi
195 @JvmName("logIntDiffsForTable")
logDiffsForTablenull196 fun BuildScope.logDiffsForTable(
197     intState: State<Int?>,
198     tableLogBuffer: TableLogBuffer,
199     columnPrefix: String = "",
200     columnName: String,
201 ) {
202     var isInitial = true
203     intState.observe { new ->
204         tableLogBuffer.logChange(columnPrefix, columnName, new, isInitial = isInitial)
205         isInitial = false
206     }
207 }
208 
209 /**
210  * Each time the flow is updated with a new value, logs the differences between the previous value
211  * and the new value to the given [tableLogBuffer].
212  *
213  * The new value's [Diffable.logDiffs] method will be used to log the differences to the table.
214  *
215  * @param columnPrefix a prefix that will be applied to every column name that gets logged.
216  */
217 @ExperimentalKairosApi
logDiffsForTablenull218 fun <T : Diffable<T>> BuildScope.logDiffsForTable(
219     diffableState: State<T>,
220     tableLogBuffer: TableLogBuffer,
221     columnPrefix: String = "",
222 ) {
223     val initialValue = diffableState.sampleDeferred()
224     effect {
225         // Fully log the initial value to the table.
226         tableLogBuffer.logChange(columnPrefix, isInitial = true) { row ->
227             initialValue.value.logFull(row)
228         }
229     }
230     diffableState.changes.observe { newState ->
231         val prevState = diffableState.sample()
232         tableLogBuffer.logDiffs(columnPrefix, prevVal = prevState, newVal = newState)
233     }
234 }
235 
236 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
237 @ExperimentalKairosApi
238 @JvmName("logBooleanDiffsForTable")
logDiffsForTablenull239 fun BuildScope.logDiffsForTable(
240     booleanState: State<Boolean>,
241     tableLogBuffer: TableLogBuffer,
242     columnPrefix: String = "",
243     columnName: String,
244 ) {
245     var isInitial = true
246     booleanState.observe { new ->
247         tableLogBuffer.logChange(columnPrefix, columnName, new, isInitial = isInitial)
248         isInitial = false
249     }
250 }
251 
252 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
253 @ExperimentalKairosApi
254 @JvmName("logStringDiffsForTable")
logDiffsForTablenull255 fun BuildScope.logDiffsForTable(
256     stringState: State<String?>,
257     tableLogBuffer: TableLogBuffer,
258     columnPrefix: String = "",
259     columnName: String,
260 ) {
261     var isInitial = true
262     stringState.observe { new ->
263         tableLogBuffer.logChange(columnPrefix, columnName, new, isInitial = isInitial)
264         isInitial = false
265     }
266 }
267 
268 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
269 @ExperimentalKairosApi
270 @JvmName("logListDiffsForTable")
logDiffsForTablenull271 fun <T> BuildScope.logDiffsForTable(
272     listState: State<List<T>>,
273     tableLogBuffer: TableLogBuffer,
274     columnPrefix: String = "",
275     columnName: String,
276 ) {
277     var isInitial = true
278     listState.observe { new ->
279         tableLogBuffer.logChange(columnPrefix, columnName, new.toString(), isInitial = isInitial)
280         isInitial = false
281     }
282 }
283