Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 55af7ba0 authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Merge "Merge changes I48a0acc7,I57fbba0a into tm-qpr-dev am: 4a685521 am:...

Merge "Merge changes I48a0acc7,I57fbba0a into tm-qpr-dev am: 4a685521 am: d6cb1eaa" into udc-dev am: 2791471b

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21872056



Change-Id: Ifab023477a03b1e78f2bc6341549a0229b666b5b
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 776027cd 2791471b
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -59,6 +59,17 @@ data class TableChange(
        int = value
    }

    /** Updates this to store the same value as [change]. */
    fun updateTo(change: TableChange) {
        reset(change.timestamp, change.columnPrefix, change.columnName)
        when (change.type) {
            DataType.STRING -> set(change.str)
            DataType.INT -> set(change.int)
            DataType.BOOLEAN -> set(change.bool)
            DataType.EMPTY -> {}
        }
    }

    /** Returns true if this object has a change. */
    fun hasData(): Boolean {
        return columnName.isNotBlank() && type != DataType.EMPTY
+36 −1
Original line number Diff line number Diff line
@@ -16,13 +16,13 @@

package com.android.systemui.log.table

import android.os.Trace
import com.android.systemui.Dumpable
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.
@@ -82,6 +82,19 @@ class TableLogBuffer(

    private val buffer = RingBuffer(maxSize) { TableChange() }

    // Stores the most recently evicted value for each column name (indexed on column name).
    //
    // Why it's necessary: Because we use a RingBuffer of a fixed size, it's possible that a column
    // that's logged infrequently will eventually get pushed out by a different column that's
    // logged more frequently. Then, that infrequently-logged column isn't present in the RingBuffer
    // at all and we have no logs that the column ever existed. This is a problem because the
    // column's information is still relevant, valid, and may be critical to debugging issues.
    //
    // Fix: When a change is being evicted from the RingBuffer, we store it in this map (based on
    // its [TableChange.getName()]. This ensures that we always have at least one value for every
    // column ever logged. See b/272016422 for more details.
    private val lastEvictedValues = mutableMapOf<String, TableChange>()

    // A [TableRowLogger] object, re-used each time [logDiffs] is called.
    // (Re-used to avoid object allocation.)
    private val tempRow = TableRowLoggerImpl(0, columnPrefix = "", this)
@@ -138,18 +151,24 @@ class TableLogBuffer(
    // timestamps.)

    private fun logChange(timestamp: Long, prefix: String, columnName: String, value: String?) {
        Trace.beginSection("TableLogBuffer#logChange(string)")
        val change = obtain(timestamp, prefix, columnName)
        change.set(value)
        Trace.endSection()
    }

    private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Boolean) {
        Trace.beginSection("TableLogBuffer#logChange(boolean)")
        val change = obtain(timestamp, prefix, columnName)
        change.set(value)
        Trace.endSection()
    }

    private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Int?) {
        Trace.beginSection("TableLogBuffer#logChange(int)")
        val change = obtain(timestamp, prefix, columnName)
        change.set(value)
        Trace.endSection()
    }

    // TODO(b/259454430): Add additional change types here.
@@ -158,6 +177,9 @@ class TableLogBuffer(
    private fun obtain(timestamp: Long, prefix: String, columnName: String): TableChange {
        verifyValidName(prefix, columnName)
        val tableChange = buffer.advance()
        if (tableChange.hasData()) {
            saveEvictedValue(tableChange)
        }
        tableChange.reset(timestamp, prefix, columnName)
        return tableChange
    }
@@ -173,10 +195,23 @@ class TableLogBuffer(
        }
    }

    private fun saveEvictedValue(change: TableChange) {
        Trace.beginSection("TableLogBuffer#saveEvictedValue")
        val name = change.getName()
        val previouslyEvicted =
            lastEvictedValues[name] ?: TableChange().also { lastEvictedValues[name] = it }
        // For recycling purposes, update the existing object in the map with the new information
        // instead of creating a new object.
        previouslyEvicted.updateTo(change)
        Trace.endSection()
    }

    @Synchronized
    override fun dump(pw: PrintWriter, args: Array<out String>) {
        pw.println(HEADER_PREFIX + name)
        pw.println("version $VERSION")

        lastEvictedValues.values.sortedBy { it.timestamp }.forEach { it.dump(pw) }
        for (i in 0 until buffer.size) {
            buffer[i].dump(pw)
        }
+88 −0
Original line number Diff line number Diff line
@@ -121,4 +121,92 @@ class TableChangeTest : SysuiTestCase() {
        assertThat(underTest.getName()).doesNotContain("original")
        assertThat(underTest.getVal()).isEqualTo("8900")
    }

    @Test
    fun updateTo_emptyToString_isString() {
        val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")

        val new = TableChange(columnPrefix = "newPrefix", columnName = "newName")
        new.set("newString")
        underTest.updateTo(new)

        assertThat(underTest.hasData()).isTrue()
        assertThat(underTest.getName()).contains("newPrefix")
        assertThat(underTest.getName()).contains("newName")
        assertThat(underTest.getVal()).isEqualTo("newString")
    }

    @Test
    fun updateTo_intToEmpty_isEmpty() {
        val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
        underTest.set(42)

        val new = TableChange(columnPrefix = "newPrefix", columnName = "newName")
        underTest.updateTo(new)

        assertThat(underTest.hasData()).isFalse()
        assertThat(underTest.getName()).contains("newPrefix")
        assertThat(underTest.getName()).contains("newName")
        assertThat(underTest.getVal()).isEqualTo("null")
    }

    @Test
    fun updateTo_stringToBool_isBool() {
        val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
        underTest.set("oldString")

        val new = TableChange(columnPrefix = "newPrefix", columnName = "newName")
        new.set(true)
        underTest.updateTo(new)

        assertThat(underTest.hasData()).isTrue()
        assertThat(underTest.getName()).contains("newPrefix")
        assertThat(underTest.getName()).contains("newName")
        assertThat(underTest.getVal()).isEqualTo("true")
    }

    @Test
    fun updateTo_intToString_isString() {
        val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
        underTest.set(43)

        val new = TableChange(columnPrefix = "newPrefix", columnName = "newName")
        new.set("newString")
        underTest.updateTo(new)

        assertThat(underTest.hasData()).isTrue()
        assertThat(underTest.getName()).contains("newPrefix")
        assertThat(underTest.getName()).contains("newName")
        assertThat(underTest.getVal()).isEqualTo("newString")
    }

    @Test
    fun updateTo_boolToInt_isInt() {
        val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
        underTest.set(false)

        val new = TableChange(columnPrefix = "newPrefix", columnName = "newName")
        new.set(44)
        underTest.updateTo(new)

        assertThat(underTest.hasData()).isTrue()
        assertThat(underTest.getName()).contains("newPrefix")
        assertThat(underTest.getName()).contains("newName")
        assertThat(underTest.getVal()).isEqualTo("44")
    }

    @Test
    fun updateTo_boolToNewBool_isNewBool() {
        val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
        underTest.set(false)

        val new = TableChange(columnPrefix = "newPrefix", columnName = "newName")
        new.set(true)
        underTest.updateTo(new)

        assertThat(underTest.hasData()).isTrue()
        assertThat(underTest.getName()).contains("newPrefix")
        assertThat(underTest.getName()).contains("newName")
        assertThat(underTest.getVal()).isEqualTo("true")
    }
}
+226 −1
Original line number Diff line number Diff line
@@ -435,11 +435,236 @@ class TableLogBufferTest : SysuiTestCase() {

        assertThat(dumpedString).doesNotContain("testString[0]")
        assertThat(dumpedString).doesNotContain("testString[1]")
        assertThat(dumpedString).doesNotContain("testString[2]")
        // The buffer should contain [MAX_SIZE + 1] entries since we also save the most recently
        // evicted value.
        assertThat(dumpedString).contains("testString[2]")
        assertThat(dumpedString).contains("testString[3]")
        assertThat(dumpedString).contains("testString[${MAX_SIZE + 2}]")
    }

    @Test
    fun columnEvicted_lastKnownColumnValueInDump() {
        systemClock.setCurrentTimeMillis(100L)
        underTest.logChange(prefix = "", columnName = "willBeEvicted", value = "evictedValue")

        // Exactly fill the buffer so that "willBeEvicted" is evicted
        for (i in 0 until MAX_SIZE) {
            systemClock.advanceTime(100L)
            val dumpString = "fillString[$i]"
            underTest.logChange(prefix = "", columnName = "fillingColumn", value = dumpString)
        }

        val dumpedString = dumpChanges()

        // Expect that we'll have both the evicted column entry...
        val evictedColumnLog =
            TABLE_LOG_DATE_FORMAT.format(100L) +
                SEPARATOR +
                "willBeEvicted" +
                SEPARATOR +
                "evictedValue"
        assertThat(dumpedString).contains(evictedColumnLog)

        // ... *and* all of the fillingColumn entries.
        val firstFillingColumnLog =
            TABLE_LOG_DATE_FORMAT.format(200L) +
                SEPARATOR +
                "fillingColumn" +
                SEPARATOR +
                "fillString[0]"
        val lastFillingColumnLog =
            TABLE_LOG_DATE_FORMAT.format(1100L) +
                SEPARATOR +
                "fillingColumn" +
                SEPARATOR +
                "fillString[9]"
        assertThat(dumpedString).contains(firstFillingColumnLog)
        assertThat(dumpedString).contains(lastFillingColumnLog)
    }

    @Test
    fun multipleColumnsEvicted_allColumnsInDump() {
        systemClock.setCurrentTimeMillis(100L)
        underTest.logChange(prefix = "", columnName = "willBeEvictedString", value = "evictedValue")
        systemClock.advanceTime(100L)
        underTest.logChange(prefix = "", columnName = "willBeEvictedInt", value = 45)
        systemClock.advanceTime(100L)
        underTest.logChange(prefix = "", columnName = "willBeEvictedBool", value = true)

        // Exactly fill the buffer so that all the above columns will be evicted
        for (i in 0 until MAX_SIZE) {
            systemClock.advanceTime(100L)
            val dumpString = "fillString[$i]"
            underTest.logChange(prefix = "", columnName = "fillingColumn", value = dumpString)
        }

        val dumpedString = dumpChanges()

        // Expect that we'll have all the evicted column entries...
        val evictedColumnLogString =
            TABLE_LOG_DATE_FORMAT.format(100L) +
                SEPARATOR +
                "willBeEvictedString" +
                SEPARATOR +
                "evictedValue"
        val evictedColumnLogInt =
            TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + "willBeEvictedInt" + SEPARATOR + "45"
        val evictedColumnLogBool =
            TABLE_LOG_DATE_FORMAT.format(300L) +
                SEPARATOR +
                "willBeEvictedBool" +
                SEPARATOR +
                "true"
        assertThat(dumpedString).contains(evictedColumnLogString)
        assertThat(dumpedString).contains(evictedColumnLogInt)
        assertThat(dumpedString).contains(evictedColumnLogBool)

        // ... *and* all of the fillingColumn entries.
        val firstFillingColumnLog =
            TABLE_LOG_DATE_FORMAT.format(400) +
                SEPARATOR +
                "fillingColumn" +
                SEPARATOR +
                "fillString[0]"
        val lastFillingColumnLog =
            TABLE_LOG_DATE_FORMAT.format(1300) +
                SEPARATOR +
                "fillingColumn" +
                SEPARATOR +
                "fillString[9]"
        assertThat(dumpedString).contains(firstFillingColumnLog)
        assertThat(dumpedString).contains(lastFillingColumnLog)
    }

    @Test
    fun multipleColumnsEvicted_differentPrefixSameName_allColumnsInDump() {
        systemClock.setCurrentTimeMillis(100L)
        underTest.logChange(prefix = "prefix1", columnName = "sameName", value = "value1")
        systemClock.advanceTime(100L)
        underTest.logChange(prefix = "prefix2", columnName = "sameName", value = "value2")
        systemClock.advanceTime(100L)
        underTest.logChange(prefix = "prefix3", columnName = "sameName", value = "value3")

        // Exactly fill the buffer so that all the above columns will be evicted
        for (i in 0 until MAX_SIZE) {
            systemClock.advanceTime(100L)
            val dumpString = "fillString[$i]"
            underTest.logChange(prefix = "", columnName = "fillingColumn", value = dumpString)
        }

        val dumpedString = dumpChanges()

        // Expect that we'll have all the evicted column entries
        val evictedColumn1 =
            TABLE_LOG_DATE_FORMAT.format(100L) +
                SEPARATOR +
                "prefix1.sameName" +
                SEPARATOR +
                "value1"
        val evictedColumn2 =
            TABLE_LOG_DATE_FORMAT.format(200L) +
                SEPARATOR +
                "prefix2.sameName" +
                SEPARATOR +
                "value2"
        val evictedColumn3 =
            TABLE_LOG_DATE_FORMAT.format(300L) +
                SEPARATOR +
                "prefix3.sameName" +
                SEPARATOR +
                "value3"
        assertThat(dumpedString).contains(evictedColumn1)
        assertThat(dumpedString).contains(evictedColumn2)
        assertThat(dumpedString).contains(evictedColumn3)
    }

    @Test
    fun multipleColumnsEvicted_dumpSortedByTimestamp() {
        systemClock.setCurrentTimeMillis(100L)
        underTest.logChange(prefix = "", columnName = "willBeEvictedFirst", value = "evictedValue")
        systemClock.advanceTime(100L)
        underTest.logChange(prefix = "", columnName = "willBeEvictedSecond", value = 45)
        systemClock.advanceTime(100L)
        underTest.logChange(prefix = "", columnName = "willBeEvictedThird", value = true)

        // Exactly fill the buffer with so that all the above columns will be evicted
        for (i in 0 until MAX_SIZE) {
            systemClock.advanceTime(100L)
            val dumpString = "fillString[$i]"
            underTest.logChange(prefix = "", columnName = "fillingColumn", value = dumpString)
        }

        val dumpedString = dumpChanges()

        // Expect that we'll have all the evicted column entries in timestamp order
        val firstEvictedLog =
            TABLE_LOG_DATE_FORMAT.format(100L) +
                SEPARATOR +
                "willBeEvictedFirst" +
                SEPARATOR +
                "evictedValue"
        val secondEvictedLog =
            TABLE_LOG_DATE_FORMAT.format(200L) +
                SEPARATOR +
                "willBeEvictedSecond" +
                SEPARATOR +
                "45"
        val thirdEvictedLog =
            TABLE_LOG_DATE_FORMAT.format(300L) +
                SEPARATOR +
                "willBeEvictedThird" +
                SEPARATOR +
                "true"
        assertThat(dumpedString).contains(firstEvictedLog)
        val stringAfterFirst = dumpedString.substringAfter(firstEvictedLog)
        assertThat(stringAfterFirst).contains(secondEvictedLog)
        val stringAfterSecond = stringAfterFirst.substringAfter(secondEvictedLog)
        assertThat(stringAfterSecond).contains(thirdEvictedLog)
    }

    @Test
    fun sameColumnEvictedMultipleTimes_onlyLastEvictionInDump() {
        systemClock.setCurrentTimeMillis(0L)

        for (i in 1 until 4) {
            systemClock.advanceTime(100L)
            val dumpString = "evicted[$i]"
            underTest.logChange(prefix = "", columnName = "evictedColumn", value = dumpString)
        }

        // Exactly fill the buffer so that all the entries for "evictedColumn" will be evicted.
        for (i in 0 until MAX_SIZE) {
            systemClock.advanceTime(100L)
            val dumpString = "fillString[$i]"
            underTest.logChange(prefix = "", columnName = "fillingColumn", value = dumpString)
        }

        val dumpedString = dumpChanges()

        // Expect that we only have the most recent evicted column entry
        val evictedColumnLog1 =
            TABLE_LOG_DATE_FORMAT.format(100L) +
                SEPARATOR +
                "evictedColumn" +
                SEPARATOR +
                "evicted[1]"
        val evictedColumnLog2 =
            TABLE_LOG_DATE_FORMAT.format(200L) +
                SEPARATOR +
                "evictedColumn" +
                SEPARATOR +
                "evicted[2]"
        val evictedColumnLog3 =
            TABLE_LOG_DATE_FORMAT.format(300L) +
                SEPARATOR +
                "evictedColumn" +
                SEPARATOR +
                "evicted[3]"
        assertThat(dumpedString).doesNotContain(evictedColumnLog1)
        assertThat(dumpedString).doesNotContain(evictedColumnLog2)
        assertThat(dumpedString).contains(evictedColumnLog3)
    }

    private fun dumpChanges(): String {
        underTest.dump(PrintWriter(outputWriter), arrayOf())
        return outputWriter.toString()