summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--profilers-ui/src/com/android/tools/profilers/cpu/analysis/AndroidFrameTimelineTab.kt4
-rw-r--r--profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisChart.java5
-rw-r--r--profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisEventsTab.kt1
-rw-r--r--profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisFramesTab.kt1
-rw-r--r--profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisPanel.java12
-rw-r--r--profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisSummaryTab.java3
-rw-r--r--profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisTab.java8
-rw-r--r--profilers-ui/src/com/android/tools/profilers/cpu/capturedetails/CaptureDetailsView.java1
-rw-r--r--profilers-ui/src/com/android/tools/profilers/cpu/capturedetails/ChartDetailsView.java14
-rw-r--r--profilers-ui/src/com/android/tools/profilers/cpu/capturedetails/TreeDetailsView.java14
-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
14 files changed, 171 insertions, 17 deletions
diff --git a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/AndroidFrameTimelineTab.kt b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/AndroidFrameTimelineTab.kt
index e2f6bac2662..06ea1820b35 100644
--- a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/AndroidFrameTimelineTab.kt
+++ b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/AndroidFrameTimelineTab.kt
@@ -54,4 +54,8 @@ class AndroidFrameTimelineTab(profilersView: StudioProfilersView, model: Android
override fun onRemoved() {
// this tab doesn't leak heavy listeners
}
+
+ override fun onReattached() {
+ // this tab doesn't remove anything to restore
+ }
} \ No newline at end of file
diff --git a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisChart.java b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisChart.java
index ae7209fe254..8f46d8ca098 100644
--- a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisChart.java
+++ b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisChart.java
@@ -162,6 +162,11 @@ public class CpuAnalysisChart extends CpuAnalysisTab<CpuAnalysisChartModel<?>> {
myActiveDetailsView.onRemoved();
}
+ @Override
+ public void onReattached() {
+ myActiveDetailsView.onReattached();
+ }
+
@VisibleForTesting
@NotNull
FilterComponent getFilterComponent() {
diff --git a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisEventsTab.kt b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisEventsTab.kt
index 80dc615ff60..47d1d9277d8 100644
--- a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisEventsTab.kt
+++ b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisEventsTab.kt
@@ -33,4 +33,5 @@ class CpuAnalysisEventsTab(profilersView: StudioProfilersView,
}
override fun onRemoved() { }
+ override fun onReattached() { }
} \ No newline at end of file
diff --git a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisFramesTab.kt b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisFramesTab.kt
index e45aaf399af..efb99282d53 100644
--- a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisFramesTab.kt
+++ b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisFramesTab.kt
@@ -69,6 +69,7 @@ class CpuAnalysisFramesTab(profilersView: StudioProfilersView,
}
override fun onRemoved() { }
+ override fun onReattached() { }
companion object {
val PAGE_SIZE_VALUES = arrayOf(10, 25, 50, 100)
diff --git a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisPanel.java b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisPanel.java
index 4e51de37526..1051b8fb23b 100644
--- a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisPanel.java
+++ b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisPanel.java
@@ -20,6 +20,7 @@ import com.android.tools.adtui.common.StudioColorsKt;
import com.android.tools.adtui.model.AspectObserver;
import com.android.tools.adtui.model.MultiSelectionModel;
import com.android.tools.adtui.model.ViewBinder;
+import com.android.tools.profilers.SoftHashMap;
import com.android.tools.profilers.StudioProfilersView;
import com.android.tools.profilers.cpu.CpuCaptureStage;
import com.google.common.annotations.VisibleForTesting;
@@ -27,6 +28,7 @@ import com.intellij.ui.components.JBTabbedPane;
import com.intellij.util.ui.JBUI;
import java.awt.BorderLayout;
import java.util.List;
+import java.util.Map;
import java.util.stream.Stream;
import javax.swing.JComponent;
import javax.swing.JLabel;
@@ -185,6 +187,7 @@ public class CpuAnalysisPanel extends AspectObserver {
*/
private class TabChangeListener implements ChangeListener {
private int myLastSelectedIndex = -1;
+ private Map<CpuAnalysisTabModel<?>, CpuAnalysisTab<?>> myCachedTabs = new SoftHashMap<>();
@Override
public void stateChanged(ChangeEvent e) {
@@ -200,12 +203,19 @@ public class CpuAnalysisPanel extends AspectObserver {
if (myLastSelectedIndex >= 0 && myLastSelectedIndex < myTabView.getTabCount()) {
CpuAnalysisTab<?> tab = (CpuAnalysisTab<?>)myTabView.getComponentAt(myLastSelectedIndex);
if (tab != null) {
+ myCachedTabs.put(tab.getModel(), tab);
tab.onRemoved();
}
myTabView.setComponentAt(myLastSelectedIndex, new JPanel());
}
if (newIndex >= 0 && newIndex < myTabView.getTabCount()) {
- myTabView.setComponentAt(newIndex, myTabViewsBinder.build(myProfilersView, mySelectedModel.getTabModelAt(newIndex)));
+ CpuAnalysisTab<?> cachedTab = myCachedTabs.get(mySelectedModel.getTabModelAt(newIndex));
+ if (cachedTab != null) {
+ cachedTab.onReattached();
+ myTabView.setComponentAt(newIndex, cachedTab);
+ } else {
+ myTabView.setComponentAt(newIndex, myTabViewsBinder.build(myProfilersView, mySelectedModel.getTabModelAt(newIndex)));
+ }
}
myLastSelectedIndex = newIndex;
}
diff --git a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisSummaryTab.java b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisSummaryTab.java
index 193007539ed..20b333d1fbe 100644
--- a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisSummaryTab.java
+++ b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisSummaryTab.java
@@ -43,6 +43,9 @@ public class CpuAnalysisSummaryTab extends CpuAnalysisTab<CpuAnalysisSummaryTabM
@Override
public void onRemoved() { }
+ @Override
+ public void onReattached() { }
+
private void initComponents() {
setLayout(new BorderLayout());
JScrollPane scrollPane = new JBScrollPane(myViewBinder.build(getProfilersView(), getModel()),
diff --git a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisTab.java b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisTab.java
index 7dcd0433d15..8dd633242a9 100644
--- a/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisTab.java
+++ b/profilers-ui/src/com/android/tools/profilers/cpu/analysis/CpuAnalysisTab.java
@@ -44,5 +44,13 @@ public abstract class CpuAnalysisTab<T extends CpuAnalysisTabModel> extends JCom
return myModel;
}
+ /**
+ * Clean up (e.g. releasing expensive listeners) when this tab is inactive
+ */
abstract public void onRemoved();
+
+ /**
+ * Restore everything cleaned up in `onRemoved` to prepare for re-attachment
+ */
+ abstract public void onReattached();
}
diff --git a/profilers-ui/src/com/android/tools/profilers/cpu/capturedetails/CaptureDetailsView.java b/profilers-ui/src/com/android/tools/profilers/cpu/capturedetails/CaptureDetailsView.java
index 76cdd9405e7..d330b29e91a 100644
--- a/profilers-ui/src/com/android/tools/profilers/cpu/capturedetails/CaptureDetailsView.java
+++ b/profilers-ui/src/com/android/tools/profilers/cpu/capturedetails/CaptureDetailsView.java
@@ -47,6 +47,7 @@ public abstract class CaptureDetailsView {
}
public abstract void onRemoved();
+ public abstract void onReattached();
@NotNull
public abstract JComponent getComponent();
diff --git a/profilers-ui/src/com/android/tools/profilers/cpu/capturedetails/ChartDetailsView.java b/profilers-ui/src/com/android/tools/profilers/cpu/capturedetails/ChartDetailsView.java
index 357a53cb3b4..3c0f0b99fb4 100644
--- a/profilers-ui/src/com/android/tools/profilers/cpu/capturedetails/ChartDetailsView.java
+++ b/profilers-ui/src/com/android/tools/profilers/cpu/capturedetails/ChartDetailsView.java
@@ -187,9 +187,10 @@ public abstract class ChartDetailsView extends CaptureDetailsView {
}
@Override
- public void onRemoved() {
- myCallChart.onDestroyed();
- }
+ public void onRemoved() { }
+
+ @Override
+ public void onReattached() { }
@NotNull
private JPanel createChartPanel() {
@@ -273,7 +274,12 @@ public abstract class ChartDetailsView extends CaptureDetailsView {
@Override
public void onRemoved() {
- myFlameChart.onDestroyed();
+ myFlameChart.onRemoved();
+ }
+
+ @Override
+ public void onReattached() {
+ myFlameChart.onReattached();
}
@NotNull
diff --git a/profilers-ui/src/com/android/tools/profilers/cpu/capturedetails/TreeDetailsView.java b/profilers-ui/src/com/android/tools/profilers/cpu/capturedetails/TreeDetailsView.java
index 4f437b828e6..d8f50125f96 100644
--- a/profilers-ui/src/com/android/tools/profilers/cpu/capturedetails/TreeDetailsView.java
+++ b/profilers-ui/src/com/android/tools/profilers/cpu/capturedetails/TreeDetailsView.java
@@ -431,7 +431,12 @@ public abstract class TreeDetailsView extends CaptureDetailsView {
@Override
public void onRemoved() {
- myTopDown.onDestroyed();
+ myTopDown.onRemoved();
+ }
+
+ @Override
+ public void onReattached() {
+ myTopDown.onReattached();
}
/**
@@ -478,7 +483,12 @@ public abstract class TreeDetailsView extends CaptureDetailsView {
@Override
public void onRemoved() {
- myBottomUp.onDestroyed();
+ myBottomUp.onRemoved();
+ }
+
+ @Override
+ public void onReattached() {
+ myBottomUp.onReattached();
}
}
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()
+ }
+ }
+}