[debugger] IDEA-385233 Do not show notification

GitOrigin-RevId: 4c6fbc0269369fcede26bc8944816cff2a495b81
This commit is contained in:
Maksim Zuev
2026-01-29 20:31:29 +00:00
committed by intellij-monorepo-bot
parent b1356651f6
commit 3cb27850fd
5 changed files with 18 additions and 141 deletions
@@ -169,8 +169,6 @@
<notificationGroup id="HotSwap" displayType="TOOL_WINDOW" toolWindowId="Debug" bundle="messages.JavaDebuggerBundle"
key="notification.group.hotswap"/>
<notificationGroup id="AsyncStackTraces" displayType="BALLOON" bundle="messages.JavaDebuggerBundle"
key="async.stack.traces.notification.group"/>
<debugger.dfaAssistProvider language="JAVA" implementationClass="com.intellij.debugger.engine.dfaassist.java.JavaDfaAssistProvider"/>
@@ -1,109 +1,28 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.engine
import com.intellij.debugger.JavaDebuggerBundle
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.debugger.statistics.DebuggerStatistics
import com.intellij.notification.Notification
import com.intellij.notification.NotificationType
import com.intellij.notification.NotificationsManager
import com.intellij.openapi.application.EDT
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.util.Key
import com.intellij.xdebugger.XDebugSessionListener
import com.intellij.xdebugger.impl.XDebugSessionImpl
import com.sun.jdi.ObjectReference
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import java.util.concurrent.TimeUnit
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
private class LastSessionPauseListener : XDebugSessionListener {
private val isPaused = MutableStateFlow(false)
private val startNs = System.nanoTime()
private val sessionStartTimestampKey = Key.create<Long>("debuggerSessionStartTimestamp")
override fun sessionPaused() {
isPaused.value = true
}
override fun sessionResumed() {
isPaused.value = false
}
@OptIn(FlowPreview::class)
suspend fun awaitNoPauseFor(duration: Duration) {
isPaused.debounce(duration).first { !it }
}
fun passedSinceSessionStartMs(): Long {
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs)
}
}
private val lastSessionPauseListenerKey = Key.create<LastSessionPauseListener>("lastSessionPauseListenerKey")
internal fun initializeLastSessionPauseListener(process: DebugProcessImpl) {
val xDebugSession = process.session.xDebugSession ?: return
val listener = LastSessionPauseListener()
process.putUserData(lastSessionPauseListenerKey, listener)
xDebugSession.addSessionListener(listener)
internal fun initializeOverheadListener(process: DebugProcessImpl) {
val startNs = System.nanoTime()
process.putUserData(sessionStartTimestampKey, startNs)
}
@OptIn(FlowPreview::class)
internal fun showOverheadNotification(process: DebugProcessImpl, overhead: ObjectReference) {
val xDebugSession = process.session.xDebugSession as? XDebugSessionImpl ?: return
val cs = xDebugSession.coroutineScope
val managerThread = DebuggerManagerThreadImpl.getCurrentThread()
cs.launch(Dispatchers.EDT) {
val project = xDebugSession.project
val listener = process.getUserData(lastSessionPauseListenerKey)
val passedSinceSessionStartMs = listener?.passedSinceSessionStartMs() ?: -1
DebuggerStatistics.logAgentOverheadDetected(project, passedSinceSessionStartMs)
if (listener != null) {
// we don't want to show notification if user is actively debugging
listener.awaitNoPauseFor(3.seconds)
}
if (process.isDetached) return@launch
val title = JavaDebuggerBundle.message("async.stack.traces.overhead.title")
val content = JavaDebuggerBundle.message("async.stack.traces.overhead.description")
val notification = Notification("AsyncStackTraces", title, content, NotificationType.WARNING)
notification.addAction(DumbAwareAction.create(JavaDebuggerBundle.message("async.stack.traces.overhead.throttle.button")) {
notification.expire()
DebuggerStatistics.logAgentOverheadNotificationThrottlingEnabled(project)
executeOnDMT(managerThread) {
val field = DebuggerUtils.findField(overhead.referenceType(), "throttleWhenOverhead")
if (field != null) {
overhead.setValue(field, overhead.virtualMachine().mirrorOf(true))
}
}
})
notification.addAction(DumbAwareAction.create(JavaDebuggerBundle.message("async.stack.traces.overhead.disable.button")) {
notification.expire()
DebuggerStatistics.logAgentOverheadNotificationAgentDisabled(project)
executeOnDMT(managerThread) {
DebuggerUtilsEx.setStaticBooleanField(process, AsyncStacksUtils.CAPTURE_STORAGE_CLASS_NAME, "ENABLED", false)
}
})
notification.addAction(DumbAwareAction.create(JavaDebuggerBundle.message("async.stack.traces.overhead.ignore.button")) {
notification.expire()
DebuggerStatistics.logAgentOverheadNotificationDismissed(project)
})
val processListener = object : DebugProcessListener {
override fun processDetached(process: DebugProcess, closedByUser: Boolean) {
notification.expire()
}
}
process.addDebugProcessListener(processListener)
notification.whenExpired {
process.removeDebugProcessListener(processListener)
}
NotificationsManager.getNotificationsManager().showNotification(notification, process.project)
DebuggerStatistics.logAgentOverheadNotificationShown(project)
internal fun onOverheadDetected(process: DebugProcessImpl) {
val project = process.project
val sessionStartNs = process.getUserData(sessionStartTimestampKey)
val sessionLengthMs = if (sessionStartNs != null) {
val durationNs = System.nanoTime() - sessionStartNs
TimeUnit.NANOSECONDS.toMillis(durationNs)
}
else {
-1
}
DebuggerStatistics.logAgentOverheadDetected(project, sessionLengthMs)
}
@@ -1,6 +1,7 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.engine;
import com.intellij.debugger.SourcePosition;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
@@ -517,24 +518,14 @@ public final class AsyncStacksUtils {
}
private static void initializeOverheadDetector(DebugProcessImpl process) {
AsyncStackTracesOverheadUtilsKt.initializeLastSessionPauseListener(process);
AsyncStackTracesOverheadUtilsKt.initializeOverheadListener(process);
String className = "com.intellij.rt.debugger.agent.OverheadDetector";
String methodName = "overheadDetected";
var breakpoint = new SyntheticMethodBreakpoint(className, methodName, null, process.getProject()) {
@Override
public boolean processLocatableEvent(@NotNull SuspendContextCommandImpl action, LocatableEvent event) {
if (event == null) return false;
try {
List<Value> args = DebuggerUtilsEx.getArgumentValues(event.thread().frame(0));
Value overheadValue = ContainerUtil.getFirstItem(args);
if (overheadValue instanceof ObjectReference overhead) {
AsyncStackTracesOverheadUtilsKt.showOverheadNotification(process, overhead);
}
}
catch (IncompatibleThreadStateException e) {
LOG.error(e);
}
AsyncStackTracesOverheadUtilsKt.onOverheadDetected(process);
return false;
}
};
@@ -21,7 +21,7 @@ import org.jetbrains.annotations.ApiStatus
object DebuggerStatistics : CounterUsagesCollector() {
override fun getGroup(): EventLogGroup = GROUP
private val GROUP = EventLogGroup("java.debugger", 15)
private val GROUP = EventLogGroup("java.debugger", 16)
// fields
@@ -76,14 +76,6 @@ object DebuggerStatistics : CounterUsagesCollector() {
private val agentOverheadDetected = GROUP.registerEvent("debugger.agent.overhead.detected",
EventFields.Long("passed_since_session_start_ms"),
"Detected noticeable overhead of the debugger agent due to async stack traces collection")
private val agentOverheadNotificationShown = GROUP.registerEvent("debugger.agent.overhead.notification.shown",
"Debugger agent overhead notification was shown (happens after overhead detected in 3 seconds)")
private val agentOverheadNotificationDismissed = GROUP.registerEvent("debugger.agent.overhead.notification.dismissed",
"Debugger agent overhead notification was dismissed")
private val agentOverheadNotificationAgentDisabled = GROUP.registerEvent("debugger.agent.overhead.notification.agent.disabled",
"Debugger agent was disabled on overhead detected")
private val agentOverheadNotificationThrottlingEnabled = GROUP.registerEvent("debugger.agent.overhead.notification.throttling.enabled",
"Throttling of debugger agent was enabled on overhead detected")
@JvmStatic
fun logProcessStatistics(debugProcess: DebugProcess) {
@@ -193,22 +185,6 @@ object DebuggerStatistics : CounterUsagesCollector() {
agentOverheadDetected.log(project, passedSinceSessionStartMs)
}
fun logAgentOverheadNotificationShown(project: Project) {
agentOverheadNotificationShown.log(project)
}
fun logAgentOverheadNotificationDismissed(project: Project) {
agentOverheadNotificationDismissed.log(project)
}
fun logAgentOverheadNotificationAgentDisabled(project: Project) {
agentOverheadNotificationAgentDisabled.log(project)
}
fun logAgentOverheadNotificationThrottlingEnabled(project: Project) {
agentOverheadNotificationThrottlingEnabled.log(project)
}
private val Breakpoint<*>.type: String? get() = xBreakpoint?.type?.id
}
@@ -558,10 +558,3 @@ debugger.variables.not.available.in.async=Variables are not available for async
exception=Exception
ref=Ref
thread.dump.during.previous.dump.evaluation.warning=Evaluation of the previous dump is still in progress. Java platform thread dump is taken.
async.stack.traces.notification.group=Async Stack Traces
async.stack.traces.overhead.title=Async stack traces overhead detected
async.stack.traces.overhead.description=Collecting async stack traces may significantly impact your application's performance
async.stack.traces.overhead.throttle.button=Throttle
async.stack.traces.overhead.ignore.button=Ignore
async.stack.traces.overhead.disable.button=Disable