-
-
Save jush/87ac548fe2c3379938b57cc3e961b72c to your computer and use it in GitHub Desktop.
A standalone Kotlin script that shows how to do custom heap analysis work with Shark, the heap parsing library that powers LeakCanary (see https://square.github.io/leakcanary/shark)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env -S JAVA_OPTS="-Xmx16g" kotlin | |
// In order to analyze big heaps you might need to modify above `-Xmx16g` Java option. | |
// Before running this script, install Kotlin with `brew install kotlin` | |
// Then run this with `kotlin shark-custom-script.main.kts` | |
// Edit this script in the latest Android Studio or IntelliJ IDEA releases to get dependency import and auto completion. | |
@file:Repository("https://repo.maven.apache.org/maven2/") | |
@file:DependsOn("com.squareup.leakcanary:shark-android:2.13") | |
import shark.HeapAnalyzer | |
import shark.HprofHeapGraph.Companion.openHeapGraph | |
import java.io.File | |
var pid: Long = ProcessHandle.current().pid() | |
println("Process ID: $pid") | |
val heapDumpFile: File = TODO("Replace this with hprof file") // = File("PATH_TO_HPROF") | |
val heapAnalyzer = HeapAnalyzer(listener = { step -> | |
println("Analysis in progress, working on: ${step.name}") | |
}) | |
heapDumpFile.openHeapGraph( | |
proguardMapping = null | |
).use { graph -> | |
println("Finding Mapbox CameraAnimator which are not skipped.") | |
println("#####") | |
val cameraAnimationsPluginImplClass = | |
graph.findClassByName("com.mapbox.maps.plugin.animation.CameraAnimationsPluginImpl")!!.instances.first() | |
// Animators HashSet | |
val animatorsInstance = cameraAnimationsPluginImplClass.readField( | |
cameraAnimationsPluginImplClass.instanceClassName, | |
"animators" | |
)!!.valueAsInstance!! | |
animatorsInstance.readFields().forEach { field -> | |
println("animatorsField field ${field.name} -> ${field.value.asObject}") | |
} | |
// The HashMap within the HashSet | |
val animatorsMapInstance = | |
animatorsInstance.readField(animatorsInstance.instanceClassName, "map")!!.valueAsInstance!! | |
animatorsMapInstance.readFields().forEach { field -> | |
println("animatorsMapInstance field ${field.name} -> ${field.value.asObject}") | |
} | |
// Get all `HashMap$Node`s from the HashMap | |
val nonNullAnimatorsNodes = animatorsMapInstance.readField( | |
animatorsMapInstance.instanceClassName, | |
"table" | |
)!!.valueAsObjectArray!!.readElements().filter { it.isNonNullReference } | |
println("Found ${nonNullAnimatorsNodes.count()} `animators` entries") | |
// Find animators which "skipped" flag is false and also those not within Mapbox package | |
nonNullAnimatorsNodes.forEach { element -> | |
val instance = element.asObject!!.asInstance!! | |
val animatorField = instance.readField("java.util.HashMap\$Node", "key")!!.valueAsInstance!! | |
val animatorClassName = animatorField.instanceClassName | |
if (animatorClassName.startsWith("com.mapbox.maps.")) { | |
val skipped = animatorField.readField( | |
"com.mapbox.maps.plugin.animation.animator.CameraAnimator", | |
"skipped" | |
)!!.value.asBoolean!! | |
if (!skipped) { | |
println("Found non-skipped animator ${animatorField.objectId} $animatorClassName") | |
// animatorField.readFields().forEach { field -> | |
// println("animatorsMapInstance field ${field.name} -> ${field.value.asObject}") | |
// } | |
} | |
} else { | |
println("Found non-mapbox animator $animatorClassName") | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment