diff options
author | Phil Nguyen <philnguyen@google.com> | 2022-05-25 22:47:06 +0000 |
---|---|---|
committer | Phil Nguyen <philnguyen@google.com> | 2022-06-25 00:45:06 +0000 |
commit | cc32ea36cbad4e080bebb753d3b801e219526be5 (patch) | |
tree | 9e6a732665c881cb386c5423b5bd21a8ff76faab /profilers | |
parent | 34689147c9b71fd2138a48cf87c5f0c7ec7d3b41 (diff) | |
download | idea-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')
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() + } + } +} |