diff --git a/platform/diagnostic/freezeInPluginNotifier/intellij.platform.diagnostic.freezes.iml b/platform/diagnostic/freezeInPluginNotifier/intellij.platform.diagnostic.freezes.iml
index e149dc717791..924234c1e094 100644
--- a/platform/diagnostic/freezeInPluginNotifier/intellij.platform.diagnostic.freezes.iml
+++ b/platform/diagnostic/freezeInPluginNotifier/intellij.platform.diagnostic.freezes.iml
@@ -17,5 +17,6 @@
+
\ No newline at end of file
diff --git a/platform/diagnostic/freezeInPluginNotifier/resources/intellij.platform.diagnostic.freezes.xml b/platform/diagnostic/freezeInPluginNotifier/resources/intellij.platform.diagnostic.freezes.xml
index 8658cdd38ae9..abec1cf61494 100644
--- a/platform/diagnostic/freezeInPluginNotifier/resources/intellij.platform.diagnostic.freezes.xml
+++ b/platform/diagnostic/freezeInPluginNotifier/resources/intellij.platform.diagnostic.freezes.xml
@@ -6,4 +6,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/platform/diagnostic/freezeInPluginNotifier/resources/messages/PluginFreezeBundle.properties b/platform/diagnostic/freezeInPluginNotifier/resources/messages/PluginFreezeBundle.properties
index fb09c32872a9..d3a3ff1f6567 100644
--- a/platform/diagnostic/freezeInPluginNotifier/resources/messages/PluginFreezeBundle.properties
+++ b/platform/diagnostic/freezeInPluginNotifier/resources/messages/PluginFreezeBundle.properties
@@ -1,4 +1,4 @@
action.disable.plugin.text=Disable plugin
action.close.panel.text=Dismiss
action.ignore.plugin.text=Ignore freezes in plugin
-notification.content.plugin.caused.freeze.detected=Freeze caused by the {0} plugin
\ No newline at end of file
+notification.content.plugin.caused.freeze.detected=Plugin ''{0}'' might be slowing things down
\ No newline at end of file
diff --git a/platform/diagnostic/freezeInPluginNotifier/src/com/intellij/platform/diagnostic/plugin/freeze/PluginFreezeNotifier.kt b/platform/diagnostic/freezeInPluginNotifier/src/com/intellij/platform/diagnostic/plugin/freeze/PluginFreezeNotifier.kt
index de3da499996c..f1a4467b8505 100644
--- a/platform/diagnostic/freezeInPluginNotifier/src/com/intellij/platform/diagnostic/plugin/freeze/PluginFreezeNotifier.kt
+++ b/platform/diagnostic/freezeInPluginNotifier/src/com/intellij/platform/diagnostic/plugin/freeze/PluginFreezeNotifier.kt
@@ -1,12 +1,12 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.diagnostic.plugin.freeze
+import com.intellij.featureStatistics.fusCollectors.LifecycleUsageTriggerCollector
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.impl.ApplicationInfoImpl
import com.intellij.openapi.extensions.PluginId
import com.intellij.openapi.fileEditor.FileEditor
-import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.ui.EditorNotificationPanel
@@ -21,23 +21,25 @@ internal class PluginFreezeNotifier : EditorNotificationProvider {
private val freezeStorageService = PluginsFreezesService.getInstance()
override fun collectNotificationData(project: Project, file: VirtualFile): Function? {
- val frozenPlugin = freezeWatcher.latestFrozenPlugin ?: return null
+ val frozenPlugin = freezeWatcher.getFreezeReason() ?: return null
val pluginDescriptor = PluginManagerCore.getPlugin(frozenPlugin) ?: return null
val application = ApplicationManager.getApplication()
if (pluginDescriptor.isImplementationDetail || ApplicationInfoImpl.getShadowInstance().isEssentialPlugin(frozenPlugin)) return null
if (pluginDescriptor.isBundled && !application.isEAP && !application.isInternal) return null
- if (freezeStorageService.shouldBeIgnored(frozenPlugin)) return null
-
- freezeStorageService.setLatestFreezeDate(frozenPlugin)
return Function {
EditorNotificationPanel(EditorNotificationPanel.Status.Warning).apply {
text = PluginFreezeBundle.message("notification.content.plugin.caused.freeze.detected", pluginDescriptor.name)
createActionLabel(PluginFreezeBundle.message("action.disable.plugin.text")) {
disablePlugin(frozenPlugin)
+
+ LifecycleUsageTriggerCollector.pluginDisabledOnFreeze(frozenPlugin)
}
createActionLabel(PluginFreezeBundle.message("action.ignore.plugin.text")) {
freezeStorageService.mutePlugin(frozenPlugin)
+
+ LifecycleUsageTriggerCollector.pluginFreezeIgnored(frozenPlugin)
+
closePanel(project)
}
createActionLabel(PluginFreezeBundle.message("action.close.panel.text")) {
@@ -47,7 +49,7 @@ internal class PluginFreezeNotifier : EditorNotificationProvider {
}
}
- //TODO support dynamic plugins
+ // TODO support dynamic plugins
private fun disablePlugin(frozenPlugin: PluginId) {
val pluginDisabled = PluginManagerCore.disablePlugin(frozenPlugin)
if (pluginDisabled) {
@@ -56,8 +58,7 @@ internal class PluginFreezeNotifier : EditorNotificationProvider {
}
private fun closePanel(project: Project) {
- freezeWatcher.latestFrozenPlugin = null
- FileEditorManager.getInstance(project).openFiles.forEach { it -> EditorNotifications.getInstance(project).updateNotifications(it) }
+ freezeWatcher.reset()
+ EditorNotifications.getInstance(project).updateAllNotifications()
}
-
}
\ No newline at end of file
diff --git a/platform/diagnostic/freezeInPluginNotifier/src/com/intellij/platform/diagnostic/plugin/freeze/PluginFreezeWatcher.kt b/platform/diagnostic/freezeInPluginNotifier/src/com/intellij/platform/diagnostic/plugin/freeze/PluginFreezeWatcher.kt
index 9db9eff4d6e5..07af9d6c9ccf 100644
--- a/platform/diagnostic/freezeInPluginNotifier/src/com/intellij/platform/diagnostic/plugin/freeze/PluginFreezeWatcher.kt
+++ b/platform/diagnostic/freezeInPluginNotifier/src/com/intellij/platform/diagnostic/plugin/freeze/PluginFreezeWatcher.kt
@@ -2,24 +2,30 @@ package com.intellij.platform.diagnostic.plugin.freeze
import com.intellij.diagnostic.IdePerformanceListener
import com.intellij.diagnostic.ThreadDump
+import com.intellij.featureStatistics.fusCollectors.LifecycleUsageTriggerCollector
import com.intellij.ide.plugins.PluginUtilImpl
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
+import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.extensions.PluginId
-import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.ProjectManager
import com.intellij.platform.diagnostic.freezeAnalyzer.FreezeAnalyzer
import com.intellij.threadDumpParser.ThreadState
import com.intellij.ui.EditorNotifications
import java.nio.file.Path
+import kotlin.io.path.absolutePathString
@Service(Service.Level.APP)
internal class PluginFreezeWatcher : IdePerformanceListener, Disposable {
@Volatile
- var latestFrozenPlugin: PluginId? = null
- private val stackTracePattern = """at (\S+)\.(\S+)\(([^:]+):(\d+)\)""".toRegex()
+ private var latestFrozenPlugin: PluginId? = null
+ @Volatile
+ private var lastFreezeDuration: Long = -1
+
+ private val stackTracePattern: Regex = """at (\S+)\.(\S+)\(([^:]+):(\d+)\)""".toRegex()
companion object {
@JvmStatic
@@ -27,19 +33,63 @@ internal class PluginFreezeWatcher : IdePerformanceListener, Disposable {
}
init {
- ApplicationManager.getApplication().messageBus.connect(this).subscribe(IdePerformanceListener.TOPIC, this)
+ ApplicationManager.getApplication().messageBus.connect(this)
+ .subscribe(IdePerformanceListener.TOPIC, this)
+ }
+
+ fun getFreezeReason(): PluginId? = latestFrozenPlugin
+
+ fun reset() {
+ latestFrozenPlugin = null
+ lastFreezeDuration = -1
}
override fun dispose() {}
+ override fun uiFreezeStarted(reportDir: Path) {
+ if (latestFrozenPlugin != null) return
+
+ lastFreezeDuration = -1
+ }
+
+ override fun uiFreezeFinished(durationMs: Long, reportDir: Path?) {
+ if (lastFreezeDuration < 0) {
+ lastFreezeDuration = durationMs
+
+ reportCounters()
+ }
+ }
+
override fun dumpedThreads(toFile: Path, dump: ThreadDump) {
+ if (latestFrozenPlugin != null) return // user have not yet handled previous failure
+
val freezeCausingThreads = FreezeAnalyzer.analyzeFreeze(dump.rawDump, null)?.threads.orEmpty()
val pluginIds = freezeCausingThreads.mapNotNull { analyzeFreezeCausingPlugin(it) }
- latestFrozenPlugin = pluginIds.groupingBy { it }.eachCount().maxByOrNull { it.value }?.key
+ val frozenPlugin = pluginIds.groupingBy { it }.eachCount().maxByOrNull { it.value }?.key ?: return
- ProjectManager.getInstance().openProjects.firstOrNull()?.let { project ->
- FileEditorManager.getInstance(project).focusedEditor?.file?.let { file ->
- EditorNotifications.getInstance(project).updateNotifications(file)
+ val freezeStorageService = PluginsFreezesService.getInstance()
+ if (freezeStorageService.shouldBeIgnored(frozenPlugin)) return
+ freezeStorageService.setLatestFreezeDate(frozenPlugin)
+
+ latestFrozenPlugin = frozenPlugin
+
+ Logger.getInstance(PluginFreezeWatcher::class.java)
+ .warn("Plugin '$frozenPlugin' caused IDE freeze." +
+ "Find thread dumps at ${toFile.absolutePathString()}")
+
+ reportCounters()
+
+ ReadAction.compute {
+ for (project in ProjectManager.getInstance().openProjects) {
+ EditorNotifications.getInstance(project).updateAllNotifications()
+ }
+ }
+ }
+
+ private fun reportCounters() {
+ latestFrozenPlugin?.let {
+ if (lastFreezeDuration > 0) {
+ LifecycleUsageTriggerCollector.pluginFreezeReported(it, lastFreezeDuration)
}
}
}
diff --git a/platform/diagnostic/freezeInPluginNotifier/src/com/intellij/platform/diagnostic/plugin/freeze/PluginsFreezesServiceState.kt b/platform/diagnostic/freezeInPluginNotifier/src/com/intellij/platform/diagnostic/plugin/freeze/PluginsFreezesServiceState.kt
index b921ebab0ec7..fccd863d5c4f 100644
--- a/platform/diagnostic/freezeInPluginNotifier/src/com/intellij/platform/diagnostic/plugin/freeze/PluginsFreezesServiceState.kt
+++ b/platform/diagnostic/freezeInPluginNotifier/src/com/intellij/platform/diagnostic/plugin/freeze/PluginsFreezesServiceState.kt
@@ -3,21 +3,20 @@ package com.intellij.platform.diagnostic.plugin.freeze
import com.intellij.openapi.components.*
import com.intellij.openapi.extensions.PluginId
-import org.jetbrains.annotations.ApiStatus
import java.time.Instant
import java.time.temporal.ChronoUnit
+private const val NOTIFICATION_COOLDOWN_DAYS: Long = 1L
+
@Service(Service.Level.APP)
@State(
name = "PluginFreezes",
- storages = [Storage(value = "pluginFreezes.xml")]
+ storages = [Storage(value = "pluginFreezes.xml", roamingType = RoamingType.DISABLED)]
)
internal class PluginsFreezesService : PersistentStateComponent {
private var state: PluginsFreezesServiceState = PluginsFreezesServiceState()
companion object {
- private const val NOTIFICATION_COOLDOWN_DAYS = 1L
-
@JvmStatic
fun getInstance(): PluginsFreezesService = service()
}
@@ -27,14 +26,19 @@ internal class PluginsFreezesService : PersistentStateComponent = mutableMapOf(),
+internal data class PluginsFreezesServiceState(
+ var latestNotificationTime: MutableMap = mutableMapOf(),
var mutedPlugins: MutableMap = mutableMapOf(),
)
diff --git a/platform/diagnostic/freezeInPluginNotifier/src/com/intellij/platform/diagnostic/plugin/freeze/ResetFreezeNotificationStateAction.kt b/platform/diagnostic/freezeInPluginNotifier/src/com/intellij/platform/diagnostic/plugin/freeze/ResetFreezeNotificationStateAction.kt
new file mode 100644
index 000000000000..133212647c3c
--- /dev/null
+++ b/platform/diagnostic/freezeInPluginNotifier/src/com/intellij/platform/diagnostic/plugin/freeze/ResetFreezeNotificationStateAction.kt
@@ -0,0 +1,14 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.diagnostic.plugin.freeze
+
+import com.intellij.openapi.actionSystem.AnActionEvent
+import com.intellij.openapi.application.WriteAction
+import com.intellij.openapi.project.DumbAwareAction
+
+internal class ResetFreezeNotificationStateAction: DumbAwareAction() {
+ override fun actionPerformed(e: AnActionEvent) {
+ WriteAction.run {
+ PluginsFreezesService.getInstance().reset()
+ }
+ }
+}
\ No newline at end of file
diff --git a/platform/platform-impl/src/com/intellij/diagnostic/dropErrorActions.kt b/platform/platform-impl/src/com/intellij/diagnostic/dropErrorActions.kt
index edf2fbbe1626..18b5caff5a39 100644
--- a/platform/platform-impl/src/com/intellij/diagnostic/dropErrorActions.kt
+++ b/platform/platform-impl/src/com/intellij/diagnostic/dropErrorActions.kt
@@ -8,10 +8,7 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Attachment
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.DumbAwareAction
-import com.intellij.openapi.ui.InputValidator
-import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.io.FileUtil
-import com.intellij.openapi.util.text.StringUtil
import com.intellij.util.TimeoutUtil
import java.awt.event.ActionEvent.CTRL_MASK
import java.awt.event.ActionEvent.SHIFT_MASK
@@ -100,27 +97,3 @@ internal class DropAnOutOfMemoryErrorAction : DumbAwareAction() {
}
}
}
-
-internal class SimulateFreeze : DumbAwareAction() {
- override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
-
- @Suppress("HardCodedStringLiteral")
- override fun actionPerformed(e: AnActionEvent) {
- val durationString = Messages.showInputDialog(
- e.project,
- "Enter freeze duration in ms",
- "Freeze Simulator",
- null,
- "",
- object : InputValidator {
- override fun checkInput(inputString: String?): Boolean = StringUtil.parseInt(inputString, -1) > 0
- override fun canClose(inputString: String?): Boolean = StringUtil.parseInt(inputString, -1) > 0
- }) ?: return
- simulatedFreeze(durationString.toLong())
- }
-
- // Keep it a function to detect it in EA
- private fun simulatedFreeze(ms: Long) {
- Thread.sleep(ms)
- }
-}
diff --git a/platform/platform-impl/src/com/intellij/featureStatistics/fusCollectors/LifecycleUsageTriggerCollector.java b/platform/platform-impl/src/com/intellij/featureStatistics/fusCollectors/LifecycleUsageTriggerCollector.java
index 8406505a8f7f..8e7b62d3bc02 100644
--- a/platform/platform-impl/src/com/intellij/featureStatistics/fusCollectors/LifecycleUsageTriggerCollector.java
+++ b/platform/platform-impl/src/com/intellij/featureStatistics/fusCollectors/LifecycleUsageTriggerCollector.java
@@ -8,6 +8,7 @@ import com.intellij.internal.statistic.collectors.fus.MethodNameRuleValidator;
import com.intellij.internal.statistic.eventLog.EventLogGroup;
import com.intellij.internal.statistic.eventLog.events.*;
import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector;
+import com.intellij.internal.statistic.utils.PluginInfo;
import com.intellij.internal.statistic.utils.StatisticsUploadAssistant;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
@@ -27,7 +28,7 @@ import static com.intellij.internal.statistic.utils.PluginInfoDetectorKt.getPlug
public final class LifecycleUsageTriggerCollector extends CounterUsagesCollector {
private static final Logger LOG = Logger.getInstance(LifecycleUsageTriggerCollector.class);
- private static final EventLogGroup LIFECYCLE = new EventLogGroup("lifecycle", 70);
+ private static final EventLogGroup LIFECYCLE = new EventLogGroup("lifecycle", 71);
private static final EventField eapField = EventFields.Boolean("eap");
private static final EventField testField = EventFields.Boolean("test");
@@ -63,7 +64,14 @@ public final class LifecycleUsageTriggerCollector extends CounterUsagesCollector
private static final EventId FRAME_DEACTIVATED = LIFECYCLE.registerEvent("frame.deactivated");
- private static final EventId1 IDE_FREEZE = LIFECYCLE.registerEvent("ide.freeze", EventFields.Long("duration_ms"));
+ private static final EventId1 IDE_FREEZE = LIFECYCLE.registerEvent("ide.freeze", EventFields.DurationMs);
+
+ private static final EventId2 IDE_FREEZE_REPORTED_PLUGIN =
+ LIFECYCLE.registerEvent("ide.freeze.plugin", EventFields.PluginInfo, EventFields.DurationMs);
+ private static final EventId1 IDE_FREEZE_PLUGIN_DISABLED =
+ LIFECYCLE.registerEvent("ide.freeze.disabled.plugin", EventFields.PluginInfo);
+ private static final EventId1 IDE_FREEZE_PLUGIN_IGNORED =
+ LIFECYCLE.registerEvent("ide.freeze.ignored.plugin", EventFields.PluginInfo);
private static final ClassEventField errorField = EventFields.Class("error");
private static final EventField memoryErrorKindField =
@@ -80,6 +88,7 @@ public final class LifecycleUsageTriggerCollector extends CounterUsagesCollector
private static final EventId IDE_DEADLOCK_DETECTED = LIFECYCLE.registerEvent("ide.deadlock.detected");
+
private enum ProjectOpenMode {New, Same, Attach}
private static final EventField projectOpenModeField = EventFields.Enum("mode", ProjectOpenMode.class, mode -> Strings.toLowerCase(mode.name()));
private static final EventId1 PROJECT_FRAME_SELECTED = LIFECYCLE.registerEvent("project.frame.selected", projectOpenModeField);
@@ -219,4 +228,16 @@ public final class LifecycleUsageTriggerCollector extends CounterUsagesCollector
public static void onEarlyErrorsIgnored(int numErrors) {
EARLY_ERRORS.log(numErrors);
}
+
+ public static void pluginFreezeReported(@NotNull PluginId pluginId, long durationMs) {
+ IDE_FREEZE_REPORTED_PLUGIN.log(getPluginInfoById(pluginId), durationMs);
+ }
+
+ public static void pluginDisabledOnFreeze(@NotNull PluginId id) {
+ IDE_FREEZE_PLUGIN_DISABLED.log(getPluginInfoById(id));
+ }
+
+ public static void pluginFreezeIgnored(@NotNull PluginId id) {
+ IDE_FREEZE_PLUGIN_IGNORED.log(getPluginInfoById(id));
+ }
}
diff --git a/platform/platform-resources/src/idea/PlatformActions.xml b/platform/platform-resources/src/idea/PlatformActions.xml
index 98216e0ad49f..e03f4b38363d 100644
--- a/platform/platform-resources/src/idea/PlatformActions.xml
+++ b/platform/platform-resources/src/idea/PlatformActions.xml
@@ -1267,7 +1267,6 @@
-
diff --git a/plugins/performanceTesting/core/resources/META-INF/plugin.xml b/plugins/performanceTesting/core/resources/META-INF/plugin.xml
index 6d9d6c4ddead..f84cc521996c 100644
--- a/plugins/performanceTesting/core/resources/META-INF/plugin.xml
+++ b/plugins/performanceTesting/core/resources/META-INF/plugin.xml
@@ -6,9 +6,9 @@
messages.PerformanceTestingBundle
+ Plugin for automated execution of test scripts, capturing performance snapshots
+ and gathering performance statistics.
+ ]]>
@@ -33,6 +33,9 @@
+
+
diff --git a/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/actions/SimulateFreeze.kt b/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/actions/SimulateFreeze.kt
new file mode 100644
index 000000000000..67cb435dd39a
--- /dev/null
+++ b/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/actions/SimulateFreeze.kt
@@ -0,0 +1,32 @@
+package com.jetbrains.performancePlugin.actions
+
+import com.intellij.openapi.actionSystem.ActionUpdateThread
+import com.intellij.openapi.actionSystem.AnActionEvent
+import com.intellij.openapi.project.DumbAwareAction
+import com.intellij.openapi.ui.InputValidator
+import com.intellij.openapi.ui.Messages
+import com.intellij.openapi.util.text.StringUtil
+
+internal class SimulateFreeze : DumbAwareAction() {
+ override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
+
+ @Suppress("HardCodedStringLiteral")
+ override fun actionPerformed(e: AnActionEvent) {
+ val durationString = Messages.showInputDialog(
+ e.project,
+ "Enter freeze duration in ms",
+ "Freeze Simulator",
+ null,
+ "",
+ object : InputValidator {
+ override fun checkInput(inputString: String?): Boolean = StringUtil.parseInt(inputString, -1) > 0
+ override fun canClose(inputString: String?): Boolean = StringUtil.parseInt(inputString, -1) > 0
+ }) ?: return
+ simulatedFreeze(durationString.toLong())
+ }
+
+ // Keep it a function to detect it in EA
+ private fun simulatedFreeze(ms: Long) {
+ Thread.sleep(ms)
+ }
+}