AT-1039 HProfAnalysis is broken on the dev server

Cherry-picked 9de38d42b2ef5aaf80c8c0a16c73d84171caa1f3:
Fix RemapException

Some hprof files have object arrays with references to objects
not included in the heap dump. As there is no information about these
objects, the best way is to treat them as null references.

GitOrigin-RevId: ae865fbde60f9a55b69d7d454fe81de7997e6ad7
This commit is contained in:
Maxim.Kolmakov
2024-07-09 10:43:51 +02:00
committed by intellij-monorepo-bot
parent 567ee4977e
commit 27a90c991d
12 changed files with 198 additions and 74 deletions

View File

@@ -2532,7 +2532,7 @@ f:com.intellij.diagnostic.hprof.classstore.ClassDefinition
- f:allRefFieldNames(com.intellij.diagnostic.hprof.classstore.ClassStore):java.util.List
- f:computeOffsetOfField(java.lang.String,com.intellij.diagnostic.hprof.classstore.ClassStore):I
- f:copyWithName(java.lang.String):com.intellij.diagnostic.hprof.classstore.ClassDefinition
- f:copyWithRemappedIDs(java.util.function.LongUnaryOperator):com.intellij.diagnostic.hprof.classstore.ClassDefinition
- f:copyWithRemappedIDs(com.intellij.diagnostic.hprof.util.IDMapper):com.intellij.diagnostic.hprof.classstore.ClassDefinition
- equals(java.lang.Object):Z
- f:getClassFieldName(I):java.lang.String
- f:getClassLoaderId():J
@@ -2559,7 +2559,7 @@ f:com.intellij.diagnostic.hprof.classstore.ClassDefinition$Companion
f:com.intellij.diagnostic.hprof.classstore.ClassStore
- <init>(it.unimi.dsi.fastutil.longs.Long2ObjectMap):V
- f:containsClass(java.lang.String):Z
- f:createStoreWithRemappedIDs(java.util.function.LongUnaryOperator):com.intellij.diagnostic.hprof.classstore.ClassStore
- f:createStoreWithRemappedIDs(com.intellij.diagnostic.hprof.util.IDMapper):com.intellij.diagnostic.hprof.classstore.ClassStore
- f:forEachClass(kotlin.jvm.functions.Function1):V
- f:get(I):com.intellij.diagnostic.hprof.classstore.ClassDefinition
- f:get(J):com.intellij.diagnostic.hprof.classstore.ClassDefinition
@@ -2578,7 +2578,7 @@ f:com.intellij.diagnostic.hprof.classstore.HProfMetadata
- f:getClassStore():com.intellij.diagnostic.hprof.classstore.ClassStore
- f:getRoots():it.unimi.dsi.fastutil.longs.Long2ObjectMap
- f:getThreads():it.unimi.dsi.fastutil.longs.Long2ObjectMap
- f:remapIds(java.util.function.LongUnaryOperator):V
- f:remapIds(com.intellij.diagnostic.hprof.util.IDMapper):V
- f:setClassStore(com.intellij.diagnostic.hprof.classstore.ClassStore):V
- f:setRoots(it.unimi.dsi.fastutil.longs.Long2ObjectMap):V
f:com.intellij.diagnostic.hprof.classstore.HProfMetadata$Companion
@@ -2724,7 +2724,7 @@ f:com.intellij.diagnostic.hprof.parser.HProfEventBasedParser
- f:getBuffer():com.intellij.diagnostic.hprof.util.HProfReadBuffer
- f:getIdSize():I
- f:remap(J):J
- f:setIdRemappingFunction(java.util.function.LongUnaryOperator):V
- f:setIDMapper(com.intellij.diagnostic.hprof.util.IDMapper):V
f:com.intellij.diagnostic.hprof.parser.HProfEventBasedParser$Companion
c:com.intellij.diagnostic.hprof.parser.HProfVisitor
- visitorContext:com.intellij.diagnostic.hprof.parser.VisitorContext
@@ -2967,6 +2967,9 @@ f:com.intellij.diagnostic.hprof.util.HprofWriter
- f:writeStackFrame(J,J,J,J,I,I):V
- f:writeStackTrace(I,J,J[]):V
- f:writeStringInUTF8(J,java.lang.String):V
com.intellij.diagnostic.hprof.util.IDMapper
- a:getID(J):J
- a:isValidID(J):Z
com.intellij.diagnostic.hprof.util.IntList
- a:get(I):I
- a:set(I,I):V
@@ -3066,7 +3069,7 @@ a:com.intellij.diagnostic.hprof.visitors.RemapIDsVisitor
- sf:Companion:com.intellij.diagnostic.hprof.visitors.RemapIDsVisitor$Companion
- <init>():V
- a:addMapping(J,I):V
- a:getRemappingFunction():java.util.function.LongUnaryOperator
- a:getIDMapper():com.intellij.diagnostic.hprof.util.IDMapper
- preVisit():V
- visitClassDump(J,J,J,J,J,com.intellij.diagnostic.hprof.parser.ConstantPoolEntry[],com.intellij.diagnostic.hprof.parser.StaticFieldEntry[],com.intellij.diagnostic.hprof.parser.InstanceFieldEntry[]):V
- visitInstanceDump(J,J,J,java.nio.ByteBuffer):V

View File

@@ -115,8 +115,9 @@ class HProfAnalysis(private val hprofFileChannel: FileChannel,
histogram.instanceCount)
parser.accept(remapIDsVisitor, "id mapping")
parser.setIdRemappingFunction(remapIDsVisitor.getRemappingFunction())
hprofMetadata.remapIds(remapIDsVisitor.getRemappingFunction())
val idMapper = remapIDsVisitor.getIDMapper()
parser.setIDMapper(idMapper)
hprofMetadata.remapIds(idMapper)
progress.text2 = DiagnosticBundle.message("hprof.analysis.progress.details.create.object.graph.files")
progress.fraction = 0.3

View File

@@ -16,8 +16,8 @@
package com.intellij.diagnostic.hprof.classstore
import com.intellij.diagnostic.hprof.parser.Type
import com.intellij.diagnostic.hprof.util.IDMapper
import org.jetbrains.annotations.NonNls
import java.util.function.LongUnaryOperator
class ClassDefinition(val name: String,
val id: Long,
@@ -99,8 +99,8 @@ class ClassDefinition(val name: String,
fun isPrimitiveArray(): Boolean = isArray() && name.length == 2
fun copyWithRemappedIDs(remappingFunction: LongUnaryOperator): ClassDefinition {
fun map(id: Long): Long = remappingFunction.applyAsLong(id)
fun copyWithRemappedIDs(idMapper: IDMapper): ClassDefinition {
fun map(id: Long): Long = idMapper.getID(id)
val newConstantFields = LongArray(constantFields.size) {
map(constantFields[it])
}

View File

@@ -16,9 +16,9 @@
package com.intellij.diagnostic.hprof.classstore
import com.intellij.diagnostic.hprof.parser.Type
import com.intellij.diagnostic.hprof.util.IDMapper
import it.unimi.dsi.fastutil.longs.Long2ObjectMap
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import java.util.function.LongUnaryOperator
class ClassStore(private val classes: Long2ObjectMap<ClassDefinition>) {
private val stringToClassDefinition = HashMap<String, ClassDefinition>()
@@ -140,10 +140,11 @@ class ClassStore(private val classes: Long2ObjectMap<ClassDefinition>) {
}
}
fun createStoreWithRemappedIDs(remappingFunction: LongUnaryOperator): ClassStore {
fun createStoreWithRemappedIDs(idMapper: IDMapper): ClassStore {
fun map(id: Long): Long = idMapper.getID(id)
val newClasses = Long2ObjectOpenHashMap<ClassDefinition>()
for (classDefinition in classes.values) {
newClasses.put(remappingFunction.applyAsLong(classDefinition.id), classDefinition.copyWithRemappedIDs(remappingFunction))
newClasses.put(map(classDefinition.id), classDefinition.copyWithRemappedIDs(idMapper))
}
return ClassStore(newClasses)
}

View File

@@ -17,26 +17,26 @@ package com.intellij.diagnostic.hprof.classstore
import com.intellij.diagnostic.hprof.navigator.RootReason
import com.intellij.diagnostic.hprof.parser.HProfEventBasedParser
import com.intellij.diagnostic.hprof.util.IDMapper
import com.intellij.diagnostic.hprof.visitors.*
import it.unimi.dsi.fastutil.longs.Long2ObjectMap
import it.unimi.dsi.fastutil.longs.Long2ObjectMaps
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import java.util.function.LongUnaryOperator
class HProfMetadata(var classStore: ClassStore, // TODO: private-set, public-get
val threads: Long2ObjectMap<ThreadInfo>,
var roots: Long2ObjectMap<RootReason>) {
class RemapException : Exception()
fun remapIds(remappingFunction: LongUnaryOperator) {
fun remapIds(idMapper: IDMapper) {
// Remap ids in class store
classStore = classStore.createStoreWithRemappedIDs(remappingFunction)
classStore = classStore.createStoreWithRemappedIDs(idMapper)
// Remap root objects' ids
val newRoots = Long2ObjectOpenHashMap<RootReason>()
for (entry in Long2ObjectMaps.fastIterable(roots)) {
try {
val newKey = remappingFunction.applyAsLong(entry.longKey)
val newKey = idMapper.getID(entry.longKey)
assert(!newRoots.containsKey(newKey))
newRoots.put(newKey, entry.value)
} catch (e: RemapException) {

View File

@@ -18,12 +18,12 @@ package com.intellij.diagnostic.hprof.parser
import com.google.common.base.Stopwatch
import com.intellij.diagnostic.hprof.util.HProfReadBuffer
import com.intellij.diagnostic.hprof.util.HProfReadBufferSlidingWindow
import com.intellij.diagnostic.hprof.util.IDMapper
import com.intellij.openapi.diagnostic.logger
import java.io.EOFException
import java.io.IOException
import java.nio.channels.FileChannel
import java.nio.charset.Charset
import java.util.function.LongUnaryOperator
class HProfEventBasedParser(fileChannel: FileChannel) : AutoCloseable {
companion object {
@@ -34,7 +34,7 @@ class HProfEventBasedParser(fileChannel: FileChannel) : AutoCloseable {
private set
private var reparsePosition: Long = 0
private var remapFunction: LongUnaryOperator? = null
private var idMapper: IDMapper? = null
val buffer: HProfReadBuffer = HProfReadBufferSlidingWindow(fileChannel, this)
private var heapRecordPosition: Long = 0
@@ -47,8 +47,8 @@ class HProfEventBasedParser(fileChannel: FileChannel) : AutoCloseable {
buffer.close()
}
fun setIdRemappingFunction(remapFunction: LongUnaryOperator) {
this.remapFunction = remapFunction
fun setIDMapper(idMapper: IDMapper) {
this.idMapper = idMapper
}
private fun initialParse() {
@@ -318,7 +318,10 @@ class HProfEventBasedParser(fileChannel: FileChannel) : AutoCloseable {
)
}
fun remap(id: Long): Long = remapFunction?.applyAsLong(id) ?: id
fun remap(id: Long): Long {
if (id == 0L) return 0L
return idMapper?.getID(id) ?: id
}
private fun readTypeSizeValue(elementType: Type): Long {
if (elementType === Type.OBJECT) {

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.diagnostic.hprof.util
interface IDMapper {
fun getID(id: Long): Long
fun isValidID(id: Long): Boolean
}

View File

@@ -15,13 +15,12 @@
*/
package com.intellij.diagnostic.hprof.visitors
import com.intellij.diagnostic.hprof.classstore.HProfMetadata
import com.intellij.diagnostic.hprof.parser.*
import com.intellij.diagnostic.hprof.util.FileBackedHashMap
import com.intellij.diagnostic.hprof.util.IDMapper
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap
import java.nio.ByteBuffer
import java.nio.channels.FileChannel
import java.util.function.LongUnaryOperator
abstract class RemapIDsVisitor : HProfVisitor() {
private var currentID = 0
@@ -40,14 +39,16 @@ abstract class RemapIDsVisitor : HProfVisitor() {
addMapping(arrayObjectId, currentID++)
}
override fun visitClassDump(classId: Long,
stackTraceSerialNumber: Long,
superClassId: Long,
classloaderClassId: Long,
instanceSize: Long,
constants: Array<ConstantPoolEntry>,
staticFields: Array<StaticFieldEntry>,
instanceFields: Array<InstanceFieldEntry>) {
override fun visitClassDump(
classId: Long,
stackTraceSerialNumber: Long,
superClassId: Long,
classloaderClassId: Long,
instanceSize: Long,
constants: Array<ConstantPoolEntry>,
staticFields: Array<StaticFieldEntry>,
instanceFields: Array<InstanceFieldEntry>,
) {
addMapping(classId, currentID++)
}
@@ -61,7 +62,7 @@ abstract class RemapIDsVisitor : HProfVisitor() {
abstract fun addMapping(oldId: Long, newId: Int)
abstract fun getRemappingFunction(): LongUnaryOperator
abstract fun getIDMapper(): IDMapper
companion object {
fun createMemoryBased(): RemapIDsVisitor {
@@ -69,11 +70,25 @@ abstract class RemapIDsVisitor : HProfVisitor() {
map.put(0, 0)
return object : RemapIDsVisitor() {
override fun addMapping(oldId: Long, newId: Int) {
map.put(oldId, newId)
if (oldId != 0L) {
map.put(oldId, newId)
}
}
override fun getRemappingFunction(): LongUnaryOperator {
return LongUnaryOperator { map.get(it).toLong() }
override fun getIDMapper(): IDMapper {
return object : IDMapper {
override fun getID(id: Long): Long {
if (isValidID(id))
return map[id].toLong()
else {
return 0
}
}
override fun isValidID(id: Long): Boolean {
return map.containsKey(id)
}
}
}
}
}
@@ -84,23 +99,32 @@ abstract class RemapIDsVisitor : HProfVisitor() {
maxInstanceCount, KEY_SIZE, VALUE_SIZE)
return object : RemapIDsVisitor() {
override fun addMapping(oldId: Long, newId: Int) {
if (oldId == 0L) return
remapIDsMap.put(oldId).putInt(newId)
}
override fun getRemappingFunction(): LongUnaryOperator {
return LongUnaryOperator { operand ->
if (operand == 0L) 0L else
{
if (remapIDsMap.containsKey(operand))
remapIDsMap[operand]!!.int.toLong()
else
throw HProfMetadata.RemapException()
override fun getIDMapper(): IDMapper {
return object : IDMapper {
override fun getID(id: Long): Long {
return if (id == 0L) 0L
else {
if (remapIDsMap.containsKey(id))
remapIDsMap[id]!!.int.toLong()
else {
return 0
}
}
}
override fun isValidID(id: Long): Boolean {
return remapIDsMap.containsKey(id)
}
}
}
}
}
fun isSupported(instanceCount: Long): Boolean {
return FileBackedHashMap.isSupported(instanceCount, KEY_SIZE, VALUE_SIZE)
}

View File

@@ -36,8 +36,6 @@ import java.lang.reflect.Array
import java.lang.reflect.Modifier
import kotlin.Any
import kotlin.ByteArray
import kotlin.IllegalArgumentException
import kotlin.IllegalStateException
import kotlin.Int
import kotlin.Long
import kotlin.LongArray
@@ -62,6 +60,8 @@ class HProfBuilder(dos: DataOutputStream, val classNameMapping: ((Class<*>) -> S
private val writer = HprofWriter(dos, idSize, System.currentTimeMillis())
private var objectFilter: (Any) -> FilterResult = { _ -> FilterResult.INCLUDE_REFERENCES_AND_INSTANCE };
init {
addObject(Class::class.java)
addObject(SoftReference::class.java)
@@ -125,12 +125,22 @@ class HProfBuilder(dos: DataOutputStream, val classNameMapping: ((Class<*>) -> S
if (o == null) {
return 0
}
val filterResult = objectFilter.invoke(o)
if (filterResult == FilterResult.TREAT_AS_NULL) {
return 0;
}
if (objectToIdMap.containsKey(o)) {
return objectToIdMap.getLong(o)
}
val objectID = nextObjectID()
objectToIdMap.put(o, objectID)
if (filterResult == FilterResult.INCLUDE_REFERENCES_ONLY) {
// Object ID has been assigned, but don't include the object itself.
// Other objects can reference it by this ID.
return objectID
}
val oClass: Class<*> = o.javaClass
addObject(oClass)
@@ -319,6 +329,19 @@ class HProfBuilder(dos: DataOutputStream, val classNameMapping: ((Class<*>) -> S
return id
}
enum class FilterResult {
INCLUDE_REFERENCES_AND_INSTANCE,
INCLUDE_REFERENCES_ONLY,
TREAT_AS_NULL
}
/**
* Optional filter on objects added to the hprof.
*/
fun setObjectFilter(filter: (Any) -> FilterResult) {
objectFilter = filter;
}
private fun getClassSerialNumber(classObjectId: Long) = classObjectIdToClassSerialNumber[classObjectId]
private fun nextStringID() = nextStringId++

View File

@@ -39,8 +39,10 @@ import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardOpenOption
open class HProfScenarioRunner(private val tmpFolder: TemporaryFolder,
private val remapInMemory: Boolean) {
open class HProfScenarioRunner(
private val tmpFolder: TemporaryFolder,
private val remapInMemory: Boolean,
) {
val regex = Regex("^com\\.intellij\\.diagnostic\\.hprof\\..*\\\$.*\\\$")
@@ -51,17 +53,34 @@ open class HProfScenarioRunner(private val tmpFolder: TemporaryFolder,
return clazz.name.replace(regex, "")
}
fun run(scenario: HProfBuilder.() -> Unit,
baselineFileName: String,
nominatedClassNames: List<String>?) {
fun createReport(
scenario: HProfBuilder.() -> Unit,
nominatedClassNames: List<String>?,
shouldMapClassNames: Boolean = true,
config: AnalysisConfig? = null,
): String {
val hprofFile = tmpFolder.newFile()
HProfTestUtils.createHProfOnFile(hprofFile,
scenario,
{ c -> mapClassName(c) })
compareReportToBaseline(hprofFile, baselineFileName, nominatedClassNames)
scenario) { c -> if (shouldMapClassNames) mapClassName(c) else c.name }
return createReport(hprofFile, nominatedClassNames, config)
}
private fun compareReportToBaseline(hprofFile: File, baselineFileName: String, nominatedClassNames: List<String>? = null) {
fun run(
scenario: HProfBuilder.() -> Unit,
baselineFileName: String,
nominatedClassNames: List<String>?,
shouldMapClassNames: Boolean = true,
config: AnalysisConfig? = null,
) {
val report = createReport(scenario, nominatedClassNames, shouldMapClassNames, config)
compareReportToBaseline(report, baselineFileName)
}
fun createReport(
hprofFile: File,
nominatedClassNames: List<String>? = null,
config: AnalysisConfig? = null,
): String {
FileChannel.open(hprofFile.toPath(), StandardOpenOption.READ).use { hprofChannel ->
val progress = object : AbstractProgressIndicatorBase() {
@@ -79,8 +98,9 @@ open class HProfScenarioRunner(private val tmpFolder: TemporaryFolder,
RemapIDsVisitor.createFileBased(openTempEmptyFileChannel(), histogram.instanceCount)
parser.accept(remapIDsVisitor, "id mapping")
parser.setIdRemappingFunction(remapIDsVisitor.getRemappingFunction())
hprofMetadata.remapIds(remapIDsVisitor.getRemappingFunction())
val idMapper = remapIDsVisitor.getIDMapper()
parser.setIDMapper(idMapper)
hprofMetadata.remapIds(idMapper)
val navigator = ObjectNavigator.createOnAuxiliaryFiles(
parser,
@@ -129,16 +149,19 @@ open class HProfScenarioRunner(private val tmpFolder: TemporaryFolder,
histogram
)
val analysisReport = AnalyzeGraph(analysisContext, memoryBackedListProvider).analyze(progress).mainReport.toString()
val baselinePath = getBaselinePath(baselineFileName)
val baseline = getBaselineContents(baselinePath)
Assert.assertEquals("Report doesn't match the baseline from file:\n$baselinePath",
baseline,
analysisReport)
return AnalyzeGraph(analysisContext, memoryBackedListProvider).analyze(progress).mainReport.toString()
}
}
fun compareReportToBaseline(analysisReport: String, baselineFileName: String) {
val baselinePath = getBaselinePath(baselineFileName)
val baseline = getBaselineContents(baselinePath)
Assert.assertEquals("Report doesn't match the baseline from file:\n$baselinePath",
baseline,
analysisReport)
}
/**
* Get the contents of the baseline file, with system-dependent line endings
*/
@@ -211,7 +234,7 @@ open class HProfScenarioRunner(private val tmpFolder: TemporaryFolder,
}
}
object memoryBackedListProvider: ListProvider {
object memoryBackedListProvider : ListProvider {
override fun createUByteList(name: String, size: Long) = MemoryBackedUByteList(size.toInt())
override fun createUShortList(name: String, size: Long) = MemoryBackedUShortList(size.toInt())
override fun createIntList(name: String, size: Long) = MemoryBackedIntList(size.toInt())

View File

@@ -19,6 +19,7 @@ import com.intellij.diagnostic.hprof.analysis.AnalysisConfig
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.rules.TemporaryFolder
@@ -197,6 +198,28 @@ class HeapAnalysisTest {
}.run(scenario, "testDominatorTreeFlameGraph.txt", null)
}
@Test
fun testMissingObjectInObjectArray() {
val scenario: HProfBuilder.() -> Unit = {
class A(val x: Any) {
}
val s = "object excluded from hprof"
setObjectFilter { o ->
if (o === s)
HProfBuilder.FilterResult.INCLUDE_REFERENCES_ONLY
else
HProfBuilder.FilterResult.INCLUDE_REFERENCES_AND_INSTANCE
}
// This will keep a reference to the object from the array, but object itself will not be
// included.
val a = A(listOf("", s))
addRootGlobalJNI(a)
}
// Check if the hprof can be parsed without throwing an exception
val report = HProfScenarioRunner(tmpFolder, remapInMemory).createReport(scenario, null)
Assert.assertTrue(report.isNotEmpty())
}
private fun configWithDisposerTreeOnly() = AnalysisConfig(
AnalysisConfig.PerClassOptions(
classNames = listOf(),

View File

@@ -19,6 +19,7 @@ import com.intellij.diagnostic.hprof.classstore.HProfMetadata
import com.intellij.diagnostic.hprof.histogram.Histogram
import com.intellij.diagnostic.hprof.navigator.ObjectNavigator
import com.intellij.diagnostic.hprof.parser.HProfEventBasedParser
import com.intellij.diagnostic.hprof.util.IDMapper
import com.intellij.diagnostic.hprof.visitors.RemapIDsVisitor
import org.junit.After
import org.junit.Assert.assertArrayEquals
@@ -29,7 +30,6 @@ import org.junit.rules.TemporaryFolder
import java.io.File
import java.nio.channels.FileChannel
import java.nio.file.StandardOpenOption
import java.util.function.LongUnaryOperator
class ObjectNavigatorTest {
@@ -82,24 +82,24 @@ class ObjectNavigatorTest {
HProfTestUtils.createHProfOnFile(hprofFile,
scenario,
classNameMapping)
val (navigator, remap) = getObjectNavigatorAndRemappingFunction(hprofFile)
val (navigator, idMapper) = getObjectNavigatorAndRemappingFunction(hprofFile)
val clashedClassNames = ArrayList<String>()
// Check that each class got assigned a unique name
clashedClassNames.add(navigator.getClassForObjectId(remap.applyAsLong(object1Id)).name)
clashedClassNames.add(navigator.getClassForObjectId(remap.applyAsLong(object2Id)).name)
clashedClassNames.add(navigator.getClassForObjectId(idMapper.getID(object1Id)).name)
clashedClassNames.add(navigator.getClassForObjectId(idMapper.getID(object2Id)).name)
clashedClassNames.sort()
assertArrayEquals(arrayOf("MyTestClass!1", "MyTestClass!2"), clashedClassNames.toArray())
// Verify goToInstanceField works correctly for classes with name clash
navigator.goTo(remap.applyAsLong(object1Id))
navigator.goTo(idMapper.getID(object1Id))
navigator.goToInstanceField("MyTestClass", "field")
assertEquals("java.lang.String", navigator.getClass().undecoratedName)
}
private fun getObjectNavigatorAndRemappingFunction(hprofFile: File): Pair<ObjectNavigator, LongUnaryOperator> {
private fun getObjectNavigatorAndRemappingFunction(hprofFile: File): Pair<ObjectNavigator, IDMapper> {
FileChannel.open(hprofFile.toPath(), StandardOpenOption.READ).use { hprofChannel ->
val parser = HProfEventBasedParser(hprofChannel)
val hprofMetadata = HProfMetadata.create(parser)
@@ -107,8 +107,9 @@ class ObjectNavigatorTest {
val remapIDsVisitor = RemapIDsVisitor.createMemoryBased()
parser.accept(remapIDsVisitor, "id mapping")
parser.setIdRemappingFunction(remapIDsVisitor.getRemappingFunction())
hprofMetadata.remapIds(remapIDsVisitor.getRemappingFunction())
val remapper = remapIDsVisitor.getIDMapper()
parser.setIDMapper(remapper)
hprofMetadata.remapIds(remapper)
return Pair(ObjectNavigator.createOnAuxiliaryFiles(
parser,
@@ -116,7 +117,7 @@ class ObjectNavigatorTest {
openTempEmptyFileChannel(),
hprofMetadata,
histogram.instanceCount
), remapIDsVisitor.getRemappingFunction())
), remapper)
}
}