1 /* <lambda>null2 * 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.CoreStartable 23 import com.android.systemui.R 24 import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL 25 import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL 26 import com.android.systemui.dump.nano.SystemUIProtoDump 27 import com.android.systemui.plugins.log.LogBuffer 28 import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager 29 import com.google.protobuf.nano.MessageNano 30 import java.io.BufferedOutputStream 31 import java.io.FileDescriptor 32 import java.io.FileOutputStream 33 import java.io.PrintWriter 34 import javax.inject.Inject 35 import javax.inject.Provider 36 37 /** 38 * Oversees SystemUI's output during bug reports (and dumpsys in general) 39 * 40 * Dump output is split into two sections, CRITICAL and NORMAL. In general, the CRITICAL section 41 * contains all dumpables that were registered to the [DumpManager], while the NORMAL sections 42 * contains all [LogBuffer]s (due to their length). 43 * 44 * The CRITICAL and NORMAL sections can be found within a bug report by searching for 45 * "SERVICE com.android.systemui/.SystemUIService" and 46 * "SERVICE com.android.systemui/.dump.SystemUIAuxiliaryDumpService", respectively. 47 * 48 * Finally, some or all of the dump can be triggered on-demand via adb (see below). 49 * 50 * ``` 51 * # For the following, let <invocation> be: 52 * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService 53 * 54 * # To dump specific target(s), specify one or more registered names: 55 * $ <invocation> NotifCollection 56 * $ <invocation> StatusBar FalsingManager BootCompleteCacheImpl 57 * 58 * # Log buffers can be dumped in the same way (and can even be mixed in with other dump targets, 59 * # although it's not clear why one would want such a thing): 60 * $ <invocation> NotifLog 61 * $ <invocation> StatusBar NotifLog BootCompleteCacheImpl 62 * 63 * # If passing -t or --tail, shows only the last N lines of any log buffers: 64 * $ <invocation> NotifLog --tail 100 65 * 66 * # Dump targets are matched using String.endsWith(), so dumpables that register using their 67 * # fully-qualified class name can still be dumped using their short name: 68 * $ <invocation> com.android.keyguard.KeyguardUpdateMonitor 69 * $ <invocation> keyguard.KeyguardUpdateMonitor 70 * $ <invocation> KeyguardUpdateMonitor 71 * 72 * # To dump all dumpables or all buffers: 73 * $ <invocation> dumpables 74 * $ <invocation> buffers 75 * 76 * # Finally, the following will simulate what we dump during the CRITICAL and NORMAL sections of a 77 * # bug report: 78 * $ <invocation> bugreport-critical 79 * $ <invocation> bugreport-normal 80 * 81 * # And if you need to be reminded of this list of commands: 82 * $ <invocation> -h 83 * $ <invocation> --help 84 * ``` 85 */ 86 class DumpHandler @Inject constructor( 87 private val context: Context, 88 private val dumpManager: DumpManager, 89 private val logBufferEulogizer: LogBufferEulogizer, 90 private val startables: MutableMap<Class<*>, Provider<CoreStartable>>, 91 private val uncaughtExceptionPreHandlerManager: UncaughtExceptionPreHandlerManager 92 ) { 93 /** 94 * Registers an uncaught exception handler 95 */ 96 fun init() { 97 uncaughtExceptionPreHandlerManager.registerHandler { _, e -> 98 if (e is Exception) { 99 logBufferEulogizer.record(e) 100 } 101 } 102 } 103 104 /** 105 * Dump the diagnostics! Behavior can be controlled via [args]. 106 */ 107 fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) { 108 Trace.beginSection("DumpManager#dump()") 109 val start = SystemClock.uptimeMillis() 110 111 val parsedArgs = try { 112 parseArgs(args) 113 } catch (e: ArgParseException) { 114 pw.println(e.message) 115 return 116 } 117 118 when { 119 parsedArgs.dumpPriority == PRIORITY_ARG_CRITICAL -> dumpCritical(pw, parsedArgs) 120 parsedArgs.dumpPriority == PRIORITY_ARG_NORMAL && !parsedArgs.proto -> { 121 dumpNormal(pw, parsedArgs) 122 } 123 else -> dumpParameterized(fd, pw, parsedArgs) 124 } 125 126 pw.println() 127 pw.println("Dump took ${SystemClock.uptimeMillis() - start}ms") 128 Trace.endSection() 129 } 130 131 private fun dumpParameterized(fd: FileDescriptor, pw: PrintWriter, args: ParsedArgs) { 132 when (args.command) { 133 "bugreport-critical" -> dumpCritical(pw, args) 134 "bugreport-normal" -> dumpNormal(pw, args) 135 "dumpables" -> dumpDumpables(pw, args) 136 "buffers" -> dumpBuffers(pw, args) 137 "config" -> dumpConfig(pw) 138 "help" -> dumpHelp(pw) 139 else -> { 140 if (args.proto) { 141 dumpProtoTargets(args.nonFlagArgs, fd, args) 142 } else { 143 dumpTargets(args.nonFlagArgs, pw, args) 144 } 145 } 146 } 147 } 148 149 private fun dumpCritical(pw: PrintWriter, args: ParsedArgs) { 150 dumpManager.dumpCritical(pw, args.rawArgs) 151 dumpConfig(pw) 152 } 153 154 private fun dumpNormal(pw: PrintWriter, args: ParsedArgs) { 155 dumpManager.dumpNormal(pw, args.rawArgs, args.tailLength) 156 logBufferEulogizer.readEulogyIfPresent(pw) 157 } 158 159 private fun dumpDumpables(pw: PrintWriter, args: ParsedArgs) { 160 if (args.listOnly) { 161 dumpManager.listDumpables(pw) 162 } else { 163 dumpManager.dumpDumpables(pw, args.rawArgs) 164 } 165 } 166 167 private fun dumpBuffers(pw: PrintWriter, args: ParsedArgs) { 168 if (args.listOnly) { 169 dumpManager.listBuffers(pw) 170 } else { 171 dumpManager.dumpBuffers(pw, args.tailLength) 172 } 173 } 174 175 private fun dumpProtoTargets( 176 targets: List<String>, 177 fd: FileDescriptor, 178 args: ParsedArgs 179 ) { 180 val systemUIProto = SystemUIProtoDump() 181 if (targets.isNotEmpty()) { 182 for (target in targets) { 183 dumpManager.dumpProtoTarget(target, systemUIProto, args.rawArgs) 184 } 185 } else { 186 dumpManager.dumpProtoDumpables(systemUIProto, args.rawArgs) 187 } 188 val buffer = BufferedOutputStream(FileOutputStream(fd)) 189 buffer.use { 190 it.write(MessageNano.toByteArray(systemUIProto)) 191 it.flush() 192 } 193 } 194 195 private fun dumpTargets( 196 targets: List<String>, 197 pw: PrintWriter, 198 args: ParsedArgs 199 ) { 200 if (targets.isNotEmpty()) { 201 for (target in targets) { 202 dumpManager.dumpTarget(target, pw, args.rawArgs, args.tailLength) 203 } 204 } else { 205 if (args.listOnly) { 206 pw.println("Dumpables:") 207 dumpManager.listDumpables(pw) 208 pw.println() 209 210 pw.println("Buffers:") 211 dumpManager.listBuffers(pw) 212 } else { 213 pw.println("Nothing to dump :(") 214 } 215 } 216 } 217 218 private fun dumpConfig(pw: PrintWriter) { 219 pw.println("SystemUiServiceComponents configuration:") 220 pw.print("vendor component: ") 221 pw.println(context.resources.getString(R.string.config_systemUIVendorServiceComponent)) 222 val services: MutableList<String> = startables.keys 223 .map({ cls: Class<*> -> cls.simpleName }) 224 .toMutableList() 225 226 services.add(context.resources.getString(R.string.config_systemUIVendorServiceComponent)) 227 dumpServiceList(pw, "global", services.toTypedArray()) 228 dumpServiceList(pw, "per-user", R.array.config_systemUIServiceComponentsPerUser) 229 } 230 231 private fun dumpServiceList(pw: PrintWriter, type: String, resId: Int) { 232 val services: Array<String> = context.resources.getStringArray(resId) 233 dumpServiceList(pw, type, services) 234 } 235 236 private fun dumpServiceList(pw: PrintWriter, type: String, services: Array<String>?) { 237 pw.print(type) 238 pw.print(": ") 239 if (services == null) { 240 pw.println("N/A") 241 return 242 } 243 pw.print(services.size) 244 pw.println(" services") 245 for (i in services.indices) { 246 pw.print(" ") 247 pw.print(i) 248 pw.print(": ") 249 pw.println(services[i]) 250 } 251 } 252 253 private fun dumpHelp(pw: PrintWriter) { 254 pw.println("Let <invocation> be:") 255 pw.println("$ adb shell dumpsys activity service com.android.systemui/.SystemUIService") 256 pw.println() 257 258 pw.println("Most common usage:") 259 pw.println("$ <invocation> <targets>") 260 pw.println("$ <invocation> NotifLog") 261 pw.println("$ <invocation> StatusBar FalsingManager BootCompleteCacheImpl") 262 pw.println("etc.") 263 pw.println() 264 265 pw.println("Special commands:") 266 pw.println("$ <invocation> dumpables") 267 pw.println("$ <invocation> buffers") 268 pw.println("$ <invocation> bugreport-critical") 269 pw.println("$ <invocation> bugreport-normal") 270 pw.println("$ <invocation> config") 271 pw.println() 272 273 pw.println("Targets can be listed:") 274 pw.println("$ <invocation> --list") 275 pw.println("$ <invocation> dumpables --list") 276 pw.println("$ <invocation> buffers --list") 277 pw.println() 278 279 pw.println("Show only the most recent N lines of buffers") 280 pw.println("$ <invocation> NotifLog --tail 30") 281 } 282 283 private fun parseArgs(args: Array<String>): ParsedArgs { 284 val mutArgs = args.toMutableList() 285 val pArgs = ParsedArgs(args, mutArgs) 286 287 val iterator = mutArgs.iterator() 288 while (iterator.hasNext()) { 289 val arg = iterator.next() 290 if (arg.startsWith("-")) { 291 iterator.remove() 292 when (arg) { 293 PRIORITY_ARG -> { 294 pArgs.dumpPriority = readArgument(iterator, PRIORITY_ARG) { 295 if (PRIORITY_OPTIONS.contains(it)) { 296 it 297 } else { 298 throw IllegalArgumentException() 299 } 300 } 301 } 302 PROTO -> pArgs.proto = true 303 "-t", "--tail" -> { 304 pArgs.tailLength = readArgument(iterator, arg) { 305 it.toInt() 306 } 307 } 308 "-l", "--list" -> { 309 pArgs.listOnly = true 310 } 311 "-h", "--help" -> { 312 pArgs.command = "help" 313 } 314 // This flag is passed as part of the proto dump in Bug reports, we can ignore 315 // it because this is our default behavior. 316 "-a" -> {} 317 else -> { 318 throw ArgParseException("Unknown flag: $arg") 319 } 320 } 321 } 322 } 323 324 if (pArgs.command == null && mutArgs.isNotEmpty() && COMMANDS.contains(mutArgs[0])) { 325 pArgs.command = mutArgs.removeAt(0) 326 } 327 328 return pArgs 329 } 330 331 private fun <T> readArgument( 332 iterator: MutableIterator<String>, 333 flag: String, 334 parser: (arg: String) -> T 335 ): T { 336 if (!iterator.hasNext()) { 337 throw ArgParseException("Missing argument for $flag") 338 } 339 val value = iterator.next() 340 341 return try { 342 parser(value).also { iterator.remove() } 343 } catch (e: Exception) { 344 throw ArgParseException("Invalid argument '$value' for flag $flag") 345 } 346 } 347 348 companion object { 349 const val PRIORITY_ARG = "--dump-priority" 350 const val PRIORITY_ARG_CRITICAL = "CRITICAL" 351 const val PRIORITY_ARG_NORMAL = "NORMAL" 352 const val PROTO = "--proto" 353 } 354 } 355 356 private val PRIORITY_OPTIONS = arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_NORMAL) 357 358 private val COMMANDS = arrayOf( 359 "bugreport-critical", 360 "bugreport-normal", 361 "buffers", 362 "dumpables", 363 "config", 364 "help" 365 ) 366 367 private class ParsedArgs( 368 val rawArgs: Array<String>, 369 val nonFlagArgs: List<String> 370 ) { 371 var dumpPriority: String? = null 372 var tailLength: Int = 0 373 var command: String? = null 374 var listOnly = false 375 var proto = false 376 } 377 378 class ArgParseException(message: String) : Exception(message) 379