blob: fa951fa09ef651f865a13cc822df59b1e4d4d2f6 [file] [log] [blame]
/*
* Copyright (C) 2020 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 android.content.Context
import android.os.SystemClock
import android.os.Trace
import com.android.systemui.R
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_HIGH
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL
import com.android.systemui.log.LogBuffer
import java.io.FileDescriptor
import java.io.PrintWriter
import javax.inject.Inject
/**
* Oversees SystemUI's output during bug reports (and dumpsys in general)
*
* Dump output is split into two sections, CRITICAL and NORMAL. In general, the CRITICAL section
* contains all dumpables that were registered to the [DumpManager], while the NORMAL sections
* contains all [LogBuffer]s (due to their length).
*
* The CRITICAL and NORMAL sections can be found within a bug report by searching for
* "SERVICE com.android.systemui/.SystemUIService" and
* "SERVICE com.android.systemui/.dump.SystemUIAuxiliaryDumpService", respectively.
*
* Finally, some or all of the dump can be triggered on-demand via adb (see below).
*
* ```
* # For the following, let <invocation> be:
* $ adb shell dumpsys activity service com.android.systemui/.SystemUIService
*
* # To dump specific target(s), specify one or more registered names:
* $ <invocation> NotifCollection
* $ <invocation> StatusBar FalsingManager BootCompleteCacheImpl
*
* # Log buffers can be dumped in the same way (and can even be mixed in with other dump targets,
* # although it's not clear why one would want such a thing):
* $ <invocation> NotifLog
* $ <invocation> StatusBar NotifLog BootCompleteCacheImpl
*
* # If passing -t or --tail, shows only the last N lines of any log buffers:
* $ <invocation> NotifLog --tail 100
*
* # Dump targets are matched using String.endsWith(), so dumpables that register using their
* # fully-qualified class name can still be dumped using their short name:
* $ <invocation> com.android.keyguard.KeyguardUpdateMonitor
* $ <invocation> keyguard.KeyguardUpdateMonitor
* $ <invocation> KeyguardUpdateMonitor
*
* # To dump all dumpables or all buffers:
* $ <invocation> dumpables
* $ <invocation> buffers
*
* # Finally, the following will simulate what we dump during the CRITICAL and NORMAL sections of a
* # bug report:
* $ <invocation> bugreport-critical
* $ <invocation> bugreport-normal
*
* # And if you need to be reminded of this list of commands:
* $ <invocation> -h
* $ <invocation> --help
* ```
*/
class DumpHandler @Inject constructor(
private val context: Context,
private val dumpManager: DumpManager,
private val logBufferEulogizer: LogBufferEulogizer
) {
/**
* Dump the diagnostics! Behavior can be controlled via [args].
*/
fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
Trace.beginSection("DumpManager#dump()")
val start = SystemClock.uptimeMillis()
val parsedArgs = try {
parseArgs(args)
} catch (e: ArgParseException) {
pw.println(e.message)
return
}
when (parsedArgs.dumpPriority) {
PRIORITY_ARG_CRITICAL -> dumpCritical(fd, pw, parsedArgs)
PRIORITY_ARG_NORMAL -> dumpNormal(pw, parsedArgs)
else -> dumpParameterized(fd, pw, parsedArgs)
}
pw.println()
pw.println("Dump took ${SystemClock.uptimeMillis() - start}ms")
Trace.endSection()
}
private fun dumpParameterized(fd: FileDescriptor, pw: PrintWriter, args: ParsedArgs) {
when (args.command) {
"bugreport-critical" -> dumpCritical(fd, pw, args)
"bugreport-normal" -> dumpNormal(pw, args)
"dumpables" -> dumpDumpables(fd, pw, args)
"buffers" -> dumpBuffers(pw, args)
"config" -> dumpConfig(pw)
"help" -> dumpHelp(pw)
else -> dumpTargets(args.nonFlagArgs, fd, pw, args)
}
}
private fun dumpCritical(fd: FileDescriptor, pw: PrintWriter, args: ParsedArgs) {
dumpManager.dumpDumpables(fd, pw, args.rawArgs)
dumpConfig(pw)
}
private fun dumpNormal(pw: PrintWriter, args: ParsedArgs) {
dumpManager.dumpBuffers(pw, args.tailLength)
logBufferEulogizer.readEulogyIfPresent(pw)
}
private fun dumpDumpables(fw: FileDescriptor, pw: PrintWriter, args: ParsedArgs) {
if (args.listOnly) {
dumpManager.listDumpables(pw)
} else {
dumpManager.dumpDumpables(fw, pw, args.rawArgs)
}
}
private fun dumpBuffers(pw: PrintWriter, args: ParsedArgs) {
if (args.listOnly) {
dumpManager.listBuffers(pw)
} else {
dumpManager.dumpBuffers(pw, args.tailLength)
}
}
private fun dumpTargets(
targets: List<String>,
fd: FileDescriptor,
pw: PrintWriter,
args: ParsedArgs
) {
if (targets.isNotEmpty()) {
for (target in targets) {
dumpManager.dumpTarget(target, fd, pw, args.rawArgs, args.tailLength)
}
} else {
if (args.listOnly) {
pw.println("Dumpables:")
dumpManager.listDumpables(pw)
pw.println()
pw.println("Buffers:")
dumpManager.listBuffers(pw)
} else {
pw.println("Nothing to dump :(")
}
}
}
private fun dumpConfig(pw: PrintWriter) {
pw.println("SystemUiServiceComponents configuration:")
pw.print("vendor component: ")
pw.println(context.resources.getString(R.string.config_systemUIVendorServiceComponent))
dumpServiceList(pw, "global", R.array.config_systemUIServiceComponents)
dumpServiceList(pw, "per-user", R.array.config_systemUIServiceComponentsPerUser)
}
private fun dumpServiceList(pw: PrintWriter, type: String, resId: Int) {
val services: Array<String>? = context.resources.getStringArray(resId)
pw.print(type)
pw.print(": ")
if (services == null) {
pw.println("N/A")
return
}
pw.print(services.size)
pw.println(" services")
for (i in services.indices) {
pw.print(" ")
pw.print(i)
pw.print(": ")
pw.println(services[i])
}
}
private fun dumpHelp(pw: PrintWriter) {
pw.println("Let <invocation> be:")
pw.println("$ adb shell dumpsys activity service com.android.systemui/.SystemUIService")
pw.println()
pw.println("Most common usage:")
pw.println("$ <invocation> <targets>")
pw.println("$ <invocation> NotifLog")
pw.println("$ <invocation> StatusBar FalsingManager BootCompleteCacheImpl")
pw.println("etc.")
pw.println()
pw.println("Special commands:")
pw.println("$ <invocation> dumpables")
pw.println("$ <invocation> buffers")
pw.println("$ <invocation> bugreport-critical")
pw.println("$ <invocation> bugreport-normal")
pw.println()
pw.println("Targets can be listed:")
pw.println("$ <invocation> --list")
pw.println("$ <invocation> dumpables --list")
pw.println("$ <invocation> buffers --list")
pw.println()
pw.println("Show only the most recent N lines of buffers")
pw.println("$ <invocation> NotifLog --tail 30")
}
private fun parseArgs(args: Array<String>): ParsedArgs {
val mutArgs = args.toMutableList()
val pArgs = ParsedArgs(args, mutArgs)
val iterator = mutArgs.iterator()
while (iterator.hasNext()) {
val arg = iterator.next()
if (arg.startsWith("-")) {
iterator.remove()
when (arg) {
PRIORITY_ARG -> {
pArgs.dumpPriority = readArgument(iterator, PRIORITY_ARG) {
if (PRIORITY_OPTIONS.contains(it)) {
it
} else {
throw IllegalArgumentException()
}
}
}
"-t", "--tail" -> {
pArgs.tailLength = readArgument(iterator, arg) {
it.toInt()
}
}
"-l", "--list" -> {
pArgs.listOnly = true
}
"-h", "--help" -> {
pArgs.command = "help"
}
else -> {
throw ArgParseException("Unknown flag: $arg")
}
}
}
}
if (pArgs.command == null && mutArgs.isNotEmpty() && COMMANDS.contains(mutArgs[0])) {
pArgs.command = mutArgs.removeAt(0)
}
return pArgs
}
private fun <T> readArgument(
iterator: MutableIterator<String>,
flag: String,
parser: (arg: String) -> T
): T {
if (!iterator.hasNext()) {
throw ArgParseException("Missing argument for $flag")
}
val value = iterator.next()
return try {
parser(value).also { iterator.remove() }
} catch (e: Exception) {
throw ArgParseException("Invalid argument '$value' for flag $flag")
}
}
companion object {
const val PRIORITY_ARG = "--dump-priority"
const val PRIORITY_ARG_CRITICAL = "CRITICAL"
const val PRIORITY_ARG_HIGH = "HIGH"
const val PRIORITY_ARG_NORMAL = "NORMAL"
}
}
private val PRIORITY_OPTIONS =
arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_HIGH, PRIORITY_ARG_NORMAL)
private val COMMANDS = arrayOf("bugreport-critical", "bugreport-normal", "buffers", "dumpables")
private class ParsedArgs(
val rawArgs: Array<String>,
val nonFlagArgs: List<String>
) {
var dumpPriority: String? = null
var tailLength: Int = 0
var command: String? = null
var listOnly = false
}
class ArgParseException(message: String) : Exception(message)