diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java index d7cfce43e674..0a4641d04f42 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java @@ -201,7 +201,7 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb private DebuggerManagerThreadImpl createManagerThread() { CoroutineScope projectScope = ((XDebuggerManagerImpl)XDebuggerManager.getInstance(project)).getCoroutineScope(); - return new DebuggerManagerThreadImpl(disposable, projectScope); + return new DebuggerManagerThreadImpl(disposable, projectScope, this); } private void reloadRenderers() { diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/DebuggerManagerThreadImpl.kt b/java/debugger/impl/src/com/intellij/debugger/engine/DebuggerManagerThreadImpl.kt index 70d7b728cf93..7fec056388bf 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/DebuggerManagerThreadImpl.kt +++ b/java/debugger/impl/src/com/intellij/debugger/engine/DebuggerManagerThreadImpl.kt @@ -8,6 +8,7 @@ import com.intellij.debugger.engine.managerThread.DebuggerCommand import com.intellij.debugger.engine.managerThread.DebuggerManagerThread import com.intellij.debugger.engine.managerThread.SuspendContextCommand import com.intellij.debugger.impl.* +import com.intellij.debugger.statistics.StatisticsStorage import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.ComponentManagerEx @@ -27,17 +28,23 @@ import kotlinx.coroutines.* import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.Nls import org.jetbrains.annotations.TestOnly +import java.lang.ref.WeakReference import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit +import kotlin.system.measureNanoTime -class DebuggerManagerThreadImpl(parent: Disposable, private val parentScope: CoroutineScope) : - InvokeAndWaitThread(), DebuggerManagerThread, Disposable { +class DebuggerManagerThreadImpl @ApiStatus.Internal @JvmOverloads constructor( + parent: Disposable, + private val parentScope: CoroutineScope, + debugProcess: DebugProcess? = null, +) : InvokeAndWaitThread(), DebuggerManagerThread, Disposable { @Volatile private var myDisposed = false private val myDebuggerThreadDispatcher = DebuggerThreadDispatcher(this) + private val myDebugProcess = WeakReference(debugProcess) val unfinishedCommands = ConcurrentCollectionFactory.createConcurrentSet() @ApiStatus.Internal @@ -163,7 +170,13 @@ class DebuggerManagerThreadImpl(parent: Disposable, private val parentScope: Cor managerCommand.notifyCancelled() } else { - managerCommand.invokeCommand(myDebuggerThreadDispatcher, coroutineScope) + val commandTimeNs = measureNanoTime { + managerCommand.invokeCommand(myDebuggerThreadDispatcher, coroutineScope) + } + myDebugProcess.get()?.let { debugProcess -> + val commandTimeMs = TimeUnit.NANOSECONDS.toMillis(commandTimeNs) + StatisticsStorage.addCommandTime(debugProcess, commandTimeMs) + } } } catch (e: VMDisconnectedException) { diff --git a/java/debugger/impl/src/com/intellij/debugger/statistics/DebuggerStatistics.kt b/java/debugger/impl/src/com/intellij/debugger/statistics/DebuggerStatistics.kt index bd3ac6496220..b94e45d78f55 100644 --- a/java/debugger/impl/src/com/intellij/debugger/statistics/DebuggerStatistics.kt +++ b/java/debugger/impl/src/com/intellij/debugger/statistics/DebuggerStatistics.kt @@ -5,6 +5,7 @@ import com.intellij.debugger.actions.JvmSmartStepIntoHandler import com.intellij.debugger.engine.DebugProcess import com.intellij.debugger.engine.DebugProcessEvents import com.intellij.debugger.engine.SteppingAction +import com.intellij.debugger.impl.DebuggerUtilsImpl import com.intellij.debugger.ui.breakpoints.Breakpoint import com.intellij.internal.statistic.eventLog.EventLogGroup import com.intellij.internal.statistic.eventLog.events.EventFields @@ -16,7 +17,7 @@ import org.jetbrains.annotations.ApiStatus object DebuggerStatistics : CounterUsagesCollector() { override fun getGroup(): EventLogGroup = GROUP - private val GROUP = EventLogGroup("java.debugger", 9) + private val GROUP = EventLogGroup("java.debugger", 10) // fields @@ -51,6 +52,10 @@ object DebuggerStatistics : CounterUsagesCollector() { private val breakpointSkipped = GROUP.registerEvent("breakpoint.skipped", EventFields.Enum("reason")) + /** Reports execution time of debugger commands in buckets, updated at the end of a debugger session. */ + private val timeBucketCount = GROUP.registerEvent("debugger.command.time.bucket.updated", EventFields.Int("bucket_upper_limit_ms"), EventFields.Count, EventFields.Boolean("is_remote")) + internal val bucketUpperLimits = (generateSequence(1L) { it * 2 }.takeWhile { it <= 2048 } + Long.MAX_VALUE).toList().toLongArray() + @JvmStatic fun logProcessStatistics(debugProcess: DebugProcess) { val collectedStats = StatisticsStorage.collectAndClearData(debugProcess) @@ -61,6 +66,13 @@ object DebuggerStatistics : CounterUsagesCollector() { is SteppingStatistic -> logSteppingOverhead(debugProcess.project, key, timeMs) } } + + val isRemote = DebuggerUtilsImpl.isRemote(debugProcess) + val bucketCounts = StatisticsStorage.collectCommandsPerformance(debugProcess) + val intUpperLimits = bucketUpperLimits.map { if (it == Long.MAX_VALUE) Int.MAX_VALUE else it.toInt() }.toIntArray() + for ((upperLimitMs, count) in intUpperLimits.zip(bucketCounts)) { + timeBucketCount.log(debugProcess.project, upperLimitMs, count, isRemote) + } } @JvmStatic diff --git a/java/debugger/impl/src/com/intellij/debugger/statistics/StatisticsStorage.kt b/java/debugger/impl/src/com/intellij/debugger/statistics/StatisticsStorage.kt index 32e29c9cf9b9..f8e27d7d0d96 100644 --- a/java/debugger/impl/src/com/intellij/debugger/statistics/StatisticsStorage.kt +++ b/java/debugger/impl/src/com/intellij/debugger/statistics/StatisticsStorage.kt @@ -4,6 +4,7 @@ package com.intellij.debugger.statistics import com.intellij.debugger.engine.DebugProcess import com.intellij.debugger.engine.SteppingAction import com.intellij.debugger.ui.breakpoints.Breakpoint +import com.intellij.openapi.diagnostic.fileLogger import com.intellij.openapi.util.Key import java.util.concurrent.ConcurrentHashMap @@ -11,6 +12,7 @@ private val KEY: Key = Key.create("DEBUGGER_STATISTICS_STORAG class StatisticsStorage { private val data = ConcurrentHashMap() + private val timeBucketCounts = IntArray(DebuggerStatistics.bucketUpperLimits.size) private fun append(key: StatisticElement, timeMs: Long) = data.merge(key, timeMs, Long::plus) private fun remove(key: StatisticElement) = data.remove(key) @@ -63,6 +65,25 @@ class StatisticsStorage { @JvmStatic fun getSteppingStatisticOrNull(token: Any?): SteppingStatistic? = token as? SteppingStatistic + + @JvmStatic + fun addCommandTime(debugProcess: DebugProcess, timeMs: Long) { + val storage = getStorage(debugProcess) + val bucketIndex = DebuggerStatistics.bucketUpperLimits.indexOfFirst { timeMs <= it } + if (bucketIndex < 0) { + fileLogger().error("Unexpected command time $timeMs, found no bucket for it: ${DebuggerStatistics.bucketUpperLimits}") + return + } + storage.timeBucketCounts[bucketIndex]++ + } + + @Synchronized + internal fun collectCommandsPerformance(debugProcess: DebugProcess): IntArray { + val storage = getStorage(debugProcess) + return storage.timeBucketCounts.copyOf().also { + storage.timeBucketCounts.fill(0) + } + } } }