diff options
author | 2017-05-16 13:31:01 +0100 | |
---|---|---|
committer | 2017-05-25 16:48:41 +0100 | |
commit | 3ee4bff4eb293363b8fa5b263db55af59508efaf (patch) | |
tree | d7ab44338513d1699156c83fbc14a42c5aca6153 | |
parent | 9b5b23555d9f82e98cabd75195eb95a1030fe1a6 (diff) |
Show RegisteredNativeAllocation sizes in ahat.
Bug: 36459946
Test: m ahat-test
Change-Id: I45f6dc19cf1e339a80e0d93b6f4bc58a93e571c7
23 files changed, 522 insertions, 131 deletions
diff --git a/tools/ahat/README.txt b/tools/ahat/README.txt index 3049871d4c..475b7bb574 100644 --- a/tools/ahat/README.txt +++ b/tools/ahat/README.txt @@ -76,6 +76,7 @@ Things to move to perflib: Release History: 1.2 Pending + Show registered native sizes of objects. Simplify presentation of sample path from gc root. 1.1 Feb 21, 2017 diff --git a/tools/ahat/src/DocString.java b/tools/ahat/src/DocString.java index c6303c8c35..7970bf8de4 100644 --- a/tools/ahat/src/DocString.java +++ b/tools/ahat/src/DocString.java @@ -126,6 +126,23 @@ class DocString { } /** + * Standard formatted DocString for describing a size. + * + * Nothing is printed for a size of zero. + * Set isPlaceHolder to true to indicate that the size field corresponds to + * for a place holder object that should be annotated specially. + */ + public static DocString size(long size, boolean isPlaceHolder) { + DocString string = new DocString(); + if (isPlaceHolder) { + string.append(DocString.removed("del")); + } else if (size != 0) { + string.appendFormat("%,14d", size); + } + return string; + } + + /** * Standard formatted DocString for describing a change in size relative to * a baseline. * @param noCurrent - whether no current object exists. diff --git a/tools/ahat/src/DominatedList.java b/tools/ahat/src/DominatedList.java index f73e3ca027..75133b2184 100644 --- a/tools/ahat/src/DominatedList.java +++ b/tools/ahat/src/DominatedList.java @@ -55,7 +55,7 @@ class DominatedList { @Override public long getSize(AhatInstance element, AhatHeap heap) { - return element.getRetainedSize(heap); + return element.getRetainedSize(heap).getSize(); } @Override diff --git a/tools/ahat/src/HeapTable.java b/tools/ahat/src/HeapTable.java index 9abbe4a4ed..b04f2aebf7 100644 --- a/tools/ahat/src/HeapTable.java +++ b/tools/ahat/src/HeapTable.java @@ -45,16 +45,6 @@ class HeapTable { List<ValueConfig<T>> getValueConfigs(); } - private static DocString sizeString(long size, boolean isPlaceHolder) { - DocString string = new DocString(); - if (isPlaceHolder) { - string.append(DocString.removed("del")); - } else if (size != 0) { - string.appendFormat("%,14d", size); - } - return string; - } - /** * Render the table to the given document. * @param query - The page query. @@ -100,10 +90,10 @@ class HeapTable { long basesize = config.getSize(base, heap.getBaseline()); total += size; basetotal += basesize; - vals.add(sizeString(size, elem.isPlaceHolder())); + vals.add(DocString.size(size, elem.isPlaceHolder())); vals.add(DocString.delta(elem.isPlaceHolder(), base.isPlaceHolder(), size, basesize)); } - vals.add(sizeString(total, elem.isPlaceHolder())); + vals.add(DocString.size(total, elem.isPlaceHolder())); vals.add(DocString.delta(elem.isPlaceHolder(), base.isPlaceHolder(), total, basetotal)); for (ValueConfig<T> value : values) { @@ -140,10 +130,10 @@ class HeapTable { long basesize = basesummary.get(heap); total += size; basetotal += basesize; - vals.add(sizeString(size, false)); + vals.add(DocString.size(size, false)); vals.add(DocString.delta(false, false, size, basesize)); } - vals.add(sizeString(total, false)); + vals.add(DocString.size(total, false)); vals.add(DocString.delta(false, false, total, basetotal)); for (ValueConfig<T> value : values) { @@ -159,7 +149,7 @@ class HeapTable { public static <T extends Diffable<T>> boolean hasNonZeroEntry(AhatHeap heap, TableConfig<T> config, List<T> elements) { AhatHeap baseheap = heap.getBaseline(); - if (heap.getSize() > 0 || baseheap.getSize() > 0) { + if (!heap.getSize().isZero() || !baseheap.getSize().isZero()) { for (T element : elements) { if (config.getSize(element, heap) > 0 || config.getSize(element.getBaseline(), baseheap) > 0) { diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java index b1d7904ef6..d6f1faa3c3 100644 --- a/tools/ahat/src/ObjectHandler.java +++ b/tools/ahat/src/ObjectHandler.java @@ -70,16 +70,6 @@ class ObjectHandler implements AhatHandler { doc.descriptions(); doc.description(DocString.text("Class"), Summarizer.summarize(cls)); - DocString sizeDescription = DocString.format("%,14d ", inst.getSize()); - sizeDescription.appendDelta(false, base.isPlaceHolder(), - inst.getSize(), base.getSize()); - doc.description(DocString.text("Size"), sizeDescription); - - DocString rsizeDescription = DocString.format("%,14d ", inst.getTotalRetainedSize()); - rsizeDescription.appendDelta(false, base.isPlaceHolder(), - inst.getTotalRetainedSize(), base.getTotalRetainedSize()); - doc.description(DocString.text("Retained Size"), rsizeDescription); - doc.description(DocString.text("Heap"), DocString.text(inst.getHeap().getName())); Collection<String> rootTypes = inst.getRootTypes(); @@ -96,6 +86,13 @@ class ObjectHandler implements AhatHandler { doc.end(); + doc.section("Object Size"); + SizeTable.table(doc, new Column(""), inst != base && !base.isPlaceHolder()); + SizeTable.row(doc, DocString.text("Shallow"), inst.getSize(), base.getSize()); + SizeTable.row(doc, DocString.text("Retained"), + inst.getTotalRetainedSize(), base.getTotalRetainedSize()); + SizeTable.end(doc); + printBitmap(doc, inst); if (inst.isClassInstance()) { printClassInstanceFields(doc, query, inst.asClassInstance()); diff --git a/tools/ahat/src/ObjectsHandler.java b/tools/ahat/src/ObjectsHandler.java index 3062d23b53..86d48f1702 100644 --- a/tools/ahat/src/ObjectsHandler.java +++ b/tools/ahat/src/ObjectsHandler.java @@ -54,23 +54,18 @@ class ObjectsHandler implements AhatHandler { doc.title("Objects"); - doc.table( - new Column("Size", Column.Align.RIGHT), - new Column("Δ", Column.Align.RIGHT, mSnapshot.isDiffed()), + SizeTable.table(doc, mSnapshot.isDiffed(), new Column("Heap"), new Column("Object")); SubsetSelector<AhatInstance> selector = new SubsetSelector(query, OBJECTS_ID, insts); for (AhatInstance inst : selector.selected()) { AhatInstance base = inst.getBaseline(); - doc.row( - DocString.format("%,14d", inst.getSize()), - DocString.delta(inst.isPlaceHolder(), base.isPlaceHolder(), - inst.getSize(), base.getSize()), + SizeTable.row(doc, inst.getSize(), base.getSize(), DocString.text(inst.getHeap().getName()), Summarizer.summarize(inst)); } - doc.end(); + SizeTable.end(doc); selector.render(doc); } } diff --git a/tools/ahat/src/OverviewHandler.java b/tools/ahat/src/OverviewHandler.java index ea305c4e94..c9f84259a9 100644 --- a/tools/ahat/src/OverviewHandler.java +++ b/tools/ahat/src/OverviewHandler.java @@ -18,16 +18,12 @@ package com.android.ahat; import com.android.ahat.heapdump.AhatHeap; import com.android.ahat.heapdump.AhatSnapshot; -import com.android.ahat.heapdump.Diffable; +import com.android.ahat.heapdump.Size; import java.io.File; import java.io.IOException; -import java.util.Collections; -import java.util.List; class OverviewHandler implements AhatHandler { - private static final String OVERVIEW_ID = "overview"; - private AhatSnapshot mSnapshot; private File mHprof; private File mBaseHprof; @@ -53,39 +49,27 @@ class OverviewHandler implements AhatHandler { } doc.end(); - doc.section("Heap Sizes"); - printHeapSizes(doc, query); + doc.section("Bytes Retained by Heap"); + printHeapSizes(doc); doc.big(Menu.getMenu()); } - private static class TableElem implements Diffable<TableElem> { - @Override public TableElem getBaseline() { - return this; - } - - @Override public boolean isPlaceHolder() { - return false; - } - } - - private void printHeapSizes(Doc doc, Query query) { - List<TableElem> dummy = Collections.singletonList(new TableElem()); - - HeapTable.TableConfig<TableElem> table = new HeapTable.TableConfig<TableElem>() { - public String getHeapsDescription() { - return "Bytes Retained by Heap"; - } - - public long getSize(TableElem element, AhatHeap heap) { - return heap.getSize(); + private void printHeapSizes(Doc doc) { + SizeTable.table(doc, new Column("Heap"), mSnapshot.isDiffed()); + Size totalSize = Size.ZERO; + Size totalBase = Size.ZERO; + for (AhatHeap heap : mSnapshot.getHeaps()) { + Size size = heap.getSize(); + Size base = heap.getBaseline().getSize(); + if (!size.isZero() || !base.isZero()) { + SizeTable.row(doc, DocString.text(heap.getName()), size, base); + totalSize = totalSize.plus(size); + totalBase = totalBase.plus(base); } - - public List<HeapTable.ValueConfig<TableElem>> getValueConfigs() { - return Collections.emptyList(); - } - }; - HeapTable.render(doc, query, OVERVIEW_ID, table, mSnapshot, dummy); + } + SizeTable.row(doc, DocString.text("Total"), totalSize, totalBase); + SizeTable.end(doc); } } diff --git a/tools/ahat/src/SiteHandler.java b/tools/ahat/src/SiteHandler.java index febf1713fb..7a831d3018 100644 --- a/tools/ahat/src/SiteHandler.java +++ b/tools/ahat/src/SiteHandler.java @@ -60,7 +60,7 @@ class SiteHandler implements AhatHandler { } public long getSize(Site element, AhatHeap heap) { - return element.getSize(heap); + return element.getSize(heap).getSize(); } public List<HeapTable.ValueConfig<Site>> getValueConfigs() { @@ -80,10 +80,7 @@ class SiteHandler implements AhatHandler { } doc.section("Objects Allocated"); - - doc.table( - new Column("Reachable Bytes Allocated", Column.Align.RIGHT), - new Column("Δ", Column.Align.RIGHT, mSnapshot.isDiffed()), + SizeTable.table(doc, mSnapshot.isDiffed(), new Column("Instances", Column.Align.RIGHT), new Column("Δ", Column.Align.RIGHT, mSnapshot.isDiffed()), new Column("Heap"), @@ -100,9 +97,7 @@ class SiteHandler implements AhatHandler { for (Site.ObjectsInfo info : selector.selected()) { Site.ObjectsInfo baseinfo = info.getBaseline(); String className = info.getClassName(); - doc.row( - DocString.format("%,14d", info.numBytes), - DocString.delta(false, false, info.numBytes, baseinfo.numBytes), + SizeTable.row(doc, info.numBytes, baseinfo.numBytes, DocString.link( DocString.formattedUri("objects?id=%d&depth=%d&heap=%s&class=%s", site.getId(), site.getDepth(), info.heap.getName(), className), @@ -111,7 +106,7 @@ class SiteHandler implements AhatHandler { DocString.text(info.heap.getName()), Summarizer.summarize(info.classObj)); } - doc.end(); + SizeTable.end(doc); selector.render(doc); } } diff --git a/tools/ahat/src/SitePrinter.java b/tools/ahat/src/SitePrinter.java index 21ca2deda4..32037f4414 100644 --- a/tools/ahat/src/SitePrinter.java +++ b/tools/ahat/src/SitePrinter.java @@ -38,7 +38,7 @@ class SitePrinter { } public long getSize(Site element, AhatHeap heap) { - return element.getSize(heap); + return element.getSize(heap).getSize(); } public List<HeapTable.ValueConfig<Site>> getValueConfigs() { diff --git a/tools/ahat/src/SizeTable.java b/tools/ahat/src/SizeTable.java new file mode 100644 index 0000000000..46e395669f --- /dev/null +++ b/tools/ahat/src/SizeTable.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2017 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.ahat; + +import com.android.ahat.heapdump.Size; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Class for rendering a table that includes all categories of Size. + * Two table formats are supported, one where a custom left column can be + * added before the size columns: + * |left column|Java Size|Native Size|...|Total Size|custom columns...| + * + * The other without the custom left column: + * |Java Size|Native Size|...|Total Size|custom columns...| + */ +class SizeTable { + /** + * Start a size table with a custom left column. + * + * |left column|Java Size|Native Size|...|Total Size|custom columns...| + * + * This should be followed by calls to the 'row' method to fill in the table + * contents and the 'end' method to end the table. + * + * Set showDiff to true if size diffs should be shown. + */ + static void table(Doc doc, Column left, boolean showDiff, Column... columns) { + List<Column> cols = new ArrayList<Column>(); + cols.add(left); + cols.add(new Column("Java Size", Column.Align.RIGHT)); + cols.add(new Column("Δ", Column.Align.RIGHT, showDiff)); + cols.add(new Column("Registered Native Size", Column.Align.RIGHT)); + cols.add(new Column("Δ", Column.Align.RIGHT, showDiff)); + cols.add(new Column("Total Size", Column.Align.RIGHT)); + cols.add(new Column("Δ", Column.Align.RIGHT, showDiff)); + cols.addAll(Arrays.asList(columns)); + doc.table(cols.toArray(new Column[cols.size()])); + } + + /** + * Add a row to the currently active size table with custom left column. + * The number of values must match the number of columns provided for the + * currently active table. + */ + static void row(Doc doc, DocString left, Size size, Size base, DocString... values) { + List<DocString> vals = new ArrayList<DocString>(); + vals.add(left); + vals.add(DocString.size(size.getJavaSize(), false)); + vals.add(DocString.delta(false, false, size.getJavaSize(), base.getJavaSize())); + vals.add(DocString.size(size.getRegisteredNativeSize(), false)); + vals.add(DocString.delta(false, false, + size.getRegisteredNativeSize(), base.getRegisteredNativeSize())); + vals.add(DocString.size(size.getSize(), false)); + vals.add(DocString.delta(false, false, size.getSize(), base.getSize())); + vals.addAll(Arrays.asList(values)); + doc.row(vals.toArray(new DocString[vals.size()])); + } + + /** + * Start a size table without a custom left column. + * + * |Java Size|Native Size|...|Total Size|custom columns...| + * This should be followed by calls to the 'row' method to fill in the table + * contents and the 'end' method to end the table. + * + * Set showDiff to true if size diffs should be shown. + */ + static void table(Doc doc, boolean showDiff, Column... columns) { + // Re-use the code for a size table with custom left column by having an + // invisible custom left column. + table(doc, new Column("", Column.Align.LEFT, false), showDiff, columns); + } + + /** + * Add a row to the currently active size table without a custom left column. + * The number of values must match the number of columns provided for the + * currently active table. + */ + static void row(Doc doc, Size size, Size base, DocString... values) { + row(doc, new DocString(), size, base, values); + } + + /** + * End the currently active table. + */ + static void end(Doc doc) { + doc.end(); + } +} diff --git a/tools/ahat/src/heapdump/AhatHeap.java b/tools/ahat/src/heapdump/AhatHeap.java index c39adc4b41..b8897a182c 100644 --- a/tools/ahat/src/heapdump/AhatHeap.java +++ b/tools/ahat/src/heapdump/AhatHeap.java @@ -18,7 +18,7 @@ package com.android.ahat.heapdump; public class AhatHeap implements Diffable<AhatHeap> { private String mName; - private long mSize = 0; + private Size mSize = Size.ZERO; private int mIndex; private AhatHeap mBaseline; private boolean mIsPlaceHolder = false; @@ -47,8 +47,8 @@ public class AhatHeap implements Diffable<AhatHeap> { return new AhatHeap(name, baseline); } - void addToSize(long increment) { - mSize += increment; + void addToSize(Size size) { + mSize = mSize.plus(size); } /** @@ -69,7 +69,7 @@ public class AhatHeap implements Diffable<AhatHeap> { /** * Returns the total number of bytes allocated on this heap. */ - public long getSize() { + public Size getSize() { return mSize; } diff --git a/tools/ahat/src/heapdump/AhatInstance.java b/tools/ahat/src/heapdump/AhatInstance.java index e6b9c00384..af369d95d8 100644 --- a/tools/ahat/src/heapdump/AhatInstance.java +++ b/tools/ahat/src/heapdump/AhatInstance.java @@ -20,17 +20,18 @@ import com.android.tools.perflib.heap.ClassObj; import com.android.tools.perflib.heap.Instance; import com.android.tools.perflib.heap.RootObj; import java.awt.image.BufferedImage; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Deque; import java.util.List; public abstract class AhatInstance implements Diffable<AhatInstance> { private long mId; - private long mSize; - private long mTotalRetainedSize; - private long mRetainedSizes[]; // Retained size indexed by heap index + private Size mSize; + private Size[] mRetainedSizes; // Retained size indexed by heap index private boolean mIsReachable; private AhatHeap mHeap; private AhatInstance mImmediateDominator; @@ -63,15 +64,10 @@ public abstract class AhatInstance implements Diffable<AhatInstance> { */ void initialize(AhatSnapshot snapshot, Instance inst) { mId = inst.getId(); - mSize = inst.getSize(); - mTotalRetainedSize = inst.getTotalRetainedSize(); + mSize = new Size(inst.getSize(), 0); mIsReachable = inst.isReachable(); List<AhatHeap> heaps = snapshot.getHeaps(); - mRetainedSizes = new long[heaps.size()]; - for (AhatHeap heap : heaps) { - mRetainedSizes[heap.getIndex()] = inst.getRetainedSize(heap.getIndex()); - } mHeap = snapshot.getHeap(inst.getHeap().getName()); @@ -130,7 +126,7 @@ public abstract class AhatInstance implements Diffable<AhatInstance> { /** * Returns the shallow number of bytes this object takes up. */ - public long getSize() { + public Size getSize() { return mSize; } @@ -138,16 +134,32 @@ public abstract class AhatInstance implements Diffable<AhatInstance> { * Returns the number of bytes belonging to the given heap that this instance * retains. */ - public long getRetainedSize(AhatHeap heap) { + public Size getRetainedSize(AhatHeap heap) { int index = heap.getIndex(); - return 0 <= index && index < mRetainedSizes.length ? mRetainedSizes[heap.getIndex()] : 0; + if (mRetainedSizes != null && 0 <= index && index < mRetainedSizes.length) { + return mRetainedSizes[heap.getIndex()]; + } + return Size.ZERO; } /** * Returns the total number of bytes this instance retains. */ - public long getTotalRetainedSize() { - return mTotalRetainedSize; + public Size getTotalRetainedSize() { + Size size = Size.ZERO; + if (mRetainedSizes != null) { + for (int i = 0; i < mRetainedSizes.length; i++) { + size = size.plus(mRetainedSizes[i]); + } + } + return size; + } + + /** + * Increment the number of registered native bytes tied to this object. + */ + void addRegisteredNativeSize(long size) { + mSize = mSize.plusRegisteredNativeSize(size); } /** @@ -452,4 +464,41 @@ public abstract class AhatInstance implements Diffable<AhatInstance> { AhatInstance newPlaceHolderInstance() { return new AhatPlaceHolderInstance(this); } + + /** + * Recursively compute the retained size of the given instance and all + * other instances it dominates. + */ + static void computeRetainedSize(AhatInstance inst, int numHeaps) { + // Note: We can't use a recursive implementation because it can lead to + // stack overflow. Use an iterative implementation instead. + // + // Objects not yet processed will have mRetainedSizes set to null. + // Once prepared, an object will have mRetaiedSizes set to an array of 0 + // sizes. + Deque<AhatInstance> deque = new ArrayDeque<AhatInstance>(); + deque.push(inst); + + while (!deque.isEmpty()) { + inst = deque.pop(); + if (inst.mRetainedSizes == null) { + inst.mRetainedSizes = new Size[numHeaps]; + for (int i = 0; i < numHeaps; i++) { + inst.mRetainedSizes[i] = Size.ZERO; + } + inst.mRetainedSizes[inst.mHeap.getIndex()] = + inst.mRetainedSizes[inst.mHeap.getIndex()].plus(inst.mSize); + deque.push(inst); + for (AhatInstance dominated : inst.mDominated) { + deque.push(dominated); + } + } else { + for (AhatInstance dominated : inst.mDominated) { + for (int i = 0; i < numHeaps; i++) { + inst.mRetainedSizes[i] = inst.mRetainedSizes[i].plus(dominated.mRetainedSizes[i]); + } + } + } + } + } } diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java b/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java index c6ad87fda5..2b3e056a1e 100644 --- a/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java +++ b/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java @@ -29,16 +29,16 @@ public class AhatPlaceHolderClassObj extends AhatClassObj { baseline.setBaseline(this); } - @Override public long getSize() { - return 0; + @Override public Size getSize() { + return Size.ZERO; } - @Override public long getRetainedSize(AhatHeap heap) { - return 0; + @Override public Size getRetainedSize(AhatHeap heap) { + return Size.ZERO; } - @Override public long getTotalRetainedSize() { - return 0; + @Override public Size getTotalRetainedSize() { + return Size.ZERO; } @Override public AhatHeap getHeap() { diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java b/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java index 9412eae9a1..4aac80484d 100644 --- a/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java +++ b/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java @@ -29,16 +29,16 @@ public class AhatPlaceHolderInstance extends AhatInstance { baseline.setBaseline(this); } - @Override public long getSize() { - return 0; + @Override public Size getSize() { + return Size.ZERO; } - @Override public long getRetainedSize(AhatHeap heap) { - return 0; + @Override public Size getRetainedSize(AhatHeap heap) { + return Size.ZERO; } - @Override public long getTotalRetainedSize() { - return 0; + @Override public Size getTotalRetainedSize() { + return Size.ZERO; } @Override public AhatHeap getHeap() { diff --git a/tools/ahat/src/heapdump/AhatSnapshot.java b/tools/ahat/src/heapdump/AhatSnapshot.java index 20b85da763..35d6c8a315 100644 --- a/tools/ahat/src/heapdump/AhatSnapshot.java +++ b/tools/ahat/src/heapdump/AhatSnapshot.java @@ -82,8 +82,7 @@ public class AhatSnapshot implements Diffable<AhatSnapshot> { Snapshot snapshot = Snapshot.createSnapshot(buffer, map); snapshot.computeDominators(); - // Properly label the class of class objects in the perflib snapshot, and - // count the total number of instances. + // Properly label the class of class objects in the perflib snapshot. final ClassObj javaLangClass = snapshot.findClass("java.lang.Class"); if (javaLangClass != null) { for (Heap heap : snapshot.getHeaps()) { @@ -134,12 +133,19 @@ public class AhatSnapshot implements Diffable<AhatSnapshot> { } }); + Map<Instance, Long> registeredNative = Perflib.getRegisteredNativeAllocations(snapshot); + // Initialize ahat snapshot and instances based on the perflib snapshot // and instances. for (AhatInstance ahat : mInstances) { Instance inst = snapshot.findInstance(ahat.getId()); ahat.initialize(this, inst); + Long registeredNativeSize = registeredNative.get(inst); + if (registeredNativeSize != null) { + ahat.addRegisteredNativeSize(registeredNativeSize); + } + if (inst.getImmediateDominator() == Snapshot.SENTINEL_ROOT) { mRooted.add(ahat); } @@ -166,6 +172,13 @@ public class AhatSnapshot implements Diffable<AhatSnapshot> { } } snapshot.dispose(); + + // Compute the retained sizes of objects. We do this explicitly now rather + // than relying on the retained sizes computed by perflib so that + // registered native sizes are included. + for (AhatInstance inst : mRooted) { + AhatInstance.computeRetainedSize(inst, mHeaps.size()); + } } /** diff --git a/tools/ahat/src/heapdump/Perflib.java b/tools/ahat/src/heapdump/Perflib.java new file mode 100644 index 0000000000..d0264a3b39 --- /dev/null +++ b/tools/ahat/src/heapdump/Perflib.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 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.ahat.heapdump; + +import com.android.tools.perflib.heap.ClassInstance; +import com.android.tools.perflib.heap.ClassObj; +import com.android.tools.perflib.heap.Instance; +import com.android.tools.perflib.heap.Snapshot; +import java.util.HashMap; +import java.util.Map; + +/** + * Collection of utilities that may be suitable to have in perflib instead of + * ahat. + */ +public class Perflib { + /** + * Return a collection of instances in the given snapshot that are tied to + * registered native allocations and their corresponding registered native + * sizes. + */ + public static Map<Instance, Long> getRegisteredNativeAllocations(Snapshot snapshot) { + Map<Instance, Long> allocs = new HashMap<Instance, Long>(); + ClassObj cleanerClass = snapshot.findClass("sun.misc.Cleaner"); + if (cleanerClass != null) { + for (Instance cleanerInst : cleanerClass.getInstancesList()) { + ClassInstance cleaner = (ClassInstance)cleanerInst; + Object referent = getField(cleaner, "referent"); + if (referent instanceof Instance) { + Instance inst = (Instance)referent; + Object thunkValue = getField(cleaner, "thunk"); + if (thunkValue instanceof ClassInstance) { + ClassInstance thunk = (ClassInstance)thunkValue; + ClassObj thunkClass = thunk.getClassObj(); + String cleanerThunkClassName = "libcore.util.NativeAllocationRegistry$CleanerThunk"; + if (thunkClass != null && cleanerThunkClassName.equals(thunkClass.getClassName())) { + for (ClassInstance.FieldValue thunkField : thunk.getValues()) { + if (thunkField.getValue() instanceof ClassInstance) { + ClassInstance registry = (ClassInstance)thunkField.getValue(); + ClassObj registryClass = registry.getClassObj(); + String registryClassName = "libcore.util.NativeAllocationRegistry"; + if (registryClass != null + && registryClassName.equals(registryClass.getClassName())) { + Object sizeValue = getField(registry, "size"); + if (sizeValue instanceof Long) { + long size = (Long)sizeValue; + if (size > 0) { + Long old = allocs.get(inst); + allocs.put(inst, old == null ? size : old + size); + } + } + break; + } + } + } + } + } + } + } + } + return allocs; + } + + /** + * Helper function to read a single field from a perflib class instance. + * Returns null if field not found. Note there is no way to distinguish + * between field not found an a field value of null. + */ + private static Object getField(ClassInstance cls, String name) { + for (ClassInstance.FieldValue field : cls.getValues()) { + if (name.equals(field.getField().getName())) { + return field.getValue(); + } + } + return null; + } +} diff --git a/tools/ahat/src/heapdump/Site.java b/tools/ahat/src/heapdump/Site.java index 738eaf0687..fdd4eea7b3 100644 --- a/tools/ahat/src/heapdump/Site.java +++ b/tools/ahat/src/heapdump/Site.java @@ -44,7 +44,7 @@ public class Site implements Diffable<Site> { // The total size of objects allocated in this site (including child sites), // organized by heap index. Heap indices outside the range of mSizesByHeap // implicitly have size 0. - private long[] mSizesByHeap; + private Size[] mSizesByHeap; // List of child sites. private List<Site> mChildren; @@ -60,14 +60,18 @@ public class Site implements Diffable<Site> { public AhatHeap heap; public AhatClassObj classObj; // May be null. public long numInstances; - public long numBytes; + public Size numBytes; private ObjectsInfo baseline; - public ObjectsInfo(AhatHeap heap, AhatClassObj classObj, long numInstances, long numBytes) { + /** + * Construct a new, empty objects info for the given heap and class + * combination. + */ + public ObjectsInfo(AhatHeap heap, AhatClassObj classObj) { this.heap = heap; this.classObj = classObj; - this.numInstances = numInstances; - this.numBytes = numBytes; + this.numInstances = 0; + this.numBytes = Size.ZERO; this.baseline = this; } @@ -107,7 +111,7 @@ public class Site implements Diffable<Site> { mLineNumber = line; mId = id; mDepth = depth; - mSizesByHeap = new long[1]; + mSizesByHeap = new Size[0]; mChildren = new ArrayList<Site>(); mObjects = new ArrayList<AhatInstance>(); mObjectsInfos = new ArrayList<ObjectsInfo>(); @@ -133,16 +137,20 @@ public class Site implements Diffable<Site> { if (inst.isReachable()) { AhatHeap heap = inst.getHeap(); if (heap.getIndex() >= site.mSizesByHeap.length) { - long[] newSizes = new long[heap.getIndex() + 1]; + Size[] newSizes = new Size[heap.getIndex() + 1]; for (int i = 0; i < site.mSizesByHeap.length; i++) { newSizes[i] = site.mSizesByHeap[i]; } + for (int i = site.mSizesByHeap.length; i < heap.getIndex() + 1; i++) { + newSizes[i] = Size.ZERO; + } site.mSizesByHeap = newSizes; } - site.mSizesByHeap[heap.getIndex()] += inst.getSize(); + site.mSizesByHeap[heap.getIndex()] + = site.mSizesByHeap[heap.getIndex()].plus(inst.getSize()); info.numInstances++; - info.numBytes += inst.getSize(); + info.numBytes = info.numBytes.plus(inst.getSize()); } if (depth > 0) { @@ -172,9 +180,9 @@ public class Site implements Diffable<Site> { } // Get the size of a site for a specific heap. - public long getSize(AhatHeap heap) { + public Size getSize(AhatHeap heap) { int index = heap.getIndex(); - return index >= 0 && index < mSizesByHeap.length ? mSizesByHeap[index] : 0; + return index >= 0 && index < mSizesByHeap.length ? mSizesByHeap[index] : Size.ZERO; } /** @@ -198,7 +206,7 @@ public class Site implements Diffable<Site> { ObjectsInfo info = classToObjectsInfo.get(classObj); if (info == null) { - info = new ObjectsInfo(heap, classObj, 0, 0); + info = new ObjectsInfo(heap, classObj); mObjectsInfos.add(info); classToObjectsInfo.put(classObj, info); } @@ -210,10 +218,10 @@ public class Site implements Diffable<Site> { } // Get the combined size of the site for all heaps. - public long getTotalSize() { - long total = 0; + public Size getTotalSize() { + Size total = Size.ZERO; for (int i = 0; i < mSizesByHeap.length; i++) { - total += mSizesByHeap[i]; + total = total.plus(mSizesByHeap[i]); } return total; } diff --git a/tools/ahat/src/heapdump/Size.java b/tools/ahat/src/heapdump/Size.java new file mode 100644 index 0000000000..7c8db900df --- /dev/null +++ b/tools/ahat/src/heapdump/Size.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017 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.ahat.heapdump; + +/** + * The Size class is used to represent how much space an instance takes up. + * + * An abstraction is introduced rather than using a long directly in order to + * more easily keep track of the different components of the size. For + * example, some instances may have associated native, code, or graphics + * sizes. + * + * Size objects are immutable. + */ +public class Size { + private final long mJavaSize; + private final long mRegisteredNativeSize; + + public static Size ZERO = new Size(0, 0); + + public Size(long javaSize, long registeredNativeSize) { + mJavaSize = javaSize; + mRegisteredNativeSize = registeredNativeSize; + } + + public long getSize() { + return mJavaSize + mRegisteredNativeSize; + } + + public long getJavaSize() { + return mJavaSize; + } + + public long getRegisteredNativeSize() { + return mRegisteredNativeSize; + } + + /** + * Returns true if all the fields of this size object are zero. + */ + public boolean isZero() { + return mJavaSize == 0 && mRegisteredNativeSize == 0; + } + + /** + * Return a new Size object that is the sum of this size and the other. + */ + public Size plus(Size other) { + if (isZero()) { + return other; + } else if (other.isZero()) { + return this; + } else { + return new Size(mJavaSize + other.mJavaSize, + mRegisteredNativeSize + other.mRegisteredNativeSize); + } + } + + /** + * Return a new Size object that has 'size' more registered native size than + * this Size object. + */ + public Size plusRegisteredNativeSize(long size) { + return new Size(mJavaSize, mRegisteredNativeSize + size); + } + + @Override public boolean equals(Object other) { + if (other instanceof Size) { + Size s = (Size)other; + return mJavaSize == s.mJavaSize && mRegisteredNativeSize == s.mRegisteredNativeSize; + } + return false; + } +} + diff --git a/tools/ahat/src/heapdump/Sort.java b/tools/ahat/src/heapdump/Sort.java index 93d147a49e..0745803817 100644 --- a/tools/ahat/src/heapdump/Sort.java +++ b/tools/ahat/src/heapdump/Sort.java @@ -32,6 +32,17 @@ import java.util.List; */ public class Sort { /** + * Compare sizes by their total size. + * This sorts sizes from smaller total size to larger total size. + */ + public static final Comparator<Size> SIZE_BY_SIZE = new Comparator<Size>() { + @Override + public int compare(Size a, Size b) { + return Long.compare(a.getSize(), b.getSize()); + } + }; + + /** * Compare instances by their total retained size. * Different instances with the same total retained size are considered * equal for the purposes of comparison. @@ -41,7 +52,7 @@ public class Sort { = new Comparator<AhatInstance>() { @Override public int compare(AhatInstance a, AhatInstance b) { - return Long.compare(b.getTotalRetainedSize(), a.getTotalRetainedSize()); + return SIZE_BY_SIZE.compare(b.getTotalRetainedSize(), a.getTotalRetainedSize()); } }; @@ -60,7 +71,7 @@ public class Sort { @Override public int compare(AhatInstance a, AhatInstance b) { - return Long.compare(b.getRetainedSize(mHeap), a.getRetainedSize(mHeap)); + return SIZE_BY_SIZE.compare(b.getRetainedSize(mHeap), a.getRetainedSize(mHeap)); } } @@ -119,7 +130,7 @@ public class Sort { @Override public int compare(Site a, Site b) { - return Long.compare(b.getSize(mHeap), a.getSize(mHeap)); + return SIZE_BY_SIZE.compare(b.getSize(mHeap), a.getSize(mHeap)); } } @@ -130,7 +141,7 @@ public class Sort { public static final Comparator<Site> SITE_BY_TOTAL_SIZE = new Comparator<Site>() { @Override public int compare(Site a, Site b) { - return Long.compare(b.getTotalSize(), a.getTotalSize()); + return SIZE_BY_SIZE.compare(b.getTotalSize(), a.getTotalSize()); } }; @@ -158,7 +169,7 @@ public class Sort { = new Comparator<Site.ObjectsInfo>() { @Override public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) { - return Long.compare(b.numBytes, a.numBytes); + return SIZE_BY_SIZE.compare(b.numBytes, a.numBytes); } }; diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/test-dump/Main.java index 7a05b1cb89..3d3de78255 100644 --- a/tools/ahat/test-dump/Main.java +++ b/tools/ahat/test-dump/Main.java @@ -20,6 +20,7 @@ import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; +import libcore.util.NativeAllocationRegistry; import org.apache.harmony.dalvik.ddmc.DdmVmInternal; /** @@ -98,6 +99,11 @@ public class Main { bigArray[i] = (byte)((i*i) & 0xFF); } + // 0x12345, 50000, and 0xABCDABCD are arbitrary values. + NativeAllocationRegistry registry = new NativeAllocationRegistry( + Main.class.getClassLoader(), 0x12345, 50000); + registry.registerNativeAllocation(anObject, 0xABCDABCD); + addedObject = baseline ? null : new AddedObject(); removedObject = baseline ? new RemovedObject() : null; modifiedObject = new ModifiedObject(); diff --git a/tools/ahat/test/InstanceTest.java b/tools/ahat/test/InstanceTest.java index 3a50150c0e..71b081c9a4 100644 --- a/tools/ahat/test/InstanceTest.java +++ b/tools/ahat/test/InstanceTest.java @@ -21,6 +21,7 @@ import com.android.ahat.heapdump.AhatHeap; import com.android.ahat.heapdump.AhatInstance; import com.android.ahat.heapdump.AhatSnapshot; import com.android.ahat.heapdump.PathElement; +import com.android.ahat.heapdump.Size; import com.android.ahat.heapdump.Value; import com.android.tools.perflib.heap.hprof.HprofClassDump; import com.android.tools.perflib.heap.hprof.HprofConstant; @@ -292,13 +293,13 @@ public class InstanceTest { // allocated on, and should be 0 for all other heaps. AhatInstance anObject = dump.getDumpedAhatInstance("anObject"); AhatSnapshot snapshot = dump.getAhatSnapshot(); - long size = anObject.getSize(); + Size size = anObject.getSize(); assertEquals(size, anObject.getTotalRetainedSize()); assertEquals(size, anObject.getRetainedSize(anObject.getHeap())); for (AhatHeap heap : snapshot.getHeaps()) { if (!heap.equals(anObject.getHeap())) { assertEquals(String.format("For heap '%s'", heap.getName()), - 0, anObject.getRetainedSize(heap)); + Size.ZERO, anObject.getRetainedSize(heap)); } } } diff --git a/tools/ahat/test/NativeAllocationTest.java b/tools/ahat/test/NativeAllocationTest.java new file mode 100644 index 0000000000..7436be8311 --- /dev/null +++ b/tools/ahat/test/NativeAllocationTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2016 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.ahat; + +import com.android.ahat.heapdump.AhatInstance; +import com.android.ahat.heapdump.AhatSnapshot; +import java.io.IOException; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class NativeAllocationTest { + + @Test + public void nativeAllocation() throws IOException { + TestDump dump = TestDump.getTestDump(); + + AhatSnapshot snapshot = dump.getAhatSnapshot(); + AhatInstance referent = dump.getDumpedAhatInstance("anObject"); + assertEquals(50000, referent.getSize().getRegisteredNativeSize()); + } +} + diff --git a/tools/ahat/test/Tests.java b/tools/ahat/test/Tests.java index 2fd3286172..c7e9b1811b 100644 --- a/tools/ahat/test/Tests.java +++ b/tools/ahat/test/Tests.java @@ -24,6 +24,7 @@ public class Tests { args = new String[]{ "com.android.ahat.DiffTest", "com.android.ahat.InstanceTest", + "com.android.ahat.NativeAllocationTest", "com.android.ahat.ObjectHandlerTest", "com.android.ahat.OverviewHandlerTest", "com.android.ahat.PerformanceTest", |