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 Jan 17, 2024
1 parent b3d1511 commit 84ed0b8
Show file tree
Hide file tree
Showing 41 changed files with 823 additions and 275 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,28 @@ public final class leakcanary/AndroidDetectLeaksInterceptor : leakcanary/DetectL
public fun waitUntilReadyForHeapAnalysis ()Lleakcanary/HeapAnalysisDecision;
}

public final class leakcanary/AndroidLiveObjectGrowthDetectorConfig {
public fun <init> ()V
public fun <init> (IILjava/util/List;Lleakcanary/HeapDumpFileProvider;Lleakcanary/HeapDumper;)V
public synthetic fun <init> (IILjava/util/List;Lleakcanary/HeapDumpFileProvider;Lleakcanary/HeapDumper;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()I
public final fun component2 ()I
public final fun component3 ()Ljava/util/List;
public final fun component4 ()Lleakcanary/HeapDumpFileProvider;
public final fun component5 ()Lleakcanary/HeapDumper;
public final fun copy (IILjava/util/List;Lleakcanary/HeapDumpFileProvider;Lleakcanary/HeapDumper;)Lleakcanary/AndroidLiveObjectGrowthDetectorConfig;
public static synthetic fun copy$default (Lleakcanary/AndroidLiveObjectGrowthDetectorConfig;IILjava/util/List;Lleakcanary/HeapDumpFileProvider;Lleakcanary/HeapDumper;ILjava/lang/Object;)Lleakcanary/AndroidLiveObjectGrowthDetectorConfig;
public final fun create ()Lshark/LiveObjectGrowthDetector;
public fun equals (Ljava/lang/Object;)Z
public final fun getHeapDumpFileProvider ()Lleakcanary/HeapDumpFileProvider;
public final fun getHeapDumper ()Lleakcanary/HeapDumper;
public final fun getMaxHeapDumps ()I
public final fun getReferenceMatchers ()Ljava/util/List;
public final fun getScenarioLoopsPerDump ()I
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

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,36 @@
package leakcanary

import androidx.test.platform.app.InstrumentationRegistry
import java.io.File
import shark.AndroidHeapGrowthIgnoredReferences
import shark.AndroidReferenceReaderFactory
import shark.LiveObjectGrowthDetector
import shark.MatchingGcRootProvider
import shark.ReferenceMatcher

data class AndroidLiveObjectGrowthDetectorConfig(
val maxHeapDumps: Int = 5,
val scenarioLoopsPerDump: Int = 5,
val referenceMatchers: List<ReferenceMatcher> = AndroidHeapGrowthIgnoredReferences.defaults,
val heapDumpFileProvider: HeapDumpFileProvider = HeapDumpFileProvider.dateFormatted(
directory = File(
InstrumentationRegistry.getInstrumentation().targetContext.filesDir,
"heap-growth-hprof"
),
prefix = "heap-growth-"
),
val heapDumper: HeapDumper = AndroidDebugHeapDumper
) {

fun create(): LiveObjectGrowthDetector {
val heapGraphProvider =
DumpingDeletingOnCloseHeapGraphProvider(heapDumpFileProvider, heapDumper)
return LiveObjectGrowthDetector.Config(
maxHeapDumps = maxHeapDumps,
scenarioLoopsPerDump = scenarioLoopsPerDump,
heapGraphProvider = heapGraphProvider,
referenceReaderFactory = AndroidReferenceReaderFactory(referenceMatchers),
gcRootProvider = MatchingGcRootProvider(referenceMatchers)
).create()
}
}

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
)
}
}
}
32 changes: 32 additions & 0 deletions leakcanary/leakcanary-jvm-test/api/leakcanary-jvm-test.api
@@ -0,0 +1,32 @@
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/JvmLiveObjectGrowthDetectorConfig {
public fun <init> ()V
public fun <init> (IILjava/util/List;Lleakcanary/HeapDumpFileProvider;Lleakcanary/HeapDumper;)V
public synthetic fun <init> (IILjava/util/List;Lleakcanary/HeapDumpFileProvider;Lleakcanary/HeapDumper;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()I
public final fun component2 ()I
public final fun component3 ()Ljava/util/List;
public final fun component4 ()Lleakcanary/HeapDumpFileProvider;
public final fun component5 ()Lleakcanary/HeapDumper;
public final fun copy (IILjava/util/List;Lleakcanary/HeapDumpFileProvider;Lleakcanary/HeapDumper;)Lleakcanary/JvmLiveObjectGrowthDetectorConfig;
public static synthetic fun copy$default (Lleakcanary/JvmLiveObjectGrowthDetectorConfig;IILjava/util/List;Lleakcanary/HeapDumpFileProvider;Lleakcanary/HeapDumper;ILjava/lang/Object;)Lleakcanary/JvmLiveObjectGrowthDetectorConfig;
public final fun create ()Lshark/LiveObjectGrowthDetector;
public fun equals (Ljava/lang/Object;)Z
public final fun getHeapDumpFileProvider ()Lleakcanary/HeapDumpFileProvider;
public final fun getHeapDumper ()Lleakcanary/HeapDumper;
public final fun getMaxHeapDumps ()I
public final fun getReferenceMatchers ()Ljava/util/List;
public final fun getScenarioLoopsPerDump ()I
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

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 comments on commit 84ed0b8

Please sign in to comment.