Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 66e396e3 authored by Ned Burns's avatar Ned Burns Committed by Automerger Merge Worker
Browse files

Merge "Freeze log buffers when a bug report is taken" into rvc-dev am:...

Merge "Freeze log buffers when a bug report is taken" into rvc-dev am: 9d4f3850 am: 4458933b am: 559f1003 am: 48925b3d

Original change: undetermined

Change-Id: Idf5f3f253c49afcc271f3cdad9414962d5979a3f
parents 2ea31992 48925b3d
Loading
Loading
Loading
Loading
+14 −1
Original line number Diff line number Diff line
@@ -27,8 +27,10 @@ import android.os.UserHandle;
import android.util.Slog;

import com.android.internal.os.BinderInternal;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.LogBufferFreezer;
import com.android.systemui.dump.SystemUIAuxiliaryDumpService;

import java.io.FileDescriptor;
@@ -40,21 +42,32 @@ public class SystemUIService extends Service {

    private final Handler mMainHandler;
    private final DumpHandler mDumpHandler;
    private final BroadcastDispatcher mBroadcastDispatcher;
    private final LogBufferFreezer mLogBufferFreezer;

    @Inject
    public SystemUIService(
            @Main Handler mainHandler,
            DumpHandler dumpHandler) {
            DumpHandler dumpHandler,
            BroadcastDispatcher broadcastDispatcher,
            LogBufferFreezer logBufferFreezer) {
        super();
        mMainHandler = mainHandler;
        mDumpHandler = dumpHandler;
        mBroadcastDispatcher = broadcastDispatcher;
        mLogBufferFreezer = logBufferFreezer;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        // Start all of SystemUI
        ((SystemUIApplication) getApplication()).startServicesIfNeeded();

        // Finish initializing dump logic
        mLogBufferFreezer.attach(mBroadcastDispatcher);

        // For debugging RescueParty
        if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) {
            throw new RuntimeException();
+16 −0
Original line number Diff line number Diff line
@@ -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(
        dumpable: RegisteredDumpable<Dumpable>,
        fd: FileDescriptor,
@@ -174,3 +188,5 @@ private data class RegisteredDumpable<T>(
    val name: String,
    val dumpable: T
)

private const val TAG = "DumpManager"
 No newline at end of file
+69 −0
Original line number Diff line number Diff line
/*
 * 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"
 No newline at end of file
+54 −25
Original line number Diff line number Diff line
@@ -74,6 +74,9 @@ class LogBuffer(
) {
    private val buffer: ArrayDeque<LogMessageImpl> = ArrayDeque()

    var frozen = false
        private set

    fun attach(dumpManager: DumpManager) {
        dumpManager.registerBuffer(name, this)
    }
@@ -112,10 +115,12 @@ class LogBuffer(
        initializer: LogMessage.() -> Unit,
        noinline printer: LogMessage.() -> String
    ) {
        if (!frozen) {
            val message = obtain(tag, level, printer)
            initializer(message)
            push(message)
        }
    }

    /**
     * Same as [log], but doesn't push the message to the buffer. Useful if you need to supply a
@@ -139,17 +144,16 @@ class LogBuffer(
     *
     * In general, you should call [log] or [document] instead of this method.
     */
    @Synchronized
    fun obtain(
        tag: String,
        level: LogLevel,
        printer: (LogMessage) -> String
    ): LogMessageImpl {
        val message = synchronized(buffer) {
            if (buffer.size > maxLogs - poolSize) {
                buffer.removeFirst()
            } else {
                LogMessageImpl.create()
            }
        val message = when {
            frozen -> LogMessageImpl.create()
            buffer.size > maxLogs - poolSize -> buffer.removeFirst()
            else -> LogMessageImpl.create()
        }
        message.reset(tag, level, System.currentTimeMillis(), printer)
        return message
@@ -158,8 +162,11 @@ class LogBuffer(
    /**
     * Pushes a message into buffer, possibly evicting an older message if the buffer is full.
     */
    @Synchronized
    fun push(message: LogMessage) {
        synchronized(buffer) {
        if (frozen) {
            return
        }
        if (buffer.size == maxLogs) {
            Log.e(TAG, "LogBuffer $name has exceeded its pool size")
            buffer.removeFirst()
@@ -170,11 +177,10 @@ class LogBuffer(
            echoToLogcat(message)
        }
    }
    }

    /** Converts the entire buffer to a newline-delimited string */
    @Synchronized
    fun dump(pw: PrintWriter, tailLength: Int) {
        synchronized(buffer) {
        val start = if (tailLength <= 0) { 0 } else { buffer.size - tailLength }

        for ((i, message) in buffer.withIndex()) {
@@ -183,6 +189,29 @@ class LogBuffer(
            }
        }
    }

    /**
     * "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) {
+116 −0
Original line number Diff line number Diff line
/*
 * 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()
    }
}