1 /* <lambda>null2 * Copyright (C) 2023 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.statusbar.notification.row 18 19 import android.util.IndentingPrintWriter 20 import android.util.Log 21 import com.android.internal.util.LatencyTracker 22 import com.android.systemui.Dumpable 23 import com.android.systemui.dagger.SysUISingleton 24 import com.android.systemui.dagger.qualifiers.Main 25 import com.android.systemui.dump.DumpManager 26 import java.io.PrintWriter 27 import java.util.UUID 28 import java.util.concurrent.ConcurrentHashMap 29 import javax.inject.Inject 30 import kotlin.math.roundToInt 31 import kotlinx.coroutines.CoroutineDispatcher 32 import kotlinx.coroutines.withContext 33 34 private const val TAG = "BigPictureStatsManager" 35 36 @SysUISingleton 37 class BigPictureStatsManager 38 @Inject 39 constructor( 40 private val latencyTracker: LatencyTracker, 41 @Main private val mainDispatcher: CoroutineDispatcher, 42 dumpManager: DumpManager 43 ) : Dumpable { 44 45 init { 46 dumpManager.registerNormalDumpable(TAG, this) 47 } 48 49 private val startTimes = ConcurrentHashMap<String, Long>() 50 private val durations = mutableListOf<Int>() 51 private val lock = Any() 52 suspend inline fun <T> measure(block: () -> T): T { 53 val key = UUID.randomUUID().toString() 54 onBegin(key) 55 try { 56 return block() 57 } catch (t: Throwable) { 58 onCancel(key) 59 throw t 60 } finally { 61 onEnd(key)?.let { duration -> trackEvent(duration) } 62 } 63 } 64 65 fun onBegin(key: String) { 66 if (startTimes.contains(key)) { 67 Log.wtf(TAG, "key $key is already in use") 68 return 69 } 70 71 startTimes[key] = System.nanoTime() 72 } 73 74 fun onEnd(key: String): Int? { 75 val startTime = 76 startTimes.remove(key) 77 ?: run { 78 Log.wtf(TAG, "No matching begin call for this $key") 79 return null 80 } 81 82 val durationInMillis = ((System.nanoTime() - startTime) / 1_000_000) 83 return durationInMillis.toInt() 84 } 85 86 fun onCancel(key: String) { 87 startTimes.remove(key) 88 } 89 90 override fun dump(pw: PrintWriter, args: Array<out String>) { 91 synchronized(lock) { 92 if (durations.isEmpty()) { 93 pw.println("No entries") 94 return 95 } 96 97 val max = durations.max() 98 val avg = durations.average().roundToInt() 99 val p90 = percentile(durations, 90.0) 100 val p99 = percentile(durations, 99.0) 101 102 with(IndentingPrintWriter(pw)) { 103 println("Lazy-loaded ${durations.size} images:") 104 increaseIndent() 105 println("Avg: $avg ms") 106 println("Max: $max ms") 107 println("P90: $p90 ms") 108 println("P99: $p99 ms") 109 } 110 } 111 } 112 113 private fun percentile(times: List<Int>, percent: Double): Int { 114 val index = (percent / 100.0 * times.size).roundToInt() - 1 115 return times.sorted()[index] 116 } 117 118 suspend fun trackEvent(duration: Int) { 119 synchronized(lock) { durations.add(duration) } 120 withContext(mainDispatcher) { 121 latencyTracker.logAction( 122 LatencyTracker.ACTION_NOTIFICATION_BIG_PICTURE_LOADED, 123 duration 124 ) 125 } 126 } 127 } 128