Merge "Fix SysUI crash when memory collection times out" into tm-qpr-dev
This commit is contained in:
@@ -62,7 +62,7 @@ import javax.inject.Inject;
|
||||
* are not.
|
||||
*/
|
||||
public class NotificationLogger implements StateListener {
|
||||
private static final String TAG = "NotificationLogger";
|
||||
static final String TAG = "NotificationLogger";
|
||||
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
/** The minimum delay in ms between reports of notification visibility. */
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package com.android.systemui.statusbar.notification.logging
|
||||
|
||||
import android.app.StatsManager
|
||||
import android.util.Log
|
||||
import android.util.StatsEvent
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
@@ -25,6 +26,7 @@ import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.shared.system.SysUiStatsLog
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import com.android.systemui.util.traceSection
|
||||
import java.lang.Exception
|
||||
import java.util.concurrent.Executor
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.roundToInt
|
||||
@@ -82,43 +84,56 @@ constructor(
|
||||
return StatsManager.PULL_SKIP
|
||||
}
|
||||
|
||||
// Notifications can only be retrieved on the main thread, so switch to that thread.
|
||||
val notifications = getAllNotificationsOnMainThread()
|
||||
val notificationMemoryUse =
|
||||
NotificationMemoryMeter.notificationMemoryUse(notifications)
|
||||
.sortedWith(
|
||||
compareBy(
|
||||
{ it.packageName },
|
||||
{ it.objectUsage.style },
|
||||
{ it.notificationKey }
|
||||
try {
|
||||
// Notifications can only be retrieved on the main thread, so switch to that thread.
|
||||
val notifications = getAllNotificationsOnMainThread()
|
||||
val notificationMemoryUse =
|
||||
NotificationMemoryMeter.notificationMemoryUse(notifications)
|
||||
.sortedWith(
|
||||
compareBy(
|
||||
{ it.packageName },
|
||||
{ it.objectUsage.style },
|
||||
{ it.notificationKey }
|
||||
)
|
||||
)
|
||||
val usageData = aggregateMemoryUsageData(notificationMemoryUse)
|
||||
usageData.forEach { (_, use) ->
|
||||
data.add(
|
||||
SysUiStatsLog.buildStatsEvent(
|
||||
SysUiStatsLog.NOTIFICATION_MEMORY_USE,
|
||||
use.uid,
|
||||
use.style,
|
||||
use.count,
|
||||
use.countWithInflatedViews,
|
||||
toKb(use.smallIconObject),
|
||||
use.smallIconBitmapCount,
|
||||
toKb(use.largeIconObject),
|
||||
use.largeIconBitmapCount,
|
||||
toKb(use.bigPictureObject),
|
||||
use.bigPictureBitmapCount,
|
||||
toKb(use.extras),
|
||||
toKb(use.extenders),
|
||||
toKb(use.smallIconViews),
|
||||
toKb(use.largeIconViews),
|
||||
toKb(use.systemIconViews),
|
||||
toKb(use.styleViews),
|
||||
toKb(use.customViews),
|
||||
toKb(use.softwareBitmaps),
|
||||
use.seenCount
|
||||
)
|
||||
)
|
||||
val usageData = aggregateMemoryUsageData(notificationMemoryUse)
|
||||
usageData.forEach { (_, use) ->
|
||||
data.add(
|
||||
SysUiStatsLog.buildStatsEvent(
|
||||
SysUiStatsLog.NOTIFICATION_MEMORY_USE,
|
||||
use.uid,
|
||||
use.style,
|
||||
use.count,
|
||||
use.countWithInflatedViews,
|
||||
toKb(use.smallIconObject),
|
||||
use.smallIconBitmapCount,
|
||||
toKb(use.largeIconObject),
|
||||
use.largeIconBitmapCount,
|
||||
toKb(use.bigPictureObject),
|
||||
use.bigPictureBitmapCount,
|
||||
toKb(use.extras),
|
||||
toKb(use.extenders),
|
||||
toKb(use.smallIconViews),
|
||||
toKb(use.largeIconViews),
|
||||
toKb(use.systemIconViews),
|
||||
toKb(use.styleViews),
|
||||
toKb(use.customViews),
|
||||
toKb(use.softwareBitmaps),
|
||||
use.seenCount
|
||||
)
|
||||
)
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
// This can happen if the device is sleeping or view walking takes too long.
|
||||
// The statsd collector will interrupt the thread and we need to handle it
|
||||
// gracefully.
|
||||
Log.w(NotificationLogger.TAG, "Timed out when measuring notification memory.", e)
|
||||
return@traceSection StatsManager.PULL_SKIP
|
||||
} catch (e: Exception) {
|
||||
// Error while collecting data, this should not crash prod SysUI. Just
|
||||
// log WTF and move on.
|
||||
Log.wtf(NotificationLogger.TAG, "Failed to measure notification memory.", e)
|
||||
return@traceSection StatsManager.PULL_SKIP
|
||||
}
|
||||
|
||||
return StatsManager.PULL_SUCCESS
|
||||
|
||||
@@ -184,19 +184,21 @@ internal object NotificationMemoryViewWalker {
|
||||
private fun computeDrawableUse(drawable: Drawable, seenObjects: HashSet<Int>): Int =
|
||||
when (drawable) {
|
||||
is BitmapDrawable -> {
|
||||
val ref = System.identityHashCode(drawable.bitmap)
|
||||
if (seenObjects.contains(ref)) {
|
||||
0
|
||||
} else {
|
||||
seenObjects.add(ref)
|
||||
drawable.bitmap.allocationByteCount
|
||||
}
|
||||
drawable.bitmap?.let {
|
||||
val ref = System.identityHashCode(it)
|
||||
if (seenObjects.contains(ref)) {
|
||||
0
|
||||
} else {
|
||||
seenObjects.add(ref)
|
||||
it.allocationByteCount
|
||||
}
|
||||
} ?: 0
|
||||
}
|
||||
else -> 0
|
||||
}
|
||||
|
||||
private fun isDrawableSoftwareBitmap(drawable: Drawable) =
|
||||
drawable is BitmapDrawable && drawable.bitmap.config != Bitmap.Config.HARDWARE
|
||||
drawable is BitmapDrawable && drawable.bitmap?.config != Bitmap.Config.HARDWARE
|
||||
|
||||
private fun identifierForView(view: View) =
|
||||
if (view.id == View.NO_ID) {
|
||||
|
||||
@@ -32,6 +32,7 @@ import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import java.lang.RuntimeException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@@ -113,6 +114,24 @@ class NotificationMemoryLoggerTest : SysuiTestCase() {
|
||||
assertThat(data).hasSize(2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onPullAtom_throwsInterruptedException_failsGracefully() {
|
||||
val pipeline: NotifPipeline = mock()
|
||||
whenever(pipeline.allNotifs).thenAnswer { throw InterruptedException("Timeout") }
|
||||
val logger = NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor)
|
||||
assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, mutableListOf()))
|
||||
.isEqualTo(StatsManager.PULL_SKIP)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onPullAtom_throwsRuntimeException_failsGracefully() {
|
||||
val pipeline: NotifPipeline = mock()
|
||||
whenever(pipeline.allNotifs).thenThrow(RuntimeException("Something broke!"))
|
||||
val logger = NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor)
|
||||
assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, mutableListOf()))
|
||||
.isEqualTo(StatsManager.PULL_SKIP)
|
||||
}
|
||||
|
||||
private fun createLoggerWithNotifications(
|
||||
notifications: List<Notification>
|
||||
): NotificationMemoryLogger {
|
||||
|
||||
Reference in New Issue
Block a user