Merge "[SB Refactor] Implement a table log buffer." into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
36c8d8681b
@@ -16,9 +16,9 @@
|
|||||||
|
|
||||||
package com.android.systemui.log
|
package com.android.systemui.log
|
||||||
|
|
||||||
import android.app.ActivityManager
|
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.dump.DumpManager
|
import com.android.systemui.dump.DumpManager
|
||||||
|
import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
|
||||||
import com.android.systemui.plugins.log.LogBuffer
|
import com.android.systemui.plugins.log.LogBuffer
|
||||||
import com.android.systemui.plugins.log.LogcatEchoTracker
|
import com.android.systemui.plugins.log.LogcatEchoTracker
|
||||||
|
|
||||||
@@ -29,15 +29,6 @@ class LogBufferFactory @Inject constructor(
|
|||||||
private val dumpManager: DumpManager,
|
private val dumpManager: DumpManager,
|
||||||
private val logcatEchoTracker: LogcatEchoTracker
|
private val logcatEchoTracker: LogcatEchoTracker
|
||||||
) {
|
) {
|
||||||
/* limitiometricMessageDeferralLogger the size of maxPoolSize for low ram (Go) devices */
|
|
||||||
private fun adjustMaxSize(requestedMaxSize: Int): Int {
|
|
||||||
return if (ActivityManager.isLowRamDeviceStatic()) {
|
|
||||||
minOf(requestedMaxSize, 20) /* low ram max log size*/
|
|
||||||
} else {
|
|
||||||
requestedMaxSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun create(
|
fun create(
|
||||||
name: String,
|
name: String,
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.log
|
||||||
|
|
||||||
|
import android.app.ActivityManager
|
||||||
|
|
||||||
|
class LogBufferHelper {
|
||||||
|
companion object {
|
||||||
|
/** If necessary, returns a limited maximum size for low ram (Go) devices */
|
||||||
|
fun adjustMaxSize(requestedMaxSize: Int): Int {
|
||||||
|
return if (ActivityManager.isLowRamDeviceStatic()) {
|
||||||
|
minOf(requestedMaxSize, 20) /* low ram max log size*/
|
||||||
|
} else {
|
||||||
|
requestedMaxSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.log.table
|
||||||
|
|
||||||
|
import com.android.systemui.util.kotlin.pairwiseBy
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface that enables logging the difference between values in table format.
|
||||||
|
*
|
||||||
|
* Many objects that we want to log are data-y objects with a collection of fields. When logging
|
||||||
|
* these objects, we want to log each field separately. This allows ABT (Android Bug Tool) to easily
|
||||||
|
* highlight changes in individual fields.
|
||||||
|
*
|
||||||
|
* See [TableLogBuffer].
|
||||||
|
*/
|
||||||
|
interface Diffable<T> {
|
||||||
|
/**
|
||||||
|
* Finds the differences between [prevVal] and [this] and logs those diffs to [row].
|
||||||
|
*
|
||||||
|
* Each implementer should determine which individual fields have changed between [prevVal] and
|
||||||
|
* [this], and only log the fields that have actually changed. This helps save buffer space.
|
||||||
|
*
|
||||||
|
* For example, if:
|
||||||
|
* - prevVal = Object(val1=100, val2=200, val3=300)
|
||||||
|
* - this = Object(val1=100, val2=200, val3=333)
|
||||||
|
*
|
||||||
|
* Then only the val3 change should be logged.
|
||||||
|
*/
|
||||||
|
fun logDiffs(prevVal: T, row: TableRowLogger)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Each time the flow is updated with a new value, logs the differences between the previous value
|
||||||
|
* and the new value to the given [tableLogBuffer].
|
||||||
|
*
|
||||||
|
* The new value's [Diffable.logDiffs] method will be used to log the differences to the table.
|
||||||
|
*
|
||||||
|
* @param columnPrefix a prefix that will be applied to every column name that gets logged.
|
||||||
|
*/
|
||||||
|
fun <T : Diffable<T>> Flow<T>.logDiffsForTable(
|
||||||
|
tableLogBuffer: TableLogBuffer,
|
||||||
|
columnPrefix: String,
|
||||||
|
initialValue: T,
|
||||||
|
): Flow<T> {
|
||||||
|
return this.pairwiseBy(initialValue) { prevVal, newVal ->
|
||||||
|
tableLogBuffer.logDiffs(columnPrefix, prevVal, newVal)
|
||||||
|
newVal
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.log.table
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A object used with [TableLogBuffer] to store changes in variables over time. Is recyclable.
|
||||||
|
*
|
||||||
|
* Each message represents a change to exactly 1 type, specified by [DataType].
|
||||||
|
*/
|
||||||
|
data class TableChange(
|
||||||
|
var timestamp: Long = 0,
|
||||||
|
var columnPrefix: String = "",
|
||||||
|
var columnName: String = "",
|
||||||
|
var type: DataType = DataType.EMPTY,
|
||||||
|
var bool: Boolean = false,
|
||||||
|
var int: Int = 0,
|
||||||
|
var str: String? = null,
|
||||||
|
) {
|
||||||
|
/** Resets to default values so that the object can be recycled. */
|
||||||
|
fun reset(timestamp: Long, columnPrefix: String, columnName: String) {
|
||||||
|
this.timestamp = timestamp
|
||||||
|
this.columnPrefix = columnPrefix
|
||||||
|
this.columnName = columnName
|
||||||
|
this.type = DataType.EMPTY
|
||||||
|
this.bool = false
|
||||||
|
this.int = 0
|
||||||
|
this.str = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets this to store a string change. */
|
||||||
|
fun set(value: String?) {
|
||||||
|
type = DataType.STRING
|
||||||
|
str = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets this to store a boolean change. */
|
||||||
|
fun set(value: Boolean) {
|
||||||
|
type = DataType.BOOLEAN
|
||||||
|
bool = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets this to store an int change. */
|
||||||
|
fun set(value: Int) {
|
||||||
|
type = DataType.INT
|
||||||
|
int = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if this object has a change. */
|
||||||
|
fun hasData(): Boolean {
|
||||||
|
return columnName.isNotBlank() && type != DataType.EMPTY
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getName(): String {
|
||||||
|
return if (columnPrefix.isNotBlank()) {
|
||||||
|
"$columnPrefix.$columnName"
|
||||||
|
} else {
|
||||||
|
columnName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getVal(): String {
|
||||||
|
return when (type) {
|
||||||
|
DataType.EMPTY -> null
|
||||||
|
DataType.STRING -> str
|
||||||
|
DataType.INT -> int
|
||||||
|
DataType.BOOLEAN -> bool
|
||||||
|
}.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class DataType {
|
||||||
|
STRING,
|
||||||
|
BOOLEAN,
|
||||||
|
INT,
|
||||||
|
EMPTY,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.log.table
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import com.android.systemui.Dumpable
|
||||||
|
import com.android.systemui.dump.DumpManager
|
||||||
|
import com.android.systemui.dump.DumpsysTableLogger
|
||||||
|
import com.android.systemui.plugins.util.RingBuffer
|
||||||
|
import com.android.systemui.util.time.SystemClock
|
||||||
|
import java.io.PrintWriter
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A logger that logs changes in table format.
|
||||||
|
*
|
||||||
|
* Some parts of System UI maintain a lot of pieces of state at once.
|
||||||
|
* [com.android.systemui.plugins.log.LogBuffer] allows us to easily log change events:
|
||||||
|
*
|
||||||
|
* - 10-10 10:10:10.456: state2 updated to newVal2
|
||||||
|
* - 10-10 10:11:00.000: stateN updated to StateN(val1=true, val2=1)
|
||||||
|
* - 10-10 10:11:02.123: stateN updated to StateN(val1=true, val2=2)
|
||||||
|
* - 10-10 10:11:05.123: state1 updated to newVal1
|
||||||
|
* - 10-10 10:11:06.000: stateN updated to StateN(val1=false, val2=3)
|
||||||
|
*
|
||||||
|
* However, it can sometimes be more useful to view the state changes in table format:
|
||||||
|
*
|
||||||
|
* - timestamp--------- | state1- | state2- | ... | stateN.val1 | stateN.val2
|
||||||
|
* - -------------------------------------------------------------------------
|
||||||
|
* - 10-10 10:10:10.123 | val1--- | val2--- | ... | false------ | 0-----------
|
||||||
|
* - 10-10 10:10:10.456 | val1--- | newVal2 | ... | false------ | 0-----------
|
||||||
|
* - 10-10 10:11:00.000 | val1--- | newVal2 | ... | true------- | 1-----------
|
||||||
|
* - 10-10 10:11:02.123 | val1--- | newVal2 | ... | true------- | 2-----------
|
||||||
|
* - 10-10 10:11:05.123 | newVal1 | newVal2 | ... | true------- | 2-----------
|
||||||
|
* - 10-10 10:11:06.000 | newVal1 | newVal2 | ... | false------ | 3-----------
|
||||||
|
*
|
||||||
|
* This class enables easy logging of the state changes in both change event format and table
|
||||||
|
* format.
|
||||||
|
*
|
||||||
|
* This class also enables easy logging of states that are a collection of fields. For example,
|
||||||
|
* stateN in the above example consists of two fields -- val1 and val2. It's useful to put each
|
||||||
|
* field into its own column so that ABT (Android Bug Tool) can easily highlight changes to
|
||||||
|
* individual fields.
|
||||||
|
*
|
||||||
|
* How it works:
|
||||||
|
*
|
||||||
|
* 1) Create an instance of this buffer via [TableLogBufferFactory].
|
||||||
|
*
|
||||||
|
* 2) For any states being logged, implement [Diffable]. Implementing [Diffable] allows the state to
|
||||||
|
* only log the fields that have *changed* since the previous update, instead of always logging all
|
||||||
|
* fields.
|
||||||
|
*
|
||||||
|
* 3) Each time a change in a state happens, call [logDiffs]. If your state is emitted using a
|
||||||
|
* [Flow], you should use the [logDiffsForTable] extension function to automatically log diffs any
|
||||||
|
* time your flow emits a new value.
|
||||||
|
*
|
||||||
|
* When a dump occurs, there will be two dumps:
|
||||||
|
*
|
||||||
|
* 1) The change events under the dumpable name "$name-changes".
|
||||||
|
*
|
||||||
|
* 2) This class will coalesce all the diffs into a table format and log them under the dumpable
|
||||||
|
* name "$name-table".
|
||||||
|
*
|
||||||
|
* @param maxSize the maximum size of the buffer. Must be > 0.
|
||||||
|
*/
|
||||||
|
class TableLogBuffer(
|
||||||
|
maxSize: Int,
|
||||||
|
private val name: String,
|
||||||
|
private val systemClock: SystemClock,
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
if (maxSize <= 0) {
|
||||||
|
throw IllegalArgumentException("maxSize must be > 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val buffer = RingBuffer(maxSize) { TableChange() }
|
||||||
|
|
||||||
|
// A [TableRowLogger] object, re-used each time [logDiffs] is called.
|
||||||
|
// (Re-used to avoid object allocation.)
|
||||||
|
private val tempRow = TableRowLoggerImpl(0, columnPrefix = "", this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log the differences between [prevVal] and [newVal].
|
||||||
|
*
|
||||||
|
* The [newVal] object's method [Diffable.logDiffs] will be used to fetch the diffs.
|
||||||
|
*
|
||||||
|
* @param columnPrefix a prefix that will be applied to every column name that gets logged. This
|
||||||
|
* ensures that all the columns related to the same state object will be grouped together in the
|
||||||
|
* table.
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
fun <T : Diffable<T>> logDiffs(columnPrefix: String, prevVal: T, newVal: T) {
|
||||||
|
val row = tempRow
|
||||||
|
row.timestamp = systemClock.currentTimeMillis()
|
||||||
|
row.columnPrefix = columnPrefix
|
||||||
|
newVal.logDiffs(prevVal, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep these individual [logChange] methods private (don't let clients give us their own
|
||||||
|
// timestamps.)
|
||||||
|
|
||||||
|
private fun logChange(timestamp: Long, prefix: String, columnName: String, value: String?) {
|
||||||
|
val change = obtain(timestamp, prefix, columnName)
|
||||||
|
change.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Boolean) {
|
||||||
|
val change = obtain(timestamp, prefix, columnName)
|
||||||
|
change.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Int) {
|
||||||
|
val change = obtain(timestamp, prefix, columnName)
|
||||||
|
change.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(b/259454430): Add additional change types here.
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun obtain(timestamp: Long, prefix: String, columnName: String): TableChange {
|
||||||
|
val tableChange = buffer.advance()
|
||||||
|
tableChange.reset(timestamp, prefix, columnName)
|
||||||
|
return tableChange
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers this buffer as dumpables in [dumpManager]. Must be called for the table to be
|
||||||
|
* dumped.
|
||||||
|
*
|
||||||
|
* This will be automatically called in [TableLogBufferFactory.create].
|
||||||
|
*/
|
||||||
|
fun registerDumpables(dumpManager: DumpManager) {
|
||||||
|
dumpManager.registerNormalDumpable("$name-changes", changeDumpable)
|
||||||
|
dumpManager.registerNormalDumpable("$name-table", tableDumpable)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val changeDumpable = Dumpable { pw, _ -> dumpChanges(pw) }
|
||||||
|
private val tableDumpable = Dumpable { pw, _ -> dumpTable(pw) }
|
||||||
|
|
||||||
|
/** Dumps the list of [TableChange] objects. */
|
||||||
|
@Synchronized
|
||||||
|
@VisibleForTesting
|
||||||
|
fun dumpChanges(pw: PrintWriter) {
|
||||||
|
for (i in 0 until buffer.size) {
|
||||||
|
buffer[i].dump(pw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Dumps an individual [TableChange]. */
|
||||||
|
private fun TableChange.dump(pw: PrintWriter) {
|
||||||
|
if (!this.hasData()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val formattedTimestamp = TABLE_LOG_DATE_FORMAT.format(timestamp)
|
||||||
|
pw.print(formattedTimestamp)
|
||||||
|
pw.print(" ")
|
||||||
|
pw.print(this.getName())
|
||||||
|
pw.print("=")
|
||||||
|
pw.print(this.getVal())
|
||||||
|
pw.println()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coalesces all the [TableChange] objects into a table of values of time and dumps the table.
|
||||||
|
*/
|
||||||
|
// TODO(b/259454430): Since this is an expensive process, it could cause the bug report dump to
|
||||||
|
// fail and/or not dump anything else. We should move this processing to ABT (Android Bug
|
||||||
|
// Tool), where we have unlimited time to process.
|
||||||
|
@Synchronized
|
||||||
|
@VisibleForTesting
|
||||||
|
fun dumpTable(pw: PrintWriter) {
|
||||||
|
val messages = buffer.iterator().asSequence().toList()
|
||||||
|
|
||||||
|
if (messages.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Create list of column headers
|
||||||
|
val headerSet = mutableSetOf<String>()
|
||||||
|
messages.forEach { headerSet.add(it.getName()) }
|
||||||
|
val headers: MutableList<String> = headerSet.toList().sorted().toMutableList()
|
||||||
|
headers.add(0, "timestamp")
|
||||||
|
|
||||||
|
// Step 2: Create a list with the current values for each column. Will be updated with each
|
||||||
|
// change.
|
||||||
|
val currentRow: MutableList<String> = MutableList(headers.size) { DEFAULT_COLUMN_VALUE }
|
||||||
|
|
||||||
|
// Step 3: For each message, make the correct update to [currentRow] and save it to [rows].
|
||||||
|
val columnIndices: Map<String, Int> =
|
||||||
|
headers.mapIndexed { index, headerName -> headerName to index }.toMap()
|
||||||
|
val allRows = mutableListOf<List<String>>()
|
||||||
|
|
||||||
|
messages.forEach {
|
||||||
|
if (!it.hasData()) {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
|
val formattedTimestamp = TABLE_LOG_DATE_FORMAT.format(it.timestamp)
|
||||||
|
if (formattedTimestamp != currentRow[0]) {
|
||||||
|
// The timestamp has updated, so save the previous row and continue to the next row
|
||||||
|
allRows.add(currentRow.toList())
|
||||||
|
currentRow[0] = formattedTimestamp
|
||||||
|
}
|
||||||
|
val columnIndex = columnIndices[it.getName()]!!
|
||||||
|
currentRow[columnIndex] = it.getVal()
|
||||||
|
}
|
||||||
|
// Add the last row
|
||||||
|
allRows.add(currentRow.toList())
|
||||||
|
|
||||||
|
// Step 4: Dump the rows
|
||||||
|
DumpsysTableLogger(
|
||||||
|
name,
|
||||||
|
headers,
|
||||||
|
allRows,
|
||||||
|
)
|
||||||
|
.printTableData(pw)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A private implementation of [TableRowLogger].
|
||||||
|
*
|
||||||
|
* Used so that external clients can't modify [timestamp].
|
||||||
|
*/
|
||||||
|
private class TableRowLoggerImpl(
|
||||||
|
var timestamp: Long,
|
||||||
|
var columnPrefix: String,
|
||||||
|
val tableLogBuffer: TableLogBuffer,
|
||||||
|
) : TableRowLogger {
|
||||||
|
/** Logs a change to a string value. */
|
||||||
|
override fun logChange(columnName: String, value: String?) {
|
||||||
|
tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Logs a change to a boolean value. */
|
||||||
|
override fun logChange(columnName: String, value: Boolean) {
|
||||||
|
tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Logs a change to an int value. */
|
||||||
|
override fun logChange(columnName: String, value: Int) {
|
||||||
|
tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val TABLE_LOG_DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
|
||||||
|
private const val DEFAULT_COLUMN_VALUE = "UNKNOWN"
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.log.table
|
||||||
|
|
||||||
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
|
import com.android.systemui.dump.DumpManager
|
||||||
|
import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
|
||||||
|
import com.android.systemui.util.time.SystemClock
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@SysUISingleton
|
||||||
|
class TableLogBufferFactory
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
private val dumpManager: DumpManager,
|
||||||
|
private val systemClock: SystemClock,
|
||||||
|
) {
|
||||||
|
fun create(
|
||||||
|
name: String,
|
||||||
|
maxSize: Int,
|
||||||
|
): TableLogBuffer {
|
||||||
|
val tableBuffer = TableLogBuffer(adjustMaxSize(maxSize), name, systemClock)
|
||||||
|
tableBuffer.registerDumpables(dumpManager)
|
||||||
|
return tableBuffer
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.log.table
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that logs a row to [TableLogBuffer].
|
||||||
|
*
|
||||||
|
* Objects that implement [Diffable] will receive an instance of this class, and can log any changes
|
||||||
|
* to individual fields using the [logChange] methods. All logged changes will be associated with
|
||||||
|
* the same timestamp.
|
||||||
|
*/
|
||||||
|
interface TableRowLogger {
|
||||||
|
/** Logs a change to a string value. */
|
||||||
|
fun logChange(columnName: String, value: String?)
|
||||||
|
|
||||||
|
/** Logs a change to a boolean value. */
|
||||||
|
fun logChange(columnName: String, value: Boolean)
|
||||||
|
|
||||||
|
/** Logs a change to an int value. */
|
||||||
|
fun logChange(columnName: String, value: Int)
|
||||||
|
}
|
||||||
@@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
package com.android.systemui.statusbar.pipeline.dagger
|
package com.android.systemui.statusbar.pipeline.dagger
|
||||||
|
|
||||||
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
|
import com.android.systemui.log.table.TableLogBuffer
|
||||||
|
import com.android.systemui.log.table.TableLogBufferFactory
|
||||||
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
|
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
|
||||||
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
|
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
|
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
|
||||||
@@ -32,6 +35,7 @@ import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiReposito
|
|||||||
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl
|
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
abstract class StatusBarPipelineModule {
|
abstract class StatusBarPipelineModule {
|
||||||
@@ -57,4 +61,15 @@ abstract class StatusBarPipelineModule {
|
|||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
|
abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
|
||||||
|
|
||||||
|
@Module
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@Provides
|
||||||
|
@SysUISingleton
|
||||||
|
@WifiTableLog
|
||||||
|
fun provideWifiTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
|
||||||
|
return factory.create("WifiTableLog", 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.statusbar.pipeline.dagger
|
||||||
|
|
||||||
|
import javax.inject.Qualifier
|
||||||
|
|
||||||
|
/** Wifi logs in table format. */
|
||||||
|
@Qualifier
|
||||||
|
@MustBeDocumented
|
||||||
|
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
|
||||||
|
annotation class WifiTableLog
|
||||||
@@ -17,12 +17,30 @@
|
|||||||
package com.android.systemui.statusbar.pipeline.wifi.data.model
|
package com.android.systemui.statusbar.pipeline.wifi.data.model
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import com.android.systemui.log.table.TableRowLogger
|
||||||
|
import com.android.systemui.log.table.Diffable
|
||||||
|
|
||||||
/** Provides information about the current wifi network. */
|
/** Provides information about the current wifi network. */
|
||||||
sealed class WifiNetworkModel {
|
sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
|
||||||
|
|
||||||
/** A model representing that we have no active wifi network. */
|
/** A model representing that we have no active wifi network. */
|
||||||
object Inactive : WifiNetworkModel() {
|
object Inactive : WifiNetworkModel() {
|
||||||
override fun toString() = "WifiNetwork.Inactive"
|
override fun toString() = "WifiNetwork.Inactive"
|
||||||
|
|
||||||
|
override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
|
||||||
|
if (prevVal is Inactive) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE)
|
||||||
|
|
||||||
|
if (prevVal is CarrierMerged) {
|
||||||
|
// The only difference between CarrierMerged and Inactive is the type
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// When changing from Active to Inactive, we need to log diffs to all the fields.
|
||||||
|
logDiffsFromActiveToNotActive(prevVal as Active, row)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,6 +51,21 @@ sealed class WifiNetworkModel {
|
|||||||
*/
|
*/
|
||||||
object CarrierMerged : WifiNetworkModel() {
|
object CarrierMerged : WifiNetworkModel() {
|
||||||
override fun toString() = "WifiNetwork.CarrierMerged"
|
override fun toString() = "WifiNetwork.CarrierMerged"
|
||||||
|
|
||||||
|
override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
|
||||||
|
if (prevVal is CarrierMerged) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED)
|
||||||
|
|
||||||
|
if (prevVal is Inactive) {
|
||||||
|
// The only difference between CarrierMerged and Inactive is the type.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// When changing from Active to CarrierMerged, we need to log diffs to all the fields.
|
||||||
|
logDiffsFromActiveToNotActive(prevVal as Active, row)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Provides information about an active wifi network. */
|
/** Provides information about an active wifi network. */
|
||||||
@@ -76,6 +109,41 @@ sealed class WifiNetworkModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
|
||||||
|
if (prevVal !is Active) {
|
||||||
|
row.logChange(COL_NETWORK_TYPE, TYPE_ACTIVE)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevVal !is Active || prevVal.networkId != networkId) {
|
||||||
|
row.logChange(COL_NETWORK_ID, networkId)
|
||||||
|
}
|
||||||
|
if (prevVal !is Active || prevVal.isValidated != isValidated) {
|
||||||
|
row.logChange(COL_VALIDATED, isValidated)
|
||||||
|
}
|
||||||
|
if (prevVal !is Active || prevVal.level != level) {
|
||||||
|
row.logChange(COL_LEVEL, level ?: LEVEL_DEFAULT)
|
||||||
|
}
|
||||||
|
if (prevVal !is Active || prevVal.ssid != ssid) {
|
||||||
|
row.logChange(COL_SSID, ssid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(b/238425913): The passpoint-related values are frequently never used, so it
|
||||||
|
// would be great to not log them when they're not used.
|
||||||
|
if (prevVal !is Active || prevVal.isPasspointAccessPoint != isPasspointAccessPoint) {
|
||||||
|
row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint)
|
||||||
|
}
|
||||||
|
if (prevVal !is Active ||
|
||||||
|
prevVal.isOnlineSignUpForPasspointAccessPoint !=
|
||||||
|
isOnlineSignUpForPasspointAccessPoint) {
|
||||||
|
row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint)
|
||||||
|
}
|
||||||
|
if (prevVal !is Active ||
|
||||||
|
prevVal.passpointProviderFriendlyName != passpointProviderFriendlyName) {
|
||||||
|
row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
// Only include the passpoint-related values in the string if we have them. (Most
|
// Only include the passpoint-related values in the string if we have them. (Most
|
||||||
// networks won't have them so they'll be mostly clutter.)
|
// networks won't have them so they'll be mostly clutter.)
|
||||||
@@ -101,4 +169,37 @@ sealed class WifiNetworkModel {
|
|||||||
internal const val MAX_VALID_LEVEL = 4
|
internal const val MAX_VALID_LEVEL = 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun logDiffsFromActiveToNotActive(prevActive: Active, row: TableRowLogger) {
|
||||||
|
row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT)
|
||||||
|
row.logChange(COL_VALIDATED, false)
|
||||||
|
row.logChange(COL_LEVEL, LEVEL_DEFAULT)
|
||||||
|
row.logChange(COL_SSID, null)
|
||||||
|
|
||||||
|
if (prevActive.isPasspointAccessPoint) {
|
||||||
|
row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
|
||||||
|
}
|
||||||
|
if (prevActive.isOnlineSignUpForPasspointAccessPoint) {
|
||||||
|
row.logChange(COL_ONLINE_SIGN_UP, false)
|
||||||
|
}
|
||||||
|
if (prevActive.passpointProviderFriendlyName != null) {
|
||||||
|
row.logChange(COL_PASSPOINT_NAME, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const val TYPE_CARRIER_MERGED = "CarrierMerged"
|
||||||
|
const val TYPE_INACTIVE = "Inactive"
|
||||||
|
const val TYPE_ACTIVE = "Active"
|
||||||
|
|
||||||
|
const val COL_NETWORK_TYPE = "type"
|
||||||
|
const val COL_NETWORK_ID = "networkId"
|
||||||
|
const val COL_VALIDATED = "isValidated"
|
||||||
|
const val COL_LEVEL = "level"
|
||||||
|
const val COL_SSID = "ssid"
|
||||||
|
const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint"
|
||||||
|
const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint"
|
||||||
|
const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName"
|
||||||
|
|
||||||
|
const val LEVEL_DEFAULT = -1
|
||||||
|
const val NETWORK_ID_DEFAULT = -1
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
|
|||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.dagger.qualifiers.Application
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
import com.android.systemui.dagger.qualifiers.Main
|
||||||
|
import com.android.systemui.log.table.TableLogBuffer
|
||||||
|
import com.android.systemui.log.table.logDiffsForTable
|
||||||
|
import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
|
||||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
|
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
|
||||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
|
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
|
||||||
@@ -82,6 +85,7 @@ class WifiRepositoryImpl @Inject constructor(
|
|||||||
broadcastDispatcher: BroadcastDispatcher,
|
broadcastDispatcher: BroadcastDispatcher,
|
||||||
connectivityManager: ConnectivityManager,
|
connectivityManager: ConnectivityManager,
|
||||||
logger: ConnectivityPipelineLogger,
|
logger: ConnectivityPipelineLogger,
|
||||||
|
@WifiTableLog wifiTableLogBuffer: TableLogBuffer,
|
||||||
@Main mainExecutor: Executor,
|
@Main mainExecutor: Executor,
|
||||||
@Application scope: CoroutineScope,
|
@Application scope: CoroutineScope,
|
||||||
wifiManager: WifiManager?,
|
wifiManager: WifiManager?,
|
||||||
@@ -199,6 +203,12 @@ class WifiRepositoryImpl @Inject constructor(
|
|||||||
|
|
||||||
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
|
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
|
||||||
}
|
}
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.logDiffsForTable(
|
||||||
|
wifiTableLogBuffer,
|
||||||
|
columnPrefix = "wifiNetwork",
|
||||||
|
initialValue = WIFI_NETWORK_DEFAULT,
|
||||||
|
)
|
||||||
// There will be multiple wifi icons in different places that will frequently
|
// There will be multiple wifi icons in different places that will frequently
|
||||||
// subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures that
|
// subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures that
|
||||||
// new subscribes will get the latest value immediately upon subscription. Otherwise, the
|
// new subscribes will get the latest value immediately upon subscription. Otherwise, the
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.log.table
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
class TableChangeTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun setString_isString() {
|
||||||
|
val underTest = TableChange()
|
||||||
|
|
||||||
|
underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
|
||||||
|
underTest.set("fakeValue")
|
||||||
|
|
||||||
|
assertThat(underTest.hasData()).isTrue()
|
||||||
|
assertThat(underTest.getVal()).isEqualTo("fakeValue")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun setBoolean_isBoolean() {
|
||||||
|
val underTest = TableChange()
|
||||||
|
|
||||||
|
underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
|
||||||
|
underTest.set(true)
|
||||||
|
|
||||||
|
assertThat(underTest.hasData()).isTrue()
|
||||||
|
assertThat(underTest.getVal()).isEqualTo("true")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun setInt_isInt() {
|
||||||
|
val underTest = TableChange()
|
||||||
|
|
||||||
|
underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
|
||||||
|
underTest.set(8900)
|
||||||
|
|
||||||
|
assertThat(underTest.hasData()).isTrue()
|
||||||
|
assertThat(underTest.getVal()).isEqualTo("8900")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun setThenReset_isEmpty() {
|
||||||
|
val underTest = TableChange()
|
||||||
|
|
||||||
|
underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
|
||||||
|
underTest.set(8900)
|
||||||
|
underTest.reset(timestamp = 0, columnPrefix = "prefix", columnName = "name")
|
||||||
|
|
||||||
|
assertThat(underTest.hasData()).isFalse()
|
||||||
|
assertThat(underTest.getVal()).isEqualTo("null")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getName_hasPrefix() {
|
||||||
|
val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
|
||||||
|
|
||||||
|
assertThat(underTest.getName()).contains("fakePrefix")
|
||||||
|
assertThat(underTest.getName()).contains("fakeName")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getName_noPrefix() {
|
||||||
|
val underTest = TableChange(columnPrefix = "", columnName = "fakeName")
|
||||||
|
|
||||||
|
assertThat(underTest.getName()).contains("fakeName")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun resetThenSet_hasNewValue() {
|
||||||
|
val underTest = TableChange()
|
||||||
|
|
||||||
|
underTest.reset(timestamp = 100, columnPrefix = "prefix", columnName = "original")
|
||||||
|
underTest.set("fakeValue")
|
||||||
|
underTest.reset(timestamp = 0, columnPrefix = "", columnName = "updated")
|
||||||
|
underTest.set(8900)
|
||||||
|
|
||||||
|
assertThat(underTest.hasData()).isTrue()
|
||||||
|
assertThat(underTest.getName()).contains("updated")
|
||||||
|
assertThat(underTest.getName()).doesNotContain("prefix")
|
||||||
|
assertThat(underTest.getName()).doesNotContain("original")
|
||||||
|
assertThat(underTest.getVal()).isEqualTo("8900")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.log.table
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.util.time.FakeSystemClock
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import java.io.PrintWriter
|
||||||
|
import java.io.StringWriter
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
class TableLogBufferTest : SysuiTestCase() {
|
||||||
|
private lateinit var underTest: TableLogBuffer
|
||||||
|
|
||||||
|
private lateinit var systemClock: FakeSystemClock
|
||||||
|
private lateinit var outputWriter: StringWriter
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
systemClock = FakeSystemClock()
|
||||||
|
outputWriter = StringWriter()
|
||||||
|
|
||||||
|
underTest = TableLogBuffer(MAX_SIZE, NAME, systemClock)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException::class)
|
||||||
|
fun maxSizeZero_throwsException() {
|
||||||
|
TableLogBuffer(maxSize = 0, "name", systemClock)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dumpChanges_strChange_logsFromNext() {
|
||||||
|
systemClock.setCurrentTimeMillis(100L)
|
||||||
|
|
||||||
|
val prevDiffable =
|
||||||
|
object : TestDiffable() {
|
||||||
|
override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
|
||||||
|
row.logChange("stringValChange", "prevStringVal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val nextDiffable =
|
||||||
|
object : TestDiffable() {
|
||||||
|
override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
|
||||||
|
row.logChange("stringValChange", "newStringVal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
underTest.logDiffs("prefix", prevDiffable, nextDiffable)
|
||||||
|
|
||||||
|
val dumpedString = dumpChanges()
|
||||||
|
|
||||||
|
assertThat(dumpedString).contains("prefix")
|
||||||
|
assertThat(dumpedString).contains("stringValChange")
|
||||||
|
assertThat(dumpedString).contains("newStringVal")
|
||||||
|
assertThat(dumpedString).doesNotContain("prevStringVal")
|
||||||
|
assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dumpChanges_boolChange_logsFromNext() {
|
||||||
|
systemClock.setCurrentTimeMillis(100L)
|
||||||
|
|
||||||
|
val prevDiffable =
|
||||||
|
object : TestDiffable() {
|
||||||
|
override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
|
||||||
|
row.logChange("booleanValChange", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val nextDiffable =
|
||||||
|
object : TestDiffable() {
|
||||||
|
override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
|
||||||
|
row.logChange("booleanValChange", true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
underTest.logDiffs("prefix", prevDiffable, nextDiffable)
|
||||||
|
|
||||||
|
val dumpedString = dumpChanges()
|
||||||
|
|
||||||
|
assertThat(dumpedString).contains("prefix")
|
||||||
|
assertThat(dumpedString).contains("booleanValChange")
|
||||||
|
assertThat(dumpedString).contains("true")
|
||||||
|
assertThat(dumpedString).doesNotContain("false")
|
||||||
|
assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dumpChanges_intChange_logsFromNext() {
|
||||||
|
systemClock.setCurrentTimeMillis(100L)
|
||||||
|
|
||||||
|
val prevDiffable =
|
||||||
|
object : TestDiffable() {
|
||||||
|
override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
|
||||||
|
row.logChange("intValChange", 12345)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val nextDiffable =
|
||||||
|
object : TestDiffable() {
|
||||||
|
override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
|
||||||
|
row.logChange("intValChange", 67890)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
underTest.logDiffs("prefix", prevDiffable, nextDiffable)
|
||||||
|
|
||||||
|
val dumpedString = dumpChanges()
|
||||||
|
|
||||||
|
assertThat(dumpedString).contains("prefix")
|
||||||
|
assertThat(dumpedString).contains("intValChange")
|
||||||
|
assertThat(dumpedString).contains("67890")
|
||||||
|
assertThat(dumpedString).doesNotContain("12345")
|
||||||
|
assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dumpChanges_noPrefix() {
|
||||||
|
systemClock.setCurrentTimeMillis(100L)
|
||||||
|
|
||||||
|
val prevDiffable =
|
||||||
|
object : TestDiffable() {
|
||||||
|
override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
|
||||||
|
row.logChange("booleanValChange", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val nextDiffable =
|
||||||
|
object : TestDiffable() {
|
||||||
|
override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
|
||||||
|
row.logChange("booleanValChange", true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WHEN there's a blank prefix
|
||||||
|
underTest.logDiffs("", prevDiffable, nextDiffable)
|
||||||
|
|
||||||
|
val dumpedString = dumpChanges()
|
||||||
|
|
||||||
|
// THEN the dump still works
|
||||||
|
assertThat(dumpedString).contains("booleanValChange")
|
||||||
|
assertThat(dumpedString).contains("true")
|
||||||
|
assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dumpChanges_multipleChangesForSameColumn_logs() {
|
||||||
|
lateinit var valToDump: String
|
||||||
|
|
||||||
|
val diffable =
|
||||||
|
object : TestDiffable() {
|
||||||
|
override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
|
||||||
|
row.logChange("valChange", valToDump)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
systemClock.setCurrentTimeMillis(12000L)
|
||||||
|
valToDump = "stateValue12"
|
||||||
|
underTest.logDiffs(columnPrefix = "", diffable, diffable)
|
||||||
|
|
||||||
|
systemClock.setCurrentTimeMillis(20000L)
|
||||||
|
valToDump = "stateValue20"
|
||||||
|
underTest.logDiffs(columnPrefix = "", diffable, diffable)
|
||||||
|
|
||||||
|
systemClock.setCurrentTimeMillis(40000L)
|
||||||
|
valToDump = "stateValue40"
|
||||||
|
underTest.logDiffs(columnPrefix = "", diffable, diffable)
|
||||||
|
|
||||||
|
systemClock.setCurrentTimeMillis(45000L)
|
||||||
|
valToDump = "stateValue45"
|
||||||
|
underTest.logDiffs(columnPrefix = "", diffable, diffable)
|
||||||
|
|
||||||
|
val dumpedString = dumpChanges()
|
||||||
|
|
||||||
|
assertThat(dumpedString).contains("valChange")
|
||||||
|
assertThat(dumpedString).contains("stateValue12")
|
||||||
|
assertThat(dumpedString).contains("stateValue20")
|
||||||
|
assertThat(dumpedString).contains("stateValue40")
|
||||||
|
assertThat(dumpedString).contains("stateValue45")
|
||||||
|
assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(12000L))
|
||||||
|
assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(20000L))
|
||||||
|
assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(40000L))
|
||||||
|
assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(45000L))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dumpChanges_multipleChangesAtOnce_logs() {
|
||||||
|
systemClock.setCurrentTimeMillis(100L)
|
||||||
|
|
||||||
|
val prevDiffable = object : TestDiffable() {}
|
||||||
|
val nextDiffable =
|
||||||
|
object : TestDiffable() {
|
||||||
|
override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
|
||||||
|
row.logChange("status", "in progress")
|
||||||
|
row.logChange("connected", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
underTest.logDiffs(columnPrefix = "", prevDiffable, nextDiffable)
|
||||||
|
|
||||||
|
val dumpedString = dumpChanges()
|
||||||
|
|
||||||
|
assertThat(dumpedString).contains("status")
|
||||||
|
assertThat(dumpedString).contains("in progress")
|
||||||
|
assertThat(dumpedString).contains("connected")
|
||||||
|
assertThat(dumpedString).contains("false")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dumpChanges_rotatesIfBufferIsFull() {
|
||||||
|
lateinit var valToDump: String
|
||||||
|
|
||||||
|
val prevDiffable = object : TestDiffable() {}
|
||||||
|
val nextDiffable =
|
||||||
|
object : TestDiffable() {
|
||||||
|
override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
|
||||||
|
row.logChange("status", valToDump)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in 0 until MAX_SIZE + 3) {
|
||||||
|
valToDump = "testString[$i]"
|
||||||
|
underTest.logDiffs(columnPrefix = "", prevDiffable, nextDiffable)
|
||||||
|
}
|
||||||
|
|
||||||
|
val dumpedString = dumpChanges()
|
||||||
|
|
||||||
|
assertThat(dumpedString).doesNotContain("testString[0]")
|
||||||
|
assertThat(dumpedString).doesNotContain("testString[1]")
|
||||||
|
assertThat(dumpedString).doesNotContain("testString[2]")
|
||||||
|
assertThat(dumpedString).contains("testString[3]")
|
||||||
|
assertThat(dumpedString).contains("testString[${MAX_SIZE + 2}]")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dumpChanges(): String {
|
||||||
|
underTest.dumpChanges(PrintWriter(outputWriter))
|
||||||
|
return outputWriter.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class TestDiffable : Diffable<TestDiffable> {
|
||||||
|
override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val NAME = "TestTableBuffer"
|
||||||
|
private const val MAX_SIZE = 10
|
||||||
@@ -18,8 +18,10 @@ package com.android.systemui.statusbar.pipeline.wifi.data.model
|
|||||||
|
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.log.table.TableRowLogger
|
||||||
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL
|
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL
|
||||||
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MIN_VALID_LEVEL
|
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MIN_VALID_LEVEL
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@@ -48,6 +50,125 @@ class WifiNetworkModelTest : SysuiTestCase() {
|
|||||||
WifiNetworkModel.Active(NETWORK_ID, level = MAX_VALID_LEVEL + 1)
|
WifiNetworkModel.Active(NETWORK_ID, level = MAX_VALID_LEVEL + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Non-exhaustive logDiffs test -- just want to make sure the logging logic isn't totally broken
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun logDiffs_inactiveToActive_logsAllActiveFields() {
|
||||||
|
val logger = TestLogger()
|
||||||
|
val activeNetwork =
|
||||||
|
WifiNetworkModel.Active(
|
||||||
|
networkId = 5,
|
||||||
|
isValidated = true,
|
||||||
|
level = 3,
|
||||||
|
ssid = "Test SSID"
|
||||||
|
)
|
||||||
|
|
||||||
|
activeNetwork.logDiffs(prevVal = WifiNetworkModel.Inactive, logger)
|
||||||
|
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE))
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5"))
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_LEVEL, "3"))
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun logDiffs_activeToInactive_resetsAllActiveFields() {
|
||||||
|
val logger = TestLogger()
|
||||||
|
val activeNetwork =
|
||||||
|
WifiNetworkModel.Active(
|
||||||
|
networkId = 5,
|
||||||
|
isValidated = true,
|
||||||
|
level = 3,
|
||||||
|
ssid = "Test SSID"
|
||||||
|
)
|
||||||
|
|
||||||
|
WifiNetworkModel.Inactive.logDiffs(prevVal = activeNetwork, logger)
|
||||||
|
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_INACTIVE))
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString()))
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun logDiffs_carrierMergedToActive_logsAllActiveFields() {
|
||||||
|
val logger = TestLogger()
|
||||||
|
val activeNetwork =
|
||||||
|
WifiNetworkModel.Active(
|
||||||
|
networkId = 5,
|
||||||
|
isValidated = true,
|
||||||
|
level = 3,
|
||||||
|
ssid = "Test SSID"
|
||||||
|
)
|
||||||
|
|
||||||
|
activeNetwork.logDiffs(prevVal = WifiNetworkModel.CarrierMerged, logger)
|
||||||
|
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE))
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5"))
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_LEVEL, "3"))
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun logDiffs_activeToCarrierMerged_resetsAllActiveFields() {
|
||||||
|
val logger = TestLogger()
|
||||||
|
val activeNetwork =
|
||||||
|
WifiNetworkModel.Active(
|
||||||
|
networkId = 5,
|
||||||
|
isValidated = true,
|
||||||
|
level = 3,
|
||||||
|
ssid = "Test SSID"
|
||||||
|
)
|
||||||
|
|
||||||
|
WifiNetworkModel.CarrierMerged.logDiffs(prevVal = activeNetwork, logger)
|
||||||
|
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED))
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString()))
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
|
||||||
|
assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun logDiffs_activeChangesLevel_onlyLevelLogged() {
|
||||||
|
val logger = TestLogger()
|
||||||
|
val prevActiveNetwork =
|
||||||
|
WifiNetworkModel.Active(
|
||||||
|
networkId = 5,
|
||||||
|
isValidated = true,
|
||||||
|
level = 3,
|
||||||
|
ssid = "Test SSID"
|
||||||
|
)
|
||||||
|
val newActiveNetwork =
|
||||||
|
WifiNetworkModel.Active(
|
||||||
|
networkId = 5,
|
||||||
|
isValidated = true,
|
||||||
|
level = 2,
|
||||||
|
ssid = "Test SSID"
|
||||||
|
)
|
||||||
|
|
||||||
|
newActiveNetwork.logDiffs(prevActiveNetwork, logger)
|
||||||
|
|
||||||
|
assertThat(logger.changes).isEqualTo(listOf(Pair(COL_LEVEL, "2")))
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestLogger : TableRowLogger {
|
||||||
|
val changes = mutableListOf<Pair<String, String>>()
|
||||||
|
|
||||||
|
override fun logChange(columnName: String, value: String?) {
|
||||||
|
changes.add(Pair(columnName, value.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun logChange(columnName: String, value: Int) {
|
||||||
|
changes.add(Pair(columnName, value.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun logChange(columnName: String, value: Boolean) {
|
||||||
|
changes.add(Pair(columnName, value.toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val NETWORK_ID = 2
|
private const val NETWORK_ID = 2
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import android.net.wifi.WifiManager.TrafficStateCallback
|
|||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
|
import com.android.systemui.log.table.TableLogBuffer
|
||||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||||
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
|
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
|
||||||
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
|
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
|
||||||
@@ -69,6 +70,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
|
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
|
||||||
@Mock private lateinit var logger: ConnectivityPipelineLogger
|
@Mock private lateinit var logger: ConnectivityPipelineLogger
|
||||||
|
@Mock private lateinit var tableLogger: TableLogBuffer
|
||||||
@Mock private lateinit var connectivityManager: ConnectivityManager
|
@Mock private lateinit var connectivityManager: ConnectivityManager
|
||||||
@Mock private lateinit var wifiManager: WifiManager
|
@Mock private lateinit var wifiManager: WifiManager
|
||||||
private lateinit var executor: Executor
|
private lateinit var executor: Executor
|
||||||
@@ -804,6 +806,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
|
|||||||
broadcastDispatcher,
|
broadcastDispatcher,
|
||||||
connectivityManager,
|
connectivityManager,
|
||||||
logger,
|
logger,
|
||||||
|
tableLogger,
|
||||||
executor,
|
executor,
|
||||||
scope,
|
scope,
|
||||||
wifiManagerToUse,
|
wifiManagerToUse,
|
||||||
|
|||||||
Reference in New Issue
Block a user