All Downloads are FREE. Search and download functionalities are using the official Maven repository.

kshark.HprofRecordReader.kt Maven / Gradle / Ivy

package kshark

import okio.BufferedSource
import kshark.GcRoot.Debugger
import kshark.GcRoot.Finalizing
import kshark.GcRoot.InternedString
import kshark.GcRoot.JavaFrame
import kshark.GcRoot.JniGlobal
import kshark.GcRoot.JniLocal
import kshark.GcRoot.JniMonitor
import kshark.GcRoot.MonitorUsed
import kshark.GcRoot.NativeStack
import kshark.GcRoot.ReferenceCleanup
import kshark.GcRoot.StickyClass
import kshark.GcRoot.ThreadBlock
import kshark.GcRoot.ThreadObject
import kshark.GcRoot.Unknown
import kshark.GcRoot.Unreachable
import kshark.GcRoot.VmInternal
import kshark.HprofRecord.HeapDumpRecord.HeapDumpInfoRecord
import kshark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord
import kshark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.FieldRecord
import kshark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.StaticFieldRecord
import kshark.HprofRecord.HeapDumpRecord.ObjectRecord.InstanceDumpRecord
import kshark.HprofRecord.HeapDumpRecord.ObjectRecord.ObjectArrayDumpRecord
import kshark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord
import kshark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.BooleanArrayDump
import kshark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ByteArrayDump
import kshark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.CharArrayDump
import kshark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.DoubleArrayDump
import kshark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.FloatArrayDump
import kshark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.IntArrayDump
import kshark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.LongArrayDump
import kshark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ShortArrayDump
import kshark.HprofRecord.LoadClassRecord
import kshark.HprofRecord.StackFrameRecord
import kshark.HprofRecord.StackTraceRecord
import kshark.HprofRecord.StringRecord
import kshark.PrimitiveType.BOOLEAN
import kshark.PrimitiveType.BYTE
import kshark.PrimitiveType.CHAR
import kshark.PrimitiveType.DOUBLE
import kshark.PrimitiveType.FLOAT
import kshark.PrimitiveType.INT
import kshark.PrimitiveType.LONG
import kshark.PrimitiveType.SHORT
import kshark.ValueHolder.BooleanHolder
import kshark.ValueHolder.ByteHolder
import kshark.ValueHolder.CharHolder
import kshark.ValueHolder.DoubleHolder
import kshark.ValueHolder.FloatHolder
import kshark.ValueHolder.IntHolder
import kshark.ValueHolder.LongHolder
import kshark.ValueHolder.ReferenceHolder
import kshark.ValueHolder.ShortHolder
import java.nio.charset.Charset

/**
 * Reads hprof content from an Okio [BufferedSource].
 *
 * Binary Dump Format reference: http://hg.openjdk.java.net/jdk6/jdk6/jdk/raw-file/tip/src/share
 * /demo/jvmti/hprof/manual.html#mozTocId848088
 *
 * The Android Hprof format differs in some ways from that reference. This parser implementation
 * is largely adapted from https://android.googlesource.com/platform/tools/base/+/studio-master-dev
 * /perflib/src/main/java/com/android/tools/perflib
 *
 * Not thread safe, should be used from a single thread.
 */
@Suppress("LargeClass", "TooManyFunctions")
class HprofRecordReader internal constructor(
  header: HprofHeader,
  private val source: BufferedSource
) {

  /**
   * How many bytes this reader has read from [source]. Can only increase.
   */
  var bytesRead = 0L
    private set

  private val identifierByteSize = header.identifierByteSize

  private val typeSizes: IntArray

  init {
    val typeSizesMap =
      PrimitiveType.byteSizeByHprofType + (PrimitiveType.REFERENCE_HPROF_TYPE to identifierByteSize)

    val maxKey = typeSizesMap.keys.max()!!

    typeSizes = IntArray(maxKey + 1) { key ->
      typeSizesMap[key] ?: 0
    }
  }

  fun sizeOf(type: Int) = typeSizes[type]

  fun readStringRecord(length: Long) = StringRecord(
    id = readId(),
    string = readUtf8(length - identifierByteSize)
  )

  fun readLoadClassRecord() = LoadClassRecord(
    classSerialNumber = readInt(),
    id = readId(),
    stackTraceSerialNumber = readInt(),
    classNameStringId = readId()
  )

  fun readStackFrameRecord() = StackFrameRecord(
    id = readId(),
    methodNameStringId = readId(),
    methodSignatureStringId = readId(),
    sourceFileNameStringId = readId(),
    classSerialNumber = readInt(),
    lineNumber = readInt()
  )

  fun readStackTraceRecord() = StackTraceRecord(
    stackTraceSerialNumber = readInt(),
    threadSerialNumber = readInt(),
    stackFrameIds = readIdArray(readInt())
  )

  fun readUnknownGcRootRecord() = Unknown(id = readId())

  fun readJniGlobalGcRootRecord() = JniGlobal(
    id = readId(),
    jniGlobalRefId = readId()
  )

  fun readJniLocalGcRootRecord() = JniLocal(
    id = readId(),
    threadSerialNumber = readInt(),
    frameNumber = readInt()
  )

  fun readJavaFrameGcRootRecord() = JavaFrame(
    id = readId(),
    threadSerialNumber = readInt(),
    frameNumber = readInt()
  )

  fun readNativeStackGcRootRecord() = NativeStack(
    id = readId(),
    threadSerialNumber = readInt()
  )

  fun readStickyClassGcRootRecord() = StickyClass(id = readId())

  fun readThreadBlockGcRootRecord() = ThreadBlock(id = readId(), threadSerialNumber = readInt())

  fun readMonitorUsedGcRootRecord() = MonitorUsed(id = readId())

  fun readThreadObjectGcRootRecord() = ThreadObject(
    id = readId(),
    threadSerialNumber = readInt(),
    stackTraceSerialNumber = readInt()
  )

  fun readInternedStringGcRootRecord() = InternedString(id = readId())

  fun readFinalizingGcRootRecord() = Finalizing(id = readId())

  fun readDebuggerGcRootRecord() = Debugger(id = readId())

  fun readReferenceCleanupGcRootRecord() = ReferenceCleanup(id = readId())

  fun readVmInternalGcRootRecord() = VmInternal(id = readId())

  fun readJniMonitorGcRootRecord() = JniMonitor(
    id = readId(),
    stackTraceSerialNumber = readInt(),
    stackDepth = readInt()
  )

  fun readUnreachableGcRootRecord() = Unreachable(id = readId())

  /**
   * Reads a full instance record after a instance dump tag.
   */
  fun readInstanceDumpRecord(): InstanceDumpRecord {
    val id = readId()
    val stackTraceSerialNumber = readInt()
    val classId = readId()
    val remainingBytesInInstance = readInt()
    val fieldValues = readByteArray(remainingBytesInInstance)
    return InstanceDumpRecord(
      id = id,
      stackTraceSerialNumber = stackTraceSerialNumber,
      classId = classId,
      fieldValues = fieldValues
    )
  }

  fun readHeapDumpInfoRecord(): HeapDumpInfoRecord {
    val heapId = readInt()
    return HeapDumpInfoRecord(heapId = heapId, heapNameStringId = readId())
  }

  /**
   * Reads a full class record after a class dump tag.
   */
  fun readClassDumpRecord(): ClassDumpRecord {
    val id = readId()
    // stack trace serial number
    val stackTraceSerialNumber = readInt()
    val superclassId = readId()
    // class loader object ID
    val classLoaderId = readId()
    // signers object ID
    val signersId = readId()
    // protection domain object ID
    val protectionDomainId = readId()
    // reserved
    readId()
    // reserved
    readId()

    // instance size (in bytes)
    // Useful to compute retained size
    val instanceSize = readInt()

    // Skip over the constant pool
    val constantPoolCount = readUnsignedShort()
    for (i in 0 until constantPoolCount) {
      // constant pool index
      skip(SHORT_SIZE)
      skip(typeSizes[readUnsignedByte()])
    }

    val staticFieldCount = readUnsignedShort()
    val staticFields = ArrayList(staticFieldCount)
    for (i in 0 until staticFieldCount) {

      val nameStringId = readId()
      val type = readUnsignedByte()
      val value = readValue(type)

      staticFields.add(
        StaticFieldRecord(
          nameStringId = nameStringId,
          type = type,
          value = value
        )
      )
    }

    val fieldCount = readUnsignedShort()
    val fields = ArrayList(fieldCount)
    for (i in 0 until fieldCount) {
      fields.add(FieldRecord(nameStringId = readId(), type = readUnsignedByte()))
    }

    return ClassDumpRecord(
      id = id,
      stackTraceSerialNumber = stackTraceSerialNumber,
      superclassId = superclassId,
      classLoaderId = classLoaderId,
      signersId = signersId,
      protectionDomainId = protectionDomainId,
      instanceSize = instanceSize,
      staticFields = staticFields,
      fields = fields
    )
  }

  /**
   * Reads a full primitive array record after a primitive array dump tag.
   */
  fun readPrimitiveArrayDumpRecord(): PrimitiveArrayDumpRecord {
    val id = readId()
    val stackTraceSerialNumber = readInt()
    // length
    val arrayLength = readInt()
    return when (val type = readUnsignedByte()) {
      BOOLEAN_TYPE -> BooleanArrayDump(
        id, stackTraceSerialNumber, readBooleanArray(arrayLength)
      )
      CHAR_TYPE -> CharArrayDump(
        id, stackTraceSerialNumber, readCharArray(arrayLength)
      )
      FLOAT_TYPE -> FloatArrayDump(
        id, stackTraceSerialNumber, readFloatArray(arrayLength)
      )
      DOUBLE_TYPE -> DoubleArrayDump(
        id, stackTraceSerialNumber, readDoubleArray(arrayLength)
      )
      BYTE_TYPE -> ByteArrayDump(
        id, stackTraceSerialNumber, readByteArray(arrayLength)
      )
      SHORT_TYPE -> ShortArrayDump(
        id, stackTraceSerialNumber, readShortArray(arrayLength)
      )
      INT_TYPE -> IntArrayDump(
        id, stackTraceSerialNumber, readIntArray(arrayLength)
      )
      LONG_TYPE -> LongArrayDump(
        id, stackTraceSerialNumber, readLongArray(arrayLength)
      )
      else -> throw IllegalStateException("Unexpected type $type")
    }
  }

  /**
   * Reads a full object array record after a object array dump tag.
   */
  fun readObjectArrayDumpRecord(): ObjectArrayDumpRecord {
    val id = readId()
    // stack trace serial number
    val stackTraceSerialNumber = readInt()
    val arrayLength = readInt()
    val arrayClassId = readId()
    val elementIds = readIdArray(arrayLength)
    return ObjectArrayDumpRecord(
      id = id,
      stackTraceSerialNumber = stackTraceSerialNumber,
      arrayClassId = arrayClassId,
      elementIds = elementIds
    )
  }

  fun skipClassDumpHeader() {
    skip(INT_SIZE * 2 + identifierByteSize * 7)
    skipClassDumpConstantPool()
  }

  fun skipClassDumpConstantPool() {
    // Skip over the constant pool
    val constantPoolCount = readUnsignedShort()
    for (i in 0 until constantPoolCount) {
      // constant pool index
      skip(SHORT.byteSize)
      skip(sizeOf(readUnsignedByte()))
    }
  }

  fun skipClassDumpStaticFields() {
    val staticFieldCount = readUnsignedShort()
    for (i in 0 until staticFieldCount) {
      skip(identifierByteSize)
      val type = readUnsignedByte()
      skip(
        if (type == PrimitiveType.REFERENCE_HPROF_TYPE) {
          identifierByteSize
        } else {
          PrimitiveType.byteSizeByHprofType.getValue(type)
        }
      )
    }
  }

  fun skipClassDumpFields() {
    val fieldCount = readUnsignedShort()
    skip((identifierByteSize + 1) * fieldCount)
  }

  fun skipInstanceDumpRecord() {
    skip(identifierByteSize + INT_SIZE + identifierByteSize)
    val remainingBytesInInstance = readInt()
    skip(remainingBytesInInstance)
  }

  fun skipClassDumpRecord() {
    skip(
      identifierByteSize + INT_SIZE + identifierByteSize + identifierByteSize + identifierByteSize +
        identifierByteSize + identifierByteSize + identifierByteSize + INT_SIZE
    )
    // Skip over the constant pool
    val constantPoolCount = readUnsignedShort()
    for (i in 0 until constantPoolCount) {
      // constant pool index
      skip(SHORT_SIZE)
      skip(typeSizes[readUnsignedByte()])
    }

    val staticFieldCount = readUnsignedShort()

    for (i in 0 until staticFieldCount) {
      skip(identifierByteSize)
      val type = readUnsignedByte()
      skip(typeSizes[type])
    }

    val fieldCount = readUnsignedShort()
    skip(fieldCount * (identifierByteSize + BYTE_SIZE))
  }

  fun skipObjectArrayDumpRecord() {
    skip(identifierByteSize + INT_SIZE)
    val arrayLength = readInt()
    skip(identifierByteSize + arrayLength * identifierByteSize)
  }

  fun skipPrimitiveArrayDumpRecord() {
    skip(identifierByteSize + INT_SIZE)
    val arrayLength = readInt()
    val type = readUnsignedByte()
    skip(arrayLength * typeSizes[type])
  }

  fun skipHeapDumpInfoRecord() {
    skip(identifierByteSize + identifierByteSize)
  }

  fun skip(byteCount: Int) {
    bytesRead += byteCount
    return source.skip(byteCount.toLong())
  }

  fun skip(byteCount: Long) {
    bytesRead += byteCount
    return source.skip(byteCount)
  }

  fun readUnsignedInt(): Long {
    return readInt().toLong() and INT_MASK
  }

  fun readUnsignedByte(): Int {
    return readByte().toInt() and BYTE_MASK
  }

  /**
   * Reads a value in the heap dump, which can be a reference or a primitive type.
   */
  fun readValue(type: Int): ValueHolder {
    return when (type) {
      PrimitiveType.REFERENCE_HPROF_TYPE -> ReferenceHolder(readId())
      BOOLEAN_TYPE -> BooleanHolder(readBoolean())
      CHAR_TYPE -> CharHolder(readChar())
      FLOAT_TYPE -> FloatHolder(readFloat())
      DOUBLE_TYPE -> DoubleHolder(readDouble())
      BYTE_TYPE -> ByteHolder(readByte())
      SHORT_TYPE -> ShortHolder(readShort())
      INT_TYPE -> IntHolder(readInt())
      LONG_TYPE -> LongHolder(readLong())
      else -> throw IllegalStateException("Unknown type $type")
    }
  }

  fun readShort(): Short {
    bytesRead += SHORT_SIZE
    return source.readShort()
  }

  fun readInt(): Int {
    bytesRead += INT_SIZE
    return source.readInt()
  }

  fun readIdArray(arrayLength: Int): LongArray {
    return LongArray(arrayLength) { readId() }
  }

  fun readBooleanArray(arrayLength: Int): BooleanArray {
    return BooleanArray(arrayLength) { readByte().toInt() != 0 }
  }

  fun readCharArray(arrayLength: Int): CharArray {
    return CharArray(arrayLength) {
      readChar()
    }
  }

  fun readString(
    byteCount: Int,
    charset: Charset
  ): String {
    bytesRead += byteCount
    return source.readString(byteCount.toLong(), charset)
  }

  fun readFloatArray(arrayLength: Int): FloatArray {
    return FloatArray(arrayLength) { readFloat() }
  }

  fun readDoubleArray(arrayLength: Int): DoubleArray {
    return DoubleArray(arrayLength) { readDouble() }
  }

  fun readShortArray(arrayLength: Int): ShortArray {
    return ShortArray(arrayLength) { readShort() }
  }

  fun readIntArray(arrayLength: Int): IntArray {
    return IntArray(arrayLength) { readInt() }
  }

  fun readLongArray(arrayLength: Int): LongArray {
    return LongArray(arrayLength) { readLong() }
  }

  fun readLong(): Long {
    bytesRead += LONG_SIZE
    return source.readLong()
  }

  fun readByte(): Byte {
    bytesRead += BYTE_SIZE
    return source.readByte()
  }

  fun readBoolean(): Boolean {
    bytesRead += BOOLEAN_SIZE
    return source.readByte()
      .toInt() != 0
  }

  fun readByteArray(byteCount: Int): ByteArray {
    bytesRead += byteCount
    return source.readByteArray(byteCount.toLong())
  }

  fun readChar(): Char {
    return readString(CHAR_SIZE, Charsets.UTF_16BE)[0]
  }

  fun readFloat(): Float {
    return Float.fromBits(readInt())
  }

  fun readDouble(): Double {
    return Double.fromBits(readLong())
  }

  fun readId(): Long {
    // As long as we don't interpret IDs, reading signed values here is fine.
    return when (identifierByteSize) {
      1 -> readByte().toLong()
      2 -> readShort().toLong()
      4 -> readInt().toLong()
      8 -> readLong()
      else -> throw IllegalArgumentException("ID Length must be 1, 2, 4, or 8")
    }
  }

  fun readUtf8(byteCount: Long): String {
    bytesRead += byteCount
    return source.readUtf8(byteCount)
  }

  fun readUnsignedShort(): Int {
    return readShort().toInt() and 0xFFFF
  }

  companion object {
    private val BOOLEAN_SIZE = BOOLEAN.byteSize
    private val CHAR_SIZE = CHAR.byteSize
    private val BYTE_SIZE = BYTE.byteSize
    private val SHORT_SIZE = SHORT.byteSize
    private val INT_SIZE = INT.byteSize
    private val LONG_SIZE = LONG.byteSize

    private val BOOLEAN_TYPE = BOOLEAN.hprofType
    private val CHAR_TYPE = CHAR.hprofType
    private val FLOAT_TYPE = FLOAT.hprofType
    private val DOUBLE_TYPE = DOUBLE.hprofType
    private val BYTE_TYPE = BYTE.hprofType
    private val SHORT_TYPE = SHORT.hprofType
    private val INT_TYPE = INT.hprofType
    private val LONG_TYPE = LONG.hprofType

    private const val INT_MASK = 0xffffffffL
    private const val BYTE_MASK = 0xff
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy