From 3868836dd49c4bdfa098577778ac347ca4bd5ce3 Mon Sep 17 00:00:00 2001 From: Konstantin Hudyakov Date: Fri, 11 Apr 2025 17:18:52 +0300 Subject: [PATCH] [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 --- .../terminal/session/TerminalOutputEvent.kt | 5 +- .../backend/StateAwareTerminalSession.kt | 6 ++ .../backend/TerminalContentChangesTracker.kt | 22 +++-- .../terminal/backend/TerminalOutput.kt | 82 ++++++++++++------- .../backend/TerminalOutputLatencyTracker.kt | 54 ++++++++++++ .../terminal/backend/TerminalSessionStart.kt | 22 ++--- .../TerminalContentChangesTrackerTest.kt | 5 +- .../TerminalCursorPositionTrackerTest.kt | 6 +- .../terminal/frontend/ReworkedTerminalView.kt | 1 - .../fus/FrontendLatencyServiceImpl.kt | 23 +----- .../session/FrontendTerminalSession.kt | 4 +- .../terminal/fus/FrontendLatencyService.kt | 4 - .../fus/ReworkedTerminalUsageCollector.kt | 55 ++++--------- 13 files changed, 154 insertions(+), 135 deletions(-) create mode 100644 plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalOutputLatencyTracker.kt diff --git a/platform/execution-impl/src/com/intellij/terminal/session/TerminalOutputEvent.kt b/platform/execution-impl/src/com/intellij/terminal/session/TerminalOutputEvent.kt index 7a806f65cf2e..c2815b32aeee 100644 --- a/platform/execution-impl/src/com/intellij/terminal/session/TerminalOutputEvent.kt +++ b/platform/execution-impl/src/com/intellij/terminal/session/TerminalOutputEvent.kt @@ -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, val startLineLogicalIndex: Long, - val firstCharIndex: Long, - val lastCharIndex: Long, + val readTime: TimeMark?, ) : TerminalOutputEvent @ApiStatus.Internal diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/StateAwareTerminalSession.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/StateAwareTerminalSession.kt index 71e166dffd42..81a58476799f 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/StateAwareTerminalSession.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/StateAwareTerminalSession.kt @@ -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() diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalContentChangesTracker.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalContentChangesTracker.kt index e0cf4548d13b..8b4bad4841d5 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalContentChangesTracker.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalContentChangesTracker.kt @@ -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): TerminalContentUpdatedEvent? { + private fun getContentUpdate(additionalLines: List): TerminalContentUpdate? { return if (anyLineChanged) { collectOutput(additionalLines) } else null } - private fun collectOutput(additionalLines: List): TerminalContentUpdatedEvent { + private fun collectOutput(additionalLines: List): 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, + val startLineLogicalIndex: Long, +) + /** * Consider the sequence of wrapped lines in the Text Buffer as a single logical line. */ diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalOutput.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalOutput.kt index ff44a38a40ef..950039b354a7 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalOutput.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalOutput.kt @@ -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> { val outputFlow = MutableSharedFlow>( // 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") + } } \ No newline at end of file diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalOutputLatencyTracker.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalOutputLatencyTracker.kt new file mode 100644 index 000000000000..23d3387d6dbd --- /dev/null +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalOutputLatencyTracker.kt @@ -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 + } +} \ No newline at end of file diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalSessionStart.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalSessionStart.kt index a83a17ed1b01..bad030804f24 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalSessionStart.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalSessionStart.kt @@ -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, ) diff --git a/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/TerminalContentChangesTrackerTest.kt b/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/TerminalContentChangesTrackerTest.kt index 3a8d2f4ba934..66bb0037cc1e 100644 --- a/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/TerminalContentChangesTrackerTest.kt +++ b/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/TerminalContentChangesTrackerTest.kt @@ -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 } diff --git a/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/TerminalCursorPositionTrackerTest.kt b/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/TerminalCursorPositionTrackerTest.kt index 96c47639e9ff..6db4280a2c38 100644 --- a/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/TerminalCursorPositionTrackerTest.kt +++ b/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/TerminalCursorPositionTrackerTest.kt @@ -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)) } } \ No newline at end of file diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/ReworkedTerminalView.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/ReworkedTerminalView.kt index efdd9839f699..aa7887f62c3d 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/ReworkedTerminalView.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/ReworkedTerminalView.kt @@ -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, ) diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/fus/FrontendLatencyServiceImpl.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/fus/FrontendLatencyServiceImpl.kt index f1402243d0c8..b8c9622ace61 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/fus/FrontendLatencyServiceImpl.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/fus/FrontendLatencyServiceImpl.kt @@ -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, 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, private val outputEditor: EditorImpl, private val alternateBufferEditor: EditorImpl, ) : FrontendOutputActivity { - - private val sessionId = AtomicReference() private val pendingEvents = ArrayBlockingQueue(100) private val pendingPaints = ArrayBlockingQueue(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, ) diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/session/FrontendTerminalSession.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/session/FrontendTerminalSession.kt index e31b49e85faf..a8d4d8fe3258 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/session/FrontendTerminalSession.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/session/FrontendTerminalSession.kt @@ -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 diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/FrontendLatencyService.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/FrontendLatencyService.kt index 8abd77809bb2..1430c10a3840 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/FrontendLatencyService.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/FrontendLatencyService.kt @@ -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, outputEditor: EditorImpl, alternateBufferEditor: EditorImpl, ): FrontendOutputActivity - } @ApiStatus.Internal diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/ReworkedTerminalUsageCollector.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/ReworkedTerminalUsageCollector.kt index 97e02a1190f7..5a88f25849a3 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/ReworkedTerminalUsageCollector.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/ReworkedTerminalUsageCollector.kt @@ -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, )