Skip to content

Commit

Permalink
Revisit heap growth detection APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
pyricau committed Mar 26, 2024
1 parent 0d178de commit 035f217
Show file tree
Hide file tree
Showing 44 changed files with 831 additions and 257 deletions.
Expand Up @@ -64,10 +64,6 @@ public final class leakcanary/EventListener$Event$HeapDumpFailed : leakcanary/Ev
public final fun getWillRetryLater ()Z
}

public abstract interface class leakcanary/HeapDumper {
public abstract fun dumpHeap (Ljava/io/File;)V
}

public final class leakcanary/LazyForwardingEventListener : leakcanary/EventListener {
public fun <init> (Lkotlin/jvm/functions/Function0;)V
public fun onEvent (Lleakcanary/EventListener$Event;)V
Expand Down
1 change: 1 addition & 0 deletions leakcanary/leakcanary-android-core/build.gradle
Expand Up @@ -8,6 +8,7 @@ dependencies {
api projects.shark.sharkAndroid
api projects.objectWatcher.objectWatcherAndroidCore
api projects.objectWatcher.objectWatcherAndroidAndroidx
api projects.leakcanary.leakcanaryCore
implementation libs.kotlin.stdlib

// Optional dependency
Expand Down
Expand Up @@ -19,6 +19,12 @@ public final class leakcanary/AndroidDetectLeaksInterceptor : leakcanary/DetectL
public fun waitUntilReadyForHeapAnalysis ()Lleakcanary/HeapAnalysisDecision;
}

public final class leakcanary/AndroidLiveObjectGrowthDetector {
public static final field INSTANCE Lleakcanary/AndroidLiveObjectGrowthDetector;
public final fun create (IILleakcanary/HeapDumpFileProvider;Lleakcanary/HeapDumper;Lshark/RepeatedObjectGrowthDetector;)Lshark/LiveObjectGrowthDetector;
public static synthetic fun create$default (Lleakcanary/AndroidLiveObjectGrowthDetector;IILleakcanary/HeapDumpFileProvider;Lleakcanary/HeapDumper;Lshark/RepeatedObjectGrowthDetector;ILjava/lang/Object;)Lshark/LiveObjectGrowthDetector;
}

public final class leakcanary/DetectLeaksAfterTestSuccess : org/junit/rules/TestRule {
public static final field Companion Lleakcanary/DetectLeaksAfterTestSuccess$Companion;
public fun <init> ()V
Expand Down
1 change: 1 addition & 0 deletions leakcanary/leakcanary-android-instrumentation/build.gradle
Expand Up @@ -6,6 +6,7 @@ plugins {

dependencies {
api projects.leakcanary.leakcanaryAndroidCore
api projects.shark.sharkAndroid

implementation libs.androidX.test.runner
implementation libs.kotlin.stdlib
Expand Down
@@ -1,9 +1,10 @@
package leakcanary

import android.os.SystemClock
import androidx.test.platform.app.InstrumentationRegistry
import java.io.File
import leakcanary.HeapAnalysisDecision.NoHeapAnalysis
import leakcanary.internal.InstrumentationHeapAnalyzer
import leakcanary.internal.InstrumentationHeapDumpFileProvider
import leakcanary.internal.RetryingHeapAnalyzer
import leakcanary.internal.friendly.checkNotMainThread
import leakcanary.internal.friendly.measureDurationMillis
Expand Down Expand Up @@ -57,7 +58,15 @@ class AndroidDetectLeaksAssert(
}
}

val heapDumpFile = InstrumentationHeapDumpFileProvider().newHeapDumpFile()
val heapDumpFileProvider = leakcanary.HeapDumpFileProvider.dateFormatted(
directory = File(
InstrumentationRegistry.getInstrumentation().targetContext.filesDir,
"instrumentation_tests"
),
prefix = "instrumentation_tests"
)

val heapDumpFile = heapDumpFileProvider.newHeapDumpFile()

val config = LeakCanary.config

Expand Down
@@ -0,0 +1,35 @@
package leakcanary

import androidx.test.platform.app.InstrumentationRegistry
import java.io.File
import shark.HeapDumpingObjectGrowthDetector
import shark.LiveObjectGrowthDetector
import shark.RepeatedObjectGrowthDetector

// TODO This name is wrong, and create method is wrong.
object AndroidLiveObjectGrowthDetector {

fun create(
maxHeapDumps: Int = 5,
scenarioLoopsPerDump: Int = 5,
heapDumpFileProvider: HeapDumpFileProvider = HeapDumpFileProvider.dateFormatted(
directory = File(
InstrumentationRegistry.getInstrumentation().targetContext.filesDir,
"heap-growth-hprof"
),
prefix = "heap-growth-"
),
heapDumper: HeapDumper = AndroidDebugHeapDumper,
objectRepeatedGrowthDetector: RepeatedObjectGrowthDetector
): LiveObjectGrowthDetector {
val heapGraphProvider =
DumpingDeletingOnCloseHeapGraphProvider(heapDumpFileProvider, heapDumper)

return HeapDumpingObjectGrowthDetector(
maxHeapDumps = maxHeapDumps,
heapGraphProvider = heapGraphProvider,
scenarioLoopsPerDump = scenarioLoopsPerDump,
detector = objectRepeatedGrowthDetector
)
}
}

This file was deleted.

30 changes: 30 additions & 0 deletions leakcanary/leakcanary-core/api/leakcanary-core.api
@@ -0,0 +1,30 @@
public final class leakcanary/DateFormatHeapDumpFileProvider : leakcanary/HeapDumpFileProvider {
public static final field Companion Lleakcanary/DateFormatHeapDumpFileProvider$Companion;
public static final field TIME_PATTERN Ljava/lang/String;
public fun <init> (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun newHeapDumpFile ()Ljava/io/File;
}

public final class leakcanary/DateFormatHeapDumpFileProvider$Companion {
}

public final class leakcanary/DumpingDeletingOnCloseHeapGraphProvider : shark/HeapGraphProvider {
public fun <init> (Lleakcanary/HeapDumpFileProvider;Lleakcanary/HeapDumper;)V
public fun openHeapGraph ()Lshark/CloseableHeapGraph;
}

public abstract interface class leakcanary/HeapDumpFileProvider {
public static final field Companion Lleakcanary/HeapDumpFileProvider$Companion;
public abstract fun newHeapDumpFile ()Ljava/io/File;
}

public final class leakcanary/HeapDumpFileProvider$Companion {
public final fun dateFormatted (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Lleakcanary/HeapDumpFileProvider;
public static synthetic fun dateFormatted$default (Lleakcanary/HeapDumpFileProvider$Companion;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lleakcanary/HeapDumpFileProvider;
}

public abstract interface class leakcanary/HeapDumper {
public abstract fun dumpHeap (Ljava/io/File;)V
}

12 changes: 12 additions & 0 deletions leakcanary/leakcanary-core/build.gradle
@@ -0,0 +1,12 @@
plugins {
id("org.jetbrains.kotlin.jvm")
id("com.vanniktech.maven.publish")
}

sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8

dependencies {
api projects.shark.shark

}
3 changes: 3 additions & 0 deletions leakcanary/leakcanary-core/gradle.properties
@@ -0,0 +1,3 @@
POM_ARTIFACT_ID=leakcanary-core
POM_NAME=LeakCanary Core
POM_PACKAGING=jar
@@ -0,0 +1,39 @@
package leakcanary

import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

class DateFormatHeapDumpFileProvider(
private val heapDumpDirectoryProvider: () -> File,
private val dateProvider: () -> Date = { Date() },
prefix: String = "",
suffix: String = ""
) : HeapDumpFileProvider {

private val dateFormatPattern =
"${escape(prefix)}$TIME_PATTERN${escape("$suffix.hprof")}"

private val timeFormatThreadLocal = object : ThreadLocal<SimpleDateFormat>() {
// Lint is drunk and thinks we use the pattern 'u'
@Suppress("NewApi")
override fun initialValue() =
SimpleDateFormat(dateFormatPattern, Locale.US)
}

override fun newHeapDumpFile(): File {
val heapDumpDirectory = heapDumpDirectoryProvider()
val date = dateProvider()
val fileName = timeFormatThreadLocal.get()!!.format(date)
return File(heapDumpDirectory, fileName)
}

private fun escape(string: String) = if (string != "") {
"'$string'"
} else ""

companion object {
const val TIME_PATTERN = "yyyy-MM-dd_HH-mm-ss_SSS"
}
}
@@ -0,0 +1,25 @@
package leakcanary

import shark.CloseableHeapGraph
import shark.HeapGraphProvider
import shark.HprofHeapGraph.Companion.openHeapGraph

class DumpingDeletingOnCloseHeapGraphProvider(
private val heapDumpFileProvider: HeapDumpFileProvider,
private val heapDumper: HeapDumper
) : HeapGraphProvider {
override fun openHeapGraph(): CloseableHeapGraph {
val heapDumpFile = heapDumpFileProvider.newHeapDumpFile()
heapDumper.dumpHeap(heapDumpFile)
check(heapDumpFile.exists()) {
"Expected file to exist after heap dump: ${heapDumpFile.absolutePath}"
}
val realGraph = heapDumpFile.openHeapGraph()
return object : CloseableHeapGraph by realGraph {
override fun close() {
realGraph.close()
heapDumpFile.delete()
}
}
}
}
@@ -0,0 +1,35 @@
package leakcanary

import java.io.File
import java.util.Date

fun interface HeapDumpFileProvider {

/**
* Returns a [File] that can be passed to a [HeapDumper] to dump the heap.
*/
fun newHeapDumpFile(): File

companion object {
fun dateFormatted(
directory: File,
prefix: String = "",
suffix: String = "",
dateProvider: () -> Date = { Date() },
): HeapDumpFileProvider {
return DateFormatHeapDumpFileProvider(
heapDumpDirectoryProvider = {
directory.apply {
mkdirs()
check(exists()) {
"Expected heap dump folder to exist: $absolutePath"
}
}
},
dateProvider = dateProvider,
prefix = prefix,
suffix = suffix
)
}
}
}
16 changes: 16 additions & 0 deletions leakcanary/leakcanary-jvm-test/api/leakcanary-jvm-test.api
@@ -0,0 +1,16 @@
public final class leakcanary/HotSpotHeapDumper : leakcanary/HeapDumper {
public static final field INSTANCE Lleakcanary/HotSpotHeapDumper;
public fun dumpHeap (Ljava/io/File;)V
}

public final class leakcanary/JvmLiveObjectGrowthDetector {
public static final field INSTANCE Lleakcanary/JvmLiveObjectGrowthDetector;
public final fun create (IILleakcanary/HeapDumpFileProvider;Lleakcanary/HeapDumper;Lshark/RepeatedObjectGrowthDetector;)Lshark/LiveObjectGrowthDetector;
public static synthetic fun create$default (Lleakcanary/JvmLiveObjectGrowthDetector;IILleakcanary/HeapDumpFileProvider;Lleakcanary/HeapDumper;Lshark/RepeatedObjectGrowthDetector;ILjava/lang/Object;)Lshark/LiveObjectGrowthDetector;
}

public final class leakcanary/TempHeapDumpFileProvider : leakcanary/HeapDumpFileProvider {
public static final field INSTANCE Lleakcanary/TempHeapDumpFileProvider;
public fun newHeapDumpFile ()Ljava/io/File;
}

Expand Up @@ -7,11 +7,9 @@ sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8

dependencies {
api projects.leakcanary.leakcanaryCore
api projects.shark.shark
api projects.shark.sharkGraph

testImplementation libs.assertjCore
testImplementation libs.junit
testImplementation projects.shark.sharkTest
testImplementation projects.shark.sharkHprofTest
}
3 changes: 3 additions & 0 deletions leakcanary/leakcanary-jvm-test/gradle.properties
@@ -0,0 +1,3 @@
POM_ARTIFACT_ID=leakcanary-jvm-test
POM_NAME=LeakCanary Jvm Test
POM_PACKAGING=jar
@@ -0,0 +1,21 @@
package leakcanary

import com.sun.management.HotSpotDiagnosticMXBean
import java.io.File
import java.lang.management.ManagementFactory

object HotSpotHeapDumper : HeapDumper {
private val hotspotMBean: HotSpotDiagnosticMXBean by lazy {
val mBeanServer = ManagementFactory.getPlatformMBeanServer()
ManagementFactory.newPlatformMXBeanProxy(
mBeanServer,
"com.sun.management:type=HotSpotDiagnostic",
HotSpotDiagnosticMXBean::class.java
)
}

override fun dumpHeap(heapDumpFile: File) {
val live = true
hotspotMBean.dumpHeap(heapDumpFile.absolutePath, live)
}
}
@@ -0,0 +1,26 @@
package leakcanary

import shark.HeapDumpingObjectGrowthDetector
import shark.LiveObjectGrowthDetector
import shark.RepeatedObjectGrowthDetector

// TODO This name is wrong, and create method is wrong.
object JvmLiveObjectGrowthDetector {

fun create(
maxHeapDumps: Int = 5,
scenarioLoopsPerDump: Int = 5,
heapDumpFileProvider: HeapDumpFileProvider = TempHeapDumpFileProvider,
heapDumper: HeapDumper = HotSpotHeapDumper,
repeatedObjectGrowthDetector: RepeatedObjectGrowthDetector
): LiveObjectGrowthDetector {
val heapGraphProvider =
DumpingDeletingOnCloseHeapGraphProvider(heapDumpFileProvider, heapDumper)
return HeapDumpingObjectGrowthDetector(
maxHeapDumps = maxHeapDumps,
heapGraphProvider = heapGraphProvider,
scenarioLoopsPerDump = scenarioLoopsPerDump,
detector = repeatedObjectGrowthDetector
)
}
}
@@ -0,0 +1,13 @@
package leakcanary

import java.io.File

object TempHeapDumpFileProvider : HeapDumpFileProvider {
override fun newHeapDumpFile(): File {
val heapDumpFile = File.createTempFile("heap-growth", ".hprof", null)
check(heapDumpFile.delete()) {
"Could not delete $heapDumpFile, needs to not exist for heap dump"
}
return heapDumpFile
}
}

0 comments on commit 035f217

Please sign in to comment.