Merge changes from topic "log-buffer" into tm-dev am: ff1b7dc538
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/18097841 Change-Id: Ic6fa080a2faa224ccb54f170d7b3874e438622e5 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -19,13 +19,14 @@ package com.android.systemui.log
|
|||||||
import android.os.Trace
|
import android.os.Trace
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.android.systemui.log.dagger.LogModule
|
import com.android.systemui.log.dagger.LogModule
|
||||||
|
import com.android.systemui.util.collection.RingBuffer
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.ArrayDeque
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.ArrayBlockingQueue
|
import java.util.concurrent.ArrayBlockingQueue
|
||||||
import java.util.concurrent.BlockingQueue
|
import java.util.concurrent.BlockingQueue
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple ring buffer of recyclable log messages
|
* A simple ring buffer of recyclable log messages
|
||||||
@@ -63,29 +64,22 @@ import kotlin.concurrent.thread
|
|||||||
*
|
*
|
||||||
* Buffers are provided by [LogModule]. Instances should be created using a [LogBufferFactory].
|
* Buffers are provided by [LogModule]. Instances should be created using a [LogBufferFactory].
|
||||||
*
|
*
|
||||||
* @param name The name of this buffer
|
* @param name The name of this buffer, printed when the buffer is dumped and in some other
|
||||||
* @param maxLogs The maximum number of messages to keep in memory at any one time, including the
|
* situations.
|
||||||
* unused pool. Must be >= [poolSize].
|
* @param maxSize The maximum number of messages to keep in memory at any one time. Buffers start
|
||||||
* @param poolSize The maximum amount that the size of the buffer is allowed to flex in response to
|
* out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches
|
||||||
* sequential calls to [document] that aren't immediately followed by a matching call to [push].
|
* the maximum, it behaves like a ring buffer.
|
||||||
*/
|
*/
|
||||||
class LogBuffer @JvmOverloads constructor(
|
class LogBuffer @JvmOverloads constructor(
|
||||||
private val name: String,
|
private val name: String,
|
||||||
private val maxLogs: Int,
|
private val maxSize: Int,
|
||||||
private val poolSize: Int,
|
|
||||||
private val logcatEchoTracker: LogcatEchoTracker,
|
private val logcatEchoTracker: LogcatEchoTracker,
|
||||||
private val systrace: Boolean = true
|
private val systrace: Boolean = true
|
||||||
) {
|
) {
|
||||||
init {
|
private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() }
|
||||||
if (maxLogs < poolSize) {
|
|
||||||
throw IllegalArgumentException("maxLogs must be greater than or equal to poolSize, " +
|
|
||||||
"but maxLogs=$maxLogs < $poolSize=poolSize")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val buffer: ArrayDeque<LogMessageImpl> = ArrayDeque()
|
private val echoMessageQueue: BlockingQueue<LogMessage>? =
|
||||||
private val echoMessageQueue: BlockingQueue<LogMessageImpl>? =
|
if (logcatEchoTracker.logInBackgroundThread) ArrayBlockingQueue(10) else null
|
||||||
if (logcatEchoTracker.logInBackgroundThread) ArrayBlockingQueue(poolSize) else null
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (logcatEchoTracker.logInBackgroundThread && echoMessageQueue != null) {
|
if (logcatEchoTracker.logInBackgroundThread && echoMessageQueue != null) {
|
||||||
@@ -104,6 +98,9 @@ class LogBuffer @JvmOverloads constructor(
|
|||||||
var frozen = false
|
var frozen = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
private val mutable
|
||||||
|
get() = !frozen && maxSize > 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs a message to the log buffer
|
* Logs a message to the log buffer
|
||||||
*
|
*
|
||||||
@@ -138,34 +135,19 @@ class LogBuffer @JvmOverloads constructor(
|
|||||||
initializer: LogMessage.() -> Unit,
|
initializer: LogMessage.() -> Unit,
|
||||||
noinline printer: LogMessage.() -> String
|
noinline printer: LogMessage.() -> String
|
||||||
) {
|
) {
|
||||||
if (!frozen) {
|
|
||||||
val message = obtain(tag, level, printer)
|
val message = obtain(tag, level, printer)
|
||||||
initializer(message)
|
initializer(message)
|
||||||
push(message)
|
commit(message)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same as [log], but doesn't push the message to the buffer. Useful if you need to supply a
|
* You should call [log] instead of this method.
|
||||||
* "reason" for doing something (the thing you supply the reason to will presumably call [push]
|
|
||||||
* on that message at some point).
|
|
||||||
*/
|
|
||||||
inline fun document(
|
|
||||||
tag: String,
|
|
||||||
level: LogLevel,
|
|
||||||
initializer: LogMessage.() -> Unit,
|
|
||||||
noinline printer: LogMessage.() -> String
|
|
||||||
): LogMessage {
|
|
||||||
val message = obtain(tag, level, printer)
|
|
||||||
initializer(message)
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains an instance of [LogMessageImpl], usually from the object pool. If the pool has been
|
|
||||||
* exhausted, creates a new instance.
|
|
||||||
*
|
*
|
||||||
* In general, you should call [log] or [document] instead of this method.
|
* Obtains the next [LogMessage] from the ring buffer. If the buffer is not yet at max size,
|
||||||
|
* grows the buffer by one.
|
||||||
|
*
|
||||||
|
* After calling [obtain], the message will now be at the end of the buffer. The caller must
|
||||||
|
* store any relevant data on the message and then call [commit].
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun obtain(
|
fun obtain(
|
||||||
@@ -173,28 +155,26 @@ class LogBuffer @JvmOverloads constructor(
|
|||||||
level: LogLevel,
|
level: LogLevel,
|
||||||
printer: (LogMessage) -> String
|
printer: (LogMessage) -> String
|
||||||
): LogMessageImpl {
|
): LogMessageImpl {
|
||||||
val message = when {
|
if (!mutable) {
|
||||||
frozen -> LogMessageImpl.create()
|
return FROZEN_MESSAGE
|
||||||
buffer.size > maxLogs - poolSize -> buffer.removeFirst()
|
|
||||||
else -> LogMessageImpl.create()
|
|
||||||
}
|
}
|
||||||
|
val message = buffer.advance()
|
||||||
message.reset(tag, level, System.currentTimeMillis(), printer)
|
message.reset(tag, level, System.currentTimeMillis(), printer)
|
||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pushes a message into buffer, possibly evicting an older message if the buffer is full.
|
* You should call [log] instead of this method.
|
||||||
|
*
|
||||||
|
* After acquiring a message via [obtain], call this method to signal to the buffer that you
|
||||||
|
* have finished filling in its data fields. The message will be echoed to logcat if
|
||||||
|
* necessary.
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun push(message: LogMessage) {
|
fun commit(message: LogMessage) {
|
||||||
if (frozen) {
|
if (!mutable) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (buffer.size == maxLogs) {
|
|
||||||
Log.e(TAG, "LogBuffer $name has exceeded its pool size")
|
|
||||||
buffer.removeFirst()
|
|
||||||
}
|
|
||||||
buffer.add(message as LogMessageImpl)
|
|
||||||
// Log in the background thread only if echoMessageQueue exists and has capacity (checking
|
// Log in the background thread only if echoMessageQueue exists and has capacity (checking
|
||||||
// capacity avoids the possibility of blocking this thread)
|
// capacity avoids the possibility of blocking this thread)
|
||||||
if (echoMessageQueue != null && echoMessageQueue.remainingCapacity() > 0) {
|
if (echoMessageQueue != null && echoMessageQueue.remainingCapacity() > 0) {
|
||||||
@@ -210,7 +190,7 @@ class LogBuffer @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Sends message to echo after determining whether to use Logcat and/or systrace. */
|
/** Sends message to echo after determining whether to use Logcat and/or systrace. */
|
||||||
private fun echoToDesiredEndpoints(message: LogMessageImpl) {
|
private fun echoToDesiredEndpoints(message: LogMessage) {
|
||||||
val includeInLogcat = logcatEchoTracker.isBufferLoggable(name, message.level) ||
|
val includeInLogcat = logcatEchoTracker.isBufferLoggable(name, message.level) ||
|
||||||
logcatEchoTracker.isTagLoggable(message.tag, message.level)
|
logcatEchoTracker.isTagLoggable(message.tag, message.level)
|
||||||
echo(message, toLogcat = includeInLogcat, toSystrace = systrace)
|
echo(message, toLogcat = includeInLogcat, toSystrace = systrace)
|
||||||
@@ -219,19 +199,17 @@ class LogBuffer @JvmOverloads constructor(
|
|||||||
/** Converts the entire buffer to a newline-delimited string */
|
/** Converts the entire buffer to a newline-delimited string */
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun dump(pw: PrintWriter, tailLength: Int) {
|
fun dump(pw: PrintWriter, tailLength: Int) {
|
||||||
val start = if (tailLength <= 0) { 0 } else { buffer.size - tailLength }
|
val iterationStart = if (tailLength <= 0) { 0 } else { max(0, buffer.size - tailLength) }
|
||||||
|
|
||||||
for ((i, message) in buffer.withIndex()) {
|
for (i in iterationStart until buffer.size) {
|
||||||
if (i >= start) {
|
dumpMessage(buffer[i], pw)
|
||||||
dumpMessage(message, pw)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* "Freezes" the contents of the buffer, making them immutable until [unfreeze] is called.
|
* "Freezes" the contents of the buffer, making it immutable until [unfreeze] is called.
|
||||||
* Calls to [log], [document], [obtain], and [push] will not affect the buffer and will return
|
* Calls to [log], [obtain], and [commit] will not affect the buffer and will return dummy
|
||||||
* dummy values if necessary.
|
* values if necessary.
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun freeze() {
|
fun freeze() {
|
||||||
@@ -293,3 +271,4 @@ class LogBuffer @JvmOverloads constructor(
|
|||||||
|
|
||||||
private const val TAG = "LogBuffer"
|
private const val TAG = "LogBuffer"
|
||||||
private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
|
private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
|
||||||
|
private val FROZEN_MESSAGE = LogMessageImpl.create()
|
||||||
@@ -27,22 +27,21 @@ class LogBufferFactory @Inject constructor(
|
|||||||
private val logcatEchoTracker: LogcatEchoTracker
|
private val logcatEchoTracker: LogcatEchoTracker
|
||||||
) {
|
) {
|
||||||
/* limit the size of maxPoolSize for low ram (Go) devices */
|
/* limit the size of maxPoolSize for low ram (Go) devices */
|
||||||
private fun poolLimit(maxPoolSize_requested: Int): Int {
|
private fun adjustMaxSize(requestedMaxSize: Int): Int {
|
||||||
if (ActivityManager.isLowRamDeviceStatic()) {
|
return if (ActivityManager.isLowRamDeviceStatic()) {
|
||||||
return minOf(maxPoolSize_requested, 20) /* low ram max log size*/
|
minOf(requestedMaxSize, 20) /* low ram max log size*/
|
||||||
} else {
|
} else {
|
||||||
return maxPoolSize_requested
|
requestedMaxSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun create(
|
fun create(
|
||||||
name: String,
|
name: String,
|
||||||
maxPoolSize: Int,
|
maxSize: Int,
|
||||||
flexSize: Int = 10,
|
|
||||||
systrace: Boolean = true
|
systrace: Boolean = true
|
||||||
): LogBuffer {
|
): LogBuffer {
|
||||||
val buffer = LogBuffer(name, poolLimit(maxPoolSize), flexSize, logcatEchoTracker, systrace)
|
val buffer = LogBuffer(name, adjustMaxSize(maxSize), logcatEchoTracker, systrace)
|
||||||
dumpManager.registerBuffer(name, buffer)
|
dumpManager.registerBuffer(name, buffer)
|
||||||
return buffer
|
return buffer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class LogcatEchoTrackerDebug private constructor(
|
|||||||
Settings.Global.getUriFor(BUFFER_PATH),
|
Settings.Global.getUriFor(BUFFER_PATH),
|
||||||
true,
|
true,
|
||||||
object : ContentObserver(Handler(mainLooper)) {
|
object : ContentObserver(Handler(mainLooper)) {
|
||||||
override fun onChange(selfChange: Boolean, uri: Uri) {
|
override fun onChange(selfChange: Boolean, uri: Uri?) {
|
||||||
super.onChange(selfChange, uri)
|
super.onChange(selfChange, uri)
|
||||||
cachedBufferLevels.clear()
|
cachedBufferLevels.clear()
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ class LogcatEchoTrackerDebug private constructor(
|
|||||||
Settings.Global.getUriFor(TAG_PATH),
|
Settings.Global.getUriFor(TAG_PATH),
|
||||||
true,
|
true,
|
||||||
object : ContentObserver(Handler(mainLooper)) {
|
object : ContentObserver(Handler(mainLooper)) {
|
||||||
override fun onChange(selfChange: Boolean, uri: Uri) {
|
override fun onChange(selfChange: Boolean, uri: Uri?) {
|
||||||
super.onChange(selfChange, uri)
|
super.onChange(selfChange, uri)
|
||||||
cachedTagLevels.clear()
|
cachedTagLevels.clear()
|
||||||
}
|
}
|
||||||
@@ -110,7 +110,7 @@ class LogcatEchoTrackerDebug private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseProp(propValue: String?): LogLevel {
|
private fun parseProp(propValue: String?): LogLevel {
|
||||||
return when (propValue?.toLowerCase()) {
|
return when (propValue?.lowercase()) {
|
||||||
"verbose" -> LogLevel.VERBOSE
|
"verbose" -> LogLevel.VERBOSE
|
||||||
"v" -> LogLevel.VERBOSE
|
"v" -> LogLevel.VERBOSE
|
||||||
"debug" -> LogLevel.DEBUG
|
"debug" -> LogLevel.DEBUG
|
||||||
|
|||||||
@@ -49,8 +49,7 @@ public class LogModule {
|
|||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
@NotificationLog
|
@NotificationLog
|
||||||
public static LogBuffer provideNotificationsLogBuffer(LogBufferFactory factory) {
|
public static LogBuffer provideNotificationsLogBuffer(LogBufferFactory factory) {
|
||||||
return factory.create("NotifLog", 1000 /* maxPoolSize */,
|
return factory.create("NotifLog", 1000 /* maxSize */, false /* systrace */);
|
||||||
10 /* flexSize */, false /* systrace */);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Provides a logging buffer for all logs related to the data layer of notifications. */
|
/** Provides a logging buffer for all logs related to the data layer of notifications. */
|
||||||
@@ -74,8 +73,7 @@ public class LogModule {
|
|||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
@NotificationSectionLog
|
@NotificationSectionLog
|
||||||
public static LogBuffer provideNotificationSectionLogBuffer(LogBufferFactory factory) {
|
public static LogBuffer provideNotificationSectionLogBuffer(LogBufferFactory factory) {
|
||||||
return factory.create("NotifSectionLog", 1000 /* maxPoolSize */,
|
return factory.create("NotifSectionLog", 1000 /* maxSize */, false /* systrace */);
|
||||||
10 /* flexSize */, false /* systrace */);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Provides a logging buffer for all logs related to the data layer of notifications. */
|
/** Provides a logging buffer for all logs related to the data layer of notifications. */
|
||||||
@@ -91,8 +89,7 @@ public class LogModule {
|
|||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
@QSLog
|
@QSLog
|
||||||
public static LogBuffer provideQuickSettingsLogBuffer(LogBufferFactory factory) {
|
public static LogBuffer provideQuickSettingsLogBuffer(LogBufferFactory factory) {
|
||||||
return factory.create("QSLog", 500 /* maxPoolSize */,
|
return factory.create("QSLog", 500 /* maxSize */, false /* systrace */);
|
||||||
10 /* flexSize */, false /* systrace */);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */
|
/** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */
|
||||||
@@ -100,8 +97,8 @@ public class LogModule {
|
|||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
@BroadcastDispatcherLog
|
@BroadcastDispatcherLog
|
||||||
public static LogBuffer provideBroadcastDispatcherLogBuffer(LogBufferFactory factory) {
|
public static LogBuffer provideBroadcastDispatcherLogBuffer(LogBufferFactory factory) {
|
||||||
return factory.create("BroadcastDispatcherLog", 500 /* maxPoolSize */,
|
return factory.create("BroadcastDispatcherLog", 500 /* maxSize */,
|
||||||
10 /* flexSize */, false /* systrace */);
|
false /* systrace */);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Provides a logging buffer for all logs related to Toasts shown by SystemUI. */
|
/** Provides a logging buffer for all logs related to Toasts shown by SystemUI. */
|
||||||
@@ -139,8 +136,8 @@ public class LogModule {
|
|||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
@QSFragmentDisableLog
|
@QSFragmentDisableLog
|
||||||
public static LogBuffer provideQSFragmentDisableLogBuffer(LogBufferFactory factory) {
|
public static LogBuffer provideQSFragmentDisableLogBuffer(LogBufferFactory factory) {
|
||||||
return factory.create("QSFragmentDisableFlagsLog", 10 /* maxPoolSize */,
|
return factory.create("QSFragmentDisableFlagsLog", 10 /* maxSize */,
|
||||||
10 /* flexSize */, false /* systrace */);
|
false /* systrace */);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
package com.android.systemui.statusbar.phone
|
package com.android.systemui.statusbar.phone
|
||||||
|
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import com.android.internal.util.RingBuffer
|
|
||||||
import com.android.systemui.dump.DumpsysTableLogger
|
import com.android.systemui.dump.DumpsysTableLogger
|
||||||
import com.android.systemui.dump.Row
|
import com.android.systemui.dump.Row
|
||||||
|
import com.android.systemui.util.collection.RingBuffer
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@@ -67,16 +67,9 @@ class NPVCDownEventState private constructor(
|
|||||||
* Do not use [append] to add new elements. Instead use [insert], as it will recycle if
|
* Do not use [append] to add new elements. Instead use [insert], as it will recycle if
|
||||||
* necessary.
|
* necessary.
|
||||||
*/
|
*/
|
||||||
class Buffer(
|
class Buffer(capacity: Int) {
|
||||||
capacity: Int
|
|
||||||
) : RingBuffer<NPVCDownEventState>(NPVCDownEventState::class.java, capacity) {
|
|
||||||
override fun append(t: NPVCDownEventState?) {
|
|
||||||
throw UnsupportedOperationException("Not supported, use insert instead")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createNewItem(): NPVCDownEventState {
|
private val buffer = RingBuffer(capacity) { NPVCDownEventState() }
|
||||||
return NPVCDownEventState()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert a new element in the buffer.
|
* Insert a new element in the buffer.
|
||||||
@@ -94,7 +87,7 @@ class NPVCDownEventState private constructor(
|
|||||||
touchSlopExceededBeforeDown: Boolean,
|
touchSlopExceededBeforeDown: Boolean,
|
||||||
lastEventSynthesized: Boolean
|
lastEventSynthesized: Boolean
|
||||||
) {
|
) {
|
||||||
nextSlot.apply {
|
buffer.advance().apply {
|
||||||
this.timeStamp = timeStamp
|
this.timeStamp = timeStamp
|
||||||
this.x = x
|
this.x = x
|
||||||
this.y = y
|
this.y = y
|
||||||
@@ -115,7 +108,7 @@ class NPVCDownEventState private constructor(
|
|||||||
* @see NPVCDownEventState.asStringList
|
* @see NPVCDownEventState.asStringList
|
||||||
*/
|
*/
|
||||||
fun toList(): List<Row> {
|
fun toList(): List<Row> {
|
||||||
return toArray().map { it.asStringList }
|
return buffer.asSequence().map { it.asStringList }.toList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.util.collection
|
||||||
|
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple ring buffer implementation
|
||||||
|
*
|
||||||
|
* Use [advance] to get the least recent item in the buffer (and then presumably fill it with
|
||||||
|
* appropriate data). This will cause it to become the most recent item.
|
||||||
|
*
|
||||||
|
* As the buffer is used, it will grow, allocating new instances of T using [factory] until it
|
||||||
|
* reaches [maxSize]. After this point, no new instances will be created. Instead, the "oldest"
|
||||||
|
* instances will be recycled from the back of the buffer and placed at the front.
|
||||||
|
*
|
||||||
|
* @param maxSize The maximum size the buffer can grow to before it begins functioning as a ring.
|
||||||
|
* @param factory A function that creates a fresh instance of T. Used by the buffer while it's
|
||||||
|
* growing to [maxSize].
|
||||||
|
*/
|
||||||
|
class RingBuffer<T>(
|
||||||
|
private val maxSize: Int,
|
||||||
|
private val factory: () -> T
|
||||||
|
) : Iterable<T> {
|
||||||
|
|
||||||
|
private val buffer = MutableList<T?>(maxSize) { null }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract representation that points to the "end" of the buffer. Increments every time
|
||||||
|
* [advance] is called and never wraps. Use [indexOf] to calculate the associated index into
|
||||||
|
* the backing array. Always points to the "next" available slot in the buffer. Before the
|
||||||
|
* buffer has completely filled, the value pointed to will be null. Afterward, it will be the
|
||||||
|
* value at the "beginning" of the buffer.
|
||||||
|
*
|
||||||
|
* This value is unlikely to overflow. Assuming [advance] is called at rate of 100 calls/ms,
|
||||||
|
* omega will overflow after a little under three million years of continuous operation.
|
||||||
|
*/
|
||||||
|
private var omega: Long = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of items currently stored in the buffer. Calls to [advance] will cause this value
|
||||||
|
* to increase by one until it reaches [maxSize].
|
||||||
|
*/
|
||||||
|
val size: Int
|
||||||
|
get() = if (omega < maxSize) omega.toInt() else maxSize
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advances the buffer's position by one and returns the value that is now present at the "end"
|
||||||
|
* of the buffer. If the buffer is not yet full, uses [factory] to create a new item.
|
||||||
|
* Otherwise, reuses the value that was previously at the "beginning" of the buffer.
|
||||||
|
*
|
||||||
|
* IMPORTANT: The value is returned as-is, without being reset. It will retain any data that
|
||||||
|
* was previously stored on it.
|
||||||
|
*/
|
||||||
|
fun advance(): T {
|
||||||
|
val index = indexOf(omega)
|
||||||
|
omega += 1
|
||||||
|
val entry = buffer[index] ?: factory().also {
|
||||||
|
buffer[index] = it
|
||||||
|
}
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value stored at [index], which can range from 0 (the "start", or oldest element
|
||||||
|
* of the buffer) to [size] - 1 (the "end", or newest element of the buffer).
|
||||||
|
*/
|
||||||
|
operator fun get(index: Int): T {
|
||||||
|
if (index < 0 || index >= size) {
|
||||||
|
throw IndexOutOfBoundsException("Index $index is out of bounds")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If omega is larger than the maxSize, then the buffer is full, and omega is equivalent
|
||||||
|
// to the "start" of the buffer. If omega is smaller than the maxSize, then the buffer is
|
||||||
|
// not yet full and our start should be 0. However, in modspace, maxSize and 0 are
|
||||||
|
// equivalent, so we can get away with using it as the start value instead.
|
||||||
|
val start = max(omega, maxSize.toLong())
|
||||||
|
|
||||||
|
return buffer[indexOf(start + index)]!!
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun forEach(action: (T) -> Unit) {
|
||||||
|
for (i in 0 until size) {
|
||||||
|
action(get(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun iterator(): Iterator<T> {
|
||||||
|
return object : Iterator<T> {
|
||||||
|
private var position: Int = 0
|
||||||
|
|
||||||
|
override fun next(): T {
|
||||||
|
if (position >= size) {
|
||||||
|
throw NoSuchElementException()
|
||||||
|
}
|
||||||
|
return get(position).also { position += 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasNext(): Boolean {
|
||||||
|
return position < size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun indexOf(position: Long): Int {
|
||||||
|
return (position % maxSize).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ import com.android.systemui.log.LogcatEchoTracker
|
|||||||
* Creates a LogBuffer that will echo everything to logcat, which is useful for debugging tests.
|
* Creates a LogBuffer that will echo everything to logcat, which is useful for debugging tests.
|
||||||
*/
|
*/
|
||||||
fun logcatLogBuffer(name: String = "EchoToLogcatLogBuffer") =
|
fun logcatLogBuffer(name: String = "EchoToLogcatLogBuffer") =
|
||||||
LogBuffer(name, 50, 50, LogcatEchoTrackerAlways())
|
LogBuffer(name, 50, LogcatEchoTrackerAlways())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [LogcatEchoTracker] that always allows echoing to the logcat.
|
* A [LogcatEchoTracker] that always allows echoing to the logcat.
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class LSShadeTransitionLoggerTest : SysuiTestCase() {
|
|||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
logger = LSShadeTransitionLogger(
|
logger = LSShadeTransitionLogger(
|
||||||
LogBuffer("Test", 10, 10, mock()),
|
LogBuffer("Test", 10, mock()),
|
||||||
gestureLogger,
|
gestureLogger,
|
||||||
displayMetrics)
|
displayMetrics)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
|
|||||||
mCommandQueue,
|
mCommandQueue,
|
||||||
mCarrierConfigTracker,
|
mCarrierConfigTracker,
|
||||||
new CollapsedStatusBarFragmentLogger(
|
new CollapsedStatusBarFragmentLogger(
|
||||||
new LogBuffer("TEST", 1, 1, mock(LogcatEchoTracker.class)),
|
new LogBuffer("TEST", 1, mock(LogcatEchoTracker.class)),
|
||||||
new DisableFlagsLogger()
|
new DisableFlagsLogger()
|
||||||
),
|
),
|
||||||
mOperatorNameViewControllerFactory,
|
mOperatorNameViewControllerFactory,
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.util.collection
|
||||||
|
|
||||||
|
import android.testing.AndroidTestingRunner
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertSame
|
||||||
|
import org.junit.Assert.assertThrows
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(AndroidTestingRunner::class)
|
||||||
|
class RingBufferTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
private val buffer = RingBuffer(5) { TestElement() }
|
||||||
|
|
||||||
|
private val history = mutableListOf<TestElement>()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBarelyFillBuffer() {
|
||||||
|
fillBuffer(5)
|
||||||
|
|
||||||
|
assertEquals(0, buffer[0].id)
|
||||||
|
assertEquals(1, buffer[1].id)
|
||||||
|
assertEquals(2, buffer[2].id)
|
||||||
|
assertEquals(3, buffer[3].id)
|
||||||
|
assertEquals(4, buffer[4].id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPartiallyFillBuffer() {
|
||||||
|
fillBuffer(3)
|
||||||
|
|
||||||
|
assertEquals(3, buffer.size)
|
||||||
|
|
||||||
|
assertEquals(0, buffer[0].id)
|
||||||
|
assertEquals(1, buffer[1].id)
|
||||||
|
assertEquals(2, buffer[2].id)
|
||||||
|
|
||||||
|
assertThrows(IndexOutOfBoundsException::class.java) { buffer[3] }
|
||||||
|
assertThrows(IndexOutOfBoundsException::class.java) { buffer[4] }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSpinBuffer() {
|
||||||
|
fillBuffer(277)
|
||||||
|
|
||||||
|
assertEquals(272, buffer[0].id)
|
||||||
|
assertEquals(273, buffer[1].id)
|
||||||
|
assertEquals(274, buffer[2].id)
|
||||||
|
assertEquals(275, buffer[3].id)
|
||||||
|
assertEquals(276, buffer[4].id)
|
||||||
|
assertThrows(IndexOutOfBoundsException::class.java) { buffer[5] }
|
||||||
|
|
||||||
|
assertEquals(5, buffer.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testElementsAreRecycled() {
|
||||||
|
fillBuffer(23)
|
||||||
|
|
||||||
|
assertSame(history[4], buffer[1])
|
||||||
|
assertSame(history[9], buffer[1])
|
||||||
|
assertSame(history[14], buffer[1])
|
||||||
|
assertSame(history[19], buffer[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testIterator() {
|
||||||
|
fillBuffer(13)
|
||||||
|
|
||||||
|
val iterator = buffer.iterator()
|
||||||
|
|
||||||
|
for (i in 0 until 5) {
|
||||||
|
assertEquals(history[8 + i], iterator.next())
|
||||||
|
}
|
||||||
|
assertFalse(iterator.hasNext())
|
||||||
|
assertThrows(NoSuchElementException::class.java) { iterator.next() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testForEach() {
|
||||||
|
fillBuffer(13)
|
||||||
|
var i = 8
|
||||||
|
|
||||||
|
buffer.forEach {
|
||||||
|
assertEquals(history[i], it)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
assertEquals(13, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fillBuffer(count: Int) {
|
||||||
|
for (i in 0 until count) {
|
||||||
|
val elem = buffer.advance()
|
||||||
|
elem.id = history.size
|
||||||
|
history.add(elem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestElement(var id: Int = 0) {
|
||||||
|
override fun toString(): String {
|
||||||
|
return "{TestElement $id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user