1 /* 2 * Copyright (C) 2020 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.content.Context 20 import android.os.SystemClock 21 import android.os.Trace 22 import com.android.systemui.R 23 import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL 24 import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_HIGH 25 import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL 26 import com.android.systemui.log.LogBuffer 27 import java.io.FileDescriptor 28 import java.io.PrintWriter 29 import javax.inject.Inject 30 31 /** 32 * Oversees SystemUI's output during bug reports (and dumpsys in general) 33 * 34 * Dump output is split into two sections, CRITICAL and NORMAL. In general, the CRITICAL section 35 * contains all dumpables that were registered to the [DumpManager], while the NORMAL sections 36 * contains all [LogBuffer]s (due to their length). 37 * 38 * The CRITICAL and NORMAL sections can be found within a bug report by searching for 39 * "SERVICE com.android.systemui/.SystemUIService" and 40 * "SERVICE com.android.systemui/.dump.SystemUIAuxiliaryDumpService", respectively. 41 * 42 * Finally, some or all of the dump can be triggered on-demand via adb (see below). 43 * 44 * ``` 45 * # For the following, let <invocation> be: 46 * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService 47 * 48 * # To dump specific target(s), specify one or more registered names: 49 * $ <invocation> NotifCollection 50 * $ <invocation> StatusBar FalsingManager BootCompleteCacheImpl 51 * 52 * # Log buffers can be dumped in the same way (and can even be mixed in with other dump targets, 53 * # although it's not clear why one would want such a thing): 54 * $ <invocation> NotifLog 55 * $ <invocation> StatusBar NotifLog BootCompleteCacheImpl 56 * 57 * # If passing -t or --tail, shows only the last N lines of any log buffers: 58 * $ <invocation> NotifLog --tail 100 59 * 60 * # Dump targets are matched using String.endsWith(), so dumpables that register using their 61 * # fully-qualified class name can still be dumped using their short name: 62 * $ <invocation> com.android.keyguard.KeyguardUpdateMonitor 63 * $ <invocation> keyguard.KeyguardUpdateMonitor 64 * $ <invocation> KeyguardUpdateMonitor 65 * 66 * # To dump all dumpables or all buffers: 67 * $ <invocation> dumpables 68 * $ <invocation> buffers 69 * 70 * # Finally, the following will simulate what we dump during the CRITICAL and NORMAL sections of a 71 * # bug report: 72 * $ <invocation> bugreport-critical 73 * $ <invocation> bugreport-normal 74 * 75 * # And if you need to be reminded of this list of commands: 76 * $ <invocation> -h 77 * $ <invocation> --help 78 * ``` 79 */ 80 class DumpHandler @Inject constructor( 81 private val context: Context, 82 private val dumpManager: DumpManager, 83 private val logBufferEulogizer: LogBufferEulogizer 84 ) { 85 /** 86 * Dump the diagnostics! Behavior can be controlled via [args]. 87 */ dumpnull88 fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) { 89 Trace.beginSection("DumpManager#dump()") 90 val start = SystemClock.uptimeMillis() 91 92 val parsedArgs = try { 93 parseArgs(args) 94 } catch (e: ArgParseException) { 95 pw.println(e.message) 96 return 97 } 98 99 when (parsedArgs.dumpPriority) { 100 PRIORITY_ARG_CRITICAL -> dumpCritical(fd, pw, parsedArgs) 101 PRIORITY_ARG_NORMAL -> dumpNormal(pw, parsedArgs) 102 else -> dumpParameterized(fd, pw, parsedArgs) 103 } 104 105 pw.println() 106 pw.println("Dump took ${SystemClock.uptimeMillis() - start}ms") 107 Trace.endSection() 108 } 109 dumpParameterizednull110 private fun dumpParameterized(fd: FileDescriptor, pw: PrintWriter, args: ParsedArgs) { 111 when (args.command) { 112 "bugreport-critical" -> dumpCritical(fd, pw, args) 113 "bugreport-normal" -> dumpNormal(pw, args) 114 "dumpables" -> dumpDumpables(fd, pw, args) 115 "buffers" -> dumpBuffers(pw, args) 116 "config" -> dumpConfig(pw) 117 "help" -> dumpHelp(pw) 118 else -> dumpTargets(args.nonFlagArgs, fd, pw, args) 119 } 120 } 121 dumpCriticalnull122 private fun dumpCritical(fd: FileDescriptor, pw: PrintWriter, args: ParsedArgs) { 123 dumpManager.dumpDumpables(fd, pw, args.rawArgs) 124 dumpConfig(pw) 125 } 126 dumpNormalnull127 private fun dumpNormal(pw: PrintWriter, args: ParsedArgs) { 128 dumpManager.dumpBuffers(pw, args.tailLength) 129 logBufferEulogizer.readEulogyIfPresent(pw) 130 } 131 dumpDumpablesnull132 private fun dumpDumpables(fw: FileDescriptor, pw: PrintWriter, args: ParsedArgs) { 133 if (args.listOnly) { 134 dumpManager.listDumpables(pw) 135 } else { 136 dumpManager.dumpDumpables(fw, pw, args.rawArgs) 137 } 138 } 139 dumpBuffersnull140 private fun dumpBuffers(pw: PrintWriter, args: ParsedArgs) { 141 if (args.listOnly) { 142 dumpManager.listBuffers(pw) 143 } else { 144 dumpManager.dumpBuffers(pw, args.tailLength) 145 } 146 } 147 dumpTargetsnull148 private fun dumpTargets( 149 targets: List<String>, 150 fd: FileDescriptor, 151 pw: PrintWriter, 152 args: ParsedArgs 153 ) { 154 if (targets.isNotEmpty()) { 155 for (target in targets) { 156 dumpManager.dumpTarget(target, fd, pw, args.rawArgs, args.tailLength) 157 } 158 } else { 159 if (args.listOnly) { 160 pw.println("Dumpables:") 161 dumpManager.listDumpables(pw) 162 pw.println() 163 164 pw.println("Buffers:") 165 dumpManager.listBuffers(pw) 166 } else { 167 pw.println("Nothing to dump :(") 168 } 169 } 170 } 171 dumpConfignull172 private fun dumpConfig(pw: PrintWriter) { 173 pw.println("SystemUiServiceComponents configuration:") 174 pw.print("vendor component: ") 175 pw.println(context.resources.getString(R.string.config_systemUIVendorServiceComponent)) 176 dumpServiceList(pw, "global", R.array.config_systemUIServiceComponents) 177 dumpServiceList(pw, "per-user", R.array.config_systemUIServiceComponentsPerUser) 178 } 179 dumpServiceListnull180 private fun dumpServiceList(pw: PrintWriter, type: String, resId: Int) { 181 val services: Array<String>? = context.resources.getStringArray(resId) 182 pw.print(type) 183 pw.print(": ") 184 if (services == null) { 185 pw.println("N/A") 186 return 187 } 188 pw.print(services.size) 189 pw.println(" services") 190 for (i in services.indices) { 191 pw.print(" ") 192 pw.print(i) 193 pw.print(": ") 194 pw.println(services[i]) 195 } 196 } 197 dumpHelpnull198 private fun dumpHelp(pw: PrintWriter) { 199 pw.println("Let <invocation> be:") 200 pw.println("$ adb shell dumpsys activity service com.android.systemui/.SystemUIService") 201 pw.println() 202 203 pw.println("Most common usage:") 204 pw.println("$ <invocation> <targets>") 205 pw.println("$ <invocation> NotifLog") 206 pw.println("$ <invocation> StatusBar FalsingManager BootCompleteCacheImpl") 207 pw.println("etc.") 208 pw.println() 209 210 pw.println("Special commands:") 211 pw.println("$ <invocation> dumpables") 212 pw.println("$ <invocation> buffers") 213 pw.println("$ <invocation> bugreport-critical") 214 pw.println("$ <invocation> bugreport-normal") 215 pw.println() 216 217 pw.println("Targets can be listed:") 218 pw.println("$ <invocation> --list") 219 pw.println("$ <invocation> dumpables --list") 220 pw.println("$ <invocation> buffers --list") 221 pw.println() 222 223 pw.println("Show only the most recent N lines of buffers") 224 pw.println("$ <invocation> NotifLog --tail 30") 225 } 226 parseArgsnull227 private fun parseArgs(args: Array<String>): ParsedArgs { 228 val mutArgs = args.toMutableList() 229 val pArgs = ParsedArgs(args, mutArgs) 230 231 val iterator = mutArgs.iterator() 232 while (iterator.hasNext()) { 233 val arg = iterator.next() 234 if (arg.startsWith("-")) { 235 iterator.remove() 236 when (arg) { 237 PRIORITY_ARG -> { 238 pArgs.dumpPriority = readArgument(iterator, PRIORITY_ARG) { 239 if (PRIORITY_OPTIONS.contains(it)) { 240 it 241 } else { 242 throw IllegalArgumentException() 243 } 244 } 245 } 246 "-t", "--tail" -> { 247 pArgs.tailLength = readArgument(iterator, arg) { 248 it.toInt() 249 } 250 } 251 "-l", "--list" -> { 252 pArgs.listOnly = true 253 } 254 "-h", "--help" -> { 255 pArgs.command = "help" 256 } 257 else -> { 258 throw ArgParseException("Unknown flag: $arg") 259 } 260 } 261 } 262 } 263 264 if (pArgs.command == null && mutArgs.isNotEmpty() && COMMANDS.contains(mutArgs[0])) { 265 pArgs.command = mutArgs.removeAt(0) 266 } 267 268 return pArgs 269 } 270 readArgumentnull271 private fun <T> readArgument( 272 iterator: MutableIterator<String>, 273 flag: String, 274 parser: (arg: String) -> T 275 ): T { 276 if (!iterator.hasNext()) { 277 throw ArgParseException("Missing argument for $flag") 278 } 279 val value = iterator.next() 280 281 return try { 282 parser(value).also { iterator.remove() } 283 } catch (e: Exception) { 284 throw ArgParseException("Invalid argument '$value' for flag $flag") 285 } 286 } 287 288 companion object { 289 const val PRIORITY_ARG = "--dump-priority" 290 const val PRIORITY_ARG_CRITICAL = "CRITICAL" 291 const val PRIORITY_ARG_HIGH = "HIGH" 292 const val PRIORITY_ARG_NORMAL = "NORMAL" 293 } 294 } 295 296 private val PRIORITY_OPTIONS = 297 arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_HIGH, PRIORITY_ARG_NORMAL) 298 299 private val COMMANDS = arrayOf("bugreport-critical", "bugreport-normal", "buffers", "dumpables") 300 301 private class ParsedArgs( 302 val rawArgs: Array<String>, 303 val nonFlagArgs: List<String> 304 ) { 305 var dumpPriority: String? = null 306 var tailLength: Int = 0 307 var command: String? = null 308 var listOnly = false 309 } 310 311 class ArgParseException(message: String) : Exception(message) 312