From 27a90c991dc0f7bb37eaa99f7799a04354fe42c3 Mon Sep 17 00:00:00 2001 From: "Maxim.Kolmakov" Date: Tue, 9 Jul 2024 10:43:51 +0200 Subject: [PATCH] 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 --- .../platform-impl/api-dump-unreviewed.txt | 13 ++-- .../hprof/analysis/HProfAnalysis.kt | 5 +- .../hprof/classstore/ClassDefinition.kt | 6 +- .../diagnostic/hprof/classstore/ClassStore.kt | 7 +- .../hprof/classstore/HProfMetadata.kt | 8 +-- .../hprof/parser/HProfEventBasedParser.kt | 13 ++-- .../diagnostic/hprof/util/IDMapper.kt | 22 ++++++ .../hprof/visitors/RemapIDsVisitor.kt | 68 +++++++++++++------ .../intellij/diagnostic/hprof/HProfBuilder.kt | 27 +++++++- .../diagnostic/hprof/HProfScenarioRunner.kt | 61 +++++++++++------ .../diagnostic/hprof/HeapAnalysisTest.kt | 23 +++++++ .../diagnostic/hprof/ObjectNavigatorTest.kt | 19 +++--- 12 files changed, 198 insertions(+), 74 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/diagnostic/hprof/util/IDMapper.kt diff --git a/platform/platform-impl/api-dump-unreviewed.txt b/platform/platform-impl/api-dump-unreviewed.txt index 99722f2f728d..cb2aa08f116d 100644 --- a/platform/platform-impl/api-dump-unreviewed.txt +++ b/platform/platform-impl/api-dump-unreviewed.txt @@ -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 - (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 - ():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 diff --git a/platform/platform-impl/src/com/intellij/diagnostic/hprof/analysis/HProfAnalysis.kt b/platform/platform-impl/src/com/intellij/diagnostic/hprof/analysis/HProfAnalysis.kt index ab7910a4ee28..487df24585bf 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/hprof/analysis/HProfAnalysis.kt +++ b/platform/platform-impl/src/com/intellij/diagnostic/hprof/analysis/HProfAnalysis.kt @@ -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 diff --git a/platform/platform-impl/src/com/intellij/diagnostic/hprof/classstore/ClassDefinition.kt b/platform/platform-impl/src/com/intellij/diagnostic/hprof/classstore/ClassDefinition.kt index dd35a30d6219..a2ca39d01e39 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/hprof/classstore/ClassDefinition.kt +++ b/platform/platform-impl/src/com/intellij/diagnostic/hprof/classstore/ClassDefinition.kt @@ -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]) } diff --git a/platform/platform-impl/src/com/intellij/diagnostic/hprof/classstore/ClassStore.kt b/platform/platform-impl/src/com/intellij/diagnostic/hprof/classstore/ClassStore.kt index 806aca060282..df103bb30403 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/hprof/classstore/ClassStore.kt +++ b/platform/platform-impl/src/com/intellij/diagnostic/hprof/classstore/ClassStore.kt @@ -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) { private val stringToClassDefinition = HashMap() @@ -140,10 +140,11 @@ class ClassStore(private val classes: Long2ObjectMap) { } } - fun createStoreWithRemappedIDs(remappingFunction: LongUnaryOperator): ClassStore { + fun createStoreWithRemappedIDs(idMapper: IDMapper): ClassStore { + fun map(id: Long): Long = idMapper.getID(id) val newClasses = Long2ObjectOpenHashMap() 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) } diff --git a/platform/platform-impl/src/com/intellij/diagnostic/hprof/classstore/HProfMetadata.kt b/platform/platform-impl/src/com/intellij/diagnostic/hprof/classstore/HProfMetadata.kt index 6c58dbbbb8c3..3bf0f097c089 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/hprof/classstore/HProfMetadata.kt +++ b/platform/platform-impl/src/com/intellij/diagnostic/hprof/classstore/HProfMetadata.kt @@ -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, var roots: Long2ObjectMap) { 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() 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) { diff --git a/platform/platform-impl/src/com/intellij/diagnostic/hprof/parser/HProfEventBasedParser.kt b/platform/platform-impl/src/com/intellij/diagnostic/hprof/parser/HProfEventBasedParser.kt index 66ec76e92244..3ca847123593 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/hprof/parser/HProfEventBasedParser.kt +++ b/platform/platform-impl/src/com/intellij/diagnostic/hprof/parser/HProfEventBasedParser.kt @@ -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) { diff --git a/platform/platform-impl/src/com/intellij/diagnostic/hprof/util/IDMapper.kt b/platform/platform-impl/src/com/intellij/diagnostic/hprof/util/IDMapper.kt new file mode 100644 index 000000000000..798df767072d --- /dev/null +++ b/platform/platform-impl/src/com/intellij/diagnostic/hprof/util/IDMapper.kt @@ -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 +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/diagnostic/hprof/visitors/RemapIDsVisitor.kt b/platform/platform-impl/src/com/intellij/diagnostic/hprof/visitors/RemapIDsVisitor.kt index 413f9a7ff1bd..19ed0e7c9efe 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/hprof/visitors/RemapIDsVisitor.kt +++ b/platform/platform-impl/src/com/intellij/diagnostic/hprof/visitors/RemapIDsVisitor.kt @@ -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, - staticFields: Array, - instanceFields: Array) { + override fun visitClassDump( + classId: Long, + stackTraceSerialNumber: Long, + superClassId: Long, + classloaderClassId: Long, + instanceSize: Long, + constants: Array, + staticFields: Array, + instanceFields: Array, + ) { 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) } diff --git a/platform/platform-tests/testSrc/com/intellij/diagnostic/hprof/HProfBuilder.kt b/platform/platform-tests/testSrc/com/intellij/diagnostic/hprof/HProfBuilder.kt index 984204b5b813..33ce4cd7eddd 100644 --- a/platform/platform-tests/testSrc/com/intellij/diagnostic/hprof/HProfBuilder.kt +++ b/platform/platform-tests/testSrc/com/intellij/diagnostic/hprof/HProfBuilder.kt @@ -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++ diff --git a/platform/platform-tests/testSrc/com/intellij/diagnostic/hprof/HProfScenarioRunner.kt b/platform/platform-tests/testSrc/com/intellij/diagnostic/hprof/HProfScenarioRunner.kt index 51a5c3dabc06..58ae4bd21faa 100644 --- a/platform/platform-tests/testSrc/com/intellij/diagnostic/hprof/HProfScenarioRunner.kt +++ b/platform/platform-tests/testSrc/com/intellij/diagnostic/hprof/HProfScenarioRunner.kt @@ -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?) { + fun createReport( + scenario: HProfBuilder.() -> Unit, + nominatedClassNames: List?, + 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? = null) { + fun run( + scenario: HProfBuilder.() -> Unit, + baselineFileName: String, + nominatedClassNames: List?, + shouldMapClassNames: Boolean = true, + config: AnalysisConfig? = null, + ) { + val report = createReport(scenario, nominatedClassNames, shouldMapClassNames, config) + compareReportToBaseline(report, baselineFileName) + } + + fun createReport( + hprofFile: File, + nominatedClassNames: List? = 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()) diff --git a/platform/platform-tests/testSrc/com/intellij/diagnostic/hprof/HeapAnalysisTest.kt b/platform/platform-tests/testSrc/com/intellij/diagnostic/hprof/HeapAnalysisTest.kt index 6095bab34857..03bef9b290c9 100644 --- a/platform/platform-tests/testSrc/com/intellij/diagnostic/hprof/HeapAnalysisTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/diagnostic/hprof/HeapAnalysisTest.kt @@ -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(), diff --git a/platform/platform-tests/testSrc/com/intellij/diagnostic/hprof/ObjectNavigatorTest.kt b/platform/platform-tests/testSrc/com/intellij/diagnostic/hprof/ObjectNavigatorTest.kt index 0f33180c8df4..b0663e2fe7b3 100644 --- a/platform/platform-tests/testSrc/com/intellij/diagnostic/hprof/ObjectNavigatorTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/diagnostic/hprof/ObjectNavigatorTest.kt @@ -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() // 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 { + private fun getObjectNavigatorAndRemappingFunction(hprofFile: File): Pair { 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) } }