diff --git a/shark/shark-heap-growth/api/shark-heap-growth.api b/shark/shark-heap-growth/api/shark-heap-growth.api index 160969d803..00e0937b8a 100644 --- a/shark/shark-heap-growth/api/shark-heap-growth.api +++ b/shark/shark-heap-growth/api/shark-heap-growth.api @@ -1,7 +1,5 @@ public final class shark/DiffingHeapGrowthDetector { - public fun ()V public fun (Lshark/ReferenceReader$Factory;Lshark/GcRootProvider;)V - public synthetic fun (Lshark/ReferenceReader$Factory;Lshark/GcRootProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun detectHeapGrowth (Lshark/DiffingHeapGrowthDetector$HeapDumpAfterLoopingScenario;Lshark/InputHeapTraversal;)Lshark/HeapTraversal; public static synthetic fun detectHeapGrowth$default (Lshark/DiffingHeapGrowthDetector;Lshark/DiffingHeapGrowthDetector$HeapDumpAfterLoopingScenario;Lshark/InputHeapTraversal;ILjava/lang/Object;)Lshark/HeapTraversal; } @@ -21,7 +19,6 @@ public final class shark/DiffingHeapGrowthDetector$HeapDumpAfterLoopingScenario public abstract interface class shark/HeapTraversal : shark/InputHeapTraversal { public static final field Companion Lshark/HeapTraversal$Companion; - public abstract fun getGrowing ()Z public abstract fun getShortestPathTree ()Lshark/ShortestPathNode; } @@ -31,7 +28,6 @@ public final class shark/HeapTraversal$Companion { public final class shark/HeapTraversalWithDiff : shark/HeapTraversal { public fun (Lshark/ShortestPathNode;Ljava/util/List;)V - public fun getGrowing ()Z public final fun getGrowingNodes ()Ljava/util/List; public fun getShortestPathTree ()Lshark/ShortestPathNode; public fun toString ()Ljava/lang/String; @@ -39,22 +35,20 @@ public final class shark/HeapTraversalWithDiff : shark/HeapTraversal { public final class shark/InitialHeapTraversal : shark/HeapTraversal { public fun (Lshark/ShortestPathNode;)V - public fun getGrowing ()Z public fun getShortestPathTree ()Lshark/ShortestPathNode; } public abstract interface class shark/InputHeapTraversal { } -public final class shark/LoopingHeapDumper { - public fun (ILshark/HeapGraphProvider;I)V - public synthetic fun (ILshark/HeapGraphProvider;IILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun dumpHeapRepeated (Lkotlin/jvm/functions/Function0;)Lkotlin/sequences/Sequence; +public final class shark/LiveHeapGrowthDetector { + public fun (ILshark/HeapGraphProvider;ILshark/LoopingHeapGrowthDetector;)V + public final fun detectRepeatedHeapGrowth (Lkotlin/jvm/functions/Function0;)Lshark/HeapTraversalWithDiff; } public final class shark/LoopingHeapGrowthDetector { public fun (Lshark/DiffingHeapGrowthDetector;)V - public final fun repeatDiffsWhileGrowing (Lkotlin/sequences/Sequence;)Lshark/HeapTraversalWithDiff; + public final fun detectRepeatedHeapGrowth (Lkotlin/sequences/Sequence;)Lshark/HeapTraversalWithDiff; } public final class shark/NoHeapTraversalYet : shark/InputHeapTraversal { diff --git a/shark/shark-heap-growth/src/main/java/shark/DiffingHeapGrowthDetector.kt b/shark/shark-heap-growth/src/main/java/shark/DiffingHeapGrowthDetector.kt index a8834af392..5b35ac0d08 100644 --- a/shark/shark-heap-growth/src/main/java/shark/DiffingHeapGrowthDetector.kt +++ b/shark/shark-heap-growth/src/main/java/shark/DiffingHeapGrowthDetector.kt @@ -8,16 +8,12 @@ import shark.HeapObject.HeapClass import shark.HeapObject.HeapInstance import shark.HeapObject.HeapObjectArray import shark.HeapObject.HeapPrimitiveArray -import shark.internal.hppc.LongScatterSet import shark.ReferenceLocationType.ARRAY_ENTRY +import shark.internal.hppc.LongScatterSet class DiffingHeapGrowthDetector( - private val referenceReaderFactory: ReferenceReader.Factory = AndroidReferenceReaderFactory( - JdkReferenceMatchers.defaults - ), - private val gcRootProvider: GcRootProvider = MatchingGcRootProvider( - JdkReferenceMatchers.defaults - ), + private val referenceReaderFactory: ReferenceReader.Factory, + private val gcRootProvider: GcRootProvider, ) { data class HeapDumpAfterLoopingScenario( diff --git a/shark/shark-heap-growth/src/main/java/shark/HeapTraversal.kt b/shark/shark-heap-growth/src/main/java/shark/HeapTraversal.kt index 94dab166d3..a0d9f3e369 100644 --- a/shark/shark-heap-growth/src/main/java/shark/HeapTraversal.kt +++ b/shark/shark-heap-growth/src/main/java/shark/HeapTraversal.kt @@ -15,12 +15,6 @@ sealed interface HeapTraversal : InputHeapTraversal { */ val shortestPathTree: ShortestPathNode - /** - * Whether this traversal yielded a [shortestPathTree] that grew compared to the previous - * traversal. - */ - val growing: Boolean - companion object { /** @@ -40,9 +34,7 @@ sealed interface HeapTraversal : InputHeapTraversal { class InitialHeapTraversal constructor( override val shortestPathTree: ShortestPathNode -) : HeapTraversal { - override val growing get() = true -} +) : HeapTraversal class HeapTraversalWithDiff( override val shortestPathTree: ShortestPathNode, @@ -52,8 +44,7 @@ class HeapTraversalWithDiff( */ val growingNodes: List ) : HeapTraversal { - override val growing get() = growingNodes.isNotEmpty() override fun toString(): String { - return "HeapTraversalWithDiff(growing=$growing), growingNodes=\n$growingNodes" + return "HeapTraversalWithDiff(growingNodes=\n$growingNodes" } } diff --git a/shark/shark-heap-growth/src/main/java/shark/LoopingHeapDumper.kt b/shark/shark-heap-growth/src/main/java/shark/LiveHeapGrowthDetector.kt similarity index 66% rename from shark/shark-heap-growth/src/main/java/shark/LoopingHeapDumper.kt rename to shark/shark-heap-growth/src/main/java/shark/LiveHeapGrowthDetector.kt index aceb9e8454..81f830ecff 100644 --- a/shark/shark-heap-growth/src/main/java/shark/LoopingHeapDumper.kt +++ b/shark/shark-heap-growth/src/main/java/shark/LiveHeapGrowthDetector.kt @@ -2,10 +2,11 @@ package shark import shark.DiffingHeapGrowthDetector.HeapDumpAfterLoopingScenario -class LoopingHeapDumper( +class LiveHeapGrowthDetector( private val maxHeapDumps: Int, private val heapGraphProvider: HeapGraphProvider, - private val scenarioLoopsPerDump: Int = 1, + private val scenarioLoopsPerDump: Int, + private val detector: LoopingHeapGrowthDetector ) { init { @@ -17,8 +18,12 @@ class LoopingHeapDumper( } } - // todo name repeat? - fun dumpHeapRepeated( + fun detectRepeatedHeapGrowth(repeatedScenario: () -> Unit): HeapTraversalWithDiff { + val heapDumps = dumpHeapRepeated(repeatedScenario) + return detector.detectRepeatedHeapGrowth(heapDumps) + } + + private fun dumpHeapRepeated( repeatedScenario: () -> Unit, ): Sequence { diff --git a/shark/shark-heap-growth/src/main/java/shark/LoopingHeapGrowthDetector.kt b/shark/shark-heap-growth/src/main/java/shark/LoopingHeapGrowthDetector.kt index ca9e9c48e7..12df6f4540 100644 --- a/shark/shark-heap-growth/src/main/java/shark/LoopingHeapGrowthDetector.kt +++ b/shark/shark-heap-growth/src/main/java/shark/LoopingHeapGrowthDetector.kt @@ -5,7 +5,7 @@ import shark.DiffingHeapGrowthDetector.HeapDumpAfterLoopingScenario class LoopingHeapGrowthDetector( private val heapGrowthDetector: DiffingHeapGrowthDetector ) { - fun repeatDiffsWhileGrowing( + fun detectRepeatedHeapGrowth( heapDumps: Sequence ): HeapTraversalWithDiff { var i = 1 @@ -18,7 +18,7 @@ class LoopingHeapGrowthDetector( SharkLog.d { "After $iterationCount (+ ${heapDump.scenarioLoopCount}) iterations and heap dump $i: ${diffResult.growingNodes.size} growing nodes" } - if (!diffResult.growing) { + if (diffResult.growingNodes.isEmpty()) { return diffResult } } diff --git a/shark/shark-heap-growth/src/test/java/shark/HeapGrowthDetectorFakeDumpTest.kt b/shark/shark-heap-growth/src/test/java/shark/HeapGrowthDetectorFakeDumpTest.kt index ed66bd2455..dfdc542231 100644 --- a/shark/shark-heap-growth/src/test/java/shark/HeapGrowthDetectorFakeDumpTest.kt +++ b/shark/shark-heap-growth/src/test/java/shark/HeapGrowthDetectorFakeDumpTest.kt @@ -18,7 +18,6 @@ class HeapGrowthDetectorFakeDumpTest { ) assertThat(heapTraversal).isInstanceOf(InitialHeapTraversal::class.java) - assertThat(heapTraversal.growing).isTrue } @Test @@ -51,7 +50,7 @@ class HeapGrowthDetectorFakeDumpTest { val traversal = detector.detectHeapGrowth(dumps) - assertThat(traversal.growing).isFalse + assertThat(traversal.growingNodes).isEmpty() } @Test @@ -69,7 +68,7 @@ class HeapGrowthDetectorFakeDumpTest { val traversal = detector.detectHeapGrowth(dumps) - assertThat(traversal.growing).isFalse + assertThat(traversal.growingNodes).isEmpty() } @Test @@ -86,9 +85,6 @@ class HeapGrowthDetectorFakeDumpTest { val traversal = detector.detectHeapGrowth(dumps) - assertThat(traversal.growing).isTrue - - traversal as HeapTraversalWithDiff assertThat(traversal.growingNodes).hasSize(1) } @@ -106,7 +102,7 @@ class HeapGrowthDetectorFakeDumpTest { val traversal = detector.detectHeapGrowth(dumps) - assertThat(traversal.growing).isFalse + assertThat(traversal.growingNodes).isEmpty() } @Test @@ -126,7 +122,6 @@ class HeapGrowthDetectorFakeDumpTest { val traversal = detector.detectHeapGrowth(dumps) - traversal as HeapTraversalWithDiff val growingNode = traversal.growingNodes.first() assertThat(growingNode.selfObjectCount).isEqualTo(1) @@ -135,12 +130,12 @@ class HeapGrowthDetectorFakeDumpTest { assertThat(growingNode.children).hasSize(1) } - private fun DiffingHeapGrowthDetector.detectHeapGrowth(heapDumps: List): HeapTraversal { + private fun DiffingHeapGrowthDetector.detectHeapGrowth(heapDumps: List): HeapTraversalWithDiff { return heapDumps.fold( initial = NoHeapTraversalYet ) { previous, dump -> detectHeapGrowth(dump, previous) - } as HeapTraversal + } as HeapTraversalWithDiff } private fun HprofWriterHelper.classWithStringsInStaticField(vararg strings: String) { diff --git a/shark/shark-heap-growth/src/test/java/shark/HeapGrowthDetectorJvmTest.kt b/shark/shark-heap-growth/src/test/java/shark/HeapGrowthDetectorJvmTest.kt index 0904a01135..1b56b95507 100644 --- a/shark/shark-heap-growth/src/test/java/shark/HeapGrowthDetectorJvmTest.kt +++ b/shark/shark-heap-growth/src/test/java/shark/HeapGrowthDetectorJvmTest.kt @@ -5,7 +5,6 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder -import shark.DiffingHeapGrowthDetector.HeapDumpAfterLoopingScenario import shark.HprofHeapGraph.Companion.openHeapGraph class HeapGrowthDetectorJvmTest { @@ -33,75 +32,63 @@ class HeapGrowthDetectorJvmTest { @Test fun `empty scenario leads to no heap growth`() { - val detector = simpleDetector() - - val heapDumper = LoopingHeapDumper( - maxHeapDumps = 10, - heapGraphProvider = this::dumpHeapGraph, - scenarioLoopsPerDump = 1 - ) + val detector = simpleDetector().live() val emptyScenario = {} - val dumps = heapDumper.dumpHeapRepeated(emptyScenario) - - val heapTraversal = detector.repeatDiffsWhileGrowing(dumps) + val heapTraversal = detector.detectRepeatedHeapGrowth(emptyScenario) - assertThat(heapTraversal.growing).isFalse + assertThat(heapTraversal.growingNodes).isEmpty() } @Test fun `leaky increase leads to heap growth`() { - val detector = simpleDetector() + val detector = simpleDetector().live() - val heapTraversal = detector.repeatDiffsWhileGrowing(sequenceOfHeapDumps { + val heapTraversal = detector.detectRepeatedHeapGrowth { leakies += Leaky() - }) + } - assertThat(heapTraversal.growing).isTrue + assertThat(heapTraversal.growingNodes).isNotEmpty } @Test fun `string leak increase leads to heap growth`() { - val detector = simpleDetector() + val detector = simpleDetector().live() var index = 0 - val heapTraversal = detector.repeatDiffsWhileGrowing(sequenceOfHeapDumps { + val heapTraversal = detector.detectRepeatedHeapGrowth { stringLeaks += "Yo ${++index}" - }) + } - assertThat(heapTraversal.growing).isTrue + assertThat(heapTraversal.growingNodes).isNotEmpty } @Test fun `leak increase that ends leads to no heap growth`() { - val detector = simpleDetector() - val maxHeapDumps = 10 val stopLeakingIndex = maxHeapDumps / 2 + val detector = simpleDetector().live(maxHeapDumps = maxHeapDumps) var index = 0 - val heapTraversal = - detector.repeatDiffsWhileGrowing(sequenceOfHeapDumps(maxHeapDumps = maxHeapDumps) { - if (++index < stopLeakingIndex) { - leakies += Leaky() - } - }) - - assertThat(heapTraversal.growing).isFalse + val heapTraversal = detector.detectRepeatedHeapGrowth { + if (++index < stopLeakingIndex) { + leakies += Leaky() + } + } + + assertThat(heapTraversal.growingNodes).isEmpty() } @Test fun `multiple leaky scenarios per dump leads to heap growth`() { - val detector = simpleDetector() - val scenarioLoopsPerDump = 5 + val detector = simpleDetector().live(scenarioLoopsPerDump = scenarioLoopsPerDump) - val heapTraversal = detector.repeatDiffsWhileGrowing(sequenceOfHeapDumps(scenarioLoopsPerDump) { + val heapTraversal = detector.detectRepeatedHeapGrowth { leakies += Leaky() - }) + } - assertThat(heapTraversal.growing).isTrue assertThat(heapTraversal.growingNodes).hasSize(1) val growingNode = heapTraversal.growingNodes.first() @@ -110,25 +97,25 @@ class HeapGrowthDetectorJvmTest { @Test fun `custom leaky linked list leads to heap growth`() { - val detector = simpleDetector() + val detector = simpleDetector().live() - val heapTraversal = detector.repeatDiffsWhileGrowing(sequenceOfHeapDumps { + val heapTraversal = detector.detectRepeatedHeapGrowth { customLeakyLinkedList = CustomLinkedList(customLeakyLinkedList) - }) + } - assertThat(heapTraversal.growing).isTrue + assertThat(heapTraversal.growingNodes).isNotEmpty } @Test fun `custom leaky linked list reports descendant to root as flattened collection`() { - val detector = simpleDetector() + val detector = simpleDetector().live() - val heapTraversal = detector.repeatDiffsWhileGrowing(sequenceOfHeapDumps { + val heapTraversal = detector.detectRepeatedHeapGrowth { customLeakyLinkedList = CustomLinkedList(customLeakyLinkedList) customLeakyLinkedList = CustomLinkedList(customLeakyLinkedList) customLeakyLinkedList = CustomLinkedList(customLeakyLinkedList) customLeakyLinkedList = CustomLinkedList(customLeakyLinkedList) - }) + } assertThat(heapTraversal.growingNodes).hasSize(1) @@ -139,11 +126,11 @@ class HeapGrowthDetectorJvmTest { @Test fun `growth along shared sub paths reported as single growth of shortest path`() { - val detector = simpleDetector() + val detector = simpleDetector().live() - val heapTraversal = detector.repeatDiffsWhileGrowing(sequenceOfHeapDumps { + val heapTraversal = detector.detectRepeatedHeapGrowth { multiLeakies += MultiLeaky() - }) + } assertThat(heapTraversal.growingNodes).hasSize(1) @@ -153,12 +140,12 @@ class HeapGrowthDetectorJvmTest { @Test fun `OpenJdk HashMap virtualized as array`() { - val detector = openJdkDetector() + val detector = openJdkDetector().live() var index = 0 - val heapTraversal = detector.repeatDiffsWhileGrowing(sequenceOfHeapDumps { + val heapTraversal = detector.detectRepeatedHeapGrowth { leakyHashMap["key${++index}"] = Leaky() - }) + } val growingNode = heapTraversal.growingNodes.first() assertThat(growingNode.nodeAndEdgeName).contains("leakyHashMap") @@ -166,40 +153,31 @@ class HeapGrowthDetectorJvmTest { @Test fun `OpenJdk ArrayList virtualized as array`() { - val detector = openJdkDetector() + val detector = openJdkDetector().live() - val heapTraversal = detector.repeatDiffsWhileGrowing(sequenceOfHeapDumps { + val heapTraversal = detector.detectRepeatedHeapGrowth { leakies += Leaky() - }) + } val growingNode = heapTraversal.growingNodes.first() assertThat(growingNode.nodeAndEdgeName).contains("leakies") } - private fun sequenceOfHeapDumps( + private fun DiffingHeapGrowthDetector.live( scenarioLoopsPerDump: Int = 1, - maxHeapDumps: Int = 5, - loopingScenario: () -> Unit, - ): Sequence { - val heapDumper = LoopingHeapDumper( - maxHeapDumps = maxHeapDumps, - heapGraphProvider = this::dumpHeapGraph, - scenarioLoopsPerDump = scenarioLoopsPerDump - ) - return heapDumper.dumpHeapRepeated(loopingScenario) + maxHeapDumps: Int = 5 + ): LiveHeapGrowthDetector { + return LiveHeapGrowthDetector(maxHeapDumps, ::dumpHeapGraph, scenarioLoopsPerDump, LoopingHeapGrowthDetector(this)) } - private fun simpleDetector(): LoopingHeapGrowthDetector { + private fun simpleDetector(): DiffingHeapGrowthDetector { val referenceMatchers = JdkReferenceMatchers.defaults + HeapTraversal.ignoredReferences - val referenceReaderFactory = ActualMatchingReferenceReaderFactory(referenceMatchers) val gcRootProvider = MatchingGcRootProvider(referenceMatchers) - return LoopingHeapGrowthDetector( - DiffingHeapGrowthDetector(referenceReaderFactory, gcRootProvider) - ) + return DiffingHeapGrowthDetector(referenceReaderFactory, gcRootProvider) } - private fun openJdkDetector(): LoopingHeapGrowthDetector { + private fun openJdkDetector(): DiffingHeapGrowthDetector { val referenceMatchers = JdkReferenceMatchers.defaults + HeapTraversal.ignoredReferences val referenceReaderFactory = VirtualizingMatchingReferenceReaderFactory( @@ -213,9 +191,7 @@ class HeapGrowthDetectorJvmTest { ) val gcRootProvider = MatchingGcRootProvider(referenceMatchers) - return LoopingHeapGrowthDetector( - DiffingHeapGrowthDetector(referenceReaderFactory, gcRootProvider) - ) + return DiffingHeapGrowthDetector(referenceReaderFactory, gcRootProvider) } private fun dumpHeapGraph(): CloseableHeapGraph { diff --git a/shark/shark/src/test/java/shark/DiffingHeapGrowthDetectorTest.kt b/shark/shark/src/test/java/shark/DiffingHeapGrowthDetectorTest.kt deleted file mode 100644 index 008ad90499..0000000000 --- a/shark/shark/src/test/java/shark/DiffingHeapGrowthDetectorTest.kt +++ /dev/null @@ -1,7 +0,0 @@ -package shark - -class DiffingHeapGrowthDetectorTest { - - - -}