summaryrefslogtreecommitdiff
path: root/profilers
diff options
context:
space:
mode:
authorPhil Nguyen <philnguyen@google.com>2022-05-25 22:47:06 +0000
committerPhil Nguyen <philnguyen@google.com>2022-06-25 00:45:06 +0000
commitcc32ea36cbad4e080bebb753d3b801e219526be5 (patch)
tree9e6a732665c881cb386c5423b5bd21a8ff76faab /profilers
parent34689147c9b71fd2138a48cf87c5f0c7ec7d3b41 (diff)
downloadidea-cc32ea36cbad4e080bebb753d3b801e219526be5.tar.gz
Cache tabs if memory allows
This change caches the tabs using soft references. This remembers the trees' expansion when switching tabs, and also improves latency. When reusing a previous tab, we need to re-register the listeners that were canceled in `onRemoved` in I41d7331e03ca282e7b1d122a00e7a9b26df2b748 Fixes: 65453906 Test: add new Change-Id: I91e1ae347447dbf5d954141b56539acc8e1c1680
Diffstat (limited to 'profilers')
-rw-r--r--profilers/src/com/android/tools/profilers/SoftHashMap.kt47
-rw-r--r--profilers/src/com/android/tools/profilers/cpu/capturedetails/CaptureDetails.kt23
-rw-r--r--profilers/src/com/android/tools/profilers/cpu/capturedetails/CpuTreeModel.kt8
-rw-r--r--profilers/testSrc/com/android/tools/profilers/SoftHashMapTest.kt47
4 files changed, 115 insertions, 10 deletions
diff --git a/profilers/src/com/android/tools/profilers/SoftHashMap.kt b/profilers/src/com/android/tools/profilers/SoftHashMap.kt
new file mode 100644
index 00000000000..158b4688229
--- /dev/null
+++ b/profilers/src/com/android/tools/profilers/SoftHashMap.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.tools.profilers
+
+import java.lang.ref.SoftReference
+
+/**
+ * Map that's strong in key and soft in value
+ */
+class SoftHashMap<K: Any, V: Any>: MutableMap<K, V> {
+ private val content = hashMapOf<K, SoftReference<V>>()
+ override val entries get() = content.mapNotNullTo(hashSetOf()) { (k, vRef) -> vRef.get()?.let { v -> entry(k, v) } }
+ override val keys get() = content.keys
+ override val size get() = content.size
+ override val values get() = content.values.mapNotNullTo(hashSetOf(), SoftReference<V>::get)
+ override fun clear() = content.clear()
+ override fun isEmpty() = content.isEmpty()
+ override fun remove(key: K): V? = content.remove(key)?.get()
+ override fun putAll(from: Map<out K, V>) = from.forEach { (k, v) -> content[k] = SoftReference(v) }
+ override fun put(key: K, value: V): V? = content.put(key, SoftReference(value))?.get()
+ override fun get(key: K): V? = content[key]?.get()
+ override fun containsValue(value: V) = content.values.any { it.get() == value }
+ override fun containsKey(key: K) = key in content
+
+ companion object {
+ fun<K, V> entry(k: K, v: V) = object: MutableMap.MutableEntry<K, V> {
+ override var value = v
+ override val key get() = k
+ override fun setValue(newValue: V): V = value.also {
+ value = newValue
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/profilers/src/com/android/tools/profilers/cpu/capturedetails/CaptureDetails.kt b/profilers/src/com/android/tools/profilers/cpu/capturedetails/CaptureDetails.kt
index 03f7b987854..dedb7317e19 100644
--- a/profilers/src/com/android/tools/profilers/cpu/capturedetails/CaptureDetails.kt
+++ b/profilers/src/com/android/tools/profilers/cpu/capturedetails/CaptureDetails.kt
@@ -31,7 +31,6 @@ import kotlin.math.max
sealed class CaptureDetails(val clockType: ClockType, val capture: CpuCapture) {
abstract val type: Type
- abstract fun onDestroyed()
sealed class ChartDetails(clockType: ClockType, cpuCapture: CpuCapture): CaptureDetails(clockType, cpuCapture) {
abstract val node: CaptureNode?
@@ -60,8 +59,12 @@ sealed class CaptureDetails(val clockType: ClockType, val capture: CpuCapture) {
}
}
- override fun onDestroyed() {
- model?.onDestroyed();
+ fun onRemoved() {
+ model?.onDestroyed()
+ }
+
+ fun onReattached() {
+ model?.onReattached()
}
}
@@ -79,7 +82,6 @@ sealed class CaptureDetails(val clockType: ClockType, val capture: CpuCapture) {
: ChartDetails(clockType, cpuCapture) {
override val node = nodes.firstOrNull()
override val type get() = Type.CALL_CHART
- override fun onDestroyed() { }
}
class FlameChart internal constructor(clockType: ClockType,
@@ -93,8 +95,17 @@ sealed class CaptureDetails(val clockType: ClockType, val capture: CpuCapture) {
override var node: CaptureNode? = null
private set
val aspect: AspectModel<Aspect> = AspectModel()
+ private val captureNodes = captureNodes.sortedWith(Comparator.comparingLong(CaptureNode::startGlobal))
init {
+ onReattached()
+ }
+
+ fun onRemoved() {
+ selectionRange.removeDependencies(aspect)
+ }
+
+ fun onReattached() {
if (captureNodes.isNotEmpty()) {
val captureNodes = captureNodes.sortedWith(Comparator.comparingLong(CaptureNode::startGlobal))
@@ -165,10 +176,6 @@ sealed class CaptureDetails(val clockType: ClockType, val capture: CpuCapture) {
}
}
- override fun onDestroyed() {
- selectionRange.removeDependencies(aspect)
- }
-
/**
* Produces a flame chart that is similar to [CallChart], but the identical methods with the same sequence of callers
* are combined into one wider bar. It converts it from [TopDownNode] as it's similar to FlameChart.
diff --git a/profilers/src/com/android/tools/profilers/cpu/capturedetails/CpuTreeModel.kt b/profilers/src/com/android/tools/profilers/cpu/capturedetails/CpuTreeModel.kt
index de7284cd5ee..9e408f83b4f 100644
--- a/profilers/src/com/android/tools/profilers/cpu/capturedetails/CpuTreeModel.kt
+++ b/profilers/src/com/android/tools/profilers/cpu/capturedetails/CpuTreeModel.kt
@@ -54,8 +54,7 @@ class CpuTreeModel<T: Aggregate<T>>(val clockType: ClockType, private val range:
})
init {
- range.addDependency(observer).onChange(Range.Aspect.RANGE, rangeChanged)
- rangeChanged()
+ onReattached()
}
fun sort(newOrder: Comparator<CpuTreeNode<T>>) {
@@ -69,6 +68,11 @@ class CpuTreeModel<T: Aggregate<T>>(val clockType: ClockType, private val range:
range.removeDependencies(observer)
}
+ fun onReattached() {
+ range.addDependency(observer).onChange(Range.Aspect.RANGE, rangeChanged)
+ rangeChanged()
+ }
+
private fun reload() {
val event = TreeModelEvent(this, arrayOf(root), null, null)
listeners.asReversed().forEach { it.treeStructureChanged(event) }
diff --git a/profilers/testSrc/com/android/tools/profilers/SoftHashMapTest.kt b/profilers/testSrc/com/android/tools/profilers/SoftHashMapTest.kt
new file mode 100644
index 00000000000..d57bfa928a2
--- /dev/null
+++ b/profilers/testSrc/com/android/tools/profilers/SoftHashMapTest.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.tools.profilers
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class SoftHashMapTest {
+ @Test
+ fun `map gives back what was put in (modulo null)`() {
+ val m = SoftHashMap<Int, String>()
+ (0 .. 10).forEach { m[it] = it.toString() }
+ fun check() = (0 .. 10).forEach { assertThat(m[it]).isAnyOf(it.toString(), null) }
+ check()
+ repeat(3) { System.gc() }
+ check()
+ }
+
+ @Test
+ fun `soft references are cleared when not enough memory`() {
+ val m = SoftHashMap<Int, String>()
+ (0 .. 10).forEach { m[it] = it.toString() }
+ try {
+ tailrec fun allocTillDeath(size: Int) {
+ IntArray(size)
+ allocTillDeath(size * 2 + 1)
+ }
+ allocTillDeath(1)
+ } catch (_: OutOfMemoryError) {
+ assertThat(m.keys).isNotEmpty()
+ assertThat(m.values).isEmpty()
+ }
+ }
+}