HeapAnalysis.kt
TLDR
The provided file, "HeapAnalysis.kt", contains a Kotlin class hierarchy for representing the result of an analysis performed by a "HeapAnalyzer". The hierarchy includes the following classes:
- "HeapAnalysis": The base class representing the result of the analysis. It includes properties for the heap dump file, creation time, dump duration, and analysis duration.
- "HeapAnalysisFailure": A subclass of HeapAnalysis, representing the case when the analysis did not complete successfully. It includes an exception object that wraps the actual exception thrown.
- "HeapAnalysisSuccess": A subclass of HeapAnalysis, representing the case when the analysis was successful. It includes additional properties such as metadata, application leaks, library leaks, and unreachable objects.
- "Leak": The base class for representing a leak found by the HeapAnalyzer. It includes properties for leak traces and retained heap byte size.
- "LibraryLeak": A subclass of Leak, representing a known leak in library code. It includes properties for the leak traces, pattern, and description.
- "ApplicationLeak": A subclass of Leak, representing a leak found in the application code. It includes properties for the leak traces.
Classes
HeapAnalysis
The base class representing the result of an analysis performed by a HeapAnalyzer. It includes properties for the heap dump file, creation time, dump duration, and analysis duration.
HeapAnalysisFailure
A subclass of HeapAnalysis, representing the case when the analysis did not complete successfully. It includes an exception object that wraps the actual exception thrown.
HeapAnalysisSuccess
A subclass of HeapAnalysis, representing the case when the analysis was successful. It includes additional properties such as metadata, application leaks, library leaks, and unreachable objects.
Leak
The base class for representing a leak found by the HeapAnalyzer. It includes properties for leak traces and retained heap byte size.
LibraryLeak
A subclass of Leak, representing a known leak in library code. It includes properties for the leak traces, pattern, and description.
ApplicationLeak
A subclass of Leak, representing a leak found in the application code. It includes properties for the leak traces.
Methods
No methods in the provided file.
package shark
import shark.internal.createSHA1Hash
import java.io.File
import java.io.Serializable
/**
* The result of an analysis performed by [HeapAnalyzer], either a [HeapAnalysisSuccess] or a
* [HeapAnalysisFailure]. This class is serializable however there are no guarantees of forward
* compatibility.
*/
sealed class HeapAnalysis : Serializable {
/**
* The hprof file that was analyzed.
*/
abstract val heapDumpFile: File
/**
* The [System.currentTimeMillis] when this [HeapAnalysis] instance was created.
*/
abstract val createdAtTimeMillis: Long
/**
* Total time spent dumping the heap.
*/
abstract val dumpDurationMillis: Long
/**
* Total time spent analyzing the heap.
*/
abstract val analysisDurationMillis: Long
companion object {
private const val serialVersionUID: Long = -8657286725869987172
const val DUMP_DURATION_UNKNOWN: Long = -1
}
}
/**
* The analysis performed by [HeapAnalyzer] did not complete successfully.
*/
data class HeapAnalysisFailure(
override val heapDumpFile: File,
override val createdAtTimeMillis: Long,
override val dumpDurationMillis: Long = DUMP_DURATION_UNKNOWN,
override val analysisDurationMillis: Long,
/**
* An exception wrapping the actual exception that was thrown.
*/
val exception: HeapAnalysisException
) : HeapAnalysis() {
override fun toString(): String {
return """====================================
HEAP ANALYSIS FAILED
You can report this failure at https://github.com/square/leakcanary/issues
Please provide the stacktrace, metadata and the heap dump file.
====================================
STACKTRACE
$exception====================================
METADATA
Build.VERSION.SDK_INT: ${androidSdkInt()}
Build.MANUFACTURER: ${androidManufacturer()}
LeakCanary version: ${leakCanaryVersion()}
Analysis duration: $analysisDurationMillis ms
Heap dump file path: ${heapDumpFile.absolutePath}
Heap dump timestamp: $createdAtTimeMillis
===================================="""
}
companion object {
private const val serialVersionUID: Long = 8483254400637792414
}
}
/**
* The result of a successful heap analysis performed by [HeapAnalyzer].
*/
data class HeapAnalysisSuccess(
override val heapDumpFile: File,
override val createdAtTimeMillis: Long,
override val dumpDurationMillis: Long = DUMP_DURATION_UNKNOWN,
override val analysisDurationMillis: Long,
val metadata: Map<String, String>,
/**
* The list of [ApplicationLeak] found in the heap dump by [HeapAnalyzer].
*/
val applicationLeaks: List<ApplicationLeak>,
/**
* The list of [LibraryLeak] found in the heap dump by [HeapAnalyzer].
*/
val libraryLeaks: List<LibraryLeak>,
val unreachableObjects: List<LeakTraceObject>
) : HeapAnalysis() {
/**
* The list of [Leak] found in the heap dump by [HeapAnalyzer], ie all [applicationLeaks] and
* all [libraryLeaks] in one list.
*/
val allLeaks: Sequence<Leak>
get() = applicationLeaks.asSequence() + libraryLeaks.asSequence()
override fun toString(): String {
return """====================================
HEAP ANALYSIS RESULT
====================================
${applicationLeaks.size} APPLICATION LEAKS
References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.
${
if (applicationLeaks.isNotEmpty()) "\n" + applicationLeaks.joinToString(
"\n\n"
) + "\n" else ""
}====================================
${libraryLeaks.size} LIBRARY LEAKS
A Library Leak is a leak caused by a known bug in 3rd party code that you do not have control over.
See https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks
${
if (libraryLeaks.isNotEmpty()) "\n" + libraryLeaks.joinToString(
"\n\n"
) + "\n" else ""
}====================================
${unreachableObjects.size} UNREACHABLE OBJECTS
An unreachable object is still in memory but LeakCanary could not find a strong reference path
from GC roots.
${
if (unreachableObjects.isNotEmpty()) "\n" + unreachableObjects.joinToString(
"\n\n"
) + "\n" else ""
}====================================
METADATA
Please include this in bug reports and Stack Overflow questions.
${
if (metadata.isNotEmpty()) "\n" + metadata.map { "${it.key}: ${it.value}" }.joinToString(
"\n"
) else ""
}
Analysis duration: $analysisDurationMillis ms
Heap dump file path: ${heapDumpFile.absolutePath}
Heap dump timestamp: $createdAtTimeMillis
Heap dump duration: ${if (dumpDurationMillis != DUMP_DURATION_UNKNOWN) "$dumpDurationMillis ms" else "Unknown"}
===================================="""
}
companion object {
private const val serialVersionUID: Long = 130453013437459642
}
}
/**
* A leak found by [HeapAnalyzer], either an [ApplicationLeak] or a [LibraryLeak].
*/
sealed class Leak : Serializable {
/**
* Group of leak traces which share the same leak signature.
*/
abstract val leakTraces: List<LeakTrace>
/**
* Sum of [LeakTrace.retainedHeapByteSize] for all elements in [leakTraces].
* Null if the retained heap size was not computed.
*/
val totalRetainedHeapByteSize: Int?
get() = if (leakTraces.first().retainedHeapByteSize == null) {
null
} else {
leakTraces.sumBy { it.retainedHeapByteSize!! }
}
/**
* Sum of [LeakTrace.retainedObjectCount] for all elements in [leakTraces].
* Null if the retained heap size was not computed.
*/
val totalRetainedObjectCount: Int?
get() = if (leakTraces.first().retainedObjectCount == null) {
null
} else {
leakTraces.sumBy { it.retainedObjectCount!! }
}
/**
* A unique SHA1 hash that represents this group of leak traces.
*
* For [ApplicationLeak] this is based on [LeakTrace.signature] and for [LibraryLeak] this is
* based on [LibraryLeak.pattern].
*/
abstract val signature: String
abstract val shortDescription: String
override fun toString(): String {
return (if (totalRetainedHeapByteSize != null) "$totalRetainedHeapByteSize bytes retained by leaking objects\n" else "") +
(if (leakTraces.size > 1) "Displaying only 1 leak trace out of ${leakTraces.size} with the same signature\n" else "") +
"Signature: $signature\n" +
leakTraces.first()
}
companion object {
private const val serialVersionUID: Long = -2287572510360910916
}
}
/**
* A leak found by [HeapAnalyzer], where the only path to the leaking object required going
* through a reference matched by [pattern], as provided to a [LibraryLeakReferenceMatcher]
* instance. This is a known leak in library code that is beyond your control.
*/
data class LibraryLeak(
override val leakTraces: List<LeakTrace>,
/**
* The pattern that matched one of the references in each of [leakTraces], as provided to a
* [LibraryLeakReferenceMatcher] instance.
*/
val pattern: ReferencePattern,
/**
* A description that conveys what we know about this library leak.
*/
val description: String
) : Leak() {
override val signature: String
get() = pattern.toString().createSHA1Hash()
override val shortDescription: String
get() = pattern.toString()
override fun toString(): String {
return """Leak pattern: $pattern
Description: $description
${super.toString()}
"""
}
companion object {
private const val serialVersionUID: Long = 3943636164568681903
}
}
/**
* A leak found by [HeapAnalyzer] in your application.
*/
data class ApplicationLeak(
override val leakTraces: List<LeakTrace>
) : Leak() {
override val signature: String
get() = leakTraces.first().signature
override val shortDescription: String
get() {
val leakTrace = leakTraces.first()
return leakTrace.suspectReferenceSubpath.firstOrNull()?.let { firstSuspectReferencePath ->
val referenceName = firstSuspectReferencePath.referenceGenericName
firstSuspectReferencePath.originObject.classSimpleName + "." + referenceName
} ?: leakTrace.leakingObject.className
}
// Override required to avoid the default toString() from data classes
override fun toString(): String {
return super.toString()
}
companion object {
private const val serialVersionUID: Long = 524928276700576863
}
}
private fun androidSdkInt(): Int {
return try {
val versionClass = Class.forName("android.os.Build\$VERSION")
val sdkIntField = versionClass.getDeclaredField("SDK_INT")
sdkIntField.get(null) as Int
} catch (e: Exception) {
-1
}
}
private fun androidManufacturer(): String {
return try {
val buildClass = Class.forName("android.os.Build")
val manufacturerField = buildClass.getDeclaredField("MANUFACTURER")
manufacturerField.get(null) as String
} catch (e: Exception) {
"Unknown"
}
}
private fun leakCanaryVersion(): String {
return try {
val versionHolderClass = Class.forName("leakcanary.internal.InternalLeakCanary")
val versionField = versionHolderClass.getDeclaredField("version")
versionField.isAccessible = true
versionField.get(null) as String
} catch (e: Exception) {
"Unknown"
}
}