1 /* 2 * 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.util.kotlin.pairwiseBy 20 import kotlinx.coroutines.flow.Flow 21 22 /** 23 * An interface that enables logging the difference between values in table format. 24 * 25 * Many objects that we want to log are data-y objects with a collection of fields. When logging 26 * these objects, we want to log each field separately. This allows ABT (Android Bug Tool) to easily 27 * highlight changes in individual fields. 28 * 29 * See [TableLogBuffer]. 30 */ 31 interface Diffable<T> { 32 /** 33 * Finds the differences between [prevVal] and this object and logs those diffs to [row]. 34 * 35 * Each implementer should determine which individual fields have changed between [prevVal] and 36 * this object, and only log the fields that have actually changed. This helps save buffer 37 * space. 38 * 39 * For example, if: 40 * - prevVal = Object(val1=100, val2=200, val3=300) 41 * - this = Object(val1=100, val2=200, val3=333) 42 * 43 * Then only the val3 change should be logged. 44 */ 45 fun logDiffs(prevVal: T, row: TableRowLogger) 46 47 /** 48 * Logs all the relevant fields of this object to [row]. 49 * 50 * As opposed to [logDiffs], this method should log *all* fields. 51 * 52 * Implementation is optional. This method will only be used with [logDiffsForTable] in order to 53 * fully log the initial value of the flow. 54 */ 55 fun logFull(row: TableRowLogger) {} 56 } 57 58 /** 59 * Each time the flow is updated with a new value, logs the differences between the previous value 60 * and the new value to the given [tableLogBuffer]. 61 * 62 * The new value's [Diffable.logDiffs] method will be used to log the differences to the table. 63 * 64 * @param columnPrefix a prefix that will be applied to every column name that gets logged. 65 */ 66 fun <T : Diffable<T>> Flow<T>.logDiffsForTable( 67 tableLogBuffer: TableLogBuffer, 68 columnPrefix: String, 69 initialValue: T, 70 ): Flow<T> { 71 // Fully log the initial value to the table. 72 val getInitialValue = { 73 tableLogBuffer.logChange(columnPrefix, isInitial = true) { row -> 74 initialValue.logFull(row) 75 } 76 initialValue 77 } 78 return this.pairwiseBy(getInitialValue) { prevVal: T, newVal: T -> 79 tableLogBuffer.logDiffs(columnPrefix, prevVal, newVal) 80 newVal 81 } 82 } 83 84 // Here and below: Various Flow<SomeType> extension functions that are effectively equivalent to the 85 // above [logDiffsForTable] method. 86 87 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */ 88 fun Flow<Boolean>.logDiffsForTable( 89 tableLogBuffer: TableLogBuffer, 90 columnPrefix: String, 91 columnName: String, 92 initialValue: Boolean, 93 ): Flow<Boolean> { 94 val initialValueFun = { 95 tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true) 96 initialValue 97 } 98 return this.pairwiseBy(initialValueFun) { prevVal, newVal: Boolean -> 99 if (prevVal != newVal) { 100 tableLogBuffer.logChange(columnPrefix, columnName, newVal) 101 } 102 newVal 103 } 104 } 105 106 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */ 107 fun Flow<Int>.logDiffsForTable( 108 tableLogBuffer: TableLogBuffer, 109 columnPrefix: String, 110 columnName: String, 111 initialValue: Int, 112 ): Flow<Int> { 113 val initialValueFun = { 114 tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true) 115 initialValue 116 } 117 return this.pairwiseBy(initialValueFun) { prevVal, newVal: Int -> 118 if (prevVal != newVal) { 119 tableLogBuffer.logChange(columnPrefix, columnName, newVal) 120 } 121 newVal 122 } 123 } 124 125 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */ 126 fun Flow<Int?>.logDiffsForTable( 127 tableLogBuffer: TableLogBuffer, 128 columnPrefix: String, 129 columnName: String, 130 initialValue: Int?, 131 ): Flow<Int?> { 132 val initialValueFun = { 133 tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true) 134 initialValue 135 } 136 return this.pairwiseBy(initialValueFun) { prevVal, newVal: Int? -> 137 if (prevVal != newVal) { 138 tableLogBuffer.logChange(columnPrefix, columnName, newVal) 139 } 140 newVal 141 } 142 } 143 144 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */ 145 fun Flow<String?>.logDiffsForTable( 146 tableLogBuffer: TableLogBuffer, 147 columnPrefix: String, 148 columnName: String, 149 initialValue: String?, 150 ): Flow<String?> { 151 val initialValueFun = { 152 tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true) 153 initialValue 154 } 155 return this.pairwiseBy(initialValueFun) { prevVal, newVal: String? -> 156 if (prevVal != newVal) { 157 tableLogBuffer.logChange(columnPrefix, columnName, newVal) 158 } 159 newVal 160 } 161 } 162 163 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */ 164 fun <T> Flow<List<T>>.logDiffsForTable( 165 tableLogBuffer: TableLogBuffer, 166 columnPrefix: String, 167 columnName: String, 168 initialValue: List<T>, 169 ): Flow<List<T>> { 170 val initialValueFun = { 171 tableLogBuffer.logChange( 172 columnPrefix, 173 columnName, 174 initialValue.toString(), 175 isInitial = true, 176 ) 177 initialValue 178 } 179 return this.pairwiseBy(initialValueFun) { prevVal, newVal: List<T> -> 180 if (prevVal != newVal) { 181 // TODO(b/267761156): Can we log list changes without using toString? 182 tableLogBuffer.logChange(columnPrefix, columnName, newVal.toString()) 183 } 184 newVal 185 } 186 } 187