mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-02-04 15:06:56 +07:00
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:
committed by
intellij-monorepo-bot
parent
567ee4977e
commit
27a90c991d
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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++
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user