From 1426c4858bb49f19e076e6f293d302952a2c5e03 Mon Sep 17 00:00:00 2001 From: Konstantin Hudyakov Date: Fri, 11 Apr 2025 16:14:36 +0300 Subject: [PATCH] [terminal] IJPL-182482 Drop BackendOutputActivity Backend output latency measurement will be reimplemented in the next commit GitOrigin-RevId: 7e541c817486a795268357d01ebcdf8e66597900 --- .../backend/StateAwareTerminalSession.kt | 7 +- .../backend/StopAwareTerminalStarter.kt | 25 +- .../backend/TerminalContentChangesTracker.kt | 15 +- .../terminal/backend/TerminalOutput.kt | 4 +- .../terminal/backend/TerminalSessionStart.kt | 9 +- .../terminal/backend/TerminalTabsManager.kt | 8 +- .../backend/fus/BackendLatencyServiceImpl.kt | 298 +----------------- .../TerminalContentChangesTrackerTest.kt | 3 +- .../TerminalCursorPositionTrackerTest.kt | 3 +- .../util/BackendOutputTestFusActivity.kt | 25 -- .../backend/util/TerminalSessionTestUtil.kt | 4 +- .../terminal/fus/BackendLatencyService.kt | 16 - 12 files changed, 24 insertions(+), 393 deletions(-) delete mode 100644 plugins/terminal/backend/testSrc/com/intellij/terminal/backend/util/BackendOutputTestFusActivity.kt 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 ac2fc3bee13c..71e166dffd42 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/StateAwareTerminalSession.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/StateAwareTerminalSession.kt @@ -14,7 +14,6 @@ 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.BackendOutputActivity import kotlin.coroutines.cancellation.CancellationException /** @@ -25,10 +24,7 @@ import kotlin.coroutines.cancellation.CancellationException * So, actually it allows restoring the state of UI that requests the [getOutputFlow]. */ @OptIn(ExperimentalCoroutinesApi::class) -internal class StateAwareTerminalSession( - private val delegate: TerminalSession, - private val fusActivity: BackendOutputActivity, -) : TerminalSession { +internal class StateAwareTerminalSession(private val delegate: TerminalSession) : TerminalSession { private val sessionModel: TerminalSessionModel = TerminalSessionModelImpl() private val outputModel: TerminalOutputModel private val alternateBufferModel: TerminalOutputModel @@ -87,7 +83,6 @@ internal class StateAwareTerminalSession( val styles = event.styles.map { it.toStyleRange() } val model = getCurrentOutputModel() model.updateContent(event.startLineLogicalIndex, event.text, styles) - fusActivity.eventCollected(event) } is TerminalCursorPositionChangedEvent -> { val model = getCurrentOutputModel() diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/StopAwareTerminalStarter.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/StopAwareTerminalStarter.kt index cdc1e37895e7..8a69b2d56ed6 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/StopAwareTerminalStarter.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/StopAwareTerminalStarter.kt @@ -1,10 +1,11 @@ package com.intellij.terminal.backend import com.jediterm.core.typeahead.TerminalTypeAheadManager -import com.jediterm.terminal.* -import com.jediterm.terminal.emulator.JediEmulator +import com.jediterm.terminal.TerminalDataStream +import com.jediterm.terminal.TerminalExecutorServiceManager +import com.jediterm.terminal.TerminalStarter +import com.jediterm.terminal.TtyConnector import com.jediterm.terminal.model.JediTerminal -import org.jetbrains.plugins.terminal.fus.BackendOutputActivity internal class StopAwareTerminalStarter( terminal: JediTerminal, @@ -12,31 +13,13 @@ internal class StopAwareTerminalStarter( dataStream: TerminalDataStream, typeAheadManager: TerminalTypeAheadManager, executorServiceManager: TerminalExecutorServiceManager, - private val fusActivity: BackendOutputActivity, ) : TerminalStarter(terminal, ttyConnector, dataStream, typeAheadManager, executorServiceManager) { @Volatile var isStopped: Boolean = false private set - override fun createEmulator(dataStream: TerminalDataStream, terminal: Terminal): JediEmulator { - return FusAwareEmulator(dataStream, terminal) - } - override fun requestEmulatorStop() { super.requestEmulatorStop() isStopped = true } - - // must be an inner class, because this thing is created in a superclass constructor, - // so fusActivity is still null at that time, and we can't pass its actual value - private inner class FusAwareEmulator( - dataStream: TerminalDataStream, - terminal: Terminal, - ) : JediEmulator(dataStream, terminal) { - override fun next() { - fusActivity.charProcessingStarted() - super.next() - fusActivity.charProcessingFinished() - } - } } 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 6e91c595b724..e0cf4548d13b 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalContentChangesTracker.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalContentChangesTracker.kt @@ -11,14 +11,12 @@ import org.jetbrains.plugins.terminal.block.session.StyledCommandOutput import org.jetbrains.plugins.terminal.block.session.collectLines import org.jetbrains.plugins.terminal.block.session.scraper.SimpleStringCollector import org.jetbrains.plugins.terminal.block.session.scraper.StylesCollectingTerminalLinesCollector -import org.jetbrains.plugins.terminal.fus.BackendOutputActivity import java.util.concurrent.CopyOnWriteArrayList import kotlin.math.min internal class TerminalContentChangesTracker( private val textBuffer: TerminalTextBuffer, private val discardedHistoryTracker: TerminalDiscardedHistoryTracker, - private val fusActivity: BackendOutputActivity, ) { private var lastChangedVisualLine: Int = 0 private var anyLineChanged: Boolean = false @@ -31,7 +29,6 @@ internal class TerminalContentChangesTracker( val line = textBuffer.effectiveHistoryLinesCount + fromIndex lastChangedVisualLine = min(lastChangedVisualLine, line) anyLineChanged = true - fusActivity.processedCharsReachedTextBuffer() } override fun linesDiscardedFromHistory(lines: List) { @@ -101,11 +98,13 @@ internal class TerminalContentChangesTracker( lastChangedVisualLine = textBuffer.effectiveHistoryLinesCount + textBuffer.screenLinesCount anyLineChanged = false - val styles = output.styleRanges.map { it.toDto() } - val charRange = fusActivity.textBufferCharacterIndices() - val contentUpdatedEvent = TerminalContentUpdatedEvent(output.text, styles, logicalLineIndex, charRange.first, charRange.last) - fusActivity.textBufferCollected(contentUpdatedEvent) - return contentUpdatedEvent + return TerminalContentUpdatedEvent( + text = output.text, + styles = output.styleRanges.map { it.toDto() }, + startLineLogicalIndex = logicalLineIndex, + firstCharIndex = -1, + lastCharIndex = -1, + ) } private fun scrapeOutput(startLine: Int, additionalLines: List): StyledCommandOutput { 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 97e8eb1908b2..ff44a38a40ef 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalOutput.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalOutput.kt @@ -12,7 +12,7 @@ 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 org.jetbrains.plugins.terminal.fus.BackendOutputActivity + @OptIn(ExperimentalCoroutinesApi::class) internal fun createTerminalOutputFlow( @@ -37,7 +37,7 @@ internal fun createTerminalOutputFlow( ) val discardedHistoryTracker = TerminalDiscardedHistoryTracker(textBuffer) - val contentChangesTracker = TerminalContentChangesTracker(textBuffer, discardedHistoryTracker, fusActivity) + val contentChangesTracker = TerminalContentChangesTracker(textBuffer, discardedHistoryTracker) val cursorPositionTracker = TerminalCursorPositionTracker(textBuffer, discardedHistoryTracker, terminalDisplay) /** 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 bc5be076d9ec..a83a17ed1b01 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalSessionStart.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalSessionStart.kt @@ -6,7 +6,6 @@ import com.intellij.openapi.project.Project import com.intellij.platform.util.coroutines.childScope import com.intellij.terminal.JBTerminalSystemSettingsProviderBase import com.intellij.terminal.TerminalExecutorServiceManagerImpl -import com.intellij.terminal.backend.fus.enableFus import com.intellij.terminal.backend.fus.installFusListener import com.intellij.terminal.session.TerminalSession import com.intellij.terminal.session.TerminalSessionTerminatedEvent @@ -28,7 +27,6 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import org.jetbrains.plugins.terminal.LocalBlockTerminalRunner import org.jetbrains.plugins.terminal.ShellStartupOptions -import org.jetbrains.plugins.terminal.fus.BackendOutputActivity import org.jetbrains.plugins.terminal.util.STOP_EMULATOR_TIMEOUT import org.jetbrains.plugins.terminal.util.waitFor import java.util.concurrent.CancellationException @@ -55,13 +53,12 @@ internal fun createTerminalSession( initialSize: TermSize, settings: JBTerminalSystemSettingsProviderBase, coroutineScope: CoroutineScope, - fusActivity: BackendOutputActivity, ): TerminalSession { val observableTtyConnector = ttyConnector as? ObservableTtyConnector ?: ObservableTtyConnector(ttyConnector) - installFusListener(observableTtyConnector, fusActivity, parentDisposable = coroutineScope.asDisposable()) + installFusListener(observableTtyConnector, parentDisposable = coroutineScope.asDisposable()) val maxHistoryLinesCount = AdvancedSettings.getInt("terminal.buffer.max.lines.count") - val services: JediTermServices = createJediTermServices(observableTtyConnector, fusActivity, initialSize, maxHistoryLinesCount, settings) + val services: JediTermServices = createJediTermServices(observableTtyConnector, initialSize, maxHistoryLinesCount, settings) val outputScope = coroutineScope.childScope("Terminal output forwarding") val shellIntegrationController = TerminalShellIntegrationController(services.controller) @@ -119,7 +116,7 @@ private fun createJediTermServices( val terminalStarter = StopAwareTerminalStarter( controller, connector, - enableFus(TtyBasedArrayDataStream(connector), fusActivity), + TtyBasedArrayDataStream(connector), typeAheadManager, executorService, fusActivity diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalTabsManager.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalTabsManager.kt index 9e04c127ff0d..8518ca8fb661 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalTabsManager.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalTabsManager.kt @@ -10,6 +10,7 @@ import com.intellij.platform.kernel.backend.findValueEntity import com.intellij.platform.kernel.backend.newValueEntity import com.intellij.platform.util.coroutines.childScope import com.intellij.terminal.session.TerminalCloseEvent +import com.intellij.terminal.session.TerminalSession import com.intellij.util.AwaitCancellationAndInvoke import com.intellij.util.awaitCancellationAndInvoke import com.jediterm.core.util.TermSize @@ -21,7 +22,6 @@ import org.jetbrains.plugins.terminal.ShellStartupOptions import org.jetbrains.plugins.terminal.block.reworked.session.TerminalSessionTab import org.jetbrains.plugins.terminal.block.reworked.session.rpc.TerminalPortForwardingId import org.jetbrains.plugins.terminal.block.reworked.session.rpc.TerminalSessionId -import org.jetbrains.plugins.terminal.fus.BackendLatencyService import java.util.concurrent.atomic.AtomicInteger @OptIn(AwaitCancellationAndInvoke::class) @@ -132,12 +132,10 @@ internal class TerminalTabsManager(private val project: Project, private val cor val (ttyConnector, configuredOptions) = startTerminalProcess(project, optionsWithSize) val observableTtyConnector = ObservableTtyConnector(ttyConnector) - val fusActivity = BackendLatencyService.getInstance().startBackendOutputActivity() - val session = createTerminalSession(project, observableTtyConnector, termSize, JBTerminalSystemSettingsProvider(), scope, fusActivity) - val stateAwareSession = StateAwareTerminalSession(session, fusActivity) + val session = createTerminalSession(project, observableTtyConnector, termSize, JBTerminalSystemSettingsProvider(), scope) + val stateAwareSession = StateAwareTerminalSession(session) val sessionEntity = newValueEntity(stateAwareSession) - fusActivity.sessionId = sessionEntity.id scope.awaitCancellationAndInvoke { sessionEntity.delete() diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/fus/BackendLatencyServiceImpl.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/fus/BackendLatencyServiceImpl.kt index caa80156be4c..f06510fae05a 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/fus/BackendLatencyServiceImpl.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/fus/BackendLatencyServiceImpl.kt @@ -1,22 +1,13 @@ package com.intellij.terminal.backend.fus import com.intellij.openapi.Disposable -import com.intellij.openapi.diagnostic.logger -import com.intellij.platform.rpc.UID import com.intellij.terminal.backend.ObservableTtyConnector import com.intellij.terminal.backend.TtyConnectorListener -import com.intellij.terminal.session.TerminalContentUpdatedEvent import com.intellij.terminal.session.TerminalWriteBytesEvent -import com.jediterm.terminal.TerminalDataStream import fleet.multiplatform.shims.ConcurrentHashMap import org.jetbrains.plugins.terminal.fus.BackendLatencyService -import org.jetbrains.plugins.terminal.fus.BackendOutputActivity import org.jetbrains.plugins.terminal.fus.BackendTypingActivity import org.jetbrains.plugins.terminal.fus.ReworkedTerminalUsageCollector -import java.util.concurrent.LinkedBlockingQueue -import java.util.concurrent.atomic.AtomicReference -import kotlin.time.Duration -import kotlin.time.TimeMark import kotlin.time.TimeSource internal class BackendLatencyServiceImpl : BackendLatencyService { @@ -30,22 +21,13 @@ internal class BackendLatencyServiceImpl : BackendLatencyService { override fun getBackendTypingActivityOrNull(bytes: ByteArray): BackendTypingActivity? { return backendTypingActivityByByteArray[bytes] } - - override fun startBackendOutputActivity(): BackendOutputActivity { - return BackendOutputActivityImpl() - } } internal fun installFusListener( ttyConnector: ObservableTtyConnector, - fusActivity: BackendOutputActivity, parentDisposable: Disposable, ) { ttyConnector.addListener(parentDisposable, object : TtyConnectorListener { - override fun charsRead(buf: CharArray, offset: Int, length: Int) { - fusActivity.charsRead(length) - } - override fun bytesWritten(bytes: ByteArray) { val typingActivity = BackendLatencyService.getInstance().getBackendTypingActivityOrNull(bytes) ?: return try { @@ -58,9 +40,6 @@ internal fun installFusListener( }) } -internal fun enableFus(stream: TerminalDataStream, fusActivity: BackendOutputActivity): TerminalDataStream = - FusAwareTtyBasedDataStream(stream, fusActivity) - private val backendTypingActivityByByteArray = ConcurrentHashMap() private class BackendTypingActivityImpl(override val id: Int, private val bytes: ByteArray) : BackendTypingActivity { @@ -77,279 +56,4 @@ private class BackendTypingActivityImpl(override val id: Int, private val bytes: override fun finishBytesProcessing() { backendTypingActivityByByteArray.remove(bytes) } -} - -private class BackendOutputActivityImpl : BackendOutputActivity { - private val sessionIdReference = AtomicReference() - - override var sessionId: UID? - get() = sessionIdReference.get() - set(value) { sessionIdReference.set(value) } - - // split into subclasses to simplify reasoning about threads and locks - - private val readingState = TerminalThreadReadingState() - private val processingState = TerminalThreadProcessingState() - private val textBufferState = TerminalThreadStateUnderTextBufferLock() - private val eventFlowState = EventFlowState() - - private data class ReadRange(val range: LongRange, val time: TimeMark) - private data class LatencyPair(val min: Latency?, val max: Latency?) - private data class Latency(val index: Long, val duration: Duration) - - /** The part of the state that is affected by the terminal emulator thread when reading and buffering characters from the TTY. */ - private class TerminalThreadReadingState { - /** The total number of characters read from the TTY stream and buffered. **/ - private var totalCharsRead = 0L - /** The queue of character index ranges read from the TTY and timestamped. **/ - val readRanges = LinkedBlockingQueue() - - /** Invoked every time a new buffer is read from the TTY. */ - fun charsRead(count: Int) { - val from = totalCharsRead - totalCharsRead += count.toLong() - val to = totalCharsRead - readRanges.add(ReadRange(from until to, TimeSource.Monotonic.markNow())) - } - } - - /** - * The part of the state that is affected by reading characters from the buffer. - * - * Usually accessed outside the text buffer lock, always in the terminal emulator thread. - */ - private class TerminalThreadProcessingState { - /** The total number of characters read from the buffer and processed by the emulator. */ - var totalCharsProcessed = 0L - private set - /** The index of the first character processed during this iteration. `null` when we're not inside an iteration. */ - var thisProcessingIterationStart: Long? = null - private set - - /** Invoked at the start of each iteration. */ - fun charProcessingStarted() { - thisProcessingIterationStart = totalCharsProcessed - } - - /** Invoked every time some characters are processed or pushed back into the buffer. In the latter case the argument is negative. */ - fun charsProcessed(count: Int) { - totalCharsProcessed += count.toLong() - } - - /** Invoked at the end of each iteration. */ - fun charProcessingFinished() { - thisProcessingIterationStart = null - } - } - - /** - * The part of the state that is only updated or accessed under the text buffer lock. - * - * Not necessarily accessed from the terminal emulator thread. - */ - private class TerminalThreadStateUnderTextBufferLock { - /** - * The index of the first character that will be included in the next buffer collection event. - * `null` if there have been no changes in the buffer since the last collection. - */ - private var nextTextBufferCollectionStart: Long? = null - /** - * The index of the last character that will be included in the next buffer collection event. - * `null` if there have been no changes in the buffer since the last collection. - */ - private var nextTextBufferCollectionEnd: Long? = null - - /** - * Invoked every time when a processed character affects the text buffer. - * - * Invoked on the terminal emulator thread. - * - * @param processingIterationStart the index of the first character processed during this processing iteration - * @param totalCharsProcessed the total number of processed characters, the same as the index of the last character processed so far - */ - fun processedCharsReachedTextBuffer(processingIterationStart: Long, totalCharsProcessed: Long) { - // This is a bit complicated. The exact sequence is this: - // 1. A processing iteration (com.intellij.terminal.backend.StopAwareTerminalStarter.FusAwareEmulator.next) starts. - // 2. A character is processed. - // 3. The text buffer may or may not be affected. If it's affected, this function is called. - // 4. Steps 2-3 continue to repeat until the end of the iteration. - // 5. The iteration ends. - // Characters that don't affect the buffer are usually control characters. For example, cursor movement. - // At any given moment the text buffer may be collected asynchronously from another thread. - // But this collection happens under the same lock this function is invoked, so it's not THAT asynchronous. - // The tricky part is to determine the range of character indices that match the buffer collection event. - // We always know the number of chars already processed, but we don't know which characters actually affected the buffer. - // We know for sure that when this callback is invoked, all characters processed so far are included in the text buffer. - // But for the next callback, if we assume that the next range starts where the previous one ended, - // we may end up with falsely large latencies because no-change characters from the previous iteration will be included as well. - // To avoid this situation, we ignore the previous range end and start the range from the first character of THIS iteration. - // But we must also account for the case when several processing iterations happen before the buffer is collected. - // That's why we only set the range start if it wasn't set yet. - if (nextTextBufferCollectionStart == null) { - nextTextBufferCollectionStart = processingIterationStart - } - nextTextBufferCollectionEnd = totalCharsProcessed - } - - /** - * Invoked every time the text buffer is collected ("scrapped"). - * - * Invoked _not_ from the terminal emulator thread but from the collecting coroutine. - * - * @return the range of the characters that have made their way into the buffer since the last collection - */ - fun textBufferCollected(): LongRange? { - val from = nextTextBufferCollectionStart - val to = nextTextBufferCollectionEnd - nextTextBufferCollectionStart = null - nextTextBufferCollectionEnd = null - if (from == null || to == null) { - LOG.error("textBufferCollected, but from==$from and to==$to, both should be non-null at this point") - return null - } - return from until to - } - } - - /** - * The part of the state that is affected by collecting the text buffer and further event processing. - * - * Accessed from different threads, so must be thread-safe. - */ - private class EventFlowState { - private val collectedRanges = ConcurrentHashMap, LongRange>() - - /** - * Invoked every time the text buffer is collected ("scrapped"). - */ - fun textBufferCollected(event: TerminalContentUpdatedEvent) { - collectedRanges[event.toIdentity()] = event.firstCharIndex..event.lastCharIndex - } - - /** - * Invoked every time the event produced by scrapping the text buffer is collected from the output flow. - * - * @return a pair of min/max latencies corresponding to the event char range, all non-`null` unless there's a bug somewhere - */ - fun eventCollected(event: TerminalContentUpdatedEvent, readRanges: LinkedBlockingQueue): LatencyPair? { - val range = collectedRanges.remove(event.toIdentity()) ?: return null - var firstCharTime: TimeMark? = null - var lastCharTime: TimeMark? = null - while (true) { - val nextRange = readRanges.peek() ?: break - if (nextRange.range.first > range.last) break // reached the part not collected yet - if (nextRange.range.last <= range.last) { // the entire range has been collected - readRanges.remove() - } - if (range.first in nextRange.range) { - firstCharTime = nextRange.time - } - if (range.last in nextRange.range) { - lastCharTime = nextRange.time - break - } - } - // The first char will have the maximum latency, as it was sitting in the buffer the longest. - // Compute the minimum latency first, as otherwise when they're essentially equal, - // we can end up in a situation when the maximum is less than the minimum by some microseconds. - val minLatency = if (lastCharTime != null) { - Latency(range.last, lastCharTime.elapsedNow()) - } - else { - LOG.warn("The last char ${range.last} was lost somewhere, it's a bug") - null - } - val maxLatency = if (firstCharTime != null) { - Latency(range.first, firstCharTime.elapsedNow()) - } - else { - LOG.warn("The first char ${range.first} was lost somewhere, it's a bug") - null - } - return LatencyPair(minLatency, maxLatency) - } - } - - override fun charsRead(count: Int) = readingState.charsRead(count) - - override fun charProcessingStarted() = processingState.charProcessingStarted() - - override fun charsProcessed(count: Int) = processingState.charsProcessed(count) - - override fun processedCharsReachedTextBuffer() { - // cross-state safe interaction: this function is called in the same thread that updates processingState, - // and under the same lock that is always used to access textBufferState - val processingIterationStart = processingState.thisProcessingIterationStart - if (processingIterationStart == null) { - LOG.error("processedCharsReachedTextBuffer should not be called outside of a processing iteration") - return - } - textBufferState.processedCharsReachedTextBuffer(processingIterationStart, processingState.totalCharsProcessed) - } - - override fun charProcessingFinished() = processingState.charProcessingFinished() - - // cross-state safe interaction: these two functions are called under the same text buffer lock textBufferState is updated under - - override fun textBufferCharacterIndices(): LongRange { - return textBufferState.textBufferCollected() ?: LongRange.EMPTY - } - - override fun textBufferCollected(event: TerminalContentUpdatedEvent) { - eventFlowState.textBufferCollected(event) - } - - override fun eventCollected(event: TerminalContentUpdatedEvent) { - // cross-state safe interaction: using the shared queue to transfer read ranges - val latency = eventFlowState.eventCollected(event, readingState.readRanges) ?: return - // If the sessionId is not known yet, we still collect statistics to ensure a consistent state but skip reporting. - // This can only happen very early during the session startup. - val sessionId = this.sessionId ?: return - if (latency.min != null) { - ReworkedTerminalUsageCollector.logBackendMinOutputLatency(sessionId, latency.min.index, latency.min.duration) - } - if (latency.max != null) { - ReworkedTerminalUsageCollector.logBackendMaxOutputLatency(sessionId, latency.max.index, latency.max.duration) - } - } -} - -// used to track individual instances of data classes -private class IdentityWrapper(private val instance: T) { - override fun equals(other: Any?): Boolean = instance === (other as? IdentityWrapper)?.instance - override fun hashCode(): Int = System.identityHashCode(instance) -} - -private class FusAwareTtyBasedDataStream( - private val original: TerminalDataStream, - private val fusActivity: BackendOutputActivity, -) : TerminalDataStream { - override fun getChar(): Char { - @Suppress("UsePropertyAccessSyntax") - val result = original.getChar() - fusActivity.charsProcessed(1) - return result - } - - override fun pushChar(c: Char) { - fusActivity.charsProcessed(-1) - original.pushChar(c) - } - - override fun readNonControlCharacters(maxChars: Int): String? { - val result = original.readNonControlCharacters(maxChars) - fusActivity.charsProcessed(result.length) - return result - } - - override fun pushBackBuffer(bytes: CharArray, length: Int) { - fusActivity.charsProcessed(-length) - original.pushBackBuffer(bytes, length) - } - - override fun isEmpty(): Boolean = original.isEmpty -} - -private fun T.toIdentity(): IdentityWrapper = IdentityWrapper(this) - -private val LOG = logger() +} \ No newline at end of file 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 92dc270379e9..3a8d2f4ba934 100644 --- a/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/TerminalContentChangesTrackerTest.kt +++ b/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/TerminalContentChangesTrackerTest.kt @@ -1,7 +1,6 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.terminal.backend -import com.intellij.terminal.backend.util.BackendOutputTestFusActivity import com.intellij.terminal.backend.util.scrollDown import com.intellij.terminal.backend.util.write import com.intellij.terminal.session.TerminalContentUpdatedEvent @@ -177,6 +176,6 @@ internal class TerminalContentChangesTrackerTest { private fun createChangesTracker(textBuffer: TerminalTextBuffer): TerminalContentChangesTracker { val discardedHistoryTracker = TerminalDiscardedHistoryTracker(textBuffer) - return TerminalContentChangesTracker(textBuffer, discardedHistoryTracker, BackendOutputTestFusActivity) + return TerminalContentChangesTracker(textBuffer, discardedHistoryTracker) } } \ No newline at end of file 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 5ce107db8224..96c47639e9ff 100644 --- a/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/TerminalCursorPositionTrackerTest.kt +++ b/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/TerminalCursorPositionTrackerTest.kt @@ -1,7 +1,6 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.terminal.backend -import com.intellij.terminal.backend.util.BackendOutputTestFusActivity import com.intellij.terminal.backend.util.write import com.intellij.terminal.session.TerminalContentUpdatedEvent import com.intellij.terminal.session.TerminalCursorPositionChangedEvent @@ -17,7 +16,7 @@ internal class TerminalCursorPositionTrackerTest { val textBuffer = TerminalTextBuffer(10, 5, StyleState(), 3) val discardedHistoryTracker = TerminalDiscardedHistoryTracker(textBuffer) val terminalDisplay = TerminalDisplayImpl(DefaultSettingsProvider()) - val contentChangesTracker = TerminalContentChangesTracker(textBuffer, discardedHistoryTracker, BackendOutputTestFusActivity) + val contentChangesTracker = TerminalContentChangesTracker(textBuffer, discardedHistoryTracker) val cursorPositionTracker = TerminalCursorPositionTracker(textBuffer, discardedHistoryTracker, terminalDisplay) // Prepare diff --git a/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/util/BackendOutputTestFusActivity.kt b/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/util/BackendOutputTestFusActivity.kt deleted file mode 100644 index e8d327ed28f8..000000000000 --- a/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/util/BackendOutputTestFusActivity.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.intellij.terminal.backend.util - -import com.intellij.platform.rpc.UID -import com.intellij.terminal.session.TerminalContentUpdatedEvent -import org.jetbrains.plugins.terminal.fus.BackendOutputActivity - -internal object BackendOutputTestFusActivity: BackendOutputActivity { - override var sessionId: UID? = null - - override fun charsRead(count: Int) { } - - override fun charProcessingStarted() { } - - override fun charsProcessed(count: Int) { } - - override fun processedCharsReachedTextBuffer() { } - - override fun charProcessingFinished() { } - - override fun textBufferCharacterIndices(): LongRange = LongRange.EMPTY - - override fun textBufferCollected(event: TerminalContentUpdatedEvent) { } - - override fun eventCollected(event: TerminalContentUpdatedEvent) { } -} diff --git a/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/util/TerminalSessionTestUtil.kt b/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/util/TerminalSessionTestUtil.kt index 78e9072f29fc..2fab0655c742 100644 --- a/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/util/TerminalSessionTestUtil.kt +++ b/plugins/terminal/backend/testSrc/com/intellij/terminal/backend/util/TerminalSessionTestUtil.kt @@ -17,7 +17,6 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import org.jetbrains.plugins.terminal.ShellStartupOptions import org.jetbrains.plugins.terminal.TerminalEngine -import org.jetbrains.plugins.terminal.fus.BackendLatencyService import org.jetbrains.plugins.terminal.reworked.util.TerminalTestUtil import java.nio.file.Files import java.nio.file.Path @@ -36,9 +35,8 @@ internal object TerminalSessionTestUtil { .initialTermSize(size) .envVariables(mapOf(EnvironmentUtil.DISABLE_OMZ_AUTO_UPDATE to "true", "HISTFILE" to "/dev/null")) .build() - val fusActivity = BackendLatencyService.getInstance().startBackendOutputActivity() val (ttyConnector, _) = startTerminalProcess(project, options) - val session = createTerminalSession(project, ttyConnector, size, JBTerminalSystemSettingsProviderBase(), coroutineScope, fusActivity) + val session = createTerminalSession(project, ttyConnector, size, JBTerminalSystemSettingsProviderBase(), coroutineScope) return session } diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/BackendLatencyService.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/BackendLatencyService.kt index ad724d54ae24..2114a05fb31d 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/BackendLatencyService.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/BackendLatencyService.kt @@ -2,8 +2,6 @@ package org.jetbrains.plugins.terminal.fus import com.intellij.openapi.components.service -import com.intellij.platform.rpc.UID -import com.intellij.terminal.session.TerminalContentUpdatedEvent import com.intellij.terminal.session.TerminalWriteBytesEvent import org.jetbrains.annotations.ApiStatus @@ -15,7 +13,6 @@ interface BackendLatencyService { } fun tryStartBackendTypingActivity(event: TerminalWriteBytesEvent) fun getBackendTypingActivityOrNull(bytes: ByteArray): BackendTypingActivity? - fun startBackendOutputActivity(): BackendOutputActivity } @ApiStatus.Internal @@ -24,16 +21,3 @@ interface BackendTypingActivity { fun reportDuration() fun finishBytesProcessing() } - -@ApiStatus.Internal -interface BackendOutputActivity { - var sessionId: UID? - fun charsRead(count: Int) - fun charProcessingStarted() - fun charsProcessed(count: Int) - fun processedCharsReachedTextBuffer() - fun charProcessingFinished() - fun textBufferCharacterIndices(): LongRange - fun textBufferCollected(event: TerminalContentUpdatedEvent) - fun eventCollected(event: TerminalContentUpdatedEvent) -}