diff --git a/platform/execution-impl/src/com/intellij/terminal/session/TerminalInputEvent.kt b/platform/execution-impl/src/com/intellij/terminal/session/TerminalInputEvent.kt index 1d94eadf9cbc..fd89d963f07b 100644 --- a/platform/execution-impl/src/com/intellij/terminal/session/TerminalInputEvent.kt +++ b/platform/execution-impl/src/com/intellij/terminal/session/TerminalInputEvent.kt @@ -15,7 +15,7 @@ data class TerminalResizeEvent(val newSize: TerminalSizeDto) : TerminalInputEven @ApiStatus.Internal @Serializable -data class TerminalWriteBytesEvent(val bytes: ByteArray) : TerminalInputEvent { +data class TerminalWriteBytesEvent(val bytes: ByteArray, val id: Int? = null) : TerminalInputEvent { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/TerminalInput.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/TerminalInput.kt index e66b78b9f503..c1ed3ca9009b 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/TerminalInput.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/TerminalInput.kt @@ -96,8 +96,8 @@ class TerminalInput( } fun sendBytes(data: ByteArray) { - val writeBytesEvent = TerminalWriteBytesEvent(data) val fusActivity = ReworkedTerminalUsageCollector.getCurrentKeyEventTypingActivityOrNull() + val writeBytesEvent = TerminalWriteBytesEvent(bytes = data, id = fusActivity?.id) fusActivity?.startTerminalInputEventProcessing(writeBytesEvent) sendEvent(writeBytesEvent) } diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/LocalTerminalDirectRunner.java b/plugins/terminal/src/org/jetbrains/plugins/terminal/LocalTerminalDirectRunner.java index 0a12b9a9a391..c95f519d8f7a 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/LocalTerminalDirectRunner.java +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/LocalTerminalDirectRunner.java @@ -25,6 +25,7 @@ import org.jetbrains.plugins.terminal.runner.LocalOptionsConfigurer; import org.jetbrains.plugins.terminal.runner.LocalShellIntegrationInjector; import org.jetbrains.plugins.terminal.runner.LocalTerminalStartCommandBuilder; +import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -188,6 +189,21 @@ public class LocalTerminalDirectRunner extends AbstractTerminalRunner) { localShellStartedEvent.log(project, @@ -88,6 +94,13 @@ object ReworkedTerminalUsageCollector : CounterUsagesCollector() { ) } + internal fun logBackendLatency(inputEventId: Int, duration: Duration) { + backendTypingLatencyEvent.log( + INPUT_EVENT_ID_FIELD with inputEventId, + DURATION_FIELD with duration + ) + } + fun startFrontendTypingActivity(e: KeyEvent): FrontendTypingActivity? { ThreadingAssertions.softAssertEventDispatchThread() if (e.id != KeyEvent.KEY_TYPED) return null @@ -104,6 +117,18 @@ object ReworkedTerminalUsageCollector : CounterUsagesCollector() { fun getFrontendTypingActivityOrNull(event: TerminalInputEvent): FrontendTypingActivity? { return frontendTypingActivityByInputEvent[InputEventIdentityWrapper(event)] } + + fun tryStartBackendTypingActivity(event: TerminalWriteBytesEvent) { + val id = event.id ?: return + val bytes = event.bytes + val activity = BackendTypingActivityImpl(id, bytes) + backendTypingActivityByByteArray[bytes] = activity + } + + @JvmStatic + fun getBackendTypingActivityOrNull(bytes: ByteArray): BackendTypingActivity? { + return backendTypingActivityByByteArray[bytes] + } } @ApiStatus.Internal @@ -115,10 +140,19 @@ interface FrontendTypingActivity { fun finishTerminalInputEventProcessing() } +@ApiStatus.Internal +interface BackendTypingActivity { + val id: Int + fun reportDuration() + fun finishBytesProcessing() +} + private val frontendTypingActivityId = AtomicInteger() private var currentKeyEventTypingActivity: FrontendTypingActivityImpl? = null private val frontendTypingActivityByInputEvent = ConcurrentHashMap() +private val backendTypingActivityByByteArray = ConcurrentHashMap() + // TerminalWriteBytesEvent is a data class, but we need to track individual events, not their content private class InputEventIdentityWrapper(private val event: TerminalInputEvent) { override fun equals(other: Any?): Boolean = event === (other as? InputEventIdentityWrapper)?.event @@ -161,4 +195,20 @@ private class FrontendTypingActivityImpl(override val id: Int) : FrontendTypingA } } +private class BackendTypingActivityImpl(override val id: Int, private val bytes: ByteArray) : BackendTypingActivity { + private val start = TimeSource.Monotonic.markNow() + + override fun reportDuration() { + val duration = start.elapsedNow() + ReworkedTerminalUsageCollector.logBackendLatency( + inputEventId = id, + duration = duration, + ) + } + + override fun finishBytesProcessing() { + backendTypingActivityByByteArray.remove(bytes) + } +} + private val LOG = logger()