1 /* 2 * Copyright (C) 2023 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.log.echo 18 19 import com.android.systemui.dagger.qualifiers.Application 20 import com.android.systemui.dagger.qualifiers.Background 21 import com.android.systemui.log.LogcatEchoTracker 22 import com.android.systemui.log.core.LogLevel 23 import com.android.systemui.log.echo.LogcatEchoTrackerCommand.Companion.ECHO_TRACKER_COMMAND_NAME 24 import com.android.systemui.statusbar.commandline.CommandRegistry 25 import com.android.systemui.util.settings.GlobalSettings 26 import javax.inject.Inject 27 import kotlinx.coroutines.CoroutineDispatcher 28 import kotlinx.coroutines.CoroutineScope 29 import kotlinx.coroutines.ExperimentalCoroutinesApi 30 import kotlinx.coroutines.launch 31 32 /** 33 * A version of [LogcatEchoTracker] that supports fine-grained echoing of log messages to logcat, 34 * filtered by buffer, tag, and log level. 35 * 36 * Filters can be added and removed via a shell command (`adb shell cmd statusbar echo`). See 37 * [LogcatEchoTrackerCommand] for details. 38 * 39 * Note that some log messages may fail to be echoed while the systemui process is first starting 40 * up, before we load the echo settings. 41 */ 42 @OptIn(ExperimentalCoroutinesApi::class) 43 class LogcatEchoTrackerDebug 44 @Inject 45 constructor( 46 @Application private val applicationScope: CoroutineScope, 47 @Background backgroundDispatcher: CoroutineDispatcher, 48 private val globalSettings: GlobalSettings, 49 private val commandRegistry: CommandRegistry, 50 ) : LogcatEchoTracker { 51 52 // This class uses a single-writer, many-readers pattern that allows us to avoid the need for 53 // locking. In this case, this means that our shared state (the override maps) can be _read_ by 54 // any number of threads, but they're always written to by a single thread (dispatched by 55 // sequentialBgDispatcher). Such a pattern allows us to use the more performant @Volatile below 56 // instead of synchronization locks. 57 // 58 // Okay: some of what I just told you is a lie. sequentialBgDispatcher does not dispatch to a 59 // single thread. Instead, it guarantees that all work it schedules is _sequential_, meaning 60 // that Job B cannot start until Job A ends (this is actually a stronger guarantee than single- 61 // threaded execution due to the possibility of suspend functions). Because 62 // sequentialBgDispatcher is dispatching from the Dispatchers.IO thread pool, each individual 63 // "write" job might run on a different thread from that pool. However, because we are 64 // enforcing sequential execution, exactly which thread an individual write job runs on doesn't 65 // matter. 66 private val sequentialBgDispatcher = backgroundDispatcher.limitedParallelism(1) 67 68 // Okay. So. Why are these @Volatile. We've eliminated the need for synchronization primitives, 69 // but now we must contend with thread memory caching. Without an explicit synchronization 70 // signal, other threads may see "stale" versions of our state when they try to read from these 71 // maps, even after they've been updated by the writer thread. @Volatile solves this problem: 72 // it eliminates the possibility of stale reads while still being much more performant than 73 // locking. 74 @Volatile private var bufferOverrides = mapOf<String, LogLevel>() 75 @Volatile private var tagOverrides = mapOf<String, LogLevel>() 76 77 private val settingFormat = LogcatEchoSettingFormat() 78 startnull79 fun start() { 80 loadEchoOverrides() 81 82 commandRegistry.registerCommand(ECHO_TRACKER_COMMAND_NAME) { 83 LogcatEchoTrackerCommand(this) 84 } 85 } 86 isBufferLoggablenull87 override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean { 88 return level >= (bufferOverrides[bufferName] ?: DEFAULT_LOG_LEVEL) 89 } 90 isTagLoggablenull91 override fun isTagLoggable(tagName: String, level: LogLevel): Boolean { 92 return level >= (tagOverrides[tagName] ?: DEFAULT_LOG_LEVEL) 93 } 94 listEchoOverridesnull95 fun listEchoOverrides(): List<LogcatEchoOverride> { 96 val list = mutableListOf<LogcatEchoOverride>() 97 98 val frozenBufferOverrides = bufferOverrides 99 val frozenTagOverrides = tagOverrides 100 101 for ((name, level) in frozenBufferOverrides) { 102 list.add(LogcatEchoOverride(EchoOverrideType.BUFFER, name, level)) 103 } 104 for ((name, level) in frozenTagOverrides) { 105 list.add(LogcatEchoOverride(EchoOverrideType.TAG, name, level)) 106 } 107 return list 108 } 109 setEchoLevelnull110 fun setEchoLevel(type: EchoOverrideType, name: String, level: LogLevel?) { 111 applicationScope.launch(sequentialBgDispatcher) { 112 val newBufferOverrides = bufferOverrides.toMutableMap() 113 val newTagOverrides = tagOverrides.toMutableMap() 114 115 val mutatedMap = 116 when (type) { 117 EchoOverrideType.BUFFER -> newBufferOverrides 118 EchoOverrideType.TAG -> newTagOverrides 119 } 120 if (level != null) { 121 mutatedMap[name] = level 122 } else { 123 mutatedMap.remove(name) 124 } 125 126 bufferOverrides = newBufferOverrides 127 tagOverrides = newTagOverrides 128 129 val list = listEchoOverrides() 130 globalSettings.putString(OVERRIDE_SETTING_PATH, settingFormat.stringifyOverrides(list)) 131 } 132 } 133 clearAllOverridesnull134 fun clearAllOverrides() { 135 applicationScope.launch(sequentialBgDispatcher) { 136 bufferOverrides = emptyMap() 137 tagOverrides = emptyMap() 138 139 val list = listEchoOverrides() 140 globalSettings.putString(OVERRIDE_SETTING_PATH, settingFormat.stringifyOverrides(list)) 141 } 142 } 143 loadEchoOverridesnull144 private fun loadEchoOverrides() { 145 applicationScope.launch(sequentialBgDispatcher) { 146 val overrideSetting = globalSettings.getString(OVERRIDE_SETTING_PATH) ?: return@launch 147 val overrideList = settingFormat.parseOverrides(overrideSetting) 148 149 val newBufferOverrides = mutableMapOf<String, LogLevel>() 150 val newTagOverrides = mutableMapOf<String, LogLevel>() 151 152 for (override in overrideList) { 153 val map = 154 when (override.type) { 155 EchoOverrideType.BUFFER -> newBufferOverrides 156 EchoOverrideType.TAG -> newTagOverrides 157 } 158 map[override.name] = override.level 159 } 160 161 bufferOverrides = newBufferOverrides 162 tagOverrides = newTagOverrides 163 } 164 } 165 } 166 167 enum class EchoOverrideType { 168 BUFFER, 169 TAG, 170 } 171 172 private const val TAG = "LogcatEchoTrackerDebug" 173 174 private const val OVERRIDE_SETTING_PATH = "systemui/logbuffer_echo_overrides" 175 176 private val DEFAULT_LOG_LEVEL = LogLevel.WARNING 177