| /* |
| * Copyright (C) 2014 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.perflib.heap.analysis; |
| |
| import com.android.testutils.TestResources; |
| import com.android.tools.perflib.heap.*; |
| import com.android.tools.perflib.captures.MemoryMappedFileBuffer; |
| |
| import junit.framework.TestCase; |
| |
| import java.io.File; |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| public class DominatorsTest extends TestCase { |
| |
| private Snapshot mSnapshot; |
| |
| public void testSimpleGraph() { |
| mSnapshot = new SnapshotBuilder(6) |
| .addReferences(1, 2, 3) |
| .addReferences(2, 4, 6) |
| .addReferences(3, 4, 5) |
| .addReferences(4, 6) |
| .addRoot(1) |
| .build(); |
| |
| mSnapshot.computeDominators(); |
| |
| assertEquals(6, mSnapshot.getReachableInstances().size()); |
| assertDominates(1, 2); |
| assertDominates(1, 3); |
| assertDominates(1, 4); |
| assertDominates(1, 6); |
| assertDominates(3, 5); |
| |
| assertParentPathToGc(2, 1); |
| assertParentPathToGc(3, 1); |
| assertParentPathToGc(4, 2, 3); |
| assertParentPathToGc(5, 3); |
| assertParentPathToGc(6, 2); |
| } |
| |
| public void testCyclicGraph() { |
| mSnapshot = new SnapshotBuilder(4) |
| .addReferences(1, 2, 3, 4) |
| .addReferences(2, 3) |
| .addReferences(3, 4) |
| .addReferences(4, 2) |
| .addRoot(1) |
| .build(); |
| |
| mSnapshot.computeDominators(); |
| |
| assertEquals(4, mSnapshot.getReachableInstances().size()); |
| assertDominates(1, 2); |
| assertDominates(1, 3); |
| assertDominates(1, 4); |
| |
| assertParentPathToGc(2, 1); |
| assertParentPathToGc(3, 1); |
| assertParentPathToGc(4, 1); |
| } |
| |
| public void testMultipleRoots() { |
| mSnapshot = new SnapshotBuilder(6) |
| .addReferences(1, 3) |
| .addReferences(2, 4) |
| .addReferences(3, 5) |
| .addReferences(4, 5) |
| .addReferences(5, 6) |
| .addRoot(1) |
| .addRoot(2) |
| .build(); |
| |
| mSnapshot.computeDominators(); |
| |
| assertEquals(6, mSnapshot.getReachableInstances().size()); |
| assertDominates(1, 3); |
| assertDominates(2, 4); |
| // Node 5 is reachable via both roots, neither of which can be the sole dominator. |
| assertEquals(mSnapshot.SENTINEL_ROOT, mSnapshot.findInstance(5).getImmediateDominator()); |
| assertDominates(5, 6); |
| |
| assertParentPathToGc(3, 1); |
| assertParentPathToGc(4, 2); |
| assertParentPathToGc(5, 3, 4); |
| assertParentPathToGc(6, 5); |
| } |
| |
| public void testDoublyLinkedList() { |
| // Node 1 points to a doubly-linked list 2-3-4-5-6-7-8-9. |
| mSnapshot = new SnapshotBuilder(9) |
| .addReferences(1, 2) |
| .addReferences(2, 3, 9) |
| .addReferences(3, 2, 4) |
| .addReferences(4, 3, 5) |
| .addReferences(5, 4, 6) |
| .addReferences(6, 5, 7) |
| .addReferences(7, 6, 8) |
| .addReferences(8, 7, 9) |
| .addReferences(9, 2, 8) |
| .addRoot(1) |
| .build(); |
| |
| mSnapshot.computeDominators(); |
| |
| assertEquals(45, mSnapshot.findInstance(1).getRetainedSize(1)); |
| assertEquals(44, mSnapshot.findInstance(2).getRetainedSize(1)); |
| for (int i = 3; i <= 9; i++) { |
| assertEquals(i, mSnapshot.findInstance(i).getRetainedSize(1)); |
| } |
| |
| assertParentPathToGc(2, 1); |
| assertParentPathToGc(3, 2); |
| assertParentPathToGc(9, 2); |
| assertParentPathToGc(4, 3); |
| assertParentPathToGc(8, 9); |
| assertParentPathToGc(5, 4); |
| assertParentPathToGc(7, 8); |
| assertParentPathToGc(6, 5, 7); |
| } |
| |
| public void testSameClassDifferentLoader() { |
| mSnapshot = new SnapshotBuilder(4) |
| .addReferences(1, 3, 2) |
| .addReferences(3, 2) |
| .addRoot(1) |
| .build(); |
| |
| assertNotNull(mSnapshot.getHeap(13).getClass(102)); |
| assertNotNull(mSnapshot.getHeap(13).getClass(103)); |
| |
| mSnapshot.computeDominators(); |
| |
| assertEquals(0, mSnapshot.getHeap(13).getClass(102).getRetainedSize(1)); |
| assertEquals(0, mSnapshot.getHeap(13).getClass(103).getRetainedSize(1)); |
| } |
| |
| public void testTopSort() { |
| mSnapshot = new SnapshotBuilder(4) |
| .addReferences(1, 3, 2) |
| .addReferences(3, 2) |
| .addRoot(1) |
| .build(); |
| |
| mSnapshot.computeDominators(); |
| |
| assertEquals(6, mSnapshot.findInstance(1).getRetainedSize(1)); |
| assertEquals(2, mSnapshot.findInstance(2).getRetainedSize(1)); |
| assertEquals(3, mSnapshot.findInstance(3).getRetainedSize(1)); |
| } |
| |
| public void testMultiplePaths() { |
| mSnapshot = new SnapshotBuilder(8) |
| .addReferences(1, 7, 8) |
| .addReferences(7, 2, 3) |
| .addReferences(8, 2) |
| .addReferences(2, 4) |
| .addReferences(3, 5) |
| .addReferences(5, 4) |
| .addReferences(4, 6) |
| .addRoot(1) |
| .build(); |
| |
| mSnapshot.computeDominators(); |
| |
| assertEquals(mSnapshot.findInstance(1), mSnapshot.findInstance(4).getImmediateDominator()); |
| assertEquals(mSnapshot.findInstance(4), mSnapshot.findInstance(6).getImmediateDominator()); |
| assertEquals(36, mSnapshot.findInstance(1).getRetainedSize(1)); |
| assertEquals(2, mSnapshot.findInstance(2).getRetainedSize(1)); |
| assertEquals(8, mSnapshot.findInstance(3).getRetainedSize(1)); |
| } |
| |
| public void testReachableInstances() { |
| mSnapshot = new SnapshotBuilder(11, 2, 1) |
| .addReferences(1, 2, 3) |
| .insertSoftReference(1, 11) |
| .addReferences(2, 4) |
| .addReferences(3, 5, 6) |
| .insertSoftReference(4, 9) |
| .addReferences(5, 7) |
| .addReferences(6, 7) |
| .addReferences(7, 8, 10) |
| .insertSoftAndHardReference(8, 10, 9) |
| .addRoot(1) |
| .build(); |
| |
| mSnapshot.computeDominators(); |
| for (Heap heap : mSnapshot.getHeaps()) { |
| ClassObj softClass = heap.getClass(SnapshotBuilder.SOFT_REFERENCE_ID); |
| if (softClass != null) { |
| assertTrue(softClass.getIsSoftReference()); |
| } |
| |
| ClassObj softAndHardClass = heap.getClass(SnapshotBuilder.SOFT_AND_HARD_REFERENCE_ID); |
| if (softAndHardClass != null) { |
| assertTrue(softAndHardClass.getIsSoftReference()); |
| } |
| } |
| |
| Instance instance9 = mSnapshot.findInstance(9); |
| assertNotNull(instance9); |
| assertNotNull(instance9.getSoftReverseReferences()); |
| assertEquals(1, instance9.getHardReverseReferences().size()); |
| assertEquals(1, instance9.getSoftReverseReferences().size()); |
| assertEquals(6, instance9.getDistanceToGcRoot()); |
| |
| Instance instance10 = mSnapshot.findInstance(10); |
| assertNotNull(instance10); |
| assertNotNull(instance10.getSoftReverseReferences()); |
| assertEquals(1, instance10.getHardReverseReferences().size()); |
| assertEquals(1, instance10.getSoftReverseReferences().size()); |
| assertEquals(4, instance10.getDistanceToGcRoot()); |
| |
| Instance instance11 = mSnapshot.findInstance(11); |
| assertNotNull(instance11); |
| assertNotNull(instance11.getSoftReverseReferences()); |
| assertEquals(0, instance11.getHardReverseReferences().size()); |
| assertEquals(1, instance11.getSoftReverseReferences().size()); |
| assertEquals(Integer.MAX_VALUE, instance11.getDistanceToGcRoot()); |
| |
| assertEquals(13, mSnapshot.getReachableInstances().size()); |
| } |
| |
| public void testSampleHprof() throws Exception { |
| File file = TestResources.getFile(getClass(), "/dialer.android-hprof"); |
| mSnapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(file)); |
| mSnapshot.computeDominators(); |
| |
| Set<Instance> topologicalSet = new HashSet<Instance>(mSnapshot.getTopologicalOrdering()); |
| assertEquals(topologicalSet.size(), mSnapshot.getTopologicalOrdering().size()); |
| |
| long totalInstanceCount = 0; |
| for (Heap heap : mSnapshot.getHeaps()) { |
| totalInstanceCount += heap.getInstancesCount(); |
| totalInstanceCount += heap.getClasses().size(); |
| } |
| assertEquals(43687, totalInstanceCount); |
| |
| assertEquals(42868, mSnapshot.getReachableInstances().size()); |
| |
| // An object reachable via two GC roots, a JNI global and a Thread. |
| Instance instance = mSnapshot.findInstance(0xB0EDFFA0); |
| assertEquals(Snapshot.SENTINEL_ROOT, instance.getImmediateDominator()); |
| |
| int appIndex = mSnapshot.getHeapIndex(mSnapshot.getHeap("app")); |
| int zygoteIndex = mSnapshot.getHeapIndex(mSnapshot.getHeap("zygote")); |
| |
| // The largest object in our sample hprof belongs to the zygote |
| ClassObj htmlParser = mSnapshot.findClass("android.text.Html$HtmlParser"); |
| assertEquals(116492, htmlParser.getRetainedSize(zygoteIndex)); |
| assertEquals(0, htmlParser.getRetainedSize(appIndex)); |
| |
| // One of the bigger objects in the app heap |
| ClassObj activityThread = mSnapshot.findClass("android.app.ActivityThread"); |
| assertEquals(853, activityThread.getRetainedSize(zygoteIndex)); |
| assertEquals(576, activityThread.getRetainedSize(appIndex)); |
| |
| mSnapshot.dispose(); |
| mSnapshot = null; |
| } |
| |
| /** |
| * Asserts that nodeA dominates nodeB in mHeap. |
| */ |
| private void assertDominates(int nodeA, int nodeB) { |
| assertEquals(mSnapshot.findInstance(nodeA), |
| mSnapshot.findInstance(nodeB).getImmediateDominator()); |
| } |
| |
| /** |
| * Asserts that one of the parents is the direct parent to node in the shortest path to the GC root. |
| */ |
| private void assertParentPathToGc(int node, int... parents) { |
| for (int parent : parents) { |
| Instance parentInstance = mSnapshot.findInstance(node).getNextInstanceToGcRoot(); |
| if (parentInstance != null && parentInstance.getId() == parent) { |
| return; |
| } |
| } |
| fail(); |
| } |
| } |