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

main.shark.HprofRecordReader.kt Maven / Gradle / Ivy

package shark

import okio.BufferedSource
import shark.GcRoot.Debugger
import shark.GcRoot.Finalizing
import shark.GcRoot.InternedString
import shark.GcRoot.JavaFrame
import shark.GcRoot.JniGlobal
import shark.GcRoot.JniLocal
import shark.GcRoot.JniMonitor
import shark.GcRoot.MonitorUsed
import shark.GcRoot.NativeStack
import shark.GcRoot.ReferenceCleanup
import shark.GcRoot.StickyClass
import shark.GcRoot.ThreadBlock
import shark.GcRoot.ThreadObject
import shark.GcRoot.Unknown
import shark.GcRoot.Unreachable
import shark.GcRoot.VmInternal
import shark.HprofRecord.HeapDumpRecord.HeapDumpInfoRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.FieldRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.StaticFieldRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.InstanceDumpRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ObjectArrayDumpRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.BooleanArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ByteArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.CharArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.DoubleArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.FloatArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.IntArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.LongArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ShortArrayDump
import shark.HprofRecord.LoadClassRecord
import shark.HprofRecord.StackFrameRecord
import shark.HprofRecord.StackTraceRecord
import shark.HprofRecord.StringRecord
import shark.PrimitiveType.BOOLEAN
import shark.PrimitiveType.BYTE
import shark.PrimitiveType.CHAR
import shark.PrimitiveType.DOUBLE
import shark.PrimitiveType.FLOAT
import shark.PrimitiveType.INT
import shark.PrimitiveType.LONG
import shark.PrimitiveType.SHORT
import shark.ValueHolder.BooleanHolder
import shark.ValueHolder.ByteHolder
import shark.ValueHolder.CharHolder
import shark.ValueHolder.DoubleHolder
import shark.ValueHolder.FloatHolder
import shark.ValueHolder.IntHolder
import shark.ValueHolder.LongHolder
import shark.ValueHolder.ReferenceHolder
import shark.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 readUnloadClassRecord() = HprofRecord.UnloadClassRecord(
    classSerialNumber = readInt()
  )

  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 skipId() {
    skip(identifierByteSize)
  }

  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 - 2024 Weber Informatics LLC | Privacy Policy