[debugger] IDEA-366895 Debugger: collect performance statistics on the command execution time

(cherry picked from commit 1de07f7dba0a674838012e1ba228c3356febf93e)

GitOrigin-RevId: b809e4cd32925e534ed853b497dc5ff6aa5f49e5
This commit is contained in:
Maksim Zuev
2025-02-03 16:42:39 +01:00
committed by intellij-monorepo-bot
parent 6565a5c303
commit ba1db90b28
4 changed files with 51 additions and 5 deletions

View File

@@ -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() {

View File

@@ -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<DebuggerCommandImpl?>(), DebuggerManagerThread, Disposable {
class DebuggerManagerThreadImpl @ApiStatus.Internal @JvmOverloads constructor(
parent: Disposable,
private val parentScope: CoroutineScope,
debugProcess: DebugProcess? = null,
) : InvokeAndWaitThread<DebuggerCommandImpl?>(), DebuggerManagerThread, Disposable {
@Volatile
private var myDisposed = false
private val myDebuggerThreadDispatcher = DebuggerThreadDispatcher(this)
private val myDebugProcess = WeakReference(debugProcess)
val unfinishedCommands = ConcurrentCollectionFactory.createConcurrentSet<DebuggerCommandImpl>()
@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) {

View File

@@ -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<DebugProcessEvents.SkippedBreakpointReason>("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

View File

@@ -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<StatisticsStorage> = Key.create("DEBUGGER_STATISTICS_STORAG
class StatisticsStorage {
private val data = ConcurrentHashMap<StatisticElement, Long>()
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)
}
}
}
}