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

Commit d3f8fc56 authored by Ioana Alexandru's avatar Ioana Alexandru
Browse files

Implement hit-ratio dumping for NotifCollectionCache

This will allow us to keep an eye on whether we end up with too many
cache misses.

Bug: 371174789
Test: NotifCollectionCacheTest for the counters, but the actual printing
isn't tested yet, we can test it manually in a follow-up
Flag: EXEMPT logging only change

Change-Id: Ie7eef8c733ca96c979e2c82b1ca3be3a2a864d8b
parent f0ccc4f7
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
@@ -202,5 +202,31 @@ class NotifCollectionCacheTest : SysuiTestCase() {
        assertNotNull(noLivesCache.cache[C])
    }

    @Test
    fun hitsAndMisses_areAccurate() {
        val fetch = { key: String -> key }

        // Construct the cache
        assertThat(underTest.getOrFetch(A, fetch)).isEqualTo(A)
        assertThat(underTest.getOrFetch(B, fetch)).isEqualTo(B)
        assertThat(underTest.getOrFetch(C, fetch)).isEqualTo(C)
        assertThat(underTest.hits.get()).isEqualTo(0)
        assertThat(underTest.misses.get()).isEqualTo(3)

        // Verify that further calls count as hits
        underTest.getOrFetch(A, fetch)
        underTest.getOrFetch(A, fetch)
        underTest.getOrFetch(B, fetch)
        underTest.getOrFetch(C, fetch)
        assertThat(underTest.hits.get()).isEqualTo(4)
        assertThat(underTest.misses.get()).isEqualTo(3)

        // Verify that a miss is counted again if the entries are cleared
        underTest.clear()
        underTest.getOrFetch(A, fetch)
        assertThat(underTest.hits.get()).isEqualTo(4)
        assertThat(underTest.misses.get()).isEqualTo(4)
    }

    private fun <V> NotifCollectionCache<V>.getLives(key: String) = this.cache[key]?.lives
}
+35 −1
Original line number Diff line number Diff line
@@ -16,10 +16,17 @@

package com.android.systemui.statusbar.notification.collection

import android.annotation.SuppressLint
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printCollection
import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.time.SystemClockImpl
import com.android.systemui.util.withIncreasedIndent
import java.io.PrintWriter
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger

/**
 * A cache in which entries can "survive" getting purged [retainCount] times, given consecutive
@@ -29,13 +36,18 @@ import java.util.concurrent.ConcurrentHashMap
 * resolve (such as drawables, or things that require binder calls). As such, [getOrFetch] is
 * recommended to be run on a background thread, while [purge] can be done from any thread.
 */
@SuppressLint("DumpableNotRegistered") // this will be dumped by container classes
class NotifCollectionCache<V>(
    private val retainCount: Int = 1,
    private val purgeTimeoutMillis: Long = 1000L,
    private val systemClock: SystemClock = SystemClockImpl(),
) {
) : Dumpable {
    @get:VisibleForTesting val cache = ConcurrentHashMap<String, CacheEntry>()

    // Counters for cache hits and misses to be used to calculate and dump the hit ratio
    @get:VisibleForTesting val misses = AtomicInteger(0)
    @get:VisibleForTesting val hits = AtomicInteger(0)

    init {
        if (retainCount < 0) {
            throw IllegalArgumentException("retainCount cannot be negative")
@@ -102,11 +114,13 @@ class NotifCollectionCache<V>(
    fun getOrFetch(key: String, fetch: (String) -> V): V {
        val entry = cache[key]
        if (entry != null) {
            hits.incrementAndGet()
            // Refresh lives on access
            entry.resetLives()
            return entry.value
        }

        misses.incrementAndGet()
        val value = fetch(key)
        cache[key] = CacheEntry(key, value)
        return value
@@ -151,4 +165,24 @@ class NotifCollectionCache<V>(
    fun clear() {
        cache.clear()
    }

    override fun dump(pwOrig: PrintWriter, args: Array<out String>) {
        val pw = pwOrig.asIndenting()

        pw.println("$TAG(retainCount = $retainCount, purgeTimeoutMillis = $purgeTimeoutMillis)")
        pw.withIncreasedIndent {
            pw.printCollection("keys present in cache", cache.keys.stream().sorted().toList())

            val misses = misses.get()
            val hits = hits.get()
            pw.println(
                "cache hit ratio = ${(hits.toFloat() / (hits + misses)) * 100}% " +
                    "($hits hits, $misses misses)"
            )
        }
    }

    companion object {
        const val TAG = "NotifCollectionCache"
    }
}