Skip to content

Commit 9d4f385

Browse files
Ned BurnsAndroid (Google) Code Review
authored andcommitted
Merge "Freeze log buffers when a bug report is taken" into rvc-dev
2 parents 247144d + 9feeca5 commit 9d4f385

5 files changed

Lines changed: 269 additions & 26 deletions

File tree

packages/SystemUI/src/com/android/systemui/SystemUIService.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@
2727
import android.util.Slog;
2828

2929
import com.android.internal.os.BinderInternal;
30+
import com.android.systemui.broadcast.BroadcastDispatcher;
3031
import com.android.systemui.dagger.qualifiers.Main;
3132
import com.android.systemui.dump.DumpHandler;
33+
import com.android.systemui.dump.LogBufferFreezer;
3234
import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
3335

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

4143
private final Handler mMainHandler;
4244
private final DumpHandler mDumpHandler;
45+
private final BroadcastDispatcher mBroadcastDispatcher;
46+
private final LogBufferFreezer mLogBufferFreezer;
4347

4448
@Inject
4549
public SystemUIService(
4650
@Main Handler mainHandler,
47-
DumpHandler dumpHandler) {
51+
DumpHandler dumpHandler,
52+
BroadcastDispatcher broadcastDispatcher,
53+
LogBufferFreezer logBufferFreezer) {
4854
super();
4955
mMainHandler = mainHandler;
5056
mDumpHandler = dumpHandler;
57+
mBroadcastDispatcher = broadcastDispatcher;
58+
mLogBufferFreezer = logBufferFreezer;
5159
}
5260

5361
@Override
5462
public void onCreate() {
5563
super.onCreate();
64+
65+
// Start all of SystemUI
5666
((SystemUIApplication) getApplication()).startServicesIfNeeded();
5767

68+
// Finish initializing dump logic
69+
mLogBufferFreezer.attach(mBroadcastDispatcher);
70+
5871
// For debugging RescueParty
5972
if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) {
6073
throw new RuntimeException();

packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,20 @@ class DumpManager @Inject constructor() {
140140
}
141141
}
142142

143+
@Synchronized
144+
fun freezeBuffers() {
145+
for (buffer in buffers.values) {
146+
buffer.dumpable.freeze()
147+
}
148+
}
149+
150+
@Synchronized
151+
fun unfreezeBuffers() {
152+
for (buffer in buffers.values) {
153+
buffer.dumpable.unfreeze()
154+
}
155+
}
156+
143157
private fun dumpDumpable(
144158
dumpable: RegisteredDumpable<Dumpable>,
145159
fd: FileDescriptor,
@@ -174,3 +188,5 @@ private data class RegisteredDumpable<T>(
174188
val name: String,
175189
val dumpable: T
176190
)
191+
192+
private const val TAG = "DumpManager"
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* 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.BroadcastReceiver
20+
import android.content.Context
21+
import android.content.Intent
22+
import android.content.IntentFilter
23+
import android.os.UserHandle
24+
import android.util.Log
25+
import com.android.systemui.broadcast.BroadcastDispatcher
26+
import com.android.systemui.dagger.qualifiers.Main
27+
import com.android.systemui.util.concurrency.DelayableExecutor
28+
import java.util.concurrent.TimeUnit
29+
import javax.inject.Inject
30+
31+
class LogBufferFreezer constructor(
32+
private val dumpManager: DumpManager,
33+
@Main private val executor: DelayableExecutor,
34+
private val freezeDuration: Long
35+
) {
36+
@Inject constructor(
37+
dumpManager: DumpManager,
38+
@Main executor: DelayableExecutor
39+
) : this(dumpManager, executor, TimeUnit.MINUTES.toMillis(5))
40+
41+
private var pendingToken: Runnable? = null
42+
43+
fun attach(broadcastDispatcher: BroadcastDispatcher) {
44+
broadcastDispatcher.registerReceiver(
45+
object : BroadcastReceiver() {
46+
override fun onReceive(context: Context?, intent: Intent?) {
47+
onBugreportStarted()
48+
}
49+
},
50+
IntentFilter("com.android.internal.intent.action.BUGREPORT_STARTED"),
51+
executor,
52+
UserHandle.ALL)
53+
}
54+
55+
private fun onBugreportStarted() {
56+
pendingToken?.run()
57+
58+
Log.i(TAG, "Freezing log buffers")
59+
dumpManager.freezeBuffers()
60+
61+
pendingToken = executor.executeDelayed({
62+
Log.i(TAG, "Unfreezing log buffers")
63+
pendingToken = null
64+
dumpManager.unfreezeBuffers()
65+
}, freezeDuration)
66+
}
67+
}
68+
69+
private const val TAG = "LogBufferFreezer"

packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ class LogBuffer(
7474
) {
7575
private val buffer: ArrayDeque<LogMessageImpl> = ArrayDeque()
7676

77+
var frozen = false
78+
private set
79+
7780
fun attach(dumpManager: DumpManager) {
7881
dumpManager.registerBuffer(name, this)
7982
}
@@ -112,9 +115,11 @@ class LogBuffer(
112115
initializer: LogMessage.() -> Unit,
113116
noinline printer: LogMessage.() -> String
114117
) {
115-
val message = obtain(tag, level, printer)
116-
initializer(message)
117-
push(message)
118+
if (!frozen) {
119+
val message = obtain(tag, level, printer)
120+
initializer(message)
121+
push(message)
122+
}
118123
}
119124

120125
/**
@@ -139,17 +144,16 @@ class LogBuffer(
139144
*
140145
* In general, you should call [log] or [document] instead of this method.
141146
*/
147+
@Synchronized
142148
fun obtain(
143149
tag: String,
144150
level: LogLevel,
145151
printer: (LogMessage) -> String
146152
): LogMessageImpl {
147-
val message = synchronized(buffer) {
148-
if (buffer.size > maxLogs - poolSize) {
149-
buffer.removeFirst()
150-
} else {
151-
LogMessageImpl.create()
152-
}
153+
val message = when {
154+
frozen -> LogMessageImpl.create()
155+
buffer.size > maxLogs - poolSize -> buffer.removeFirst()
156+
else -> LogMessageImpl.create()
153157
}
154158
message.reset(tag, level, System.currentTimeMillis(), printer)
155159
return message
@@ -158,33 +162,58 @@ class LogBuffer(
158162
/**
159163
* Pushes a message into buffer, possibly evicting an older message if the buffer is full.
160164
*/
165+
@Synchronized
161166
fun push(message: LogMessage) {
162-
synchronized(buffer) {
163-
if (buffer.size == maxLogs) {
164-
Log.e(TAG, "LogBuffer $name has exceeded its pool size")
165-
buffer.removeFirst()
166-
}
167-
buffer.add(message as LogMessageImpl)
168-
if (logcatEchoTracker.isBufferLoggable(name, message.level) ||
169-
logcatEchoTracker.isTagLoggable(message.tag, message.level)) {
170-
echoToLogcat(message)
171-
}
167+
if (frozen) {
168+
return
169+
}
170+
if (buffer.size == maxLogs) {
171+
Log.e(TAG, "LogBuffer $name has exceeded its pool size")
172+
buffer.removeFirst()
173+
}
174+
buffer.add(message as LogMessageImpl)
175+
if (logcatEchoTracker.isBufferLoggable(name, message.level) ||
176+
logcatEchoTracker.isTagLoggable(message.tag, message.level)) {
177+
echoToLogcat(message)
172178
}
173179
}
174180

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

180-
for ((i, message) in buffer.withIndex()) {
181-
if (i >= start) {
182-
dumpMessage(message, pw)
183-
}
186+
for ((i, message) in buffer.withIndex()) {
187+
if (i >= start) {
188+
dumpMessage(message, pw)
184189
}
185190
}
186191
}
187192

193+
/**
194+
* "Freezes" the contents of the buffer, making them immutable until [unfreeze] is called.
195+
* Calls to [log], [document], [obtain], and [push] will not affect the buffer and will return
196+
* dummy values if necessary.
197+
*/
198+
@Synchronized
199+
fun freeze() {
200+
if (!frozen) {
201+
log(TAG, LogLevel.DEBUG, { str1 = name }, { "$str1 frozen" })
202+
frozen = true
203+
}
204+
}
205+
206+
/**
207+
* Undoes the effects of calling [freeze].
208+
*/
209+
@Synchronized
210+
fun unfreeze() {
211+
if (frozen) {
212+
log(TAG, LogLevel.DEBUG, { str1 = name }, { "$str1 unfrozen" })
213+
frozen = false
214+
}
215+
}
216+
188217
private fun dumpMessage(message: LogMessage, pw: PrintWriter) {
189218
pw.print(DATE_FORMAT.format(message.timestamp))
190219
pw.print(" ")
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* 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.BroadcastReceiver
20+
import android.content.IntentFilter
21+
import android.os.UserHandle
22+
import androidx.test.filters.SmallTest
23+
import com.android.systemui.SysuiTestCase
24+
import com.android.systemui.broadcast.BroadcastDispatcher
25+
import com.android.systemui.util.concurrency.FakeExecutor
26+
import com.android.systemui.util.mockito.any
27+
import com.android.systemui.util.mockito.capture
28+
import com.android.systemui.util.mockito.eq
29+
import com.android.systemui.util.time.FakeSystemClock
30+
import org.junit.Before
31+
import org.junit.Test
32+
import org.mockito.ArgumentCaptor
33+
import org.mockito.Captor
34+
import org.mockito.Mock
35+
import org.mockito.Mockito.never
36+
import org.mockito.Mockito.times
37+
import org.mockito.Mockito.verify
38+
import org.mockito.MockitoAnnotations
39+
40+
@SmallTest
41+
class LogBufferFreezerTest : SysuiTestCase() {
42+
43+
lateinit var freezer: LogBufferFreezer
44+
lateinit var receiver: BroadcastReceiver
45+
46+
@Mock
47+
lateinit var dumpManager: DumpManager
48+
@Mock
49+
lateinit var broadcastDispatcher: BroadcastDispatcher
50+
@Captor
51+
lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
52+
53+
val clock = FakeSystemClock()
54+
val executor = FakeExecutor(clock)
55+
56+
@Before
57+
fun setUp() {
58+
MockitoAnnotations.initMocks(this)
59+
60+
freezer = LogBufferFreezer(dumpManager, executor, 500)
61+
62+
freezer.attach(broadcastDispatcher)
63+
64+
verify(broadcastDispatcher)
65+
.registerReceiver(
66+
capture(receiverCaptor),
67+
any(IntentFilter::class.java),
68+
eq(executor),
69+
any(UserHandle::class.java))
70+
receiver = receiverCaptor.value
71+
}
72+
73+
@Test
74+
fun testBuffersAreFrozenInResponseToBroadcast() {
75+
// WHEN the bugreport intent is fired
76+
receiver.onReceive(null, null)
77+
78+
// THEN the buffers are frozen
79+
verify(dumpManager).freezeBuffers()
80+
}
81+
82+
@Test
83+
fun testBuffersAreUnfrozenAfterTimeout() {
84+
// GIVEN that we've already frozen the buffers in response to a broadcast
85+
receiver.onReceive(null, null)
86+
verify(dumpManager).freezeBuffers()
87+
88+
// WHEN the timeout expires
89+
clock.advanceTime(501)
90+
91+
// THEN the buffers are unfrozen
92+
verify(dumpManager).unfreezeBuffers()
93+
}
94+
95+
@Test
96+
fun testBuffersAreNotPrematurelyUnfrozen() {
97+
// GIVEN that we received a broadcast 499ms ago (shortly before the timeout would expire)
98+
receiver.onReceive(null, null)
99+
verify(dumpManager).freezeBuffers()
100+
clock.advanceTime(499)
101+
102+
// WHEN we receive a second broadcast
103+
receiver.onReceive(null, null)
104+
105+
// THEN the buffers are frozen a second time
106+
verify(dumpManager, times(2)).freezeBuffers()
107+
108+
// THEN when we advance beyond the first timeout, nothing happens
109+
clock.advanceTime(101)
110+
verify(dumpManager, never()).unfreezeBuffers()
111+
112+
// THEN only when we advance past the reset timeout window are the buffers unfrozen
113+
clock.advanceTime(401)
114+
verify(dumpManager).unfreezeBuffers()
115+
}
116+
}

0 commit comments

Comments
 (0)