/* * 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 be: * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService * * # To dump specific target(s), specify one or more registered names: * $ NotifCollection * $ 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): * $ NotifLog * $ StatusBar NotifLog BootCompleteCacheImpl * * # If passing -t or --tail, shows only the last N lines of any log buffers: * $ 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: * $ com.android.keyguard.KeyguardUpdateMonitor * $ keyguard.KeyguardUpdateMonitor * $ KeyguardUpdateMonitor * * # To dump all dumpables or all buffers: * $ dumpables * $ buffers * * # Finally, the following will simulate what we dump during the CRITICAL and NORMAL sections of a * # bug report: * $ bugreport-critical * $ bugreport-normal * * # And if you need to be reminded of this list of commands: * $ -h * $ --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) { 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, 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? = 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 be:") pw.println("$ adb shell dumpsys activity service com.android.systemui/.SystemUIService") pw.println() pw.println("Most common usage:") pw.println("$ ") pw.println("$ NotifLog") pw.println("$ StatusBar FalsingManager BootCompleteCacheImpl") pw.println("etc.") pw.println() pw.println("Special commands:") pw.println("$ dumpables") pw.println("$ buffers") pw.println("$ bugreport-critical") pw.println("$ bugreport-normal") pw.println() pw.println("Targets can be listed:") pw.println("$ --list") pw.println("$ dumpables --list") pw.println("$ buffers --list") pw.println() pw.println("Show only the most recent N lines of buffers") pw.println("$ NotifLog --tail 30") } private fun parseArgs(args: Array): 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 readArgument( iterator: MutableIterator, 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, val nonFlagArgs: List ) { var dumpPriority: String? = null var tailLength: Int = 0 var command: String? = null var listOnly = false } class ArgParseException(message: String) : Exception(message)