From 714b80e049ac24336f032c5a757f6743b17f4e8b Mon Sep 17 00:00:00 2001 From: Lev Zagnetin Date: Mon, 14 Apr 2025 10:14:08 +0300 Subject: [PATCH] [ui-tests][driver] AT-2051 Add test to open JCEF view and detect leaks GitOrigin-RevId: c6f689e43e7014deae8f2afee2242282b7fb144c --- .../intellij/driver/sdk/JcefLeakDetector.kt | 13 ++++ .../intellij/driver/sdk/MemoryDumpHelper.kt | 11 ++++ .../remotedriver/jcef/JcefLeakDetector.kt | 60 +++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/JcefLeakDetector.kt create mode 100644 platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/MemoryDumpHelper.kt create mode 100644 plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/jcef/JcefLeakDetector.kt diff --git a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/JcefLeakDetector.kt b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/JcefLeakDetector.kt new file mode 100644 index 000000000000..a99a19bcb637 --- /dev/null +++ b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/JcefLeakDetector.kt @@ -0,0 +1,13 @@ +package com.intellij.driver.sdk + +import com.intellij.driver.client.Driver +import com.intellij.driver.client.Remote +import com.intellij.driver.sdk.ui.remote.REMOTE_ROBOT_MODULE_ID + +@Remote("com.jetbrains.performancePlugin.remotedriver.jcef.JcefLeakDetector", plugin = REMOTE_ROBOT_MODULE_ID) +@Suppress("unused") +interface JcefLeakDetector { + fun analyzeSnapshot(path: String): List +} + +fun Driver.analyzeSnapshot(path: String): List = utility(JcefLeakDetector::class).analyzeSnapshot(path) \ No newline at end of file diff --git a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/MemoryDumpHelper.kt b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/MemoryDumpHelper.kt new file mode 100644 index 000000000000..ed3eb403cfe8 --- /dev/null +++ b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/MemoryDumpHelper.kt @@ -0,0 +1,11 @@ +package com.intellij.driver.sdk + +import com.intellij.driver.client.Driver +import com.intellij.driver.client.Remote + +@Remote("com.intellij.util.MemoryDumpHelper") +interface MemoryDumpHelper { + fun captureMemoryDump(dumpPath: String): Array +} + +fun Driver.captureMemoryDump(dumpPath: String): Any = utility(MemoryDumpHelper::class).captureMemoryDump(dumpPath) \ No newline at end of file diff --git a/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/jcef/JcefLeakDetector.kt b/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/jcef/JcefLeakDetector.kt new file mode 100644 index 000000000000..f845b110489c --- /dev/null +++ b/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/jcef/JcefLeakDetector.kt @@ -0,0 +1,60 @@ +package com.jetbrains.performancePlugin.remotedriver.jcef + +import com.intellij.diagnostic.hprof.action.SystemTempFilenameSupplier +import com.intellij.diagnostic.hprof.analysis.* +import com.intellij.diagnostic.hprof.util.AnalysisReport +import com.intellij.diagnostic.hprof.util.ListProvider +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.progress.EmptyProgressIndicator +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManager +import java.nio.channels.FileChannel +import java.nio.file.Paths +import java.nio.file.StandardOpenOption + + +@Suppress("unused") +internal class JcefLeakDetector { + companion object { + private val LOG = logger() + + fun analyzeSnapshot(path: String): List { + + val analysisResult = mutableListOf() + FileChannel.open(Paths.get(path), StandardOpenOption.READ).use { channel -> + val analysis = HProfAnalysis(channel, SystemTempFilenameSupplier()) { analysisContext, listProvider, progressIndicator -> + AnalyzeProjectGraph(analysisContext, listProvider).analyze(progressIndicator).mainReport.toString() + } + analysis.onlyStrongReferences = true + analysis.includeClassesAsRoots = false + analysis.setIncludeMetaInfo(false) + analysisResult += analysis.analyze(ProgressManager.getGlobalProgressIndicator() ?: EmptyProgressIndicator()) + analysisResult.removeIf({it == ""}) + if (analysisResult.isNotEmpty()) { + LOG.error("Snapshot analysis result: $analysisResult") + } + } + return analysisResult + } + } + + + private class AnalyzeProjectGraph(analysisContext: AnalysisContext, listProvider: ListProvider) + : AnalyzeGraph(analysisContext, listProvider) { + override fun analyze(progress: ProgressIndicator): AnalysisReport = AnalysisReport().apply { + traverseInstanceGraph(progress, this) + + val navigator = analysisContext.navigator + for (l in 1..navigator.instanceCount) { + val classDefinition = navigator.getClassForObjectId(l) + if (classDefinition.name == "org.intellij.plugins.markdown.ui.preview.jcef.MarkdownJCEFHtmlPanel") { + navigator.goTo(l) + val gcRootPathsTree = GCRootPathsTree(analysisContext, AnalysisConfig.TreeDisplayOptions.all(showSize = false), null) + gcRootPathsTree.registerObject(l.toInt()) + if (!gcRootPathsTree.printTree().contains("com.jetbrains.performancePlugin.remotedriver.jcef.JcefComponentWrapper")) + mainReport.append(gcRootPathsTree.printTree()) + } + } + } + } +}