• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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