<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