Skip to content

Commit 27b6f0b

Browse files
authored
Merge pull request #2676 from square/py/improve_heap_diff
Iterating on heap growth based on friction / feedback from trying to integrate it inside Square.
2 parents 023890b + cfc8360 commit 27b6f0b

File tree

32 files changed

+1493
-726
lines changed

32 files changed

+1493
-726
lines changed

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref =
4141
# and they'll automatically resolve to higher version without having to necessarily resort to a
4242
# resolution strategy.
4343
androidX-fragment = { module = "androidx.fragment:fragment", version = "1.0.0" }
44+
androidX-multidex = { module = "androidx.multidex:multidex", version = "2.0.1" }
4445
# Exposed transitively, avoid increasing
4546
androidX-startup = { module = "androidx.startup:startup-runtime", version = "1.0.0" }
4647
androidX-test-core = { module = "androidx.test:core", version = "1.4.0" }
@@ -55,6 +56,7 @@ androidX-test-junitKtx = { module = "androidx.test.ext:junit-ktx", version.ref =
5556
androidX-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version = "2.2.0" }
5657
androidX-work-runtime = { module = "androidx.work:work-runtime", version.ref = "workManager" }
5758
androidX-work-multiprocess = { module = "androidx.work:work-multiprocess", version.ref = "workManager" }
59+
androidX-collections = { module = "androidx.collection:collection-ktx", version = "1.4.0" }
5860

5961
androidSupport = { module = "com.android.support:support-v4", version = "28.0.0" }
6062
assertjCore = { module = "org.assertj:assertj-core", version = "3.9.1" }

leakcanary/leakcanary-android-instrumentation/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ dependencies {
1515
androidTestImplementation projects.objectWatcher.objectWatcherAndroid
1616
// Plumber auto installer for running tests
1717
androidTestImplementation projects.plumber.plumberAndroid
18+
androidTestImplementation libs.androidX.multidex
1819
androidTestImplementation libs.androidX.test.core
1920
androidTestImplementation libs.androidX.test.espresso
2021
androidTestImplementation libs.androidX.test.rules
@@ -28,6 +29,7 @@ android {
2829
targetSdk versions.compileSdk
2930
minSdk versions.minSdk
3031
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
32+
multiDexEnabled true
3133
}
3234
buildFeatures.buildConfig = false
3335
namespace 'com.squareup.leakcanary.instrumentation'

leakcanary/leakcanary-android-instrumentation/src/androidTest/AndroidManifest.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
-->
1717
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
1818

19-
<application>
19+
<application
20+
android:name="androidx.multidex.MultiDexApplication"
21+
>
2022
<activity android:name="leakcanary.TestActivity"/>
2123
</application>
2224
</manifest>

leakcanary/leakcanary-android-test/src/main/java/leakcanary/RepeatingAndroidInProcessScenario.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package leakcanary
22

3-
import shark.RepeatingScenarioObjectGrowthDetector
43
import shark.HeapGraphProvider
54
import shark.ObjectGrowthDetector
5+
import shark.RepeatingScenarioObjectGrowthDetector
66
import shark.repeatingScenario
77

88
/**
@@ -23,7 +23,8 @@ fun ObjectGrowthDetector.repeatingAndroidInProcessScenario(
2323
return repeatingScenario(
2424
heapGraphProvider = HeapGraphProvider.dumpingAndDeleting(
2525
heapDumper = HeapDumper.forAndroidInProcess()
26-
.withGc(gcTrigger = GcTrigger.inProcess()),
26+
.withGc(gcTrigger = GcTrigger.inProcess())
27+
.withDetectorWarmup(this),
2728
heapDumpFileProvider = HeapDumpFileProvider.tempFile()
2829
),
2930
maxHeapDumps = maxHeapDumps,

leakcanary/leakcanary-core/api/leakcanary-core.api

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,19 @@ public final class leakcanary/HeapDumperKt {
4949
public static synthetic fun withGc$default (Lleakcanary/HeapDumper;Lleakcanary/GcTrigger;ILjava/lang/Object;)Lleakcanary/HeapDumper;
5050
}
5151

52+
public final class leakcanary/ObjectGrowthWarmupHeapDumper : leakcanary/HeapDumper {
53+
public static final field Companion Lleakcanary/ObjectGrowthWarmupHeapDumper$Companion;
54+
public fun <init> (Lshark/ObjectGrowthDetector;Lleakcanary/HeapDumper;)V
55+
public fun dumpHeap (Ljava/io/File;)V
56+
}
57+
58+
public final class leakcanary/ObjectGrowthWarmupHeapDumper$Companion {
59+
}
60+
61+
public final class leakcanary/ObjectGrowthWarmupHeapDumperKt {
62+
public static final fun withDetectorWarmup (Lleakcanary/HeapDumper;Lshark/ObjectGrowthDetector;)Lleakcanary/HeapDumper;
63+
}
64+
5265
public final class leakcanary/TempHeapDumpFileProvider : leakcanary/HeapDumpFileProvider {
5366
public static final field INSTANCE Lleakcanary/TempHeapDumpFileProvider;
5467
public fun newHeapDumpFile ()Ljava/io/File;

leakcanary/leakcanary-core/build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,9 @@ targetCompatibility = JavaVersion.VERSION_1_8
99
dependencies {
1010
api projects.leakcanary.leakcanaryGc
1111
api projects.shark.shark
12+
implementation libs.okio2
13+
14+
testImplementation libs.assertjCore
15+
testImplementation libs.junit
16+
testImplementation projects.shark.sharkHprofTest
1217
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package leakcanary
2+
3+
import java.io.File
4+
import shark.ByteArraySourceProvider
5+
import shark.ObjectGrowthDetector
6+
import shark.repeatingScenario
7+
import okio.ByteString.Companion.decodeHex
8+
import shark.HprofHeapGraph.Companion.openHeapGraph
9+
10+
class ObjectGrowthWarmupHeapDumper(
11+
private val objectGrowthDetector: ObjectGrowthDetector,
12+
private val delegate: HeapDumper
13+
) : HeapDumper {
14+
15+
private var warm = false
16+
17+
override fun dumpHeap(heapDumpFile: File) {
18+
if (!warm) {
19+
warmup()
20+
warm = true
21+
}
22+
delegate.dumpHeap(heapDumpFile)
23+
}
24+
25+
private fun warmup() {
26+
val heapDumpsAsHex = listOf({ heapDump1Hex() }, { heapDump2Hex() }, { heapDump3Hex() })
27+
val heapDumpsAsHexIterator = heapDumpsAsHex.iterator()
28+
val warmupDetector = objectGrowthDetector.repeatingScenario(
29+
heapGraphProvider = {
30+
ByteArraySourceProvider(
31+
heapDumpsAsHexIterator.next()().decodeHex().toByteArray()
32+
).openHeapGraph()
33+
},
34+
maxHeapDumps = heapDumpsAsHex.size,
35+
scenarioLoopsPerDump = 1
36+
)
37+
warmupDetector.findRepeatedlyGrowingObjects {}
38+
}
39+
40+
@SuppressWarnings("MaxLineLength")
41+
companion object {
42+
// Header:
43+
// 4a4156412050524f46494c4520312e302e33 is the header version string
44+
// then 00 is the string separator
45+
// 00000004 is the identifier byte size, 4 bytes
46+
// 0b501e7e ca55e77e (obsolete cassette) is a cool heap dump timestamp.
47+
internal fun heapDump1Hex() =
48+
"4a4156412050524f46494c4520312e302e3300000000040b501e7eca55e77e01000000000000001b000000016a6176612e6c616e672e7265662e5265666572656e63650200000000000000100000000100000002000000010000000101000000000000000c000000037265666572656e74010000000000000014000000046a6176612e6c616e672e4f626a656374020000000000000010000000010000000500000001000000040c000000000000006520000000050000000100000000000000000000000000000000000000000000000000000000000000000000050000000520000000020000000100000005000000000000000000000000000000000000000000000004000000000001000000030205000000022c000000000000000001000000000000001f000000066a6176612e6c616e672e7265662e5765616b5265666572656e6365020000000000000010000000010000000700000001000000060c00000000000000302000000007000000010000000200000000000000000000000000000000000000000000000400000000000005000000072c0000000000000000010000000000000021000000086c65616b63616e6172792e4b657965645765616b5265666572656e6365020000000000000010000000010000000900000001000000080100000000000000180000000a6865617044756d70557074696d654d696c6c69730100000000000000070000000b6b65790100000000000000080000000c6e616d650100000000000000150000000d7761746368557074696d654d696c6c69730100000000000000180000000e72657461696e6564557074696d654d696c6c69730c00000000000000622000000009000000010000000700000000000000000000000000000000000000000000001c000000010000000a0b000000000000753000040000000b020000000c020000000d0b0000000e0b0500000009210000000f0000000100000005000000002c0000000000000000010000000000000016000000106a6176612e6c616e672e4f626a6563745b5d020000000000000010000000010000001100000001000000100c000000000000004520000000110000000100000005000000000000000000000000000000000000000000000000000000000000050000001122000000120000000100000001000000110000000f2c000000000000000001000000000000000a00000013486f6c64657202000000000000001000000001000000140000000100000013010000000000000008000000156c6973740c00000000000000392000000014000000010000000500000000000000000000000000000000000000000000000000000001000000150200000012000005000000142c0000000000000000"
49+
50+
internal fun heapDump2Hex() =
51+
"4a4156412050524f46494c4520312e302e3300000000040b501e7eca55e77e01000000000000001b000000016a6176612e6c616e672e7265662e5265666572656e63650200000000000000100000000100000002000000010000000101000000000000000c000000037265666572656e74010000000000000014000000046a6176612e6c616e672e4f626a656374020000000000000010000000010000000500000001000000040c000000000000006520000000050000000100000000000000000000000000000000000000000000000000000000000000000000050000000520000000020000000100000005000000000000000000000000000000000000000000000004000000000001000000030205000000022c000000000000000001000000000000001f000000066a6176612e6c616e672e7265662e5765616b5265666572656e6365020000000000000010000000010000000700000001000000060c00000000000000302000000007000000010000000200000000000000000000000000000000000000000000000400000000000005000000072c0000000000000000010000000000000021000000086c65616b63616e6172792e4b657965645765616b5265666572656e6365020000000000000010000000010000000900000001000000080100000000000000180000000a6865617044756d70557074696d654d696c6c69730100000000000000070000000b6b65790100000000000000080000000c6e616d650100000000000000150000000d7761746368557074696d654d696c6c69730100000000000000180000000e72657461696e6564557074696d654d696c6c69730c00000000000000732000000009000000010000000700000000000000000000000000000000000000000000001c000000010000000a0b000000000000753000040000000b020000000c020000000d0b0000000e0b0500000009210000000f00000001000000050000000021000000100000000100000005000000002c0000000000000000010000000000000016000000116a6176612e6c616e672e4f626a6563745b5d020000000000000010000000010000001200000001000000110c000000000000004920000000120000000100000005000000000000000000000000000000000000000000000000000000000000050000001222000000130000000100000002000000120000000f000000102c000000000000000001000000000000000a00000014486f6c64657202000000000000001000000001000000150000000100000014010000000000000008000000166c6973740c00000000000000392000000015000000010000000500000000000000000000000000000000000000000000000000000001000000160200000013000005000000152c0000000000000000"
52+
53+
internal fun heapDump3Hex() =
54+
"4a4156412050524f46494c4520312e302e3300000000040b501e7eca55e77e01000000000000001b000000016a6176612e6c616e672e7265662e5265666572656e63650200000000000000100000000100000002000000010000000101000000000000000c000000037265666572656e74010000000000000014000000046a6176612e6c616e672e4f626a656374020000000000000010000000010000000500000001000000040c000000000000006520000000050000000100000000000000000000000000000000000000000000000000000000000000000000050000000520000000020000000100000005000000000000000000000000000000000000000000000004000000000001000000030205000000022c000000000000000001000000000000001f000000066a6176612e6c616e672e7265662e5765616b5265666572656e6365020000000000000010000000010000000700000001000000060c00000000000000302000000007000000010000000200000000000000000000000000000000000000000000000400000000000005000000072c0000000000000000010000000000000021000000086c65616b63616e6172792e4b657965645765616b5265666572656e6365020000000000000010000000010000000900000001000000080100000000000000180000000a6865617044756d70557074696d654d696c6c69730100000000000000070000000b6b65790100000000000000080000000c6e616d650100000000000000150000000d7761746368557074696d654d696c6c69730100000000000000180000000e72657461696e6564557074696d654d696c6c69730c00000000000000842000000009000000010000000700000000000000000000000000000000000000000000001c000000010000000a0b000000000000753000040000000b020000000c020000000d0b0000000e0b0500000009210000000f000000010000000500000000210000001000000001000000050000000021000000110000000100000005000000002c0000000000000000010000000000000016000000126a6176612e6c616e672e4f626a6563745b5d020000000000000010000000010000001300000001000000120c000000000000004d20000000130000000100000005000000000000000000000000000000000000000000000000000000000000050000001322000000140000000100000003000000130000000f00000010000000112c000000000000000001000000000000000a00000015486f6c64657202000000000000001000000001000000160000000100000015010000000000000008000000176c6973740c00000000000000392000000016000000010000000500000000000000000000000000000000000000000000000000000001000000170200000014000005000000162c0000000000000000"
55+
}
56+
}
57+
58+
fun HeapDumper.withDetectorWarmup(objectGrowthDetector: ObjectGrowthDetector): HeapDumper =
59+
ObjectGrowthWarmupHeapDumper(objectGrowthDetector, this)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package leakcanary
2+
3+
import okio.ByteString.Companion.decodeHex
4+
import okio.ByteString.Companion.toByteString
5+
import org.assertj.core.api.Assertions.assertThat
6+
import org.junit.Test
7+
import shark.HprofHeader
8+
import shark.dumpToBytes
9+
10+
class ObjectGrowthWarmupHeapDumperTest {
11+
12+
@Test fun `heap dump 1 as hex constant matches generated heap dump hex`() {
13+
assertThat(ObjectGrowthWarmupHeapDumper.heapDump1Hex()).isEqualTo(dumpGrowingListHeapAsHex(1))
14+
}
15+
16+
@Test fun `heap dump 2 as hex constant matches generated heap dump hex`() {
17+
assertThat(ObjectGrowthWarmupHeapDumper.heapDump2Hex()).isEqualTo(dumpGrowingListHeapAsHex(2))
18+
}
19+
20+
@Test fun `heap dump 3 as hex constant matches generated heap dump hex`() {
21+
assertThat(ObjectGrowthWarmupHeapDumper.heapDump3Hex()).isEqualTo(dumpGrowingListHeapAsHex(3))
22+
}
23+
24+
private fun dumpGrowingListHeapAsHex(listItemCount: Int): String {
25+
val heapDumpTimestamp = ("0b501e7e" + "ca55e77e").decodeHex().toByteArray().toLong()
26+
return dumpToBytes(hprofHeader = HprofHeader(heapDumpTimestamp = heapDumpTimestamp)) {
27+
"Holder" clazz {
28+
val refs = (1..listItemCount).map {
29+
instance(objectClassId)
30+
}.toTypedArray()
31+
staticField["list"] = objectArray(*refs)
32+
}
33+
}.toByteString().hex()
34+
}
35+
36+
private fun ByteArray.toLong(): Long {
37+
check(size == 8)
38+
var pos = 0
39+
return (this[pos++].toLong() and 0xffL shl 56
40+
or (this[pos++].toLong() and 0xffL shl 48)
41+
or (this[pos++].toLong() and 0xffL shl 40)
42+
or (this[pos++].toLong() and 0xffL shl 32)
43+
or (this[pos++].toLong() and 0xffL shl 24)
44+
or (this[pos++].toLong() and 0xffL shl 16)
45+
or (this[pos++].toLong() and 0xffL shl 8)
46+
or (this[pos].toLong() and 0xffL))
47+
}
48+
}

leakcanary/leakcanary-jvm-test/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ dependencies {
1212

1313
testImplementation libs.assertjCore
1414
testImplementation libs.junit
15+
testImplementation projects.shark.sharkHprofTest
1516
}

leakcanary/leakcanary-jvm-test/src/main/java/leakcanary/RepeatingJvmInProcessScenario.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ fun ObjectGrowthDetector.repeatingJvmInProcessScenario(
2323
return repeatingScenario(
2424
heapGraphProvider = HeapGraphProvider.dumpingAndDeleting(
2525
heapDumper = HeapDumper.forJvmInProcess()
26-
.withGc(gcTrigger = GcTrigger.inProcess()),
26+
.withGc(gcTrigger = GcTrigger.inProcess())
27+
.withDetectorWarmup(this),
2728
heapDumpFileProvider = HeapDumpFileProvider.tempFile()
2829
),
2930
maxHeapDumps = maxHeapDumps,

0 commit comments

Comments
 (0)