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