Skip to content

Instantly share code, notes, and snippets.

@jush
Forked from pyricau/shark-custom-script.main.kts
Last active May 6, 2024 10:23
Show Gist options
  • Save jush/87ac548fe2c3379938b57cc3e961b72c to your computer and use it in GitHub Desktop.
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)
#!/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