1 /* <lambda>null2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.dump 18 19 import android.os.Trace 20 import java.io.PrintWriter 21 22 /** 23 * Utility for logging nice table data to be parsed (and pretty printed) in bugreports. The general 24 * idea here is to feed your nice, table-like data to this class, which embeds the schema and rows 25 * into the dumpsys, wrapped in a known start and stop tags. Later, one can build a simple parser 26 * and pretty-print this data in a table 27 * 28 * Note: Something should be said here about silently eating errors by filtering out malformed 29 * lines. Because this class is expected to be utilized only during a dumpsys, it doesn't feel 30 * most correct to throw an exception here (since an exception can often be the reason that this 31 * class is created). Because of this, [DumpsysTableLogger] will simply filter out invalid lines 32 * based solely on line length. This behavior might need to be revisited in the future. 33 * 34 * USAGE: 35 * Assuming we have some data that would be logged to dumpsys like so: 36 * 37 * ``` 38 * 1: field1=val1, field2=val2..., fieldN=valN 39 * //... 40 * M: field1M=val1M, ..., fieldNM 41 * ``` 42 * 43 * You can break the `field<n>` values out into a columns spec: 44 * ``` 45 * val cols = [field1, field2,...,fieldN] 46 * ``` 47 * And then take all of the historical data lines (1 through M), and break them out into their own 48 * lists: 49 * ``` 50 * val rows = [ 51 * [field10, field20,..., fieldN0], 52 * //... 53 * [field1M, field2M,..., fieldNM] 54 * ] 55 * ``` 56 * 57 * Lastly, create a bugreport-unique section name, and use the table logger to write the data to 58 * dumpsys: 59 * ``` 60 * val logger = DumpsysTableLogger(uniqueName, cols, rows) 61 * logger.printTableData(pw) 62 * ``` 63 * 64 * The expected output in the dumpsys would be: 65 * ``` 66 * SystemUI TableSection START: <SectionName> 67 * version 1 68 * col1|col2|...|colN 69 * field10|field20|...|fieldN0 70 * //... 71 * field1M|field2M|...|fieldNM 72 * SystemUI TableSection END: <SectionName> 73 * ``` 74 * 75 * @param sectionName A name for the table data section. Should be unique in the bugreport 76 * @param columns Definition for the columns of the table. This should be the same length as all 77 * data rows 78 * @param rows List of rows to be displayed in the table 79 */ 80 class DumpsysTableLogger( 81 private val sectionName: String, 82 private val columns: List<String>, 83 private val rows: List<Row> 84 ) { 85 86 fun printTableData(pw: PrintWriter) { 87 Trace.beginSection("DumpsysTableLogger#printTableData") 88 printSectionStart(pw) 89 printSchema(pw) 90 printData(pw) 91 printSectionEnd(pw) 92 Trace.endSection() 93 } 94 95 private fun printSectionStart(pw: PrintWriter) { 96 pw.append(HEADER_PREFIX).println(sectionName) 97 pw.append("version ").println(VERSION) 98 } 99 100 private fun printSectionEnd(pw: PrintWriter) { 101 pw.append(FOOTER_PREFIX).println(sectionName) 102 } 103 104 private fun printSchema(pw: PrintWriter) { 105 columns.joinTo(pw, separator = SEPARATOR).println() 106 } 107 108 private fun printData(pw: PrintWriter) { 109 val count = columns.size 110 rows.forEach { dataLine -> 111 if (dataLine.size == count) { 112 dataLine.joinTo(pw, separator = SEPARATOR).println() 113 } 114 } 115 } 116 } 117 118 typealias Row = List<String> 119 120 /** 121 * DO NOT CHANGE! (but if you must...) 122 * 1. Update the version number 123 * 2. Update any consumers to parse the new version 124 */ 125 private const val HEADER_PREFIX = "SystemUI TableSection START: " 126 private const val FOOTER_PREFIX = "SystemUI TableSection END: " 127 private const val SEPARATOR = "|" // TBD 128 private const val VERSION = "1"