mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[terminal] IJPL-182482 Reimplement the backend output latency measurement
This implementation is more simple: 1. We track the last time of reading from the TTY. 2. We remember the time from step 1 when we text buffer is changed the first time. Here output accumulation session starts. 3. Once output is collected, we put the time from step 2 to the created `TerminalOutputUpdatedEvent` and reset it. Here output accumulation session ends. 4. Once we update the backend models with this event, we report the backend output latency and forward the event to the frontend. GitOrigin-RevId: ed4e4920b46e5bdd7feb27fa7200e4858b629632
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1426c4858b
commit
3868836dd4
@@ -7,6 +7,7 @@ import com.intellij.terminal.session.dto.TerminalOutputModelStateDto
|
||||
import com.intellij.terminal.session.dto.TerminalStateDto
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import kotlin.time.TimeMark
|
||||
|
||||
@ApiStatus.Internal
|
||||
@Serializable
|
||||
@@ -15,11 +16,11 @@ sealed interface TerminalOutputEvent
|
||||
@ApiStatus.Internal
|
||||
@Serializable
|
||||
data class TerminalContentUpdatedEvent(
|
||||
val id: Int,
|
||||
val text: String,
|
||||
val styles: List<StyleRangeDto>,
|
||||
val startLineLogicalIndex: Long,
|
||||
val firstCharIndex: Long,
|
||||
val lastCharIndex: Long,
|
||||
val readTime: TimeMark?,
|
||||
) : TerminalOutputEvent
|
||||
|
||||
@ApiStatus.Internal
|
||||
|
||||
+6
@@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.jetbrains.plugins.terminal.block.reworked.*
|
||||
import org.jetbrains.plugins.terminal.block.ui.TerminalUiUtils
|
||||
import org.jetbrains.plugins.terminal.fus.ReworkedTerminalUsageCollector
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
/**
|
||||
@@ -83,6 +84,11 @@ internal class StateAwareTerminalSession(private val delegate: TerminalSession)
|
||||
val styles = event.styles.map { it.toStyleRange() }
|
||||
val model = getCurrentOutputModel()
|
||||
model.updateContent(event.startLineLogicalIndex, event.text, styles)
|
||||
|
||||
val latency = event.readTime?.elapsedNow()
|
||||
if (latency != null) {
|
||||
ReworkedTerminalUsageCollector.logBackendOutputLatency(event.id, latency)
|
||||
}
|
||||
}
|
||||
is TerminalCursorPositionChangedEvent -> {
|
||||
val model = getCurrentOutputModel()
|
||||
|
||||
+13
-9
@@ -2,7 +2,7 @@
|
||||
package com.intellij.terminal.backend
|
||||
|
||||
import com.intellij.terminal.session.StyleRange
|
||||
import com.intellij.terminal.session.TerminalContentUpdatedEvent
|
||||
import com.intellij.terminal.session.dto.StyleRangeDto
|
||||
import com.intellij.terminal.session.dto.toDto
|
||||
import com.jediterm.terminal.model.TerminalLine
|
||||
import com.jediterm.terminal.model.TerminalTextBuffer
|
||||
@@ -21,7 +21,7 @@ internal class TerminalContentChangesTracker(
|
||||
private var lastChangedVisualLine: Int = 0
|
||||
private var anyLineChanged: Boolean = false
|
||||
|
||||
private val listeners: MutableList<(TerminalContentUpdatedEvent) -> Unit> = CopyOnWriteArrayList()
|
||||
private val listeners: MutableList<(TerminalContentUpdate) -> Unit> = CopyOnWriteArrayList()
|
||||
|
||||
init {
|
||||
textBuffer.addChangesListener(object : TextBufferChangesListener {
|
||||
@@ -58,11 +58,11 @@ internal class TerminalContentChangesTracker(
|
||||
})
|
||||
}
|
||||
|
||||
fun addHistoryOverflowListener(listener: (TerminalContentUpdatedEvent) -> Unit) {
|
||||
fun addHistoryOverflowListener(listener: (TerminalContentUpdate) -> Unit) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
fun getContentUpdate(): TerminalContentUpdatedEvent? {
|
||||
fun getContentUpdate(): TerminalContentUpdate? {
|
||||
return getContentUpdate(emptyList())
|
||||
}
|
||||
|
||||
@@ -73,14 +73,14 @@ internal class TerminalContentChangesTracker(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getContentUpdate(additionalLines: List<TerminalLine>): TerminalContentUpdatedEvent? {
|
||||
private fun getContentUpdate(additionalLines: List<TerminalLine>): TerminalContentUpdate? {
|
||||
return if (anyLineChanged) {
|
||||
collectOutput(additionalLines)
|
||||
}
|
||||
else null
|
||||
}
|
||||
|
||||
private fun collectOutput(additionalLines: List<TerminalLine>): TerminalContentUpdatedEvent {
|
||||
private fun collectOutput(additionalLines: List<TerminalLine>): TerminalContentUpdate {
|
||||
check(anyLineChanged) { "It is expected that this method is called only if something is changed" }
|
||||
|
||||
// Transform to the TextBuffer coordinates: negative indexes for history, positive for the screen.
|
||||
@@ -98,12 +98,10 @@ internal class TerminalContentChangesTracker(
|
||||
lastChangedVisualLine = textBuffer.effectiveHistoryLinesCount + textBuffer.screenLinesCount
|
||||
anyLineChanged = false
|
||||
|
||||
return TerminalContentUpdatedEvent(
|
||||
return TerminalContentUpdate(
|
||||
text = output.text,
|
||||
styles = output.styleRanges.map { it.toDto() },
|
||||
startLineLogicalIndex = logicalLineIndex,
|
||||
firstCharIndex = -1,
|
||||
lastCharIndex = -1,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -121,6 +119,12 @@ internal class TerminalContentChangesTracker(
|
||||
}
|
||||
}
|
||||
|
||||
internal data class TerminalContentUpdate(
|
||||
val text: String,
|
||||
val styles: List<StyleRangeDto>,
|
||||
val startLineLogicalIndex: Long,
|
||||
)
|
||||
|
||||
/**
|
||||
* Consider the sequence of wrapped lines in the Text Buffer as a single logical line.
|
||||
*/
|
||||
|
||||
@@ -3,26 +3,24 @@ package com.intellij.terminal.backend
|
||||
|
||||
import com.intellij.terminal.session.*
|
||||
import com.intellij.terminal.session.dto.toDto
|
||||
import com.intellij.util.asDisposable
|
||||
import com.jediterm.terminal.CursorShape
|
||||
import com.jediterm.terminal.emulator.mouse.MouseFormat
|
||||
import com.jediterm.terminal.emulator.mouse.MouseMode
|
||||
import com.jediterm.terminal.model.TerminalTextBuffer
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import org.jetbrains.plugins.terminal.block.reworked.TerminalShellIntegrationEventsListener
|
||||
import org.jetbrains.plugins.terminal.block.ui.withLock
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
private val outputUpdateEventIdCounter = AtomicInteger(0)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal fun createTerminalOutputFlow(
|
||||
textBuffer: TerminalTextBuffer,
|
||||
fusActivity: BackendOutputActivity,
|
||||
terminalDisplay: TerminalDisplayImpl,
|
||||
controller: ObservableJediTerminal,
|
||||
services: JediTermServices,
|
||||
shellIntegrationController: TerminalShellIntegrationController,
|
||||
coroutineScope: CoroutineScope,
|
||||
ensureEmulationActive: () -> Unit,
|
||||
): MutableSharedFlow<List<TerminalOutputEvent>> {
|
||||
val outputFlow = MutableSharedFlow<List<TerminalOutputEvent>>(
|
||||
// Do not buffer a lot of events here.
|
||||
@@ -36,23 +34,39 @@ internal fun createTerminalOutputFlow(
|
||||
onBufferOverflow = BufferOverflow.SUSPEND
|
||||
)
|
||||
|
||||
val textBuffer = services.textBuffer
|
||||
val controller = services.controller
|
||||
val terminalDisplay = services.terminalDisplay
|
||||
|
||||
val discardedHistoryTracker = TerminalDiscardedHistoryTracker(textBuffer)
|
||||
val contentChangesTracker = TerminalContentChangesTracker(textBuffer, discardedHistoryTracker)
|
||||
val cursorPositionTracker = TerminalCursorPositionTracker(textBuffer, discardedHistoryTracker, terminalDisplay)
|
||||
val outputLatencyTracker = TerminalOutputLatencyTracker(services.ttyConnector, textBuffer, coroutineScope.asDisposable())
|
||||
|
||||
/**
|
||||
* Events should be sent in the following order: content update, cursor position update, other events.
|
||||
* This function allows providing content update if it is precalculated, and the other optional event to be sent last.
|
||||
*/
|
||||
fun collectAndSendEvents(
|
||||
contentUpdateEvent: TerminalContentUpdatedEvent?,
|
||||
contentUpdate: TerminalContentUpdate?,
|
||||
otherEvent: TerminalOutputEvent?,
|
||||
ensureActive: () -> Unit = { ensureEmulationActive() },
|
||||
ensureActive: () -> Unit = { ensureEmulationActive(services.terminalStarter) },
|
||||
) {
|
||||
textBuffer.withLock {
|
||||
val contentUpdate = contentUpdateEvent ?: contentChangesTracker.getContentUpdate()
|
||||
val actualContentUpdate = contentUpdate ?: contentChangesTracker.getContentUpdate()
|
||||
val contentUpdateEvent = if (actualContentUpdate != null) {
|
||||
TerminalContentUpdatedEvent(
|
||||
id = outputUpdateEventIdCounter.getAndIncrement(),
|
||||
text = actualContentUpdate.text,
|
||||
styles = actualContentUpdate.styles,
|
||||
startLineLogicalIndex = actualContentUpdate.startLineLogicalIndex,
|
||||
readTime = outputLatencyTracker.getCurUpdateTtyReadTimeAndReset(),
|
||||
)
|
||||
}
|
||||
else null
|
||||
|
||||
val cursorPositionUpdate = cursorPositionTracker.getCursorPositionUpdate()
|
||||
val updates = listOfNotNull(contentUpdate, cursorPositionUpdate, otherEvent)
|
||||
val updates = listOfNotNull(contentUpdateEvent, cursorPositionUpdate, otherEvent)
|
||||
if (updates.isNotEmpty()) {
|
||||
// Block the shell output reading if any of the following:
|
||||
// 1. There are no active collectors: then there is no need to read the shell output.
|
||||
@@ -67,14 +81,18 @@ internal fun createTerminalOutputFlow(
|
||||
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
while (true) {
|
||||
collectAndSendEvents(contentUpdateEvent = null, otherEvent = null, ensureActive = { ensureActive(); ensureEmulationActive() })
|
||||
collectAndSendEvents(
|
||||
contentUpdate = null,
|
||||
otherEvent = null,
|
||||
ensureActive = { ensureActive(); ensureEmulationActive(services.terminalStarter) }
|
||||
)
|
||||
|
||||
delay(10)
|
||||
}
|
||||
}
|
||||
|
||||
contentChangesTracker.addHistoryOverflowListener { contentUpdate ->
|
||||
collectAndSendEvents(contentUpdateEvent = contentUpdate, otherEvent = null)
|
||||
collectAndSendEvents(contentUpdate = contentUpdate, otherEvent = null)
|
||||
}
|
||||
|
||||
var curState = TerminalState(
|
||||
@@ -96,28 +114,28 @@ internal fun createTerminalOutputFlow(
|
||||
override fun arrowKeysModeChanged(isApplication: Boolean) {
|
||||
textBuffer.withLock {
|
||||
curState = curState.copy(isApplicationArrowKeys = isApplication)
|
||||
collectAndSendEvents(contentUpdateEvent = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
collectAndSendEvents(contentUpdate = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun keypadModeChanged(isApplication: Boolean) {
|
||||
textBuffer.withLock {
|
||||
curState = curState.copy(isApplicationKeypad = isApplication)
|
||||
collectAndSendEvents(contentUpdateEvent = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
collectAndSendEvents(contentUpdate = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun autoNewLineChanged(isEnabled: Boolean) {
|
||||
textBuffer.withLock {
|
||||
curState = curState.copy(isAutoNewLine = isEnabled)
|
||||
collectAndSendEvents(contentUpdateEvent = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
collectAndSendEvents(contentUpdate = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun altSendsEscapeChanged(isEnabled: Boolean) {
|
||||
textBuffer.withLock {
|
||||
curState = curState.copy(isAltSendsEscape = isEnabled)
|
||||
collectAndSendEvents(contentUpdateEvent = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
collectAndSendEvents(contentUpdate = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +147,7 @@ internal fun createTerminalOutputFlow(
|
||||
override fun beforeAlternateScreenBufferChanged(isEnabled: Boolean) {
|
||||
textBuffer.withLock {
|
||||
curState = curState.copy(isAlternateScreenBuffer = isEnabled)
|
||||
collectAndSendEvents(contentUpdateEvent = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
collectAndSendEvents(contentUpdate = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -138,48 +156,48 @@ internal fun createTerminalOutputFlow(
|
||||
override fun cursorVisibilityChanged(isVisible: Boolean) {
|
||||
textBuffer.withLock {
|
||||
curState = curState.copy(isCursorVisible = isVisible)
|
||||
collectAndSendEvents(contentUpdateEvent = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
collectAndSendEvents(contentUpdate = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun cursorShapeChanged(cursorShape: CursorShape?) {
|
||||
textBuffer.withLock {
|
||||
curState = curState.copy(cursorShape = cursorShape)
|
||||
collectAndSendEvents(contentUpdateEvent = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
collectAndSendEvents(contentUpdate = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseModeChanged(mode: MouseMode) {
|
||||
textBuffer.withLock {
|
||||
curState = curState.copy(mouseMode = mode)
|
||||
collectAndSendEvents(contentUpdateEvent = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
collectAndSendEvents(contentUpdate = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseFormatChanged(format: MouseFormat) {
|
||||
textBuffer.withLock {
|
||||
curState = curState.copy(mouseFormat = format)
|
||||
collectAndSendEvents(contentUpdateEvent = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
collectAndSendEvents(contentUpdate = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun bracketedPasteModeChanged(isEnabled: Boolean) {
|
||||
textBuffer.withLock {
|
||||
curState = curState.copy(isBracketedPasteMode = isEnabled)
|
||||
collectAndSendEvents(contentUpdateEvent = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
collectAndSendEvents(contentUpdate = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun windowTitleChanged(title: String) {
|
||||
textBuffer.withLock {
|
||||
curState = curState.copy(windowTitle = title)
|
||||
collectAndSendEvents(contentUpdateEvent = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
collectAndSendEvents(contentUpdate = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun beep() {
|
||||
textBuffer.withLock {
|
||||
collectAndSendEvents(contentUpdateEvent = null, otherEvent = TerminalBeepEvent)
|
||||
collectAndSendEvents(contentUpdate = null, otherEvent = TerminalBeepEvent)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -188,26 +206,32 @@ internal fun createTerminalOutputFlow(
|
||||
override fun initialized() {
|
||||
textBuffer.withLock {
|
||||
curState = curState.copy(isShellIntegrationEnabled = true)
|
||||
collectAndSendEvents(contentUpdateEvent = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
collectAndSendEvents(contentUpdate = null, otherEvent = TerminalStateChangedEvent(curState.toDto()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun commandStarted(command: String) {
|
||||
collectAndSendEvents(contentUpdateEvent = null, otherEvent = TerminalCommandStartedEvent(command))
|
||||
collectAndSendEvents(contentUpdate = null, otherEvent = TerminalCommandStartedEvent(command))
|
||||
}
|
||||
|
||||
override fun commandFinished(command: String, exitCode: Int) {
|
||||
collectAndSendEvents(contentUpdateEvent = null, otherEvent = TerminalCommandFinishedEvent(command, exitCode))
|
||||
collectAndSendEvents(contentUpdate = null, otherEvent = TerminalCommandFinishedEvent(command, exitCode))
|
||||
}
|
||||
|
||||
override fun promptStarted() {
|
||||
collectAndSendEvents(contentUpdateEvent = null, otherEvent = TerminalPromptStartedEvent)
|
||||
collectAndSendEvents(contentUpdate = null, otherEvent = TerminalPromptStartedEvent)
|
||||
}
|
||||
|
||||
override fun promptFinished() {
|
||||
collectAndSendEvents(contentUpdateEvent = null, otherEvent = TerminalPromptFinishedEvent)
|
||||
collectAndSendEvents(contentUpdate = null, otherEvent = TerminalPromptFinishedEvent)
|
||||
}
|
||||
})
|
||||
|
||||
return outputFlow
|
||||
}
|
||||
|
||||
private fun ensureEmulationActive(starter: StopAwareTerminalStarter) {
|
||||
if (Thread.interrupted() || starter.isStopped) {
|
||||
throw CancellationException("Terminal emulation was stopped")
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.intellij.terminal.backend
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.jediterm.terminal.model.TerminalTextBuffer
|
||||
import com.jediterm.terminal.model.TextBufferChangesListener
|
||||
import kotlin.time.TimeMark
|
||||
import kotlin.time.TimeSource
|
||||
|
||||
internal class TerminalOutputLatencyTracker(
|
||||
ttyConnector: ObservableTtyConnector,
|
||||
textBuffer: TerminalTextBuffer,
|
||||
parentDisposable: Disposable,
|
||||
) {
|
||||
// Variables are guarded by TerminalTextBuffer lock
|
||||
|
||||
/**
|
||||
* Last moment when we read some bytes from the TTY.
|
||||
* Guarded by TerminalTextBuffer lock.
|
||||
*/
|
||||
private var lastTtyReadTime: TimeMark? = null
|
||||
|
||||
/**
|
||||
* First moment of reading the bytes from the TTY, that caused changing the text buffer.
|
||||
* Guarded by TerminalTextBuffer lock.
|
||||
*/
|
||||
private var firstChangeTtyReadTime: TimeMark? = null
|
||||
|
||||
init {
|
||||
ttyConnector.addListener(parentDisposable, object : TtyConnectorListener {
|
||||
override fun charsRead(buf: CharArray, offset: Int, length: Int) {
|
||||
lastTtyReadTime = TimeSource.Monotonic.markNow()
|
||||
}
|
||||
})
|
||||
|
||||
textBuffer.addChangesListener(object : TextBufferChangesListener {
|
||||
override fun linesChanged(fromIndex: Int) {
|
||||
if (firstChangeTtyReadTime == null) {
|
||||
firstChangeTtyReadTime = lastTtyReadTime
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first time of reading bytes from the TTY that corresponds
|
||||
* to the currently uncollected changes in the text buffer.
|
||||
* And then resets the timer, so the current text buffer changes are considered as collected.
|
||||
*/
|
||||
fun getCurUpdateTtyReadTimeAndReset(): TimeMark? {
|
||||
val result = firstChangeTtyReadTime
|
||||
firstChangeTtyReadTime = null
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,6 @@ import org.jetbrains.plugins.terminal.LocalBlockTerminalRunner
|
||||
import org.jetbrains.plugins.terminal.ShellStartupOptions
|
||||
import org.jetbrains.plugins.terminal.util.STOP_EMULATOR_TIMEOUT
|
||||
import org.jetbrains.plugins.terminal.util.waitFor
|
||||
import java.util.concurrent.CancellationException
|
||||
|
||||
internal fun startTerminalProcess(
|
||||
project: Project,
|
||||
@@ -64,13 +63,9 @@ internal fun createTerminalSession(
|
||||
val shellIntegrationController = TerminalShellIntegrationController(services.controller)
|
||||
shellIntegrationController.addListener(TerminalShellIntegrationStatisticsListener(project))
|
||||
val outputFlow = createTerminalOutputFlow(
|
||||
services.textBuffer,
|
||||
fusActivity,
|
||||
services.terminalDisplay,
|
||||
services.controller,
|
||||
services,
|
||||
shellIntegrationController,
|
||||
outputScope,
|
||||
ensureEmulationActive = { ensureEmulationActive(services.terminalStarter) }
|
||||
)
|
||||
|
||||
val inputScope = coroutineScope.childScope("Terminal input handling")
|
||||
@@ -101,8 +96,7 @@ internal fun createTerminalSession(
|
||||
}
|
||||
|
||||
private fun createJediTermServices(
|
||||
connector: TtyConnector,
|
||||
fusActivity: BackendOutputActivity,
|
||||
connector: ObservableTtyConnector,
|
||||
termSize: TermSize,
|
||||
maxHistoryLinesCount: Int,
|
||||
settings: JBTerminalSystemSettingsProviderBase,
|
||||
@@ -118,11 +112,10 @@ private fun createJediTermServices(
|
||||
connector,
|
||||
TtyBasedArrayDataStream(connector),
|
||||
typeAheadManager,
|
||||
executorService,
|
||||
fusActivity
|
||||
executorService
|
||||
)
|
||||
|
||||
return JediTermServices(textBuffer, terminalDisplay, controller, executorService, terminalStarter)
|
||||
return JediTermServices(textBuffer, terminalDisplay, controller, executorService, terminalStarter, connector)
|
||||
}
|
||||
|
||||
private fun startTerminalEmulation(terminalStarter: TerminalStarter) {
|
||||
@@ -142,16 +135,11 @@ private fun startTerminalEmulation(terminalStarter: TerminalStarter) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun ensureEmulationActive(starter: StopAwareTerminalStarter) {
|
||||
if (Thread.interrupted() || starter.isStopped) {
|
||||
throw CancellationException("Terminal emulation was stopped")
|
||||
}
|
||||
}
|
||||
|
||||
internal class JediTermServices(
|
||||
val textBuffer: TerminalTextBuffer,
|
||||
val terminalDisplay: TerminalDisplayImpl,
|
||||
val controller: ObservableJediTerminal,
|
||||
val executorService: TerminalExecutorServiceManager,
|
||||
val terminalStarter: StopAwareTerminalStarter,
|
||||
val ttyConnector: ObservableTtyConnector,
|
||||
)
|
||||
|
||||
+2
-3
@@ -3,7 +3,6 @@ package com.intellij.terminal.backend
|
||||
|
||||
import com.intellij.terminal.backend.util.scrollDown
|
||||
import com.intellij.terminal.backend.util.write
|
||||
import com.intellij.terminal.session.TerminalContentUpdatedEvent
|
||||
import com.jediterm.terminal.model.StyleState
|
||||
import com.jediterm.terminal.model.TerminalTextBuffer
|
||||
import junit.framework.TestCase.assertEquals
|
||||
@@ -49,7 +48,7 @@ internal class TerminalContentChangesTrackerTest {
|
||||
textBuffer.write("sixth", 3, 0)
|
||||
|
||||
// Update will be saved there on the next scroll
|
||||
var update: TerminalContentUpdatedEvent? = null
|
||||
var update: TerminalContentUpdate? = null
|
||||
contentChangesTracker.addHistoryOverflowListener {
|
||||
update = it
|
||||
}
|
||||
@@ -91,7 +90,7 @@ internal class TerminalContentChangesTrackerTest {
|
||||
textBuffer.write("sixth", 3, 0)
|
||||
|
||||
// Update will be saved there on the next scroll
|
||||
var update: TerminalContentUpdatedEvent? = null
|
||||
var update: TerminalContentUpdate? = null
|
||||
contentChangesTracker.addHistoryOverflowListener {
|
||||
update = it
|
||||
}
|
||||
|
||||
+1
-5
@@ -2,7 +2,6 @@
|
||||
package com.intellij.terminal.backend
|
||||
|
||||
import com.intellij.terminal.backend.util.write
|
||||
import com.intellij.terminal.session.TerminalContentUpdatedEvent
|
||||
import com.intellij.terminal.session.TerminalCursorPositionChangedEvent
|
||||
import com.jediterm.terminal.model.StyleState
|
||||
import com.jediterm.terminal.model.TerminalTextBuffer
|
||||
@@ -31,10 +30,7 @@ internal class TerminalCursorPositionTrackerTest {
|
||||
val contentUpdate = contentChangesTracker.getContentUpdate() ?: error("Content update is null")
|
||||
val cursorUpdate = cursorPositionTracker.getCursorPositionUpdate() ?: error("Cursor update is null")
|
||||
|
||||
// We expect that moving cursor position to the next line creates this line in the TextBuffer,
|
||||
// and it is being caught by the TerminalContentChangesTracker.
|
||||
// The character indices correspond to LongRange.EMPTY used in the test FUS activity stub.
|
||||
assertThat(contentUpdate).isEqualTo(TerminalContentUpdatedEvent("", emptyList(), 1, 1L, 0L))
|
||||
assertThat(contentUpdate).isEqualTo(TerminalContentUpdate("", emptyList(), 1))
|
||||
assertThat(cursorUpdate).isEqualTo(TerminalCursorPositionChangedEvent(1, 0))
|
||||
}
|
||||
}
|
||||
@@ -133,7 +133,6 @@ internal class ReworkedTerminalView(
|
||||
outputEditor.putUserData(TerminalBlocksModel.KEY, blocksModel)
|
||||
|
||||
val fusActivity = FrontendLatencyService.getInstance().startFrontendOutputActivity(
|
||||
sessionFuture,
|
||||
outputEditor = outputEditor as EditorImpl,
|
||||
alternateBufferEditor = alternateBufferEditor as EditorImpl,
|
||||
)
|
||||
|
||||
+2
-21
@@ -2,23 +2,18 @@ package com.intellij.terminal.frontend.fus
|
||||
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.editor.impl.EditorImpl
|
||||
import com.intellij.platform.rpc.UID
|
||||
import com.intellij.terminal.session.TerminalContentUpdatedEvent
|
||||
import com.intellij.terminal.session.TerminalInputEvent
|
||||
import com.intellij.terminal.session.TerminalSession
|
||||
import com.intellij.terminal.session.TerminalWriteBytesEvent
|
||||
import com.intellij.util.concurrency.ThreadingAssertions
|
||||
import fleet.multiplatform.shims.ConcurrentHashMap
|
||||
import org.jetbrains.plugins.terminal.block.reworked.session.FrontendTerminalSession
|
||||
import org.jetbrains.plugins.terminal.fus.FrontendLatencyService
|
||||
import org.jetbrains.plugins.terminal.fus.FrontendOutputActivity
|
||||
import org.jetbrains.plugins.terminal.fus.FrontendTypingActivity
|
||||
import org.jetbrains.plugins.terminal.fus.ReworkedTerminalUsageCollector
|
||||
import java.awt.event.KeyEvent
|
||||
import java.util.concurrent.ArrayBlockingQueue
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.time.TimeMark
|
||||
import kotlin.time.TimeSource
|
||||
|
||||
@@ -41,11 +36,10 @@ internal class FrontendLatencyServiceImpl : FrontendLatencyService {
|
||||
}
|
||||
|
||||
override fun startFrontendOutputActivity(
|
||||
sessionFuture: CompletableFuture<TerminalSession>,
|
||||
outputEditor: EditorImpl,
|
||||
alternateBufferEditor: EditorImpl,
|
||||
): FrontendOutputActivity {
|
||||
return FrontendOutputActivityImpl(sessionFuture, outputEditor, alternateBufferEditor)
|
||||
return FrontendOutputActivityImpl(outputEditor, alternateBufferEditor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,21 +84,15 @@ private class FrontendTypingActivityImpl(override val id: Int) : FrontendTypingA
|
||||
}
|
||||
|
||||
private class FrontendOutputActivityImpl(
|
||||
sessionFuture: CompletableFuture<TerminalSession>,
|
||||
private val outputEditor: EditorImpl,
|
||||
private val alternateBufferEditor: EditorImpl,
|
||||
) : FrontendOutputActivity {
|
||||
|
||||
private val sessionId = AtomicReference<UID?>()
|
||||
private val pendingEvents = ArrayBlockingQueue<ReceivedEvent>(100)
|
||||
private val pendingPaints = ArrayBlockingQueue<ReceivedEvent>(100)
|
||||
private var editorRepaintRequests = 0L
|
||||
private var editorRepaintRequestsBeforeModelUpdate = 0L
|
||||
|
||||
init {
|
||||
sessionFuture.whenComplete { session, _ ->
|
||||
sessionId.set((session as? FrontendTerminalSession?)?.id?.eid)
|
||||
}
|
||||
outputEditor.setRepaintCallback { editorRepaintRequested() }
|
||||
alternateBufferEditor.setRepaintCallback { editorRepaintRequested() }
|
||||
outputEditor.setPaintCallback { editorPainted() }
|
||||
@@ -150,15 +138,8 @@ private class FrontendOutputActivityImpl(
|
||||
|
||||
private fun reportLatency(receivedEvent: ReceivedEvent, painted: Boolean) {
|
||||
val latency = receivedEvent.time.elapsedNow()
|
||||
val sessionId = this.sessionId.get()
|
||||
if (sessionId == null) {
|
||||
LOG.error("For some reason sessionId was not initialized, likely a bug")
|
||||
return
|
||||
}
|
||||
ReworkedTerminalUsageCollector.logFrontendOutputLatency(
|
||||
sessionId = sessionId,
|
||||
firstCharIndex = receivedEvent.event.firstCharIndex,
|
||||
lastCharIndex = receivedEvent.event.lastCharIndex,
|
||||
eventId = receivedEvent.event.id,
|
||||
duration = latency,
|
||||
repainted = painted,
|
||||
)
|
||||
|
||||
+1
-3
@@ -10,7 +10,6 @@ import kotlinx.coroutines.channels.SendChannel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.plugins.terminal.block.reworked.session.rpc.TerminalSessionApi
|
||||
import org.jetbrains.plugins.terminal.block.reworked.session.rpc.TerminalSessionId
|
||||
|
||||
@@ -20,8 +19,7 @@ import org.jetbrains.plugins.terminal.block.reworked.session.rpc.TerminalSession
|
||||
* Normally, it should be located in the frontend module, but it can't be moved there
|
||||
* because it should be accessible from the shared terminal widget creating API with a lot of external usages.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
class FrontendTerminalSession(val id: TerminalSessionId) : TerminalSession {
|
||||
internal class FrontendTerminalSession(private val id: TerminalSessionId) : TerminalSession {
|
||||
@Volatile
|
||||
override var isClosed: Boolean = false
|
||||
private set
|
||||
|
||||
@@ -5,11 +5,9 @@ import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.editor.impl.EditorImpl
|
||||
import com.intellij.terminal.session.TerminalContentUpdatedEvent
|
||||
import com.intellij.terminal.session.TerminalInputEvent
|
||||
import com.intellij.terminal.session.TerminalSession
|
||||
import com.intellij.terminal.session.TerminalWriteBytesEvent
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.awt.event.KeyEvent
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
@ApiStatus.Internal
|
||||
interface FrontendLatencyService {
|
||||
@@ -26,11 +24,9 @@ interface FrontendLatencyService {
|
||||
fun getFrontendTypingActivityOrNull(event: TerminalInputEvent): FrontendTypingActivity?
|
||||
|
||||
fun startFrontendOutputActivity(
|
||||
sessionFuture: CompletableFuture<TerminalSession>,
|
||||
outputEditor: EditorImpl,
|
||||
alternateBufferEditor: EditorImpl,
|
||||
): FrontendOutputActivity
|
||||
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
|
||||
+14
-41
@@ -6,7 +6,6 @@ import com.intellij.internal.statistic.eventLog.events.EventFields
|
||||
import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Version
|
||||
import com.intellij.platform.rpc.UID
|
||||
import com.intellij.util.system.OS
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.plugins.terminal.fus.TerminalShellInfoStatistics.KNOWN_SHELLS
|
||||
@@ -26,11 +25,7 @@ object ReworkedTerminalUsageCollector : CounterUsagesCollector() {
|
||||
private val SHELL_STR_FIELD = EventFields.String("shell", KNOWN_SHELLS.toList())
|
||||
private val EXIT_CODE_FIELD = EventFields.Int("exit_code")
|
||||
private val EXECUTION_TIME_FIELD = EventFields.Long("execution_time", "Time in milliseconds")
|
||||
private val INPUT_EVENT_ID_FIELD = EventFields.Int("input_event_id")
|
||||
private val SESSION_ID = EventFields.Int("session_id")
|
||||
private val CHAR_INDEX = EventFields.Long("char_index")
|
||||
private val FIRST_CHAR_INDEX = EventFields.Long("first_char_index")
|
||||
private val LAST_CHAR_INDEX = EventFields.Long("last_char_index")
|
||||
private val EVENT_ID_FIELD = EventFields.Int("event_id")
|
||||
private val DURATION_FIELD = EventFields.createDurationField(DurationUnit.MILLISECONDS, "duration_millis")
|
||||
private val REPAINTED_FIELD = EventFields.Boolean("editor_repainted")
|
||||
|
||||
@@ -52,35 +47,25 @@ object ReworkedTerminalUsageCollector : CounterUsagesCollector() {
|
||||
|
||||
private val frontendTypingLatencyEvent = GROUP.registerVarargEvent(
|
||||
"terminal.frontend.typing.latency",
|
||||
INPUT_EVENT_ID_FIELD,
|
||||
EVENT_ID_FIELD,
|
||||
DURATION_FIELD,
|
||||
)
|
||||
|
||||
private val backendTypingLatencyEvent = GROUP.registerVarargEvent(
|
||||
"terminal.backend.typing.latency",
|
||||
INPUT_EVENT_ID_FIELD,
|
||||
EVENT_ID_FIELD,
|
||||
DURATION_FIELD,
|
||||
)
|
||||
|
||||
private val backendMinOutputLatencyEvent = GROUP.registerVarargEvent(
|
||||
"terminal.backend.min.output.latency",
|
||||
SESSION_ID,
|
||||
CHAR_INDEX,
|
||||
DURATION_FIELD,
|
||||
)
|
||||
|
||||
private val backendMaxOutputLatencyEvent = GROUP.registerVarargEvent(
|
||||
"terminal.backend.max.output.latency",
|
||||
SESSION_ID,
|
||||
CHAR_INDEX,
|
||||
private val backendOutputLatencyEvent = GROUP.registerVarargEvent(
|
||||
"terminal.backend.output.latency",
|
||||
EVENT_ID_FIELD,
|
||||
DURATION_FIELD,
|
||||
)
|
||||
|
||||
private val frontendOutputLatencyEvent = GROUP.registerVarargEvent(
|
||||
"terminal.frontend.output.latency",
|
||||
SESSION_ID,
|
||||
FIRST_CHAR_INDEX,
|
||||
LAST_CHAR_INDEX,
|
||||
EVENT_ID_FIELD,
|
||||
DURATION_FIELD,
|
||||
REPAINTED_FIELD,
|
||||
)
|
||||
@@ -110,7 +95,7 @@ object ReworkedTerminalUsageCollector : CounterUsagesCollector() {
|
||||
@ApiStatus.Internal
|
||||
fun logFrontendLatency(inputEventId: Int, duration: Duration) {
|
||||
frontendTypingLatencyEvent.log(
|
||||
INPUT_EVENT_ID_FIELD with inputEventId,
|
||||
EVENT_ID_FIELD with inputEventId,
|
||||
DURATION_FIELD with duration
|
||||
)
|
||||
}
|
||||
@@ -118,35 +103,23 @@ object ReworkedTerminalUsageCollector : CounterUsagesCollector() {
|
||||
@ApiStatus.Internal
|
||||
fun logBackendLatency(inputEventId: Int, duration: Duration) {
|
||||
backendTypingLatencyEvent.log(
|
||||
INPUT_EVENT_ID_FIELD with inputEventId,
|
||||
EVENT_ID_FIELD with inputEventId,
|
||||
DURATION_FIELD with duration
|
||||
)
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
fun logBackendMinOutputLatency(sessionId: UID, charIndex: Long, duration: Duration) {
|
||||
backendMinOutputLatencyEvent.log(
|
||||
SESSION_ID with sessionId,
|
||||
CHAR_INDEX with charIndex,
|
||||
fun logBackendOutputLatency(eventId: Int, duration: Duration) {
|
||||
backendOutputLatencyEvent.log(
|
||||
EVENT_ID_FIELD with eventId,
|
||||
DURATION_FIELD with duration
|
||||
)
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
fun logBackendMaxOutputLatency(sessionId: UID, charIndex: Long, duration: Duration) {
|
||||
backendMaxOutputLatencyEvent.log(
|
||||
SESSION_ID with sessionId,
|
||||
CHAR_INDEX with charIndex,
|
||||
DURATION_FIELD with duration
|
||||
)
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
fun logFrontendOutputLatency(sessionId: UID, firstCharIndex: Long, lastCharIndex: Long, duration: Duration, repainted: Boolean) {
|
||||
fun logFrontendOutputLatency(eventId: Int, duration: Duration, repainted: Boolean) {
|
||||
frontendOutputLatencyEvent.log(
|
||||
SESSION_ID with sessionId,
|
||||
FIRST_CHAR_INDEX with firstCharIndex,
|
||||
LAST_CHAR_INDEX with lastCharIndex,
|
||||
EVENT_ID_FIELD with eventId,
|
||||
DURATION_FIELD with duration,
|
||||
REPAINTED_FIELD with repainted,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user