blob: 96641f42d396b4274c065df23fbcc21d0964e384 [file] [log] [blame]
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,
)
}
}
}