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, )