[terminal] IJPL-182482 Reimplement backend typing latency measurement

Pass event time and id to the TerminalStarter. Implement the separate method for that in our override for that.
This way is more explicit and safe because it is not using the global state and matching by event object.

GitOrigin-RevId: ddbf4373bf6226c72bd8db3bc0d8b849d4ba3112
This commit is contained in:
Konstantin Hudyakov
2025-04-15 07:32:53 +00:00
committed by intellij-monorepo-bot
parent f612b74235
commit 1ae8a2eb0d
6 changed files with 74 additions and 30 deletions
@@ -12,6 +12,7 @@ import org.jetbrains.plugins.terminal.block.ui.withLock
import org.jetbrains.plugins.terminal.util.STOP_EMULATOR_TIMEOUT
import org.jetbrains.plugins.terminal.util.waitFor
import java.util.concurrent.CancellationException
import kotlin.time.TimeSource
internal fun createTerminalInputChannel(
services: JediTermServices,
@@ -50,7 +51,7 @@ private fun handleInputEvent(event: TerminalInputEvent, services: JediTermServic
when (event) {
is TerminalWriteBytesEvent -> {
terminalStarter.sendBytes(event.bytes, false)
terminalStarter.sendTrackedBytes(event.bytes, event.id, eventTime = TimeSource.Monotonic.markNow())
}
is TerminalResizeEvent -> {
terminalStarter.postResize(event.newSize.toTermSize(), RequestOrigin.User)
@@ -1,25 +0,0 @@
package com.intellij.terminal.backend
import com.jediterm.core.typeahead.TerminalTypeAheadManager
import com.jediterm.terminal.TerminalDataStream
import com.jediterm.terminal.TerminalExecutorServiceManager
import com.jediterm.terminal.TerminalStarter
import com.jediterm.terminal.TtyConnector
import com.jediterm.terminal.model.JediTerminal
internal class StopAwareTerminalStarter(
terminal: JediTerminal,
ttyConnector: TtyConnector,
dataStream: TerminalDataStream,
typeAheadManager: TerminalTypeAheadManager,
executorServiceManager: TerminalExecutorServiceManager,
) : TerminalStarter(terminal, ttyConnector, dataStream, typeAheadManager, executorServiceManager) {
@Volatile
var isStopped: Boolean = false
private set
override fun requestEmulatorStop() {
super.requestEmulatorStop()
isStopped = true
}
}
@@ -230,7 +230,7 @@ internal fun createTerminalOutputFlow(
return outputFlow
}
private fun ensureEmulationActive(starter: StopAwareTerminalStarter) {
private fun ensureEmulationActive(starter: TerminalStarterEx) {
if (Thread.interrupted() || starter.isStopped) {
throw CancellationException("Terminal emulation was stopped")
}
@@ -104,7 +104,7 @@ private fun createJediTermServices(
val controller = ObservableJediTerminal(terminalDisplay, textBuffer, styleState)
val typeAheadManager = TerminalTypeAheadManager(JediTermTypeAheadModel(controller, textBuffer, settings))
val executorService = TerminalExecutorServiceManagerImpl()
val terminalStarter = StopAwareTerminalStarter(
val terminalStarter = TerminalStarterEx(
controller,
connector,
TtyBasedArrayDataStream(connector),
@@ -137,6 +137,6 @@ internal class JediTermServices(
val terminalDisplay: TerminalDisplayImpl,
val controller: ObservableJediTerminal,
val executorService: TerminalExecutorServiceManager,
val terminalStarter: StopAwareTerminalStarter,
val terminalStarter: TerminalStarterEx,
val ttyConnector: ObservableTtyConnector,
)
@@ -0,0 +1,68 @@
package com.intellij.terminal.backend
import com.intellij.openapi.diagnostic.thisLogger
import com.jediterm.core.input.KeyEvent
import com.jediterm.core.typeahead.TerminalTypeAheadManager
import com.jediterm.terminal.TerminalDataStream
import com.jediterm.terminal.TerminalExecutorServiceManager
import com.jediterm.terminal.TerminalStarter
import com.jediterm.terminal.TtyConnector
import com.jediterm.terminal.model.JediTerminal
import org.jetbrains.plugins.terminal.fus.ReworkedTerminalUsageCollector
import java.io.IOException
import java.util.concurrent.ScheduledExecutorService
import kotlin.time.TimeMark
internal class TerminalStarterEx(
terminal: JediTerminal,
private val ttyConnector: TtyConnector,
dataStream: TerminalDataStream,
typeAheadManager: TerminalTypeAheadManager,
executorServiceManager: TerminalExecutorServiceManager,
) : TerminalStarter(terminal, ttyConnector, dataStream, typeAheadManager, executorServiceManager) {
@Volatile
var isStopped: Boolean = false
private set
private val singleThreadScheduledExecutor: ScheduledExecutorService = executorServiceManager.singleThreadScheduledExecutor
@Volatile
private var isLastSentByteEscape = false
override fun requestEmulatorStop() {
super.requestEmulatorStop()
isStopped = true
}
/**
* Use for sending bytes for that typing latency should be reported.
* [eventTime] is the moment when this writing bytes event was initialized.
*/
fun sendTrackedBytes(bytes: ByteArray, eventId: Int, eventTime: TimeMark) {
val length = bytes.size
if (length > 0) {
isLastSentByteEscape = bytes[length - 1].toInt() == KeyEvent.VK_ESCAPE
}
execute {
try {
ttyConnector.write(bytes)
val latency = eventTime.elapsedNow()
ReworkedTerminalUsageCollector.logBackendTypingLatency(eventId, latency)
}
catch (e: IOException) {
thisLogger().info("Cannot write to TtyConnector ${ttyConnector.javaClass.getName()}, connected: ${ttyConnector.isConnected}", e)
}
}
}
override fun isLastSentByteEscape(): Boolean {
return isLastSentByteEscape || super.isLastSentByteEscape()
}
private inline fun execute(crossinline action: () -> Unit) {
if (!singleThreadScheduledExecutor.isShutdown) {
singleThreadScheduledExecutor.execute { action() }
}
}
}
@@ -99,7 +99,7 @@ object ReworkedTerminalUsageCollector : CounterUsagesCollector() {
}
@ApiStatus.Internal
fun logBackendLatency(inputEventId: Int, duration: Duration) {
fun logBackendTypingLatency(inputEventId: Int, duration: Duration) {
backendTypingLatencyEvent.log(
EVENT_ID_FIELD with inputEventId,
DURATION_FIELD with duration