Skip to content

Commit

Permalink
Add retained size to leak reports
Browse files Browse the repository at this point in the history
Fixes #162
  • Loading branch information
pyricau committed Aug 28, 2015
1 parent 0b0ab29 commit c355a6d
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* Added LeakCanary SHA to text report [#120](https://github.com/square/leakcanary/issues/120).
* Renamed all resources to begin with `leak_canary_` instead of `__leak_canary`[#161](https://github.com/square/leakcanary/pull/161)
* No crash when heap dump fails [#226](https://github.com/square/leakcanary/issues/226).
* Add retained size to leak reports [#162](https://github.com/square/leakcanary/issues/162).

### Public API changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@
public final class AnalysisResult implements Serializable {

public static AnalysisResult noLeak(long analysisDurationMs) {
return new AnalysisResult(false, false, null, null, null, analysisDurationMs);
return new AnalysisResult(false, false, null, null, null, 0, analysisDurationMs);
}

public static AnalysisResult leakDetected(boolean excludedLeak, String className,
LeakTrace leakTrace, long analysisDurationMs) {
return new AnalysisResult(true, excludedLeak, className, leakTrace, null, analysisDurationMs);
LeakTrace leakTrace, long retainedHeapSize, long analysisDurationMs) {
return new AnalysisResult(true, excludedLeak, className, leakTrace, null, retainedHeapSize,
analysisDurationMs);
}

public static AnalysisResult failure(Throwable failure, long analysisDurationMs) {
return new AnalysisResult(false, false, null, null, failure, analysisDurationMs);
return new AnalysisResult(false, false, null, null, failure, 0, analysisDurationMs);
}

/** True if a leak was found in the heap dump. */
Expand All @@ -56,16 +57,23 @@ public static AnalysisResult failure(Throwable failure, long analysisDurationMs)
/** Null unless the analysis failed. */
public final Throwable failure;

/**
* The number of bytes which would be freed if all references to the leaking object were
* released. 0 if {@link #leakFound} is false.
*/
public final long retainedHeapSize;

/** Total time spent analyzing the heap. */
public final long analysisDurationMs;

private AnalysisResult(boolean leakFound, boolean excludedLeak, String className,
LeakTrace leakTrace, Throwable failure, long analysisDurationMs) {
LeakTrace leakTrace, Throwable failure, long retainedHeapSize, long analysisDurationMs) {
this.leakFound = leakFound;
this.excludedLeak = excludedLeak;
this.className = className;
this.leakTrace = leakTrace;
this.failure = failure;
this.retainedHeapSize = retainedHeapSize;
this.analysisDurationMs = analysisDurationMs;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.squareup.haha.perflib.HprofParser;
import com.squareup.haha.perflib.Instance;
import com.squareup.haha.perflib.RootObj;
import com.squareup.haha.perflib.RootType;
import com.squareup.haha.perflib.Snapshot;
import com.squareup.haha.perflib.Type;
import com.squareup.haha.perflib.io.HprofBuffer;
Expand Down Expand Up @@ -119,10 +120,56 @@ private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapsh

String className = leakingRef.getClassObj().getClassName();

return leakDetected(result.excludingKnownLeaks, className, leakTrace,
// Side effect: computes retained size.
snapshot.computeDominators();

Instance leakingInstance = result.leakingNode.instance;

long retainedSize = leakingInstance.getTotalRetainedSize();

retainedSize += computeBitmapRetainedSize(snapshot, leakingInstance);

return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
since(analysisStartNanoTime));
}

private int computeBitmapRetainedSize(Snapshot snapshot, Instance leakingInstance) {
// Bitmaps and bitmap byte arrays are held by native gc roots, so they aren't included in the
// retained size because their dominator is a native gc root. So instead, we go in and compute
// that extra size.

int bitmapRetainedSize = 0;
ClassObj bitmapClass = snapshot.findClass("android.graphics.Bitmap");
for (Instance bitmapInstance : bitmapClass.getInstancesList()) {
if (isDominator(leakingInstance, bitmapInstance)) {
ArrayInstance mBufferInstance = fieldValue(classInstanceValues(bitmapInstance), "mBuffer");
bitmapRetainedSize += mBufferInstance.getTotalRetainedSize();
}
}
return bitmapRetainedSize;
}

private boolean isDominator(Instance dominator, Instance instance) {
while ((instance = getImmediateDominator(instance)) != null) {
if (instance == dominator) {
return true;
}
}
return false;
}

private Instance getImmediateDominator(Instance instance) {
Instance dominator = instance.getImmediateDominator();
// Ignore native roots.
if (dominator instanceof RootObj) {
RootObj rootObj = (RootObj) dominator;
if (rootObj.getRootType() == RootType.UNKNOWN) {
return instance.getNextInstanceToGcRoot();
}
}
return dominator;
}

private LeakTrace buildLeakTrace(LeakNode leakingNode) {
List<LeakTraceElement> elements = new ArrayList<>();
// We iterate from the leak to the GC root
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.HONEYCOMB;
import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.text.format.Formatter.formatShortFileSize;
import static com.squareup.leakcanary.LeakCanary.leakInfo;
import static com.squareup.leakcanary.internal.LeakCanaryInternals.classSimpleName;
import static com.squareup.leakcanary.internal.LeakCanaryInternals.findNextAvailableHprofFile;
Expand All @@ -44,8 +45,7 @@
*/
public class DisplayLeakService extends AbstractAnalysisResultService {

@Override
protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
@Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
String leakInfo = leakInfo(this, heapDump, result, true);
if (leakInfo.length() < 4000) {
Log.d("LeakCanary", leakInfo);
Expand Down Expand Up @@ -107,7 +107,8 @@ protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
String contentTitle;
if (result.failure == null) {
contentTitle =
getString(R.string.leak_canary_class_has_leaked, classSimpleName(result.className));
getString(R.string.leak_canary_class_has_leaked, classSimpleName(result.className),
formatShortFileSize(this, result.retainedHeapSize));
} else {
contentTitle = getString(R.string.leak_canary_analysis_failed);
}
Expand All @@ -118,8 +119,7 @@ protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
}

@TargetApi(HONEYCOMB)
private void notify(String contentTitle, String contentText,
PendingIntent pendingIntent) {
private void notify(String contentTitle, String contentText, PendingIntent pendingIntent) {
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.squareup.leakcanary.internal.DisplayLeakActivity;
import com.squareup.leakcanary.internal.HeapAnalyzerService;

import static android.text.format.Formatter.formatShortFileSize;
import static com.squareup.leakcanary.internal.LeakCanaryInternals.isInServiceProcess;
import static com.squareup.leakcanary.internal.LeakCanaryInternals.setEnabled;

Expand Down Expand Up @@ -96,6 +97,7 @@ public static String leakInfo(Context context, HeapDump heapDump, AnalysisResult
info += " (" + heapDump.referenceName + ")";
}
info += " has leaked:\n" + result.leakTrace.toString() + "\n";
info += "* Retaining: " + formatShortFileSize(context, result.retainedHeapSize) + ".\n";
if (detailed) {
detailedString = "\n* Details:\n" + result.leakTrace.toDetailedString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
import static android.text.format.DateUtils.FORMAT_SHOW_TIME;
import static android.text.format.Formatter.formatShortFileSize;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static com.squareup.leakcanary.LeakCanary.leakInfo;
Expand Down Expand Up @@ -255,8 +256,8 @@ public void onItemClick(AdapterView<?> parent, View view, int position, long id)
}
HeapDump heapDump = visibleLeak.heapDump;
adapter.update(result.leakTrace, heapDump.referenceKey, heapDump.referenceName);
setTitle(
getString(R.string.leak_canary_class_has_leaked, classSimpleName(result.className)));
setTitle(getString(R.string.leak_canary_class_has_leaked, classSimpleName(result.className),
formatShortFileSize(this, result.retainedHeapSize)));
}
} else {
if (listAdapter instanceof LeakListAdapter) {
Expand Down Expand Up @@ -337,7 +338,8 @@ class LeakListAdapter extends BaseAdapter {
String title;
if (leak.result.failure == null) {
title = index + getString(R.string.leak_canary_class_has_leaked,
classSimpleName(leak.result.className));
classSimpleName(leak.result.className),
formatShortFileSize(DisplayLeakActivity.this, leak.result.retainedHeapSize));
} else {
title = index
+ leak.result.failure.getClass().getSimpleName()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
-->
<resources>

<string name="leak_canary_class_has_leaked">%s has leaked</string>
<string name="leak_canary_class_has_leaked">%1$s has leaked %2$s</string>
<string name="leak_canary_class_leak_ignored">Ignored %s leak</string>
<string name="leak_canary_analysis_failed">Leak analysis failed</string>
<string name="leak_canary_leak_list_title">Leaks in %s</string>
Expand Down

0 comments on commit c355a6d

Please sign in to comment.