Skip to content

Commit

Permalink
Inline collaborators and add support for proper hprof handling on fai…
Browse files Browse the repository at this point in the history
…lures
  • Loading branch information
pyricau committed Jun 4, 2024
1 parent 5738869 commit 9a34652
Show file tree
Hide file tree
Showing 22 changed files with 487 additions and 350 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ fun HeapDiff.Companion.repeatingAndroidInProcessScenario(
.withDetectorWarmup(objectGrowthDetector, androidHeap = true),
heapDumpStorageStrategy: HeapDumpStorageStrategy = HeapDumpStorageStrategy.DeleteOnHeapDumpClose(),
): RepeatingScenarioObjectGrowthDetector {
return repeatingDumpingTestScenario(
return DumpingRepeatingScenarioObjectGrowthDetector(
objectGrowthDetector = objectGrowthDetector,
heapDumpDirectoryProvider = heapDumpDirectoryProvider,
heapDumpFileProvider = TestHeapDumpFileProvider(heapDumpDirectoryProvider),
heapDumper = heapDumper,
heapDumpStorageStrategy = heapDumpStorageStrategy,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ fun HeapDiff.Companion.repeatingUiAutomatorScenario(
UiAutomatorShellFileDeleter.deleteFileUsingShell(heapDumpFile)
},
): RepeatingScenarioObjectGrowthDetector {
return repeatingDumpingTestScenario(
return DumpingRepeatingScenarioObjectGrowthDetector(
objectGrowthDetector = objectGrowthDetector,
heapDumpDirectoryProvider = heapDumpDirectoryProvider,
heapDumpFileProvider = TestHeapDumpFileProvider(heapDumpDirectoryProvider),
heapDumper = heapDumper,
heapDumpStorageStrategy = heapDumpStorageStrategy,
)
Expand Down
57 changes: 49 additions & 8 deletions leakcanary/leakcanary-core/api/leakcanary-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,9 @@ public final class leakcanary/DatetimeFormattedHeapDumpFileProvider : leakcanary
public final class leakcanary/DatetimeFormattedHeapDumpFileProvider$Companion {
}

public final class leakcanary/DumpingHeapGraphProvider : shark/HeapGraphProvider {
public fun <init> (Lleakcanary/HeapDumpFileProvider;Lleakcanary/HeapDumper;Lleakcanary/DumpingHeapGraphProvider$HeapDumpClosedListener;)V
public synthetic fun <init> (Lleakcanary/HeapDumpFileProvider;Lleakcanary/HeapDumper;Lleakcanary/DumpingHeapGraphProvider$HeapDumpClosedListener;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun openHeapGraph ()Lshark/CloseableHeapGraph;
}

public abstract interface class leakcanary/DumpingHeapGraphProvider$HeapDumpClosedListener {
public abstract fun onHeapDumpClosed (Ljava/io/File;)V
public final class leakcanary/DumpingRepeatingScenarioObjectGrowthDetector : shark/RepeatingScenarioObjectGrowthDetector {
public fun <init> (Lshark/ObjectGrowthDetector;Lleakcanary/HeapDumpFileProvider;Lleakcanary/HeapDumper;Lleakcanary/HeapDumpStorageStrategy;)V
public fun findRepeatedlyGrowingObjects (IILkotlin/jvm/functions/Function0;)Lshark/HeapDiff;
}

public abstract interface class leakcanary/HeapDumpDirectoryProvider {
Expand All @@ -27,6 +22,52 @@ public abstract interface class leakcanary/HeapDumpFileProvider {
public abstract fun newHeapDumpFile ()Ljava/io/File;
}

public abstract interface class leakcanary/HeapDumpStorageStrategy {
public abstract fun onHeapDiffResult (Ljava/lang/Object;)V
public abstract fun onHeapDumpClosed (Ljava/io/File;)V
public abstract fun onHeapDumped (Ljava/io/File;)V
}

public final class leakcanary/HeapDumpStorageStrategy$DefaultImpls {
public static fun onHeapDiffResult (Lleakcanary/HeapDumpStorageStrategy;Ljava/lang/Object;)V
public static fun onHeapDumpClosed (Lleakcanary/HeapDumpStorageStrategy;Ljava/io/File;)V
public static fun onHeapDumped (Lleakcanary/HeapDumpStorageStrategy;Ljava/io/File;)V
}

public final class leakcanary/HeapDumpStorageStrategy$DeleteOnHeapDumpClose : leakcanary/HeapDumpStorageStrategy {
public fun <init> ()V
public fun <init> (Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun onHeapDiffResult (Ljava/lang/Object;)V
public fun onHeapDumpClosed (Ljava/io/File;)V
public fun onHeapDumped (Ljava/io/File;)V
}

public final class leakcanary/HeapDumpStorageStrategy$KeepHeapDumps : leakcanary/HeapDumpStorageStrategy {
public static final field INSTANCE Lleakcanary/HeapDumpStorageStrategy$KeepHeapDumps;
public fun onHeapDiffResult (Ljava/lang/Object;)V
public fun onHeapDumpClosed (Ljava/io/File;)V
public fun onHeapDumped (Ljava/io/File;)V
}

public final class leakcanary/HeapDumpStorageStrategy$KeepHeapDumpsOnObjectsGrowing : leakcanary/HeapDumpStorageStrategy {
public fun <init> ()V
public fun <init> (Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun onHeapDiffResult (Ljava/lang/Object;)V
public fun onHeapDumpClosed (Ljava/io/File;)V
public fun onHeapDumped (Ljava/io/File;)V
}

public final class leakcanary/HeapDumpStorageStrategy$KeepZippedHeapDumpsOnObjectsGrowing : leakcanary/HeapDumpStorageStrategy {
public fun <init> ()V
public fun <init> (Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun onHeapDiffResult (Ljava/lang/Object;)V
public fun onHeapDumpClosed (Ljava/io/File;)V
public fun onHeapDumped (Ljava/io/File;)V
}

public abstract interface class leakcanary/HeapDumper {
public static final field Companion Lleakcanary/HeapDumper$Companion;
public abstract fun dumpHeap (Ljava/io/File;)V
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package leakcanary

import java.io.File
import shark.HeapDiff
import shark.HeapTraversalInput
import shark.HeapTraversalOutput
import shark.HprofHeapGraph.Companion.openHeapGraph
import shark.InitialState
import shark.ObjectGrowthDetector
import shark.RepeatingScenarioObjectGrowthDetector
import shark.SharkLog

/**
* A [RepeatingScenarioObjectGrowthDetector] suitable for junit based automated tests that
* can dump the heap.
*
* @see [RepeatingScenarioObjectGrowthDetector.findRepeatedlyGrowingObjects]
*/
class DumpingRepeatingScenarioObjectGrowthDetector(
private val objectGrowthDetector: ObjectGrowthDetector,
private val heapDumpFileProvider: HeapDumpFileProvider,
private val heapDumper: HeapDumper,
private val heapDumpStorageStrategy: HeapDumpStorageStrategy,
) : RepeatingScenarioObjectGrowthDetector {

override fun findRepeatedlyGrowingObjects(
maxHeapDumps: Int,
scenarioLoopsPerDump: Int,
roundTripScenario: () -> Unit
): HeapDiff {
val heapDiff = try {
findRepeatedlyGrowingObjectsInner(scenarioLoopsPerDump, maxHeapDumps, roundTripScenario)
} catch (exception: Throwable) {
heapDumpStorageStrategy.onHeapDiffResult(Result.failure(exception))
throw exception
}
heapDumpStorageStrategy.onHeapDiffResult(Result.success(heapDiff))
return heapDiff
}

private fun findRepeatedlyGrowingObjectsInner(
scenarioLoopsPerDump: Int,
maxHeapDumps: Int,
roundTripScenario: () -> Unit
): HeapDiff {
var lastTraversalOutput: HeapTraversalInput = InitialState(scenarioLoopsPerDump)
for (i in 1..maxHeapDumps) {
repeat(scenarioLoopsPerDump) {
roundTripScenario()
}
val heapDumpFile = heapDumpFileProvider.newHeapDumpFile()
heapDumper.dumpHeap(heapDumpFile)
check(heapDumpFile.exists()) {
"Expected file to exist after heap dump: ${heapDumpFile.absolutePath}"
}
heapDumpStorageStrategy.onHeapDumped(heapDumpFile)
lastTraversalOutput = try {
heapDumpFile.findGrowingObjects(lastTraversalOutput)
} finally {
heapDumpStorageStrategy.onHeapDumpClosed(heapDumpFile)
}
if (lastTraversalOutput is HeapDiff) {
if (!lastTraversalOutput.isGrowing) {
return lastTraversalOutput
} else if (i < maxHeapDumps) {
// Log unless it's the last diff, which typically gets printed by calling code.
SharkLog.d {
"After ${lastTraversalOutput.traversalCount} heap dumps with $scenarioLoopsPerDump scenario iterations before each, " +
"${lastTraversalOutput.growingObjects.size} growing nodes:\n" + lastTraversalOutput.growingObjects
}
}
}
}
check(lastTraversalOutput is HeapDiff) {
"Final output should be a HeapGrowth, traversalCount ${lastTraversalOutput.traversalCount - 1} " +
"should be >= 2. Output: $lastTraversalOutput"
}
return lastTraversalOutput
}

private fun File.findGrowingObjects(
previousTraversal: HeapTraversalInput
): HeapTraversalOutput {
return openHeapGraph().use { heapGraph ->
objectGrowthDetector.findGrowingObjects(
heapGraph = heapGraph,
previousTraversal = previousTraversal,
)
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import java.io.File

fun interface HeapDumpDirectoryProvider {
/**
* Expected to be only once per [HeapDumpFileProvider] implementation instance.
* Expected to be called only once per [HeapDumpFileProvider] implementation instance.
*/
fun heapDumpDirectory(): File
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import java.io.File
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import shark.HeapDiff
import shark.RepeatingHeapGraphObjectGrowthDetector
import shark.SharkLog

interface HeapDumpStorageStrategy : DumpingHeapGraphProvider.HeapDumpClosedListener,
RepeatingHeapGraphObjectGrowthDetector.CompletionListener {
interface HeapDumpStorageStrategy {

/**
* Deletes heap dumps as soon as we're done traversing them. This is the most disk space
Expand All @@ -17,6 +16,7 @@ interface HeapDumpStorageStrategy : DumpingHeapGraphProvider.HeapDumpClosedListe
private val deleteFile: (File) -> Unit = { it.delete() }
) : HeapDumpStorageStrategy {
override fun onHeapDumpClosed(heapDumpFile: File) {
SharkLog.d { "DeleteOnHeapDumpClose: deleting closed heap dump ${heapDumpFile.absolutePath}" }
deleteFile(heapDumpFile)
}
}
Expand All @@ -37,19 +37,34 @@ interface HeapDumpStorageStrategy : DumpingHeapGraphProvider.HeapDumpClosedListe
) : HeapDumpStorageStrategy {
// This assumes the detector instance is always used from the same thread, which seems like a
// safe enough assumption for tests.
private val closedHeapDumpFiles = mutableListOf<File>()
private val heapDumpFiles = mutableListOf<File>()

override fun onHeapDumpClosed(heapDumpFile: File) {
closedHeapDumpFiles += heapDumpFile
override fun onHeapDumped(heapDumpFile: File) {
heapDumpFiles += heapDumpFile
}

override fun onObjectGrowthDetectionComplete(result: HeapDiff) {
if (!result.isGrowing) {
closedHeapDumpFiles.forEach {
override fun onHeapDiffResult(result: Result<HeapDiff>) {
if (result.isSuccess && !result.getOrThrow().isGrowing) {
SharkLog.d {
"KeepHeapDumpsOnObjectsGrowing: not growing, deleting heap dumps:" +
heapDumpFiles.joinToString(
prefix = "\n",
separator = "\n"
) { it.absolutePath }
}
heapDumpFiles.forEach {
deleteFile(it)
}
} else {
SharkLog.d {
"KeepHeapDumpsOnObjectsGrowing: failure or growing, keeping heap dumps:" +
heapDumpFiles.joinToString(
prefix = "\n",
separator = "\n"
) { it.absolutePath }
}
}
closedHeapDumpFiles.clear()
heapDumpFiles.clear()
}
}

Expand All @@ -66,22 +81,37 @@ interface HeapDumpStorageStrategy : DumpingHeapGraphProvider.HeapDumpClosedListe
) : HeapDumpStorageStrategy {
// This assumes the detector instance is always used from the same thread, which seems like a
// safe enough assumption for tests.
private val closedHeapDumpFiles = mutableListOf<File>()
private val heapDumpFiles = mutableListOf<File>()

override fun onHeapDumpClosed(heapDumpFile: File) {
closedHeapDumpFiles += heapDumpFile
override fun onHeapDumped(heapDumpFile: File) {
heapDumpFiles += heapDumpFile
}

override fun onObjectGrowthDetectionComplete(result: HeapDiff) {
if (result.isGrowing) {
closedHeapDumpFiles.forEach {
override fun onHeapDiffResult(result: Result<HeapDiff>) {
if (result.isFailure || result.getOrThrow().isGrowing) {
SharkLog.d {
"KeepZippedHeapDumpsOnObjectsGrowing: failure or growing, zipping heap dumps:" +
heapDumpFiles.joinToString(
prefix = "\n",
separator = "\n"
) { it.absolutePath }
}
heapDumpFiles.forEach {
it.zipFile()
}
} else {
SharkLog.d {
"KeepZippedHeapDumpsOnObjectsGrowing: not growing, deleting heap dumps:" +
heapDumpFiles.joinToString(
prefix = "\n",
separator = "\n"
) { it.absolutePath }
}
}
closedHeapDumpFiles.forEach {
heapDumpFiles.forEach {
deleteFile(it)
}
closedHeapDumpFiles.clear()
heapDumpFiles.clear()
}

private fun File.zipFile(destination: File = File(parent, "$nameWithoutExtension.zip")): File {
Expand All @@ -101,7 +131,9 @@ interface HeapDumpStorageStrategy : DumpingHeapGraphProvider.HeapDumpClosedListe
}
}

override fun onHeapDumpClosed(heapDumpFile: File) = Unit
fun onHeapDumpClosed(heapDumpFile: File) = Unit

fun onHeapDumped(heapDumpFile: File) = Unit

override fun onObjectGrowthDetectionComplete(result: HeapDiff) = Unit
fun onHeapDiffResult(result: Result<HeapDiff>) = Unit
}
Loading

0 comments on commit 9a34652

Please sign in to comment.