xref: /aosp_15_r20/external/leakcanary2/shark-hprof/src/main/java/shark/HprofWriter.kt (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)

<lambda>null1 package shark
2 
3 import okio.Buffer
4 import okio.BufferedSink
5 import okio.Okio
6 import shark.GcRoot.Debugger
7 import shark.GcRoot.Finalizing
8 import shark.GcRoot.InternedString
9 import shark.GcRoot.JavaFrame
10 import shark.GcRoot.JniGlobal
11 import shark.GcRoot.JniLocal
12 import shark.GcRoot.JniMonitor
13 import shark.GcRoot.MonitorUsed
14 import shark.GcRoot.NativeStack
15 import shark.GcRoot.ReferenceCleanup
16 import shark.GcRoot.StickyClass
17 import shark.GcRoot.ThreadBlock
18 import shark.GcRoot.ThreadObject
19 import shark.GcRoot.Unknown
20 import shark.GcRoot.Unreachable
21 import shark.GcRoot.VmInternal
22 import shark.HprofRecord.HeapDumpEndRecord
23 import shark.HprofRecord.HeapDumpRecord.GcRootRecord
24 import shark.HprofRecord.HeapDumpRecord.HeapDumpInfoRecord
25 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord
26 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.InstanceDumpRecord
27 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ObjectArrayDumpRecord
28 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord
29 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.BooleanArrayDump
30 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ByteArrayDump
31 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.CharArrayDump
32 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.DoubleArrayDump
33 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.FloatArrayDump
34 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.IntArrayDump
35 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.LongArrayDump
36 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ShortArrayDump
37 import shark.HprofRecord.LoadClassRecord
38 import shark.HprofRecord.StackTraceRecord
39 import shark.HprofRecord.StringRecord
40 import shark.PrimitiveType.BOOLEAN
41 import shark.PrimitiveType.BYTE
42 import shark.PrimitiveType.CHAR
43 import shark.PrimitiveType.DOUBLE
44 import shark.PrimitiveType.FLOAT
45 import shark.PrimitiveType.INT
46 import shark.PrimitiveType.LONG
47 import shark.PrimitiveType.SHORT
48 import shark.ValueHolder.BooleanHolder
49 import shark.ValueHolder.ByteHolder
50 import shark.ValueHolder.CharHolder
51 import shark.ValueHolder.DoubleHolder
52 import shark.ValueHolder.FloatHolder
53 import shark.ValueHolder.IntHolder
54 import shark.ValueHolder.LongHolder
55 import shark.ValueHolder.ReferenceHolder
56 import shark.ValueHolder.ShortHolder
57 import java.io.Closeable
58 import java.io.File
59 
60 /**
61  * Generates Hprof files.
62  *
63  * Call [openWriterFor] to obtain a new instance.
64  *
65  * Call [write] to add records and [close] when you're done.
66  */
67 class HprofWriter private constructor(
68   private val sink: BufferedSink,
69   val hprofHeader: HprofHeader
70 ) : Closeable {
71 
72   @Deprecated(
73     "Replaced by HprofWriter.hprofHeader.identifierByteSize",
74     ReplaceWith("hprofHeader.identifierByteSize")
75   )
76   val identifierByteSize: Int
77     get() = hprofHeader.identifierByteSize
78 
79   @Deprecated(
80     "Replaced by HprofWriter.hprofHeader.version",
81     ReplaceWith("hprofHeader.version")
82   )
83   val hprofVersion: Hprof.HprofVersion
84     get() = Hprof.HprofVersion.valueOf(hprofHeader.version.name)
85 
86   private val workBuffer = Buffer()
87 
88   /**
89    * Appends a [HprofRecord] to the heap dump. If [record] is a [HprofRecord.HeapDumpRecord] then
90    * it will not be written to an in memory buffer and written to file only when the next a record
91    * that is not a [HprofRecord.HeapDumpRecord] is written or when [close] is called.
92    */
93   fun write(record: HprofRecord) {
94     sink.write(record)
95   }
96 
97   /**
98    * Helper method for creating a [ByteArray] for [InstanceDumpRecord.fieldValues] from a
99    * list of [ValueHolder].
100    */
101   fun valuesToBytes(values: List<ValueHolder>): ByteArray {
102     val valuesBuffer = Buffer()
103     values.forEach { value ->
104       valuesBuffer.writeValue(value)
105     }
106     return valuesBuffer.readByteArray()
107   }
108 
109   /**
110    * Flushes to disk all [HprofRecord.HeapDumpRecord] that are currently written to the in memory
111    * buffer, then closes the file.
112    */
113   override fun close() {
114     sink.flushHeapBuffer()
115     sink.close()
116   }
117 
118   private fun BufferedSink.writeValue(wrapper: ValueHolder) {
119     when (wrapper) {
120       is ReferenceHolder -> writeId(wrapper.value)
121       is BooleanHolder -> writeBoolean(wrapper.value)
122       is CharHolder -> write(charArrayOf(wrapper.value))
123       is FloatHolder -> writeFloat(wrapper.value)
124       is DoubleHolder -> writeDouble(wrapper.value)
125       is ByteHolder -> writeByte(wrapper.value.toInt())
126       is ShortHolder -> writeShort(wrapper.value.toInt())
127       is IntHolder -> writeInt(wrapper.value)
128       is LongHolder -> writeLong(wrapper.value)
129     }
130   }
131 
132   @Suppress("LongMethod")
133   private fun BufferedSink.write(record: HprofRecord) {
134     when (record) {
135       is StringRecord -> {
136         writeNonHeapRecord(HprofRecordTag.STRING_IN_UTF8.tag) {
137           writeId(record.id)
138           writeUtf8(record.string)
139         }
140       }
141       is LoadClassRecord -> {
142         writeNonHeapRecord(HprofRecordTag.LOAD_CLASS.tag) {
143           writeInt(record.classSerialNumber)
144           writeId(record.id)
145           writeInt(record.stackTraceSerialNumber)
146           writeId(record.classNameStringId)
147         }
148       }
149       is StackTraceRecord -> {
150         writeNonHeapRecord(HprofRecordTag.STACK_TRACE.tag) {
151           writeInt(record.stackTraceSerialNumber)
152           writeInt(record.threadSerialNumber)
153           writeInt(record.stackFrameIds.size)
154           writeIdArray(record.stackFrameIds)
155         }
156       }
157       is GcRootRecord -> {
158         with(workBuffer) {
159           when (val gcRoot = record.gcRoot) {
160             is Unknown -> {
161               writeByte(HprofRecordTag.ROOT_UNKNOWN.tag)
162               writeId(gcRoot.id)
163             }
164             is JniGlobal -> {
165               writeByte(
166                 HprofRecordTag.ROOT_JNI_GLOBAL.tag
167               )
168               writeId(gcRoot.id)
169               writeId(gcRoot.jniGlobalRefId)
170             }
171             is JniLocal -> {
172               writeByte(HprofRecordTag.ROOT_JNI_LOCAL.tag)
173               writeId(gcRoot.id)
174               writeInt(gcRoot.threadSerialNumber)
175               writeInt(gcRoot.frameNumber)
176             }
177             is JavaFrame -> {
178               writeByte(HprofRecordTag.ROOT_JAVA_FRAME.tag)
179               writeId(gcRoot.id)
180               writeInt(gcRoot.threadSerialNumber)
181               writeInt(gcRoot.frameNumber)
182             }
183             is NativeStack -> {
184               writeByte(HprofRecordTag.ROOT_NATIVE_STACK.tag)
185               writeId(gcRoot.id)
186               writeInt(gcRoot.threadSerialNumber)
187             }
188             is StickyClass -> {
189               writeByte(HprofRecordTag.ROOT_STICKY_CLASS.tag)
190               writeId(gcRoot.id)
191             }
192             is ThreadBlock -> {
193               writeByte(HprofRecordTag.ROOT_THREAD_BLOCK.tag)
194               writeId(gcRoot.id)
195               writeInt(gcRoot.threadSerialNumber)
196             }
197             is MonitorUsed -> {
198               writeByte(HprofRecordTag.ROOT_MONITOR_USED.tag)
199               writeId(gcRoot.id)
200             }
201             is ThreadObject -> {
202               writeByte(HprofRecordTag.ROOT_THREAD_OBJECT.tag)
203               writeId(gcRoot.id)
204               writeInt(gcRoot.threadSerialNumber)
205               writeInt(gcRoot.stackTraceSerialNumber)
206             }
207             is ReferenceCleanup -> {
208               writeByte(HprofRecordTag.ROOT_REFERENCE_CLEANUP.tag)
209               writeId(gcRoot.id)
210             }
211             is VmInternal -> {
212               writeByte(HprofRecordTag.ROOT_VM_INTERNAL.tag)
213               writeId(gcRoot.id)
214             }
215             is JniMonitor -> {
216               writeByte(HprofRecordTag.ROOT_JNI_MONITOR.tag)
217               writeId(gcRoot.id)
218               writeInt(gcRoot.stackTraceSerialNumber)
219               writeInt(gcRoot.stackDepth)
220             }
221             is InternedString -> {
222               writeByte(HprofRecordTag.ROOT_INTERNED_STRING.tag)
223               writeId(gcRoot.id)
224             }
225             is Finalizing -> {
226               writeByte(HprofRecordTag.ROOT_FINALIZING.tag)
227               writeId(gcRoot.id)
228             }
229             is Debugger -> {
230               writeByte(HprofRecordTag.ROOT_DEBUGGER.tag)
231               writeId(gcRoot.id)
232             }
233             is Unreachable -> {
234               writeByte(HprofRecordTag.ROOT_UNREACHABLE.tag)
235               writeId(gcRoot.id)
236             }
237           }
238         }
239       }
240       is ClassDumpRecord -> {
241         with(workBuffer) {
242           writeByte(HprofRecordTag.CLASS_DUMP.tag)
243           writeId(record.id)
244           writeInt(record.stackTraceSerialNumber)
245           writeId(record.superclassId)
246           writeId(record.classLoaderId)
247           writeId(record.signersId)
248           writeId(record.protectionDomainId)
249           // reserved
250           writeId(0)
251           // reserved
252           writeId(0)
253           writeInt(record.instanceSize)
254           // Not writing anything in the constant pool
255           val constantPoolCount = 0
256           writeShort(constantPoolCount)
257           writeShort(record.staticFields.size)
258           record.staticFields.forEach { field ->
259             writeId(field.nameStringId)
260             writeByte(field.type)
261             writeValue(field.value)
262           }
263           writeShort(record.fields.size)
264           record.fields.forEach { field ->
265             writeId(field.nameStringId)
266             writeByte(field.type)
267           }
268         }
269       }
270       is InstanceDumpRecord -> {
271         with(workBuffer) {
272           writeByte(HprofRecordTag.INSTANCE_DUMP.tag)
273           writeId(record.id)
274           writeInt(record.stackTraceSerialNumber)
275           writeId(record.classId)
276           writeInt(record.fieldValues.size)
277           write(record.fieldValues)
278         }
279       }
280       is ObjectArrayDumpRecord -> {
281         with(workBuffer) {
282           writeByte(HprofRecordTag.OBJECT_ARRAY_DUMP.tag)
283           writeId(record.id)
284           writeInt(record.stackTraceSerialNumber)
285           writeInt(record.elementIds.size)
286           writeId(record.arrayClassId)
287           writeIdArray(record.elementIds)
288         }
289       }
290       is PrimitiveArrayDumpRecord -> {
291         with(workBuffer) {
292           writeByte(HprofRecordTag.PRIMITIVE_ARRAY_DUMP.tag)
293           writeId(record.id)
294           writeInt(record.stackTraceSerialNumber)
295 
296           when (record) {
297             is BooleanArrayDump -> {
298               writeInt(record.array.size)
299               writeByte(BOOLEAN.hprofType)
300               write(record.array)
301             }
302             is CharArrayDump -> {
303               writeInt(record.array.size)
304               writeByte(CHAR.hprofType)
305               write(record.array)
306             }
307             is FloatArrayDump -> {
308               writeInt(record.array.size)
309               writeByte(FLOAT.hprofType)
310               write(record.array)
311             }
312             is DoubleArrayDump -> {
313               writeInt(record.array.size)
314               writeByte(DOUBLE.hprofType)
315               write(record.array)
316             }
317             is ByteArrayDump -> {
318               writeInt(record.array.size)
319               writeByte(BYTE.hprofType)
320               write(record.array)
321             }
322             is ShortArrayDump -> {
323               writeInt(record.array.size)
324               writeByte(SHORT.hprofType)
325               write(record.array)
326             }
327             is IntArrayDump -> {
328               writeInt(record.array.size)
329               writeByte(INT.hprofType)
330               write(record.array)
331             }
332             is LongArrayDump -> {
333               writeInt(record.array.size)
334               writeByte(LONG.hprofType)
335               write(record.array)
336             }
337           }
338         }
339       }
340       is HeapDumpInfoRecord -> {
341         with(workBuffer) {
342           writeByte(HprofRecordTag.HEAP_DUMP_INFO.tag)
343           writeInt(record.heapId)
344           writeId(record.heapNameStringId)
345         }
346       }
347       is HeapDumpEndRecord -> {
348         throw IllegalArgumentException("HprofWriter automatically emits HeapDumpEndRecord")
349       }
350     }
351   }
352 
353   private fun BufferedSink.writeDouble(value: Double) {
354     writeLong(value.toBits())
355   }
356 
357   private fun BufferedSink.writeFloat(value: Float) {
358     writeInt(value.toBits())
359   }
360 
361   private fun BufferedSink.writeBoolean(value: Boolean) {
362     writeByte(if (value) 1 else 0)
363   }
364 
365   private fun BufferedSink.writeIdArray(array: LongArray) {
366     array.forEach { writeId(it) }
367   }
368 
369   private fun BufferedSink.write(array: BooleanArray) {
370     array.forEach { writeByte(if (it) 1 else 0) }
371   }
372 
373   private fun BufferedSink.write(array: CharArray) {
374     writeString(String(array), Charsets.UTF_16BE)
375   }
376 
377   private fun BufferedSink.write(array: FloatArray) {
378     array.forEach { writeFloat(it) }
379   }
380 
381   private fun BufferedSink.write(array: DoubleArray) {
382     array.forEach { writeDouble(it) }
383   }
384 
385   private fun BufferedSink.write(array: ShortArray) {
386     array.forEach { writeShort(it.toInt()) }
387   }
388 
389   private fun BufferedSink.write(array: IntArray) {
390     array.forEach { writeInt(it) }
391   }
392 
393   private fun BufferedSink.write(array: LongArray) {
394     array.forEach { writeLong(it) }
395   }
396 
397   private fun BufferedSink.writeNonHeapRecord(
398     tag: Int,
399     block: BufferedSink.() -> Unit
400   ) {
401     flushHeapBuffer()
402     workBuffer.block()
403     writeTagHeader(tag, workBuffer.size())
404     writeAll(workBuffer)
405   }
406 
407   private fun BufferedSink.flushHeapBuffer() {
408     if (workBuffer.size() > 0) {
409       writeTagHeader(HprofRecordTag.HEAP_DUMP.tag, workBuffer.size())
410       writeAll(workBuffer)
411       writeTagHeader(HprofRecordTag.HEAP_DUMP_END.tag, 0)
412     }
413   }
414 
415   private fun BufferedSink.writeTagHeader(
416     tag: Int,
417     length: Long
418   ) {
419     writeByte(tag)
420     // number of microseconds since the time stamp in the header
421     writeInt(0)
422     writeInt(length.toInt())
423   }
424 
425   private fun BufferedSink.writeId(id: Long) {
426     when (hprofHeader.identifierByteSize) {
427       1 -> writeByte(id.toInt())
428       2 -> writeShort(id.toInt())
429       4 -> writeInt(id.toInt())
430       8 -> writeLong(id)
431       else -> throw IllegalArgumentException("ID Length must be 1, 2, 4, or 8")
432     }
433   }
434 
435   companion object {
436 
437     fun openWriterFor(
438       hprofFile: File,
439       hprofHeader: HprofHeader = HprofHeader()
440     ): HprofWriter {
441       return openWriterFor(Okio.buffer(Okio.sink(hprofFile.outputStream())), hprofHeader)
442     }
443 
444     fun openWriterFor(
445       hprofSink: BufferedSink,
446       hprofHeader: HprofHeader = HprofHeader()
447     ): HprofWriter {
448       hprofSink.writeUtf8(hprofHeader.version.versionString)
449       hprofSink.writeByte(0)
450       hprofSink.writeInt(hprofHeader.identifierByteSize)
451       hprofSink.writeLong(hprofHeader.heapDumpTimestamp)
452       return HprofWriter(hprofSink, hprofHeader)
453     }
454 
455     @Deprecated(
456       "Replaced by HprofWriter.openWriterFor()",
457       ReplaceWith(
458         "shark.HprofWriter.openWriterFor(hprofFile)"
459       )
460     )
461     fun open(
462       hprofFile: File,
463       identifierByteSize: Int = 4,
464       hprofVersion: Hprof.HprofVersion = Hprof.HprofVersion.ANDROID
465     ): HprofWriter = openWriterFor(
466       hprofFile,
467       HprofHeader(
468         version = HprofVersion.valueOf(hprofVersion.name),
469         identifierByteSize = identifierByteSize
470       )
471     )
472   }
473 }
474