Merge "Freeze log buffers when a bug report is taken" into rvc-dev
This commit is contained in:
@@ -27,8 +27,10 @@ import android.os.UserHandle;
|
|||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
|
|
||||||
import com.android.internal.os.BinderInternal;
|
import com.android.internal.os.BinderInternal;
|
||||||
|
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||||
import com.android.systemui.dagger.qualifiers.Main;
|
import com.android.systemui.dagger.qualifiers.Main;
|
||||||
import com.android.systemui.dump.DumpHandler;
|
import com.android.systemui.dump.DumpHandler;
|
||||||
|
import com.android.systemui.dump.LogBufferFreezer;
|
||||||
import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
|
import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
@@ -40,21 +42,32 @@ public class SystemUIService extends Service {
|
|||||||
|
|
||||||
private final Handler mMainHandler;
|
private final Handler mMainHandler;
|
||||||
private final DumpHandler mDumpHandler;
|
private final DumpHandler mDumpHandler;
|
||||||
|
private final BroadcastDispatcher mBroadcastDispatcher;
|
||||||
|
private final LogBufferFreezer mLogBufferFreezer;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SystemUIService(
|
public SystemUIService(
|
||||||
@Main Handler mainHandler,
|
@Main Handler mainHandler,
|
||||||
DumpHandler dumpHandler) {
|
DumpHandler dumpHandler,
|
||||||
|
BroadcastDispatcher broadcastDispatcher,
|
||||||
|
LogBufferFreezer logBufferFreezer) {
|
||||||
super();
|
super();
|
||||||
mMainHandler = mainHandler;
|
mMainHandler = mainHandler;
|
||||||
mDumpHandler = dumpHandler;
|
mDumpHandler = dumpHandler;
|
||||||
|
mBroadcastDispatcher = broadcastDispatcher;
|
||||||
|
mLogBufferFreezer = logBufferFreezer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
|
// Start all of SystemUI
|
||||||
((SystemUIApplication) getApplication()).startServicesIfNeeded();
|
((SystemUIApplication) getApplication()).startServicesIfNeeded();
|
||||||
|
|
||||||
|
// Finish initializing dump logic
|
||||||
|
mLogBufferFreezer.attach(mBroadcastDispatcher);
|
||||||
|
|
||||||
// For debugging RescueParty
|
// For debugging RescueParty
|
||||||
if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) {
|
if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) {
|
||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
|
|||||||
@@ -140,6 +140,20 @@ class DumpManager @Inject constructor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun freezeBuffers() {
|
||||||
|
for (buffer in buffers.values) {
|
||||||
|
buffer.dumpable.freeze()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun unfreezeBuffers() {
|
||||||
|
for (buffer in buffers.values) {
|
||||||
|
buffer.dumpable.unfreeze()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun dumpDumpable(
|
private fun dumpDumpable(
|
||||||
dumpable: RegisteredDumpable<Dumpable>,
|
dumpable: RegisteredDumpable<Dumpable>,
|
||||||
fd: FileDescriptor,
|
fd: FileDescriptor,
|
||||||
@@ -174,3 +188,5 @@ private data class RegisteredDumpable<T>(
|
|||||||
val name: String,
|
val name: String,
|
||||||
val dumpable: T
|
val dumpable: T
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private const val TAG = "DumpManager"
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* 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.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.os.UserHandle
|
||||||
|
import android.util.Log
|
||||||
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
|
import com.android.systemui.dagger.qualifiers.Main
|
||||||
|
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class LogBufferFreezer constructor(
|
||||||
|
private val dumpManager: DumpManager,
|
||||||
|
@Main private val executor: DelayableExecutor,
|
||||||
|
private val freezeDuration: Long
|
||||||
|
) {
|
||||||
|
@Inject constructor(
|
||||||
|
dumpManager: DumpManager,
|
||||||
|
@Main executor: DelayableExecutor
|
||||||
|
) : this(dumpManager, executor, TimeUnit.MINUTES.toMillis(5))
|
||||||
|
|
||||||
|
private var pendingToken: Runnable? = null
|
||||||
|
|
||||||
|
fun attach(broadcastDispatcher: BroadcastDispatcher) {
|
||||||
|
broadcastDispatcher.registerReceiver(
|
||||||
|
object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
onBugreportStarted()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
IntentFilter("com.android.internal.intent.action.BUGREPORT_STARTED"),
|
||||||
|
executor,
|
||||||
|
UserHandle.ALL)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onBugreportStarted() {
|
||||||
|
pendingToken?.run()
|
||||||
|
|
||||||
|
Log.i(TAG, "Freezing log buffers")
|
||||||
|
dumpManager.freezeBuffers()
|
||||||
|
|
||||||
|
pendingToken = executor.executeDelayed({
|
||||||
|
Log.i(TAG, "Unfreezing log buffers")
|
||||||
|
pendingToken = null
|
||||||
|
dumpManager.unfreezeBuffers()
|
||||||
|
}, freezeDuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val TAG = "LogBufferFreezer"
|
||||||
@@ -74,6 +74,9 @@ class LogBuffer(
|
|||||||
) {
|
) {
|
||||||
private val buffer: ArrayDeque<LogMessageImpl> = ArrayDeque()
|
private val buffer: ArrayDeque<LogMessageImpl> = ArrayDeque()
|
||||||
|
|
||||||
|
var frozen = false
|
||||||
|
private set
|
||||||
|
|
||||||
fun attach(dumpManager: DumpManager) {
|
fun attach(dumpManager: DumpManager) {
|
||||||
dumpManager.registerBuffer(name, this)
|
dumpManager.registerBuffer(name, this)
|
||||||
}
|
}
|
||||||
@@ -112,9 +115,11 @@ class LogBuffer(
|
|||||||
initializer: LogMessage.() -> Unit,
|
initializer: LogMessage.() -> Unit,
|
||||||
noinline printer: LogMessage.() -> String
|
noinline printer: LogMessage.() -> String
|
||||||
) {
|
) {
|
||||||
val message = obtain(tag, level, printer)
|
if (!frozen) {
|
||||||
initializer(message)
|
val message = obtain(tag, level, printer)
|
||||||
push(message)
|
initializer(message)
|
||||||
|
push(message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -139,17 +144,16 @@ class LogBuffer(
|
|||||||
*
|
*
|
||||||
* In general, you should call [log] or [document] instead of this method.
|
* In general, you should call [log] or [document] instead of this method.
|
||||||
*/
|
*/
|
||||||
|
@Synchronized
|
||||||
fun obtain(
|
fun obtain(
|
||||||
tag: String,
|
tag: String,
|
||||||
level: LogLevel,
|
level: LogLevel,
|
||||||
printer: (LogMessage) -> String
|
printer: (LogMessage) -> String
|
||||||
): LogMessageImpl {
|
): LogMessageImpl {
|
||||||
val message = synchronized(buffer) {
|
val message = when {
|
||||||
if (buffer.size > maxLogs - poolSize) {
|
frozen -> LogMessageImpl.create()
|
||||||
buffer.removeFirst()
|
buffer.size > maxLogs - poolSize -> buffer.removeFirst()
|
||||||
} else {
|
else -> LogMessageImpl.create()
|
||||||
LogMessageImpl.create()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
message.reset(tag, level, System.currentTimeMillis(), printer)
|
message.reset(tag, level, System.currentTimeMillis(), printer)
|
||||||
return message
|
return message
|
||||||
@@ -158,33 +162,58 @@ class LogBuffer(
|
|||||||
/**
|
/**
|
||||||
* Pushes a message into buffer, possibly evicting an older message if the buffer is full.
|
* Pushes a message into buffer, possibly evicting an older message if the buffer is full.
|
||||||
*/
|
*/
|
||||||
|
@Synchronized
|
||||||
fun push(message: LogMessage) {
|
fun push(message: LogMessage) {
|
||||||
synchronized(buffer) {
|
if (frozen) {
|
||||||
if (buffer.size == maxLogs) {
|
return
|
||||||
Log.e(TAG, "LogBuffer $name has exceeded its pool size")
|
}
|
||||||
buffer.removeFirst()
|
if (buffer.size == maxLogs) {
|
||||||
}
|
Log.e(TAG, "LogBuffer $name has exceeded its pool size")
|
||||||
buffer.add(message as LogMessageImpl)
|
buffer.removeFirst()
|
||||||
if (logcatEchoTracker.isBufferLoggable(name, message.level) ||
|
}
|
||||||
logcatEchoTracker.isTagLoggable(message.tag, message.level)) {
|
buffer.add(message as LogMessageImpl)
|
||||||
echoToLogcat(message)
|
if (logcatEchoTracker.isBufferLoggable(name, message.level) ||
|
||||||
}
|
logcatEchoTracker.isTagLoggable(message.tag, message.level)) {
|
||||||
|
echoToLogcat(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Converts the entire buffer to a newline-delimited string */
|
/** Converts the entire buffer to a newline-delimited string */
|
||||||
|
@Synchronized
|
||||||
fun dump(pw: PrintWriter, tailLength: Int) {
|
fun dump(pw: PrintWriter, tailLength: Int) {
|
||||||
synchronized(buffer) {
|
val start = if (tailLength <= 0) { 0 } else { buffer.size - tailLength }
|
||||||
val start = if (tailLength <= 0) { 0 } else { buffer.size - tailLength }
|
|
||||||
|
|
||||||
for ((i, message) in buffer.withIndex()) {
|
for ((i, message) in buffer.withIndex()) {
|
||||||
if (i >= start) {
|
if (i >= start) {
|
||||||
dumpMessage(message, pw)
|
dumpMessage(message, pw)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Freezes" the contents of the buffer, making them immutable until [unfreeze] is called.
|
||||||
|
* Calls to [log], [document], [obtain], and [push] will not affect the buffer and will return
|
||||||
|
* dummy values if necessary.
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
fun freeze() {
|
||||||
|
if (!frozen) {
|
||||||
|
log(TAG, LogLevel.DEBUG, { str1 = name }, { "$str1 frozen" })
|
||||||
|
frozen = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undoes the effects of calling [freeze].
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
fun unfreeze() {
|
||||||
|
if (frozen) {
|
||||||
|
log(TAG, LogLevel.DEBUG, { str1 = name }, { "$str1 unfrozen" })
|
||||||
|
frozen = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun dumpMessage(message: LogMessage, pw: PrintWriter) {
|
private fun dumpMessage(message: LogMessage, pw: PrintWriter) {
|
||||||
pw.print(DATE_FORMAT.format(message.timestamp))
|
pw.print(DATE_FORMAT.format(message.timestamp))
|
||||||
pw.print(" ")
|
pw.print(" ")
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* 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.BroadcastReceiver
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.os.UserHandle
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
|
import com.android.systemui.util.concurrency.FakeExecutor
|
||||||
|
import com.android.systemui.util.mockito.any
|
||||||
|
import com.android.systemui.util.mockito.capture
|
||||||
|
import com.android.systemui.util.mockito.eq
|
||||||
|
import com.android.systemui.util.time.FakeSystemClock
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.ArgumentCaptor
|
||||||
|
import org.mockito.Captor
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.never
|
||||||
|
import org.mockito.Mockito.times
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
class LogBufferFreezerTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
lateinit var freezer: LogBufferFreezer
|
||||||
|
lateinit var receiver: BroadcastReceiver
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
lateinit var dumpManager: DumpManager
|
||||||
|
@Mock
|
||||||
|
lateinit var broadcastDispatcher: BroadcastDispatcher
|
||||||
|
@Captor
|
||||||
|
lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
|
||||||
|
|
||||||
|
val clock = FakeSystemClock()
|
||||||
|
val executor = FakeExecutor(clock)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
|
freezer = LogBufferFreezer(dumpManager, executor, 500)
|
||||||
|
|
||||||
|
freezer.attach(broadcastDispatcher)
|
||||||
|
|
||||||
|
verify(broadcastDispatcher)
|
||||||
|
.registerReceiver(
|
||||||
|
capture(receiverCaptor),
|
||||||
|
any(IntentFilter::class.java),
|
||||||
|
eq(executor),
|
||||||
|
any(UserHandle::class.java))
|
||||||
|
receiver = receiverCaptor.value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBuffersAreFrozenInResponseToBroadcast() {
|
||||||
|
// WHEN the bugreport intent is fired
|
||||||
|
receiver.onReceive(null, null)
|
||||||
|
|
||||||
|
// THEN the buffers are frozen
|
||||||
|
verify(dumpManager).freezeBuffers()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBuffersAreUnfrozenAfterTimeout() {
|
||||||
|
// GIVEN that we've already frozen the buffers in response to a broadcast
|
||||||
|
receiver.onReceive(null, null)
|
||||||
|
verify(dumpManager).freezeBuffers()
|
||||||
|
|
||||||
|
// WHEN the timeout expires
|
||||||
|
clock.advanceTime(501)
|
||||||
|
|
||||||
|
// THEN the buffers are unfrozen
|
||||||
|
verify(dumpManager).unfreezeBuffers()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBuffersAreNotPrematurelyUnfrozen() {
|
||||||
|
// GIVEN that we received a broadcast 499ms ago (shortly before the timeout would expire)
|
||||||
|
receiver.onReceive(null, null)
|
||||||
|
verify(dumpManager).freezeBuffers()
|
||||||
|
clock.advanceTime(499)
|
||||||
|
|
||||||
|
// WHEN we receive a second broadcast
|
||||||
|
receiver.onReceive(null, null)
|
||||||
|
|
||||||
|
// THEN the buffers are frozen a second time
|
||||||
|
verify(dumpManager, times(2)).freezeBuffers()
|
||||||
|
|
||||||
|
// THEN when we advance beyond the first timeout, nothing happens
|
||||||
|
clock.advanceTime(101)
|
||||||
|
verify(dumpManager, never()).unfreezeBuffers()
|
||||||
|
|
||||||
|
// THEN only when we advance past the reset timeout window are the buffers unfrozen
|
||||||
|
clock.advanceTime(401)
|
||||||
|
verify(dumpManager).unfreezeBuffers()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user