<lambda>null1 package shark.internal
2
3 import java.util.EnumSet
4 import kotlin.math.max
5 import shark.GcRoot
6 import shark.GcRoot.StickyClass
7 import shark.HprofHeader
8 import shark.HprofRecordReader
9 import shark.HprofRecordTag
10 import shark.HprofRecordTag.CLASS_DUMP
11 import shark.HprofRecordTag.INSTANCE_DUMP
12 import shark.HprofRecordTag.LOAD_CLASS
13 import shark.HprofRecordTag.OBJECT_ARRAY_DUMP
14 import shark.HprofRecordTag.PRIMITIVE_ARRAY_DUMP
15 import shark.HprofRecordTag.ROOT_DEBUGGER
16 import shark.HprofRecordTag.ROOT_FINALIZING
17 import shark.HprofRecordTag.ROOT_INTERNED_STRING
18 import shark.HprofRecordTag.ROOT_JAVA_FRAME
19 import shark.HprofRecordTag.ROOT_JNI_GLOBAL
20 import shark.HprofRecordTag.ROOT_JNI_LOCAL
21 import shark.HprofRecordTag.ROOT_JNI_MONITOR
22 import shark.HprofRecordTag.ROOT_MONITOR_USED
23 import shark.HprofRecordTag.ROOT_NATIVE_STACK
24 import shark.HprofRecordTag.ROOT_REFERENCE_CLEANUP
25 import shark.HprofRecordTag.ROOT_STICKY_CLASS
26 import shark.HprofRecordTag.ROOT_THREAD_BLOCK
27 import shark.HprofRecordTag.ROOT_THREAD_OBJECT
28 import shark.HprofRecordTag.ROOT_UNKNOWN
29 import shark.HprofRecordTag.ROOT_UNREACHABLE
30 import shark.HprofRecordTag.ROOT_VM_INTERNAL
31 import shark.HprofRecordTag.STRING_IN_UTF8
32 import shark.HprofVersion
33 import shark.HprofVersion.ANDROID
34 import shark.OnHprofRecordTagListener
35 import shark.PrimitiveType
36 import shark.PrimitiveType.INT
37 import shark.ProguardMapping
38 import shark.StreamingHprofReader
39 import shark.ValueHolder
40 import shark.internal.IndexedObject.IndexedClass
41 import shark.internal.IndexedObject.IndexedInstance
42 import shark.internal.IndexedObject.IndexedObjectArray
43 import shark.internal.IndexedObject.IndexedPrimitiveArray
44 import shark.internal.hppc.IntObjectPair
45 import shark.internal.hppc.LongLongScatterMap
46 import shark.internal.hppc.LongObjectPair
47 import shark.internal.hppc.LongObjectScatterMap
48 import shark.internal.hppc.LongScatterSet
49 import shark.internal.hppc.to
50
51 /**
52 * This class is not thread safe, should be used from a single thread.
53 */
54 internal class HprofInMemoryIndex private constructor(
55 private val positionSize: Int,
56 private val hprofStringCache: LongObjectScatterMap<String>,
57 private val classNames: LongLongScatterMap,
58 private val classIndex: SortedBytesMap,
59 private val instanceIndex: SortedBytesMap,
60 private val objectArrayIndex: SortedBytesMap,
61 private val primitiveArrayIndex: SortedBytesMap,
62 private val gcRoots: List<GcRoot>,
63 private val proguardMapping: ProguardMapping?,
64 private val bytesForClassSize: Int,
65 private val bytesForInstanceSize: Int,
66 private val bytesForObjectArraySize: Int,
67 private val bytesForPrimitiveArraySize: Int,
68 private val useForwardSlashClassPackageSeparator: Boolean,
69 val classFieldsReader: ClassFieldsReader,
70 private val classFieldsIndexSize: Int,
71 private val stickyClassGcRootIds: LongScatterSet,
72 ) {
73
74 val classCount: Int
75 get() = classIndex.size
76
77 val instanceCount: Int
78 get() = instanceIndex.size
79
80 val objectArrayCount: Int
81 get() = objectArrayIndex.size
82
83 val primitiveArrayCount: Int
84 get() = primitiveArrayIndex.size
85
86 fun fieldName(
87 classId: Long,
88 id: Long
89 ): String {
90 val fieldNameString = hprofStringById(id)
91 return proguardMapping?.let {
92 val classNameStringId = classNames[classId]
93 val classNameString = hprofStringById(classNameStringId)
94 proguardMapping.deobfuscateFieldName(classNameString, fieldNameString)
95 } ?: fieldNameString
96 }
97
98 fun className(classId: Long): String {
99 // String, primitive types
100 val classNameStringId = classNames[classId]
101 val classNameString = hprofStringById(classNameStringId)
102 return (proguardMapping?.deobfuscateClassName(classNameString) ?: classNameString).run {
103 if (useForwardSlashClassPackageSeparator) {
104 // JVM heap dumps use "/" for package separators (vs "." for Android heap dumps)
105 replace('/', '.')
106 } else this
107 }
108 }
109
110 /**
111 * Returns the first class that matches the provided name, prioritizing system classes (as held
112 * by sticky class gc roots).
113 *
114 * On Android, all currently loaded classes are sticky. The heap dump may also contain classes
115 * that were unloaded but not garbage collected yet, leading to classes being present twice
116 * in the heap dump. To work around that we prioritize classes that are held by a sticky class GC
117 * root.
118 */
119 fun classId(className: String): Long? {
120 val internalClassName = if (useForwardSlashClassPackageSeparator) {
121 // JVM heap dumps use "/" for package separators (vs "." for Android heap dumps)
122 className.replace('.', '/')
123 } else className
124
125 // Note: this performs two linear scans over arrays
126 val hprofStringId = hprofStringCache.entrySequence()
127 .firstOrNull { it.second == internalClassName }
128 ?.first ?: return null
129
130 val classNamesIterator = classNames.entrySequence().iterator()
131
132 var firstNonStickyMatchingClass: Long? = null
133 while(classNamesIterator.hasNext()) {
134 val (classId, classNameStringId) = classNamesIterator.next()
135 if (hprofStringId == classNameStringId) {
136 if (classId in stickyClassGcRootIds) {
137 return classId
138 } else {
139 firstNonStickyMatchingClass = classId
140 }
141 }
142 }
143 return firstNonStickyMatchingClass
144 }
145
146 fun indexedClassSequence(): Sequence<LongObjectPair<IndexedClass>> {
147 return classIndex.entrySequence()
148 .map {
149 val id = it.first
150 val array = it.second
151 id to array.readClass()
152 }
153 }
154
155 fun indexedInstanceSequence(): Sequence<LongObjectPair<IndexedInstance>> {
156 return instanceIndex.entrySequence()
157 .map {
158 val id = it.first
159 val array = it.second
160 val instance = IndexedInstance(
161 position = array.readTruncatedLong(positionSize),
162 classId = array.readId(),
163 recordSize = array.readTruncatedLong(bytesForInstanceSize)
164 )
165 id to instance
166 }
167 }
168
169 fun indexedObjectArraySequence(): Sequence<LongObjectPair<IndexedObjectArray>> {
170 return objectArrayIndex.entrySequence()
171 .map {
172 val id = it.first
173 val array = it.second
174 val objectArray = IndexedObjectArray(
175 position = array.readTruncatedLong(positionSize),
176 arrayClassId = array.readId(),
177 recordSize = array.readTruncatedLong(bytesForObjectArraySize)
178 )
179 id to objectArray
180 }
181 }
182
183 fun indexedPrimitiveArraySequence(): Sequence<LongObjectPair<IndexedPrimitiveArray>> {
184 return primitiveArrayIndex.entrySequence()
185 .map {
186 val id = it.first
187 val array = it.second
188
189 val primitiveArray = IndexedPrimitiveArray(
190 position = array.readTruncatedLong(positionSize),
191 primitiveType = PrimitiveType.values()[array.readByte()
192 .toInt()],
193 recordSize = array.readTruncatedLong(bytesForPrimitiveArraySize)
194 )
195 id to primitiveArray
196 }
197 }
198
199 fun indexedObjectSequence(): Sequence<LongObjectPair<IndexedObject>> {
200 return indexedClassSequence() +
201 indexedInstanceSequence() +
202 indexedObjectArraySequence() +
203 indexedPrimitiveArraySequence()
204 }
205
206 fun gcRoots(): List<GcRoot> {
207 return gcRoots
208 }
209
210 fun objectAtIndex(index: Int): LongObjectPair<IndexedObject> {
211 require(index > 0)
212 if (index < classIndex.size) {
213 val objectId = classIndex.keyAt(index)
214 val array = classIndex.getAtIndex(index)
215 return objectId to array.readClass()
216 }
217 var shiftedIndex = index - classIndex.size
218 if (shiftedIndex < instanceIndex.size) {
219 val objectId = instanceIndex.keyAt(shiftedIndex)
220 val array = instanceIndex.getAtIndex(shiftedIndex)
221 return objectId to IndexedInstance(
222 position = array.readTruncatedLong(positionSize),
223 classId = array.readId(),
224 recordSize = array.readTruncatedLong(bytesForInstanceSize)
225 )
226 }
227 shiftedIndex -= instanceIndex.size
228 if (shiftedIndex < objectArrayIndex.size) {
229 val objectId = objectArrayIndex.keyAt(shiftedIndex)
230 val array = objectArrayIndex.getAtIndex(shiftedIndex)
231 return objectId to IndexedObjectArray(
232 position = array.readTruncatedLong(positionSize),
233 arrayClassId = array.readId(),
234 recordSize = array.readTruncatedLong(bytesForObjectArraySize)
235 )
236 }
237 shiftedIndex -= objectArrayIndex.size
238 require(index < primitiveArrayIndex.size)
239 val objectId = primitiveArrayIndex.keyAt(shiftedIndex)
240 val array = primitiveArrayIndex.getAtIndex(shiftedIndex)
241 return objectId to IndexedPrimitiveArray(
242 position = array.readTruncatedLong(positionSize),
243 primitiveType = PrimitiveType.values()[array.readByte()
244 .toInt()],
245 recordSize = array.readTruncatedLong(bytesForPrimitiveArraySize)
246 )
247 }
248
249 @Suppress("ReturnCount")
250 fun indexedObjectOrNull(objectId: Long): IntObjectPair<IndexedObject>? {
251 var index = classIndex.indexOf(objectId)
252 if (index >= 0) {
253 val array = classIndex.getAtIndex(index)
254 return index to array.readClass()
255 }
256 index = instanceIndex.indexOf(objectId)
257 if (index >= 0) {
258 val array = instanceIndex.getAtIndex(index)
259 return classIndex.size + index to IndexedInstance(
260 position = array.readTruncatedLong(positionSize),
261 classId = array.readId(),
262 recordSize = array.readTruncatedLong(bytesForInstanceSize)
263 )
264 }
265 index = objectArrayIndex.indexOf(objectId)
266 if (index >= 0) {
267 val array = objectArrayIndex.getAtIndex(index)
268 return classIndex.size + instanceIndex.size + index to IndexedObjectArray(
269 position = array.readTruncatedLong(positionSize),
270 arrayClassId = array.readId(),
271 recordSize = array.readTruncatedLong(bytesForObjectArraySize)
272 )
273 }
274 index = primitiveArrayIndex.indexOf(objectId)
275 if (index >= 0) {
276 val array = primitiveArrayIndex.getAtIndex(index)
277 return classIndex.size + instanceIndex.size + index + primitiveArrayIndex.size to IndexedPrimitiveArray(
278 position = array.readTruncatedLong(positionSize),
279 primitiveType = PrimitiveType.values()[array.readByte()
280 .toInt()],
281 recordSize = array.readTruncatedLong(bytesForPrimitiveArraySize)
282 )
283 }
284 return null
285 }
286
287 private fun ByteSubArray.readClass(): IndexedClass {
288 val position = readTruncatedLong(positionSize)
289 val superclassId = readId()
290 val instanceSize = readInt()
291
292 val recordSize = readTruncatedLong(bytesForClassSize)
293 val fieldsIndex = readTruncatedLong(classFieldsIndexSize).toInt()
294
295 return IndexedClass(
296 position = position,
297 superclassId = superclassId,
298 instanceSize = instanceSize,
299 recordSize = recordSize,
300 fieldsIndex = fieldsIndex
301 )
302 }
303
304 @Suppress("ReturnCount")
305 fun objectIdIsIndexed(objectId: Long): Boolean {
306 if (classIndex[objectId] != null) {
307 return true
308 }
309 if (instanceIndex[objectId] != null) {
310 return true
311 }
312 if (objectArrayIndex[objectId] != null) {
313 return true
314 }
315 if (primitiveArrayIndex[objectId] != null) {
316 return true
317 }
318 return false
319 }
320
321 private fun hprofStringById(id: Long): String {
322 return hprofStringCache[id] ?: throw IllegalArgumentException("Hprof string $id not in cache")
323 }
324
325 private class Builder(
326 longIdentifiers: Boolean,
327 maxPosition: Long,
328 classCount: Int,
329 instanceCount: Int,
330 objectArrayCount: Int,
331 primitiveArrayCount: Int,
332 val bytesForClassSize: Int,
333 val bytesForInstanceSize: Int,
334 val bytesForObjectArraySize: Int,
335 val bytesForPrimitiveArraySize: Int,
336 val classFieldsTotalBytes: Int,
337 val stickyClassGcRootIds: LongScatterSet,
338 ) : OnHprofRecordTagListener {
339
340 private val identifierSize = if (longIdentifiers) 8 else 4
341 private val positionSize = byteSizeForUnsigned(maxPosition)
342 private val classFieldsIndexSize = byteSizeForUnsigned(classFieldsTotalBytes.toLong())
343
344 /**
345 * Map of string id to string
346 * This currently keeps all the hprof strings that we could care about: class names,
347 * static field names and instance fields names
348 */
349 // TODO Replacing with a radix trie reversed into a sparse array of long to trie leaf could save
350 // memory. Can be stored as 3 arrays: array of keys, array of values which are indexes into
351 // a large array of string bytes. Each "entry" consists of a size, the index of the previous
352 // segment and then the segment content.
353
354 private val hprofStringCache = LongObjectScatterMap<String>()
355
356 /**
357 * class id to string id
358 */
359 private val classNames = LongLongScatterMap(expectedElements = classCount)
360
361 private val classFieldBytes = ByteArray(classFieldsTotalBytes)
362
363 private var classFieldsIndex = 0
364
365 private val classIndex = UnsortedByteEntries(
366 bytesPerValue = positionSize + identifierSize + 4 + bytesForClassSize + classFieldsIndexSize,
367 longIdentifiers = longIdentifiers,
368 initialCapacity = classCount
369 )
370 private val instanceIndex = UnsortedByteEntries(
371 bytesPerValue = positionSize + identifierSize + bytesForInstanceSize,
372 longIdentifiers = longIdentifiers,
373 initialCapacity = instanceCount
374 )
375 private val objectArrayIndex = UnsortedByteEntries(
376 bytesPerValue = positionSize + identifierSize + bytesForObjectArraySize,
377 longIdentifiers = longIdentifiers,
378 initialCapacity = objectArrayCount
379 )
380 private val primitiveArrayIndex = UnsortedByteEntries(
381 bytesPerValue = positionSize + 1 + bytesForPrimitiveArraySize,
382 longIdentifiers = longIdentifiers,
383 initialCapacity = primitiveArrayCount
384 )
385
386 // Pre seeding gc roots with the sticky class gc roots we've already parsed.
387 private val gcRoots: MutableList<GcRoot> = ArrayList<GcRoot>(stickyClassGcRootIds.size()).apply {
388 stickyClassGcRootIds.elementSequence().forEach {classId ->
389 add(StickyClass(classId))
390 }
391 }
392
393 private fun HprofRecordReader.copyToClassFields(byteCount: Int) {
394 for (i in 1..byteCount) {
395 classFieldBytes[classFieldsIndex++] = readByte()
396 }
397 }
398
399 private fun lastClassFieldsShort() =
400 ((classFieldBytes[classFieldsIndex - 2].toInt() and 0xff shl 8) or
401 (classFieldBytes[classFieldsIndex - 1].toInt() and 0xff)).toShort()
402
403 @Suppress("LongMethod")
404 override fun onHprofRecord(
405 tag: HprofRecordTag,
406 length: Long,
407 reader: HprofRecordReader
408 ) {
409 when (tag) {
410 STRING_IN_UTF8 -> {
411 hprofStringCache[reader.readId()] = reader.readUtf8(length - identifierSize)
412 }
413 LOAD_CLASS -> {
414 // classSerialNumber
415 reader.skip(INT.byteSize)
416 val id = reader.readId()
417 // stackTraceSerialNumber
418 reader.skip(INT.byteSize)
419 val classNameStringId = reader.readId()
420 classNames[id] = classNameStringId
421 }
422 ROOT_UNKNOWN -> {
423 reader.readUnknownGcRootRecord().apply {
424 if (id != ValueHolder.NULL_REFERENCE) {
425 gcRoots += this
426 }
427 }
428 }
429 ROOT_JNI_GLOBAL -> {
430 reader.readJniGlobalGcRootRecord().apply {
431 if (id != ValueHolder.NULL_REFERENCE) {
432 gcRoots += this
433 }
434 }
435 }
436 ROOT_JNI_LOCAL -> {
437 reader.readJniLocalGcRootRecord().apply {
438 if (id != ValueHolder.NULL_REFERENCE) {
439 gcRoots += this
440 }
441 }
442 }
443 ROOT_JAVA_FRAME -> {
444 reader.readJavaFrameGcRootRecord().apply {
445 if (id != ValueHolder.NULL_REFERENCE) {
446 gcRoots += this
447 }
448 }
449 }
450 ROOT_NATIVE_STACK -> {
451 reader.readNativeStackGcRootRecord().apply {
452 if (id != ValueHolder.NULL_REFERENCE) {
453 gcRoots += this
454 }
455 }
456 }
457 ROOT_STICKY_CLASS -> {
458 // We already parse these gc roots in the initial scan.
459 reader.skipId()
460 }
461 ROOT_THREAD_BLOCK -> {
462 reader.readThreadBlockGcRootRecord().apply {
463 if (id != ValueHolder.NULL_REFERENCE) {
464 gcRoots += this
465 }
466 }
467 }
468 ROOT_MONITOR_USED -> {
469 reader.readMonitorUsedGcRootRecord().apply {
470 if (id != ValueHolder.NULL_REFERENCE) {
471 gcRoots += this
472 }
473 }
474 }
475 ROOT_THREAD_OBJECT -> {
476 reader.readThreadObjectGcRootRecord().apply {
477 if (id != ValueHolder.NULL_REFERENCE) {
478 gcRoots += this
479 }
480 }
481 }
482 ROOT_INTERNED_STRING -> {
483 reader.readInternedStringGcRootRecord().apply {
484 if (id != ValueHolder.NULL_REFERENCE) {
485 gcRoots += this
486 }
487 }
488 }
489 ROOT_FINALIZING -> {
490 reader.readFinalizingGcRootRecord().apply {
491 if (id != ValueHolder.NULL_REFERENCE) {
492 gcRoots += this
493 }
494 }
495 }
496 ROOT_DEBUGGER -> {
497 reader.readDebuggerGcRootRecord().apply {
498 if (id != ValueHolder.NULL_REFERENCE) {
499 gcRoots += this
500 }
501 }
502 }
503 ROOT_REFERENCE_CLEANUP -> {
504 reader.readReferenceCleanupGcRootRecord().apply {
505 if (id != ValueHolder.NULL_REFERENCE) {
506 gcRoots += this
507 }
508 }
509 }
510 ROOT_VM_INTERNAL -> {
511 reader.readVmInternalGcRootRecord().apply {
512 if (id != ValueHolder.NULL_REFERENCE) {
513 gcRoots += this
514 }
515 }
516 }
517 ROOT_JNI_MONITOR -> {
518 reader.readJniMonitorGcRootRecord().apply {
519 if (id != ValueHolder.NULL_REFERENCE) {
520 gcRoots += this
521 }
522 }
523 }
524 ROOT_UNREACHABLE -> {
525 reader.readUnreachableGcRootRecord().apply {
526 if (id != ValueHolder.NULL_REFERENCE) {
527 gcRoots += this
528 }
529 }
530 }
531 CLASS_DUMP -> {
532 val bytesReadStart = reader.bytesRead
533 val id = reader.readId()
534 // stack trace serial number
535 reader.skip(INT.byteSize)
536 val superclassId = reader.readId()
537 reader.skip(5 * identifierSize)
538
539 // instance size (in bytes)
540 // Useful to compute retained size
541 val instanceSize = reader.readInt()
542
543 reader.skipClassDumpConstantPool()
544
545 val startPosition = classFieldsIndex
546
547 val bytesReadFieldStart = reader.bytesRead
548
549 reader.copyToClassFields(2)
550 val staticFieldCount = lastClassFieldsShort().toInt() and 0xFFFF
551 for (i in 0 until staticFieldCount) {
552 reader.copyToClassFields(identifierSize)
553 reader.copyToClassFields(1)
554 val type = classFieldBytes[classFieldsIndex - 1].toInt() and 0xff
555 if (type == PrimitiveType.REFERENCE_HPROF_TYPE) {
556 reader.copyToClassFields(identifierSize)
557 } else {
558 reader.copyToClassFields(PrimitiveType.byteSizeByHprofType.getValue(type))
559 }
560 }
561
562 reader.copyToClassFields(2)
563 val fieldCount = lastClassFieldsShort().toInt() and 0xFFFF
564 for (i in 0 until fieldCount) {
565 reader.copyToClassFields(identifierSize)
566 reader.copyToClassFields(1)
567 }
568
569 val fieldsSize = (reader.bytesRead - bytesReadFieldStart).toInt()
570 val recordSize = reader.bytesRead - bytesReadStart
571
572 classIndex.append(id)
573 .apply {
574 writeTruncatedLong(bytesReadStart, positionSize)
575 writeId(superclassId)
576 writeInt(instanceSize)
577 writeTruncatedLong(recordSize, bytesForClassSize)
578 writeTruncatedLong(startPosition.toLong(), classFieldsIndexSize)
579 }
580 require(startPosition + fieldsSize == classFieldsIndex) {
581 "Expected $classFieldsIndex to have moved by $fieldsSize and be equal to ${startPosition + fieldsSize}"
582 }
583 }
584 INSTANCE_DUMP -> {
585 val bytesReadStart = reader.bytesRead
586 val id = reader.readId()
587 reader.skip(INT.byteSize)
588 val classId = reader.readId()
589 val remainingBytesInInstance = reader.readInt()
590 reader.skip(remainingBytesInInstance)
591 val recordSize = reader.bytesRead - bytesReadStart
592 instanceIndex.append(id)
593 .apply {
594 writeTruncatedLong(bytesReadStart, positionSize)
595 writeId(classId)
596 writeTruncatedLong(recordSize, bytesForInstanceSize)
597 }
598 }
599 OBJECT_ARRAY_DUMP -> {
600 val bytesReadStart = reader.bytesRead
601 val id = reader.readId()
602 // stack trace serial number
603 reader.skip(INT.byteSize)
604 val size = reader.readInt()
605 val arrayClassId = reader.readId()
606 reader.skip(identifierSize * size)
607 // record size - (ID+INT + INT + ID)
608 val recordSize = reader.bytesRead - bytesReadStart
609 objectArrayIndex.append(id)
610 .apply {
611 writeTruncatedLong(bytesReadStart, positionSize)
612 writeId(arrayClassId)
613 writeTruncatedLong(recordSize, bytesForObjectArraySize)
614 }
615 }
616 PRIMITIVE_ARRAY_DUMP -> {
617 val bytesReadStart = reader.bytesRead
618 val id = reader.readId()
619 reader.skip(INT.byteSize)
620 val size = reader.readInt()
621 val type = PrimitiveType.primitiveTypeByHprofType.getValue(reader.readUnsignedByte())
622 reader.skip(size * type.byteSize)
623 val recordSize = reader.bytesRead - bytesReadStart
624 primitiveArrayIndex.append(id)
625 .apply {
626 writeTruncatedLong(bytesReadStart, positionSize)
627 writeByte(type.ordinal.toByte())
628 writeTruncatedLong(recordSize, bytesForPrimitiveArraySize)
629 }
630 }
631 }
632 }
633
634 fun buildIndex(
635 proguardMapping: ProguardMapping?,
636 hprofHeader: HprofHeader
637 ): HprofInMemoryIndex {
638 require(classFieldsIndex == classFieldBytes.size) {
639 "Read $classFieldsIndex into fields bytes instead of expected ${classFieldBytes.size}"
640 }
641
642 val sortedInstanceIndex = instanceIndex.moveToSortedMap()
643 val sortedObjectArrayIndex = objectArrayIndex.moveToSortedMap()
644 val sortedPrimitiveArrayIndex = primitiveArrayIndex.moveToSortedMap()
645 val sortedClassIndex = classIndex.moveToSortedMap()
646 // Passing references to avoid copying the underlying data structures.
647 return HprofInMemoryIndex(
648 positionSize = positionSize,
649 hprofStringCache = hprofStringCache,
650 classNames = classNames,
651 classIndex = sortedClassIndex,
652 instanceIndex = sortedInstanceIndex,
653 objectArrayIndex = sortedObjectArrayIndex,
654 primitiveArrayIndex = sortedPrimitiveArrayIndex,
655 gcRoots = gcRoots,
656 proguardMapping = proguardMapping,
657 bytesForClassSize = bytesForClassSize,
658 bytesForInstanceSize = bytesForInstanceSize,
659 bytesForObjectArraySize = bytesForObjectArraySize,
660 bytesForPrimitiveArraySize = bytesForPrimitiveArraySize,
661 useForwardSlashClassPackageSeparator = hprofHeader.version != ANDROID,
662 classFieldsReader = ClassFieldsReader(identifierSize, classFieldBytes),
663 classFieldsIndexSize = classFieldsIndexSize,
664 stickyClassGcRootIds = stickyClassGcRootIds,
665 )
666 }
667 }
668
669 companion object {
670
671 private fun byteSizeForUnsigned(maxValue: Long): Int {
672 var value = maxValue
673 var byteCount = 0
674 while (value != 0L) {
675 value = value shr 8
676 byteCount++
677 }
678 return byteCount
679 }
680
681 fun indexHprof(
682 reader: StreamingHprofReader,
683 hprofHeader: HprofHeader,
684 proguardMapping: ProguardMapping?,
685 indexedGcRootTags: Set<HprofRecordTag>
686 ): HprofInMemoryIndex {
687
688 // First pass to count and correctly size arrays once and for all.
689 var maxClassSize = 0L
690 var maxInstanceSize = 0L
691 var maxObjectArraySize = 0L
692 var maxPrimitiveArraySize = 0L
693 var classCount = 0
694 var instanceCount = 0
695 var objectArrayCount = 0
696 var primitiveArrayCount = 0
697 var classFieldsTotalBytes = 0
698 val stickyClassGcRootIds = LongScatterSet()
699
700 val bytesRead = reader.readRecords(
701 EnumSet.of(
702 CLASS_DUMP,
703 INSTANCE_DUMP,
704 OBJECT_ARRAY_DUMP,
705 PRIMITIVE_ARRAY_DUMP,
706 ROOT_STICKY_CLASS
707 )
708 ) { tag, _, reader ->
709 val bytesReadStart = reader.bytesRead
710 when (tag) {
711 CLASS_DUMP -> {
712 classCount++
713 reader.skipClassDumpHeader()
714 val bytesReadStaticFieldStart = reader.bytesRead
715 reader.skipClassDumpStaticFields()
716 reader.skipClassDumpFields()
717 maxClassSize = max(maxClassSize, reader.bytesRead - bytesReadStart)
718 classFieldsTotalBytes += (reader.bytesRead - bytesReadStaticFieldStart).toInt()
719 }
720 INSTANCE_DUMP -> {
721 instanceCount++
722 reader.skipInstanceDumpRecord()
723 maxInstanceSize = max(maxInstanceSize, reader.bytesRead - bytesReadStart)
724 }
725 OBJECT_ARRAY_DUMP -> {
726 objectArrayCount++
727 reader.skipObjectArrayDumpRecord()
728 maxObjectArraySize = max(maxObjectArraySize, reader.bytesRead - bytesReadStart)
729 }
730 PRIMITIVE_ARRAY_DUMP -> {
731 primitiveArrayCount++
732 reader.skipPrimitiveArrayDumpRecord()
733 maxPrimitiveArraySize = max(maxPrimitiveArraySize, reader.bytesRead - bytesReadStart)
734 }
735 ROOT_STICKY_CLASS -> {
736 // StickyClass has only 1 field: id. Our API 23 emulators in CI are creating heap
737 // dumps with duplicated sticky class roots, up to 30K times for some objects.
738 // There's no point in keeping all these in our list of roots, 1 per each is enough
739 // so we deduplicate with stickyClassGcRootIds.
740 val id = reader.readStickyClassGcRootRecord().id
741 if (id != ValueHolder.NULL_REFERENCE) {
742 stickyClassGcRootIds += id
743 }
744 }
745 }
746 }
747
748 val bytesForClassSize = byteSizeForUnsigned(maxClassSize)
749 val bytesForInstanceSize = byteSizeForUnsigned(maxInstanceSize)
750 val bytesForObjectArraySize = byteSizeForUnsigned(maxObjectArraySize)
751 val bytesForPrimitiveArraySize = byteSizeForUnsigned(maxPrimitiveArraySize)
752
753 val indexBuilderListener = Builder(
754 longIdentifiers = hprofHeader.identifierByteSize == 8,
755 maxPosition = bytesRead,
756 classCount = classCount,
757 instanceCount = instanceCount,
758 objectArrayCount = objectArrayCount,
759 primitiveArrayCount = primitiveArrayCount,
760 bytesForClassSize = bytesForClassSize,
761 bytesForInstanceSize = bytesForInstanceSize,
762 bytesForObjectArraySize = bytesForObjectArraySize,
763 bytesForPrimitiveArraySize = bytesForPrimitiveArraySize,
764 classFieldsTotalBytes = classFieldsTotalBytes,
765 stickyClassGcRootIds
766 )
767
768 val recordTypes = EnumSet.of(
769 STRING_IN_UTF8,
770 LOAD_CLASS,
771 CLASS_DUMP,
772 INSTANCE_DUMP,
773 OBJECT_ARRAY_DUMP,
774 PRIMITIVE_ARRAY_DUMP
775 ) + HprofRecordTag.rootTags.intersect(indexedGcRootTags)
776
777 reader.readRecords(recordTypes, indexBuilderListener)
778 return indexBuilderListener.buildIndex(proguardMapping, hprofHeader)
779 }
780 }
781 }
782