<lambda>null1 package shark
2
3 import okio.Buffer
4 import shark.GcRoot.StickyClass
5 import shark.HprofRecord.HeapDumpRecord.GcRootRecord
6 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord
7 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.FieldRecord
8 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.StaticFieldRecord
9 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.InstanceDumpRecord
10 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ObjectArrayDumpRecord
11 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.CharArrayDump
12 import shark.HprofRecord.LoadClassRecord
13 import shark.HprofRecord.StringRecord
14 import shark.PrimitiveType.BOOLEAN
15 import shark.PrimitiveType.BYTE
16 import shark.PrimitiveType.CHAR
17 import shark.PrimitiveType.DOUBLE
18 import shark.PrimitiveType.FLOAT
19 import shark.PrimitiveType.INT
20 import shark.PrimitiveType.LONG
21 import shark.PrimitiveType.SHORT
22 import shark.ValueHolder.BooleanHolder
23 import shark.ValueHolder.ByteHolder
24 import shark.ValueHolder.CharHolder
25 import shark.ValueHolder.DoubleHolder
26 import shark.ValueHolder.FloatHolder
27 import shark.ValueHolder.IntHolder
28 import shark.ValueHolder.LongHolder
29 import shark.ValueHolder.ReferenceHolder
30 import shark.ValueHolder.ShortHolder
31 import java.io.Closeable
32 import java.io.File
33 import java.util.UUID
34 import kotlin.random.Random
35 import kotlin.reflect.KClass
36 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.LongArrayDump
37
38 class HprofWriterHelper constructor(
39 private val writer: HprofWriter
40 ) : Closeable {
41
42 private var lastId = 0L
43 private val id: Long
44 get() = ++lastId
45
46 private val weakRefKeyRandom = Random(42)
47
48 // Sequence identical for every test run
49 private val weakRefKey: String
50 get() =
51 UUID(weakRefKeyRandom.nextLong(), weakRefKeyRandom.nextLong()).toString()
52
53 private val typeSizes =
54 PrimitiveType.byteSizeByHprofType + (PrimitiveType.REFERENCE_HPROF_TYPE to writer.hprofHeader.identifierByteSize)
55
56 private val classDumps = mutableMapOf<Long, ClassDumpRecord>()
57
58 private val objectClassId = clazz(superclassId = 0, className = "java.lang.Object")
59 private val objectArrayClassId = arrayClass("java.lang.Object")
60 private val stringClassId = clazz(
61 className = "java.lang.String", fields = listOf(
62 "value" to ReferenceHolder::class,
63 "count" to IntHolder::class
64 )
65 )
66
67 private val referenceClassId = clazz(
68 className = "java.lang.ref.Reference",
69 fields = listOf(
70 "referent" to ReferenceHolder::class
71 )
72 )
73
74 private val weakReferenceClassId = clazz(
75 className = "java.lang.ref.WeakReference",
76 superclassId = referenceClassId
77 )
78 private val keyedWeakReferenceClassId = clazz(
79 superclassId = weakReferenceClassId,
80 className = "leakcanary.KeyedWeakReference",
81 staticFields = listOf("heapDumpUptimeMillis" to LongHolder(30000)),
82 fields = listOf(
83 "key" to ReferenceHolder::class,
84 "name" to ReferenceHolder::class,
85 "watchUptimeMillis" to LongHolder::class,
86 "retainedUptimeMillis" to LongHolder::class
87 )
88 )
89
90 fun clazz(
91 className: String,
92 superclassId: Long = -1L, // -1 defaults to java.lang.Object
93 staticFields: List<Pair<String, ValueHolder>> = emptyList(),
94 fields: List<Pair<String, KClass<out ValueHolder>>> = emptyList()
95 ): Long {
96 val classNameRecord = StringRecord(id, className)
97 writer.write(classNameRecord)
98 val loadClass = LoadClassRecord(1, id, 1, classNameRecord.id)
99 writer.write(loadClass)
100
101 val staticFieldRecords = staticFields.map {
102 val fieldName = StringRecord(id, it.first)
103 writer.write(fieldName)
104 StaticFieldRecord(fieldName.id, typeOf(it.second), it.second)
105 }
106
107 val fieldRecords = fields.map {
108 val fieldName = StringRecord(id, it.first)
109 writer.write(fieldName)
110 FieldRecord(fieldName.id, typeOf(it.second))
111 }
112
113 var instanceSize = fieldRecords.sumBy {
114 typeSizes.getValue(it.type)
115 }
116
117 var nextUpId = if (superclassId == -1L) objectClassId else superclassId
118 while (nextUpId != 0L) {
119 val nextUp = classDumps[nextUpId]!!
120 instanceSize += nextUp.fields.sumBy {
121 typeSizes.getValue(it.type)
122 }
123 nextUpId = nextUp.superclassId
124 }
125 val classDump = ClassDumpRecord(
126 id = loadClass.id,
127 stackTraceSerialNumber = 1,
128 superclassId = if (superclassId == -1L) objectClassId else superclassId,
129 classLoaderId = 0,
130 signersId = 0,
131 protectionDomainId = 0,
132 instanceSize = instanceSize,
133 staticFields = staticFieldRecords,
134 fields = fieldRecords
135 )
136 classDumps[loadClass.id] = classDump
137 writer.write(classDump)
138 val gcRoot = StickyClass(classDump.id)
139 gcRoot(gcRoot)
140 return classDump.id
141 }
142
143 fun stringRecord(
144 name: String
145 ): StringRecord {
146 val stringRecord = StringRecord(id, name)
147 writer.write(stringRecord)
148 return stringRecord
149 }
150
151 fun clazz(
152 classNameRecord: StringRecord,
153 superclassId: Long = -1L, // -1 defaults to java.lang.Object
154 staticFields: List<Pair<Long, ValueHolder>> = emptyList(),
155 fields: List<Pair<Long, KClass<out ValueHolder>>> = emptyList()
156 ): Long {
157 val loadClass = LoadClassRecord(1, id, 1, classNameRecord.id)
158 writer.write(loadClass)
159
160 val staticFieldRecords = staticFields.map {
161 StaticFieldRecord(it.first, typeOf(it.second), it.second)
162 }
163
164 val fieldRecords = fields.map {
165 FieldRecord(it.first, typeOf(it.second))
166 }
167
168 var instanceSize = fieldRecords.sumBy {
169 typeSizes.getValue(it.type)
170 }
171
172 var nextUpId = if (superclassId == -1L) objectClassId else superclassId
173 while (nextUpId != 0L) {
174 val nextUp = classDumps[nextUpId]!!
175 instanceSize += nextUp.fields.sumBy {
176 typeSizes.getValue(it.type)
177 }
178 nextUpId = nextUp.superclassId
179 }
180 val classDump = ClassDumpRecord(
181 id = loadClass.id,
182 stackTraceSerialNumber = 1,
183 superclassId = if (superclassId == -1L) objectClassId else superclassId,
184 classLoaderId = 0,
185 signersId = 0,
186 protectionDomainId = 0,
187 instanceSize = instanceSize,
188 staticFields = staticFieldRecords,
189 fields = fieldRecords
190 )
191 classDumps[loadClass.id] = classDump
192 writer.write(classDump)
193 val gcRoot = StickyClass(classDump.id)
194 gcRoot(gcRoot)
195 return classDump.id
196 }
197
198 fun gcRoot(gcRoot: GcRoot) {
199 val gcRootRecord = GcRootRecord(gcRoot = gcRoot)
200 writer.write(gcRootRecord)
201 }
202
203 fun arrayClass(className: String): Long {
204 return clazz(className = "$className[]")
205 }
206
207 fun string(
208 string: String
209 ): ReferenceHolder {
210 return instance(
211 stringClassId,
212 fields = listOf(string.charArrayDump, IntHolder(string.length))
213 )
214 }
215
216 fun keyedWeakReference(
217 referentInstanceId: ReferenceHolder
218 ): ReferenceHolder {
219 val referenceKey = string(weakRefKey)
220 return instance(
221 classId = keyedWeakReferenceClassId,
222 fields = listOf(
223 referenceKey,
224 string("its lifecycle has ended"),
225 LongHolder(5000),
226 LongHolder(20000),
227 ReferenceHolder(referentInstanceId.value)
228 )
229 )
230 }
231
232 fun instance(
233 classId: Long,
234 fields: List<ValueHolder> = emptyList()
235 ): ReferenceHolder {
236 val instanceDump = InstanceDumpRecord(
237 id = id,
238 stackTraceSerialNumber = 1,
239 classId = classId,
240 fieldValues = writer.valuesToBytes(fields)
241 )
242 writer.write(instanceDump)
243 return ReferenceHolder(instanceDump.id)
244 }
245
246 inner class InstanceAndClassDefinition {
247 val field = LinkedHashMap<String, ValueHolder>()
248 val staticField = LinkedHashMap<String, ValueHolder>()
249 }
250
251 inner class ClassDefinition {
252 val staticField = LinkedHashMap<String, ValueHolder>()
253 }
254
255 infix fun String.watchedInstance(block: InstanceAndClassDefinition.() -> Unit): ReferenceHolder {
256 val instance = this.instance(block)
257 keyedWeakReference(instance)
258 return instance
259 }
260
261 infix fun String.instance(block: InstanceAndClassDefinition.() -> Unit): ReferenceHolder {
262 val definition = InstanceAndClassDefinition()
263 block(definition)
264
265 val classFields = definition.field.map {
266 it.key to it.value::class
267 }
268
269 val staticFields = definition.staticField.map { it.key to it.value }
270
271 val instanceFields = definition.field.map { it.value }
272
273 return instance(clazz(this, fields = classFields, staticFields = staticFields), instanceFields)
274 }
275
276 infix fun String.clazz(block: ClassDefinition.() -> Unit): Long {
277 val definition = ClassDefinition()
278 block(definition)
279
280 val staticFields = definition.staticField.map { it.key to it.value }
281 return clazz(this, staticFields = staticFields)
282 }
283
284 val String.charArrayDump: ReferenceHolder
285 get() {
286 val arrayDump = CharArrayDump(id, 1, toCharArray())
287 writer.write(arrayDump)
288 return ReferenceHolder(arrayDump.id)
289 }
290
291 fun objectArray(
292 vararg elements: ReferenceHolder
293 ): ReferenceHolder {
294 return objectArrayOf(objectArrayClassId, *elements)
295 }
296
297 fun objectArrayOf(
298 classId: Long,
299 vararg elements: ReferenceHolder
300 ): ReferenceHolder {
301 return ReferenceHolder(objectArray(classId, elements.map { it.value }.toLongArray()))
302 }
303
304 fun objectArray(
305 classId: Long,
306 array: LongArray
307 ): Long {
308 val arrayDump = ObjectArrayDumpRecord(id, 1, classId, array)
309 writer.write(arrayDump)
310 return arrayDump.id
311 }
312
313 fun primitiveLongArray(array: LongArray): Long {
314 val arrayDump = LongArrayDump(id, 1, array)
315 writer.write(arrayDump)
316 return arrayDump.id
317 }
318
319 private fun typeOf(wrapper: ValueHolder): Int {
320 return when (wrapper) {
321 is ReferenceHolder -> PrimitiveType.REFERENCE_HPROF_TYPE
322 is BooleanHolder -> BOOLEAN.hprofType
323 is CharHolder -> CHAR.hprofType
324 is FloatHolder -> FLOAT.hprofType
325 is DoubleHolder -> DOUBLE.hprofType
326 is ByteHolder -> BYTE.hprofType
327 is ShortHolder -> SHORT.hprofType
328 is IntHolder -> INT.hprofType
329 is LongHolder -> LONG.hprofType
330 }
331 }
332
333 private fun typeOf(wrapperClass: KClass<out ValueHolder>): Int {
334 return when (wrapperClass) {
335 ReferenceHolder::class -> PrimitiveType.REFERENCE_HPROF_TYPE
336 BooleanHolder::class -> BOOLEAN.hprofType
337 CharHolder::class -> CHAR.hprofType
338 FloatHolder::class -> FLOAT.hprofType
339 DoubleHolder::class -> DOUBLE.hprofType
340 ByteHolder::class -> BYTE.hprofType
341 ShortHolder::class -> SHORT.hprofType
342 IntHolder::class -> INT.hprofType
343 LongHolder::class -> LONG.hprofType
344 else -> throw IllegalArgumentException("Unexpected class $wrapperClass")
345 }
346 }
347
348 override fun close() {
349 writer.close()
350 }
351 }
352
dumpnull353 fun File.dump(block: HprofWriterHelper.() -> Unit) {
354 HprofWriterHelper(HprofWriter.openWriterFor(this))
355 .use(block)
356 }
357
dumpnull358 fun dump(
359 hprofHeader: HprofHeader = HprofHeader(),
360 block: HprofWriterHelper.() -> Unit
361 ): DualSourceProvider {
362 val buffer = Buffer()
363 HprofWriterHelper(HprofWriter.openWriterFor(buffer, hprofHeader))
364 .use(block)
365 return ByteArraySourceProvider(buffer.readByteArray())
366 }
367
dumpToBytesnull368 fun dumpToBytes(
369 hprofHeader: HprofHeader = HprofHeader(),
370 block: HprofWriterHelper.() -> Unit
371 ): ByteArray {
372 val buffer = Buffer()
373 HprofWriterHelper(HprofWriter.openWriterFor(buffer, hprofHeader))
374 .use(block)
375 return buffer.readByteArray()
376 }
377
Listnull378 fun List<HprofRecord>.asHprofBytes(): DualSourceProvider {
379 val buffer = Buffer()
380 HprofWriter.openWriterFor(buffer)
381 .use { writer ->
382 forEach { record ->
383 writer.write(record)
384 }
385 }
386 return ByteArraySourceProvider(buffer.readByteArray())
387 }
388