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