<lambda>null1 package shark
2
3 import okio.buffer
4 import okio.source
5 import shark.AndroidReferenceMatchers.FINALIZER_WATCHDOG_DAEMON
6 import shark.AndroidReferenceMatchers.REFERENCES
7 import shark.HprofHeapGraph.Companion.openHeapGraph
8 import java.io.File
9 import java.io.FileWriter
10 import java.util.EnumSet
11 import java.util.UUID
12
13 /**
14 * Tests different values for the lru cache size when performing the leak analysss, measuring IO
15 * reads and memory retained by the cache for each cache size, and outputting the result as
16 * a CSV.
17 *
18 * Data saved at https://docs.google.com/spreadsheets/d/14BRd1CJO2_WRBqNQRdfDUDLhr3_2R5N461a74XA4pjE/edit?usp=sharing
19 */
20 fun main() {
21 val tmpHeapDumpFolder = createTemporaryFolder()
22
23 val lruCacheSizes = 100..20000 step 500
24 val files = listOf("leak_asynctask_o", "leak_asynctask_m")
25 val computeRetainedHeapSizeList = listOf(false, true)
26
27 val stats = mutableListOf<MutableList<Int>>()
28 for (lruCacheSize in lruCacheSizes) {
29 val row = mutableListOf<Int>()
30 row.add(lruCacheSize)
31 stats.add(row)
32 }
33
34 for (filename in files) {
35 val hprofFile = "$filename.hprof".classpathFile()
36 val bytes = hprofFile.inputStream().source().buffer().readByteArray()
37 lruCacheSizes.forEachIndexed { index, lruCacheSize ->
38 val row = stats[index]
39 for (computeRetainedHeapSize in computeRetainedHeapSizeList) {
40 val (randomAccessReads, lruRetainedSize) = trackAnalyzeMetrics(
41 hprofFile, bytes, tmpHeapDumpFolder, computeRetainedHeapSize, lruCacheSize
42 )
43 val bytesRead = randomAccessReads.sum()
44 val readCount = randomAccessReads.size
45 row.add(bytesRead)
46 row.add(readCount)
47 row.add(lruRetainedSize)
48 }
49 }
50 }
51
52 tmpHeapDumpFolder.recursiveDelete()
53
54 FileWriter("lru_cache_tuning.csv").use {
55 with(it) {
56 append("lru_size")
57 for (filename in files) {
58 for (computeRetainedHeapSize in computeRetainedHeapSizeList) {
59 listOf("bytes_read", "read_count", "lru_retained").forEach { column ->
60 append(",${filename}_size_${computeRetainedHeapSize}_$column")
61 }
62 }
63 }
64 append('\n')
65 for (statRow in stats) {
66 append(statRow.joinToString(",", postfix = "\n"))
67 }
68 }
69 }
70 }
71
trackAnalyzeMetricsnull72 private fun trackAnalyzeMetrics(
73 hprofFile: File,
74 bytes: ByteArray,
75 tmpHeapDumpFolder: File,
76 computeRetainedHeapSize: Boolean,
77 lruCacheSize: Int
78 ): Pair<List<Int>, Int> {
79 println(
80 "Analysing ${hprofFile.name} computeRetainedHeapSize=$computeRetainedHeapSize lruCacheSize=$lruCacheSize"
81 )
82
83 val source = MetricsDualSourceProvider(ByteArraySourceProvider(bytes))
84 val heapAnalyzer = HeapAnalyzer(OnAnalysisProgressListener.NO_OP)
85 val heapAfterAnalysis = withLruCacheSize(lruCacheSize) {
86 source.openHeapGraph().use { graph ->
87 val analysis = heapAnalyzer.analyze(
88 heapDumpFile = hprofFile,
89 graph = graph,
90 leakingObjectFinder = FilteringLeakingObjectFinder(
91 AndroidObjectInspectors.appLeakingObjectFilters
92 ),
93 referenceMatchers = AndroidReferenceMatchers.appDefaults,
94 computeRetainedHeapSize = computeRetainedHeapSize,
95 objectInspectors = AndroidObjectInspectors.appDefaults,
96 metadataExtractor = AndroidMetadataExtractor
97 )
98 check(analysis is HeapAnalysisSuccess) {
99 "Expected success not $analysis"
100 }
101 tmpHeapDumpFolder.dumpHeap()
102 }
103 }
104 val randomAccessReads = source.sourcesMetrics[3]
105
106 val lruCacheAnalysis = heapAnalyzer.analyze(
107 heapDumpFile = heapAfterAnalysis,
108 referenceMatchers = AndroidReferenceMatchers.buildKnownReferences(
109 EnumSet.of(REFERENCES, FINALIZER_WATCHDOG_DAEMON)
110 ),
111 leakingObjectFinder = { graph ->
112 setOf(graph.findClassByName("shark.internal.LruCache")!!.instances.single().objectId)
113 },
114 computeRetainedHeapSize = true
115 )
116 check(lruCacheAnalysis is HeapAnalysisSuccess) {
117 "Expected success not $lruCacheAnalysis"
118 }
119 val lruRetainedSize =
120 lruCacheAnalysis.allLeaks.single().leakTraces.single().retainedHeapByteSize!!
121
122 println(
123 "${randomAccessReads.sum()} bytes in ${randomAccessReads.size} reads, retaining $lruRetainedSize bytes in cache"
124 )
125
126 return randomAccessReads to lruRetainedSize
127 }
128
withLruCacheSizenull129 private fun <T> withLruCacheSize(
130 lruCacheSize: Int,
131 block: () -> T
132 ): T {
133 val sizeBefore = HprofHeapGraph.INTERNAL_LRU_CACHE_SIZE
134 HprofHeapGraph.INTERNAL_LRU_CACHE_SIZE = lruCacheSize
135 try {
136 return block()
137 } finally {
138 HprofHeapGraph.INTERNAL_LRU_CACHE_SIZE = sizeBefore
139 }
140 }
141
createTemporaryFoldernull142 private fun createTemporaryFolder(): File {
143 val createdFolder = File.createTempFile("shark", "", null)
144 createdFolder.delete()
145 createdFolder.mkdir()
146 return createdFolder
147 }
148
recursiveDeletenull149 private fun File.recursiveDelete() {
150 val files = listFiles()
151 if (files != null) {
152 for (each in files) {
153 each.recursiveDelete()
154 }
155 }
156 delete()
157 }
158
dumpHeapnull159 private fun File.dumpHeap(): File {
160 val testHprofFile = File(this, "${UUID.randomUUID()}.hprof")
161 JvmTestHeapDumper.dumpHeap(testHprofFile.absolutePath)
162 return testHprofFile
163 }
164