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

Commit 97559684 authored by Evan Laird's avatar Evan Laird
Browse files

Add a table data logger for ABT

This CL adds support for a simple, tabular-data logger for dumpsys to be
consumed by something that can pretty-print the data.

Test: atest DumpsysTableLoggerTest
Bug: 220386889
Change-Id: I2db7d6891d4d9a18b72b95432c053efa2d49c2e6
parent 3a9b1b82
Loading
Loading
Loading
Loading
+125 −0
Original line number Original line Diff line number Diff line
/*
 * 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.dump

import java.io.PrintWriter

/**
 * Utility for logging nice table data to be parsed (and pretty printed) in bugreports. The general
 * idea here is to feed your nice, table-like data to this class, which embeds the schema and rows
 * into the dumpsys, wrapped in a known start and stop tags. Later, one can build a simple parser
 * and pretty-print this data in a table
 *
 * Note: Something should be said here about silently eating errors by filtering out malformed
 * lines. Because this class is expected to be utilized only during a dumpsys, it doesn't feel
 * most correct to throw an exception here (since an exception can often be the reason that this
 * class is created). Because of this, [DumpsysTableLogger] will simply filter out invalid lines
 * based solely on line length. This behavior might need to be revisited in the future.
 *
 * USAGE:
 * Assuming we have some data that would be logged to dumpsys like so:
 *
 * ```
 *      1: field1=val1, field2=val2..., fieldN=valN
 *      //...
 *      M: field1M=val1M, ..., fieldNM
 * ```
 *
 * You can break the `field<n>` values out into a columns spec:
 * ```
 *      val cols = [field1, field2,...,fieldN]
 * ```
 * And then take all of the historical data lines (1 through M), and break them out into their own
 * lists:
 * ```
 *      val rows = [
 *          [field10, field20,..., fieldN0],
 *          //...
 *          [field1M, field2M,..., fieldNM]
 *      ]
 * ```
 *
 * Lastly, create a bugreport-unique section name, and use the table logger to write the data to
 * dumpsys:
 * ```
 *      val logger = DumpsysTableLogger(uniqueName, cols, rows)
 *      logger.printTableData(pw)
 * ```
 *
 * The expected output in the dumpsys would be:
 * ```
 *      SystemUI TableSection START: <SectionName>
 *      version 1
 *      col1|col2|...|colN
 *      field10|field20|...|fieldN0
 *      //...
 *      field1M|field2M|...|fieldNM
 *      SystemUI TableSection END: <SectionName>
 * ```
 *
 *  @param sectionName A name for the table data section. Should be unique in the bugreport
 *  @param columns Definition for the columns of the table. This should be the same length as all
 *      data rows
 *  @param rows List of rows to be displayed in the table
 */
class DumpsysTableLogger(
    private val sectionName: String,
    private val columns: List<String>,
    private val rows: List<Row>
) {

    fun printTableData(pw: PrintWriter) {
        printSectionStart(pw)
        printSchema(pw)
        printData(pw)
        printSectionEnd(pw)
    }

    private fun printSectionStart(pw: PrintWriter) {
        pw.println(HEADER_PREFIX + sectionName)
        pw.println("version $VERSION")
    }

    private fun printSectionEnd(pw: PrintWriter) {
        pw.println(FOOTER_PREFIX + sectionName)
    }

    private fun printSchema(pw: PrintWriter) {
        pw.println(columns.joinToString(separator = SEPARATOR))
    }

    private fun printData(pw: PrintWriter) {
        val count = columns.size
        rows
            .filter { it.size == count }
            .forEach { dataLine ->
                pw.println(dataLine.joinToString(separator = SEPARATOR))
        }
    }
}

typealias Row = List<String>

/**
 * DO NOT CHANGE! (but if you must...)
 *  1. Update the version number
 *  2. Update any consumers to parse the new version
 */
private const val HEADER_PREFIX = "SystemUI TableSection START: "
private const val FOOTER_PREFIX = "SystemUI TableSection END: "
private const val SEPARATOR = "|" // TBD
private const val VERSION = "1"
 No newline at end of file
+28 −0
Original line number Original line Diff line number Diff line
@@ -46,6 +46,34 @@ open class ConnectivityState {
        }
        }
    }
    }


    protected open fun tableColumns(): List<String> {
        return listOf(
            "connected",
            "enabled",
            "activityIn",
            "activityOut",
            "level",
            "iconGroup",
            "inetCondition",
            "rssi",
            "time")
    }

    protected open fun tableData(): List<String> {
        return listOf(
            connected,
            enabled,
            activityIn,
            activityOut,
            level,
            iconGroup,
            inetCondition,
            rssi,
            sSDF.format(time)).map {
                it.toString()
        }
    }

    protected open fun copyFrom(other: ConnectivityState) {
    protected open fun copyFrom(other: ConnectivityState) {
        connected = other.connected
        connected = other.connected
        enabled = other.enabled
        enabled = other.enabled
+2 −0
Original line number Original line Diff line number Diff line
@@ -828,6 +828,8 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
                    + (mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - i) + "): "
                    + (mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - i) + "): "
                    + mMobileStatusHistory[i & (STATUS_HISTORY_SIZE - 1)]);
                    + mMobileStatusHistory[i & (STATUS_HISTORY_SIZE - 1)]);
        }
        }

        dumpTableData(pw);
    }
    }


    /** Box for QS icon info */
    /** Box for QS icon info */
+46 −0
Original line number Original line Diff line number Diff line
@@ -162,6 +162,52 @@ internal class MobileState(
        builder.append("displayInfo=$telephonyDisplayInfo")
        builder.append("displayInfo=$telephonyDisplayInfo")
    }
    }


    override fun tableColumns(): List<String> {
        val columns = listOf("dataSim",
            "networkName",
            "networkNameData",
            "dataConnected",
            "roaming",
            "isDefault",
            "isEmergency",
            "airplaneMode",
            "carrierNetworkChangeMode",
            "userSetup",
            "dataState",
            "defaultDataOff",
            "showQuickSettingsRatIcon",
            "voiceServiceState",
            "isInService",
            "serviceState",
            "signalStrength",
            "displayInfo")

        return super.tableColumns() + columns
    }

    override fun tableData(): List<String> {
        val columns = listOf(dataSim,
                networkName,
                networkNameData,
                dataConnected,
                roaming,
                isDefault,
                isEmergency,
                airplaneMode,
                carrierNetworkChangeMode,
                userSetup,
                dataState,
                defaultDataOff,
                showQuickSettingsRatIcon(),
                getVoiceServiceState(),
                isInService(),
                serviceState?.minLog() ?: "(null)",
                signalStrength?.minLog() ?: "(null)",
                telephonyDisplayInfo).map { it.toString() }

        return super.tableData() + columns
    }

    override fun equals(other: Any?): Boolean {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (this === other) return true
        if (javaClass != other?.javaClass) return false
        if (javaClass != other?.javaClass) return false
+61 −10
Original line number Original line Diff line number Diff line
@@ -22,9 +22,12 @@ import android.content.Context;
import android.util.Log;
import android.util.Log;


import com.android.settingslib.SignalIcon.IconGroup;
import com.android.settingslib.SignalIcon.IconGroup;
import com.android.systemui.dump.DumpsysTableLogger;


import java.io.PrintWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.BitSet;
import java.util.List;




/**
/**
@@ -193,6 +196,26 @@ public abstract class SignalController<T extends ConnectivityState, I extends Ic
        pw.println("  - " + mTag + " -----");
        pw.println("  - " + mTag + " -----");
        pw.println("  Current State: " + mCurrentState);
        pw.println("  Current State: " + mCurrentState);
        if (RECORD_HISTORY) {
        if (RECORD_HISTORY) {
            List<ConnectivityState> history = getOrderedHistoryExcludingCurrentState();
            for (int i = 0; i < history.size(); i++) {
                pw.println("  Previous State(" + (i + 1) + "): " + mHistory[i]);
            }
        }
    }

    /**
     * mHistory is a ring, so use this method to get the time-ordered (from youngest to oldest)
     * list of historical states. Filters out any state whose `time` is `0`.
     *
     * For ease of compatibility, this list returns JUST the historical states, not the current
     * state which has yet to be copied into the history
     *
     * @see #getOrderedHistory()
     * @return historical states, ordered from newest to oldest
     */
    List<ConnectivityState> getOrderedHistoryExcludingCurrentState() {
        ArrayList<ConnectivityState> history = new ArrayList<>();

        // Count up the states that actually contain time stamps, and only display those.
        // Count up the states that actually contain time stamps, and only display those.
        int size = 0;
        int size = 0;
        for (int i = 0; i < HISTORY_SIZE; i++) {
        for (int i = 0; i < HISTORY_SIZE; i++) {
@@ -201,10 +224,38 @@ public abstract class SignalController<T extends ConnectivityState, I extends Ic
        // Print out the previous states in ordered number.
        // Print out the previous states in ordered number.
        for (int i = mHistoryIndex + HISTORY_SIZE - 1;
        for (int i = mHistoryIndex + HISTORY_SIZE - 1;
                i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
                i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
                pw.println("  Previous State(" + (mHistoryIndex + HISTORY_SIZE - i) + "): "
            history.add(mHistory[i & (HISTORY_SIZE - 1)]);
                        + mHistory[i & (HISTORY_SIZE - 1)]);
        }
        }

        return history;
    }
    }

    /**
     * Get the ordered history states, including the current yet-to-be-copied state. Useful for
     * logging
     *
     * @see #getOrderedHistoryExcludingCurrentState()
     * @return [currentState, historicalState...] array
     */
    List<ConnectivityState> getOrderedHistory() {
        ArrayList<ConnectivityState> history = new ArrayList<>();
        // Start with the current state
        history.add(mCurrentState);
        history.addAll(getOrderedHistoryExcludingCurrentState());
        return history;
    }

    void dumpTableData(PrintWriter pw) {
        List<List<String>> tableData = new ArrayList<List<String>>();
        List<ConnectivityState> history = getOrderedHistory();
        for (int i = 0; i < history.size(); i++) {
            tableData.add(history.get(i).tableData());
        }

        DumpsysTableLogger logger =
                new DumpsysTableLogger(mTag, mCurrentState.tableColumns(), tableData);

        logger.printTableData(pw);
    }
    }


    final void notifyListeners() {
    final void notifyListeners() {
Loading