psmux 3.3.4

Terminal multiplexer for Windows - tmux alternative for PowerShell and Windows Terminal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
# PSMUX vs Direct PowerShell Typing Benchmark
# Types 10 long sentences with spaces at realistic speed
# Measures per-character render latency for both environments
# Uses screen buffer polling for fair comparison

$ErrorActionPreference = "Continue"
$PSMUX = (Get-Command psmux -EA Stop).Source
$psmuxDir = "$env:USERPROFILE\.psmux"
$SESSION = "bench_typing"

# Compile tools
$csc = "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe"
$benchExe = "$env:TEMP\psmux_typing_benchmark.exe"
$injectorExe = "$env:TEMP\psmux_injector.exe"
$timedExe = "$env:TEMP\psmux_timed_injector.exe"

Write-Host "Compiling benchmark tools..." -ForegroundColor DarkGray
& $csc /nologo /optimize /out:$benchExe "$PSScriptRoot\typing_benchmark.cs" 2>&1 | Out-Null
if (-not (Test-Path $benchExe)) {
    Write-Host "FAILED to compile typing_benchmark.cs" -ForegroundColor Red
    exit 1
}
& $csc /nologo /optimize /out:$injectorExe "$PSScriptRoot\injector.cs" 2>&1 | Out-Null
& $csc /nologo /optimize /out:$timedExe "$PSScriptRoot\timed_injector.cs" 2>&1 | Out-Null

Write-Host ""
Write-Host ("=" * 75) -ForegroundColor Cyan
Write-Host "PSMUX vs DIRECT POWERSHELL TYPING BENCHMARK" -ForegroundColor Cyan
Write-Host "10 long sentences, realistic typing speed, render latency comparison" -ForegroundColor Cyan
Write-Host ("=" * 75) -ForegroundColor Cyan

# 10 realistic long sentences with spaces (40-80 chars each)
$sentences = @(
    "the quick brown fox jumps over the lazy dog and runs back home again"
    "pack my box with five dozen liquor jugs before the party starts tonight"
    "how vexingly quick daft zebras jump across the wide open fields today"
    "the five boxing wizards jump quickly through the dark misty forest path"
    "a large fawn jumped quickly over white zinc boxes left near the highway"
    "crazy frederick bought many very exquisite opal jewels from the old shop"
    "we promptly judged antique ivory buckles for the next prize competition"
    "sixty zippers were quickly picked from the woven jute bag on the floor"
    "back in june we delivered oxygen equipment of the same size to the city"
    "playing a quiet game of chess with the king requires very careful moves"
)

$INTERVAL_MS = 40  # ~150 WPM, fast realistic typing

function Cleanup-Psmux {
    & $PSMUX kill-session -t $SESSION 2>&1 | Out-Null
    Start-Sleep -Milliseconds 500
}

function Parse-BenchmarkOutput {
    param([string[]]$Lines)
    $summary = $null
    $csvData = @()
    foreach ($line in $Lines) {
        if ($line -match "^SUMMARY ") {
            $summary = @{}
            $parts = $line -replace "^SUMMARY ", "" -split " "
            foreach ($p in $parts) {
                $kv = $p -split "="
                if ($kv.Length -eq 2) {
                    $summary[$kv[0]] = $kv[1]
                }
            }
        } elseif ($line -match "^\d+,\d+,") {
            $csvData += $line
        }
    }
    return @{ Summary = $summary; CSV = $csvData }
}

# =========================================================================
# PHASE 1: PSMUX Benchmark (capture-pane based monitoring)
# =========================================================================
Write-Host "`n"
Write-Host ("=" * 75) -ForegroundColor Yellow
Write-Host "PHASE 1: PSMUX (through multiplexer)" -ForegroundColor Yellow
Write-Host ("=" * 75) -ForegroundColor Yellow

Cleanup-Psmux
Remove-Item "$psmuxDir\input_debug.log" -Force -EA SilentlyContinue

$env:PSMUX_INPUT_DEBUG = "1"
$psmuxProc = Start-Process -FilePath $PSMUX -ArgumentList "new-session","-s",$SESSION -PassThru
$env:PSMUX_INPUT_DEBUG = $null
$PID_TUI = $psmuxProc.Id
Write-Host "Launched psmux TUI PID: $PID_TUI" -ForegroundColor Cyan
Start-Sleep -Seconds 5

& $PSMUX has-session -t $SESSION 2>$null
if ($LASTEXITCODE -ne 0) { Write-Host "Session FAILED" -ForegroundColor Red; exit 1 }
for ($i = 0; $i -lt 40; $i++) {
    Start-Sleep -Milliseconds 500
    $cap = & $PSMUX capture-pane -t $SESSION -p 2>&1 | Out-String
    if ($cap -match "PS [A-Z]:\\") { break }
}
Write-Host "Session ready.`n" -ForegroundColor Green

$psmuxResults = @()
$sentenceNum = 0

foreach ($sentence in $sentences) {
    $sentenceNum++
    Write-Host "  Sentence $sentenceNum/$($sentences.Count): '$($sentence.Substring(0, [Math]::Min(50, $sentence.Length)))...'" -ForegroundColor White -NoNewline

    # Clear and prepare
    & $PSMUX send-keys -t $SESSION C-c 2>&1 | Out-Null
    Start-Sleep -Milliseconds 200
    & $PSMUX send-keys -t $SESSION "clear" Enter 2>&1 | Out-Null
    Start-Sleep -Seconds 1

    # Get baseline
    $baseCap = & $PSMUX capture-pane -t $SESSION -p 2>&1 | Out-String
    $baseLen = 0
    foreach ($l in ($baseCap -split "`n")) {
        if ($l.Trim()) { $baseLen = $l.TrimEnd().Length }
    }

    # Start monitor job (polls capture-pane every 20ms)
    $monJob = Start-Job -ScriptBlock {
        param($PSMUX, $SESSION, $baseLen, $totalChars, $timeoutMs)
        $sw = [System.Diagnostics.Stopwatch]::StartNew()
        $prevLen = $baseLen
        $firstMs = 0
        $lastMs = 0
        $lastChangeMs = 0
        $maxGap = 0
        $stallCount = 0
        $burstCount = 0
        $gaps = [System.Collections.ArrayList]::new()
        $allSeen = $false

        while ($sw.ElapsedMilliseconds -lt $timeoutMs -and -not $allSeen) {
            $cap = & $PSMUX capture-pane -t $SESSION -p 2>&1 | Out-String
            $ts = $sw.ElapsedMilliseconds

            # Concatenate all non-empty lines to handle wrapping
            $allText = ""
            $lines = $cap -split "`n"
            $foundPrompt = $false
            foreach ($l in $lines) {
                $trimmed = $l.TrimEnd()
                if ($trimmed -match "^.*PS [A-Z]:\\" -and -not $foundPrompt) {
                    $foundPrompt = $true
                    $allText = $trimmed
                } elseif ($foundPrompt -and $trimmed -and $trimmed -notmatch "^.*PS [A-Z]:\\") {
                    $allText += $trimmed
                } elseif ($foundPrompt -and $trimmed -match "^.*PS [A-Z]:\\") {
                    break
                }
            }

            $curLen = $allText.Length
            if ($curLen -ne $prevLen -and $curLen -gt $prevLen) {
                $delta = $curLen - $prevLen
                if ($firstMs -eq 0) { $firstMs = $ts }
                $lastMs = $ts

                if ($lastChangeMs -gt 0) {
                    $gap = $ts - $lastChangeMs
                    $null = $gaps.Add($gap)
                    if ($gap -gt $maxGap) { $maxGap = $gap }
                    if ($gap -gt 200) { $stallCount++ }
                    if ($delta -gt 5) { $burstCount++ }
                }
                $lastChangeMs = $ts
                $prevLen = $curLen
            }

            if (($curLen - $baseLen) -ge $totalChars) { $allSeen = $true }
            Start-Sleep -Milliseconds 20
        }

        $sortedGaps = @($gaps | Sort-Object)
        $p50 = if ($sortedGaps.Count -gt 0) { $sortedGaps[[Math]::Floor($sortedGaps.Count * 0.5)] } else { 0 }
        $p90 = if ($sortedGaps.Count -gt 0) { $sortedGaps[[Math]::Floor($sortedGaps.Count * 0.9)] } else { 0 }
        $p99 = if ($sortedGaps.Count -gt 0) { $sortedGaps[[Math]::Floor($sortedGaps.Count * 0.99)] } else { 0 }
        $avg = if ($sortedGaps.Count -gt 0) { [Math]::Round(($gaps | Measure-Object -Average).Average, 1) } else { 0 }

        return @{
            FirstMs   = $firstMs
            LastMs    = $lastMs
            RenderMs  = $lastMs - $firstMs
            Samples   = $gaps.Count
            Stalls    = $stallCount
            Bursts    = $burstCount
            MaxGapMs  = $maxGap
            AvgGapMs  = $avg
            P50Ms     = $p50
            P90Ms     = $p90
            P99Ms     = $p99
            AllSeen   = $allSeen
        }
    } -ArgumentList $PSMUX, $SESSION, $baseLen, $sentence.Length, (($sentence.Length * $INTERVAL_MS) + 8000)

    Start-Sleep -Milliseconds 100

    # Inject the sentence
    & $timedExe $PID_TUI $sentence $INTERVAL_MS 2>&1 | Out-Null

    # Wait for monitor to finish
    $result = $monJob | Wait-Job -Timeout 30 | Receive-Job
    Remove-Job $monJob -Force -EA SilentlyContinue

    if ($result) {
        $psmuxResults += [PSCustomObject]@{
            Num      = $sentenceNum
            Chars    = $sentence.Length
            RenderMs = $result.RenderMs
            AvgGap   = $result.AvgGapMs
            P50      = $result.P50Ms
            P90      = $result.P90Ms
            P99      = $result.P99Ms
            MaxGap   = $result.MaxGapMs
            Stalls   = $result.Stalls
            Bursts   = $result.Bursts
            AllSeen  = $result.AllSeen
        }
        $stallStr = if ($result.Stalls -gt 0) { " STALLS=$($result.Stalls)" } else { "" }
        Write-Host " | render=$($result.RenderMs)ms max_gap=$($result.MaxGapMs)ms p90=$($result.P90Ms)ms$stallStr" -ForegroundColor $(if ($result.Stalls -gt 0) {"Red"} else {"Green"})
    } else {
        Write-Host " | FAILED (no data)" -ForegroundColor Red
        $psmuxResults += [PSCustomObject]@{
            Num = $sentenceNum; Chars = $sentence.Length; RenderMs = -1
            AvgGap = -1; P50 = -1; P90 = -1; P99 = -1; MaxGap = -1
            Stalls = -1; Bursts = -1; AllSeen = $false
        }
    }
}

# Collect psmux debug log stats
$inputLog = "$psmuxDir\input_debug.log"
$psmuxStage2 = 0
$psmuxSuppressed = 0
if (Test-Path $inputLog) {
    $logLines = Get-Content $inputLog -EA SilentlyContinue
    $psmuxStage2 = @($logLines | Where-Object { $_ -match "stage2:" -and $_ -match "chars in 20ms" }).Count
    $psmuxSuppressed = @($logLines | Where-Object { $_ -match "suppressed char" }).Count
}

Cleanup-Psmux
try { if (-not $psmuxProc.HasExited) { Stop-Process -Id $psmuxProc.Id -Force -EA SilentlyContinue } } catch {}

# =========================================================================
# PHASE 2: Direct PowerShell (screen buffer monitoring)
# =========================================================================
Write-Host "`n"
Write-Host ("=" * 75) -ForegroundColor Yellow
Write-Host "PHASE 2: DIRECT POWERSHELL (no multiplexer)" -ForegroundColor Yellow
Write-Host ("=" * 75) -ForegroundColor Yellow

# Launch a plain pwsh in a new console window
$pwshProc = Start-Process -FilePath "pwsh" -ArgumentList "-NoProfile","-NoExit","-Command","function prompt { 'BENCH> ' }" -PassThru
$PID_PWSH = $pwshProc.Id
Write-Host "Launched direct pwsh PID: $PID_PWSH" -ForegroundColor Cyan
Start-Sleep -Seconds 4

$directResults = @()
$sentenceNum = 0

foreach ($sentence in $sentences) {
    $sentenceNum++
    Write-Host "  Sentence $sentenceNum/$($sentences.Count): '$($sentence.Substring(0, [Math]::Min(50, $sentence.Length)))...'" -ForegroundColor White -NoNewline

    # Clear screen first via injector
    & $injectorExe $PID_PWSH "clear{ENTER}"
    Start-Sleep -Seconds 1

    # Run the benchmark tool (injects + monitors screen buffer)
    $benchOutput = & $benchExe $PID_PWSH $sentence $INTERVAL_MS 2>&1
    $parsed = Parse-BenchmarkOutput -Lines ($benchOutput | ForEach-Object { $_.ToString() })

    if ($parsed.Summary) {
        $s = $parsed.Summary
        $directResults += [PSCustomObject]@{
            Num      = $sentenceNum
            Chars    = $sentence.Length
            RenderMs = [int]$s["render_ms"]
            AvgGap   = [int]$s["avg_gap_ms"]
            P50      = [int]$s["p50_ms"]
            P90      = [int]$s["p90_ms"]
            P99      = [int]$s["p99_ms"]
            MaxGap   = [int]$s["max_gap_ms"]
            Stalls   = [int]$s["stalls"]
            Bursts   = [int]$s["bursts"]
            AllSeen  = $s["all_seen"] -eq "True"
        }
        $stallStr = if ([int]$s["stalls"] -gt 0) { " STALLS=$($s["stalls"])" } else { "" }
        Write-Host " | render=$($s["render_ms"])ms max_gap=$($s["max_gap_ms"])ms p90=$($s["p90_ms"])ms$stallStr" -ForegroundColor $(if ([int]$s["stalls"] -gt 0) {"Red"} else {"Green"})
    } else {
        Write-Host " | FAILED (no summary)" -ForegroundColor Red
        $directResults += [PSCustomObject]@{
            Num = $sentenceNum; Chars = $sentence.Length; RenderMs = -1
            AvgGap = -1; P50 = -1; P90 = -1; P99 = -1; MaxGap = -1
            Stalls = -1; Bursts = -1; AllSeen = $false
        }
    }

    # Small gap between sentences
    Start-Sleep -Milliseconds 500
}

# Kill direct pwsh
try { Stop-Process -Id $PID_PWSH -Force -EA SilentlyContinue } catch {}

# =========================================================================
# COMPARISON TABLE
# =========================================================================
Write-Host "`n"
Write-Host ("=" * 75) -ForegroundColor Cyan
Write-Host "HEAD TO HEAD COMPARISON" -ForegroundColor Cyan
Write-Host ("=" * 75) -ForegroundColor Cyan
Write-Host "Typing speed: ${INTERVAL_MS}ms between chars (~$([Math]::Round(1000/$INTERVAL_MS * 60 / 5)) WPM)" -ForegroundColor White
Write-Host ""

Write-Host "PSMUX RESULTS (through multiplexer):" -ForegroundColor Yellow
$psmuxResults | Format-Table Num, Chars, RenderMs, AvgGap, P50, P90, P99, MaxGap, Stalls, Bursts -AutoSize
Write-Host ""
Write-Host "DIRECT POWERSHELL RESULTS (no multiplexer):" -ForegroundColor Yellow
$directResults | Format-Table Num, Chars, RenderMs, AvgGap, P50, P90, P99, MaxGap, Stalls, Bursts -AutoSize

# Aggregate stats
$validPsmux = @($psmuxResults | Where-Object { $_.RenderMs -ge 0 })
$validDirect = @($directResults | Where-Object { $_.RenderMs -ge 0 })

Write-Host ""
Write-Host ("=" * 75) -ForegroundColor Cyan
Write-Host "AGGREGATE STATISTICS" -ForegroundColor Cyan
Write-Host ("=" * 75) -ForegroundColor Cyan

if ($validPsmux.Count -gt 0) {
    $pAvgRender = [Math]::Round(($validPsmux | Measure-Object -Property RenderMs -Average).Average)
    $pAvgP90    = [Math]::Round(($validPsmux | Measure-Object -Property P90 -Average).Average)
    $pAvgP99    = [Math]::Round(($validPsmux | Measure-Object -Property P99 -Average).Average)
    $pMaxGap    = ($validPsmux | Measure-Object -Property MaxGap -Maximum).Maximum
    $pTotalStalls = ($validPsmux | Measure-Object -Property Stalls -Sum).Sum
    $pTotalBursts = ($validPsmux | Measure-Object -Property Bursts -Sum).Sum

    Write-Host ""
    Write-Host "  PSMUX:" -ForegroundColor Yellow
    Write-Host "    Avg render span:   ${pAvgRender}ms" -ForegroundColor White
    Write-Host "    Avg P90 gap:       ${pAvgP90}ms" -ForegroundColor White
    Write-Host "    Avg P99 gap:       ${pAvgP99}ms" -ForegroundColor White
    Write-Host "    Worst single gap:  ${pMaxGap}ms" -ForegroundColor $(if ($pMaxGap -gt 200) {"Red"} else {"Green"})
    Write-Host "    Total stalls:      $pTotalStalls" -ForegroundColor $(if ($pTotalStalls -gt 0) {"Red"} else {"Green"})
    Write-Host "    Total bursts:      $pTotalBursts" -ForegroundColor $(if ($pTotalBursts -gt 0) {"Yellow"} else {"Green"})
    Write-Host "    Stage2 triggers:   $psmuxStage2" -ForegroundColor $(if ($psmuxStage2 -gt 0) {"Yellow"} else {"Green"})
    Write-Host "    Chars suppressed:  $psmuxSuppressed" -ForegroundColor $(if ($psmuxSuppressed -gt 0) {"Red"} else {"Green"})
}

if ($validDirect.Count -gt 0) {
    $dAvgRender = [Math]::Round(($validDirect | Measure-Object -Property RenderMs -Average).Average)
    $dAvgP90    = [Math]::Round(($validDirect | Measure-Object -Property P90 -Average).Average)
    $dAvgP99    = [Math]::Round(($validDirect | Measure-Object -Property P99 -Average).Average)
    $dMaxGap    = ($validDirect | Measure-Object -Property MaxGap -Maximum).Maximum
    $dTotalStalls = ($validDirect | Measure-Object -Property Stalls -Sum).Sum
    $dTotalBursts = ($validDirect | Measure-Object -Property Bursts -Sum).Sum

    Write-Host ""
    Write-Host "  DIRECT POWERSHELL:" -ForegroundColor Yellow
    Write-Host "    Avg render span:   ${dAvgRender}ms" -ForegroundColor White
    Write-Host "    Avg P90 gap:       ${dAvgP90}ms" -ForegroundColor White
    Write-Host "    Avg P99 gap:       ${dAvgP99}ms" -ForegroundColor White
    Write-Host "    Worst single gap:  ${dMaxGap}ms" -ForegroundColor $(if ($dMaxGap -gt 200) {"Red"} else {"Green"})
    Write-Host "    Total stalls:      $dTotalStalls" -ForegroundColor $(if ($dTotalStalls -gt 0) {"Red"} else {"Green"})
    Write-Host "    Total bursts:      $dTotalBursts" -ForegroundColor $(if ($dTotalBursts -gt 0) {"Yellow"} else {"Green"})
}

if ($validPsmux.Count -gt 0 -and $validDirect.Count -gt 0) {
    Write-Host ""
    Write-Host ("=" * 75) -ForegroundColor Cyan
    Write-Host "DELTA (PSMUX OVERHEAD)" -ForegroundColor Cyan
    Write-Host ("=" * 75) -ForegroundColor Cyan
    $renderOverhead = $pAvgRender - $dAvgRender
    $p90Overhead = $pAvgP90 - $dAvgP90
    Write-Host "    Render span overhead: +${renderOverhead}ms per sentence" -ForegroundColor $(if ($renderOverhead -gt 500) {"Red"} elseif ($renderOverhead -gt 100) {"Yellow"} else {"Green"})
    Write-Host "    P90 gap overhead:     +${p90Overhead}ms" -ForegroundColor $(if ($p90Overhead -gt 50) {"Red"} elseif ($p90Overhead -gt 20) {"Yellow"} else {"Green"})
    $maxGapDelta = $pMaxGap - $dMaxGap
    Write-Host "    Max gap overhead:     +${maxGapDelta}ms" -ForegroundColor $(if ($maxGapDelta -gt 100) {"Red"} elseif ($maxGapDelta -gt 50) {"Yellow"} else {"Green"})

    if ($pTotalStalls -gt 0 -and $dTotalStalls -eq 0) {
        Write-Host "`n    VERDICT: PSMUX has $pTotalStalls stall(s) that direct PowerShell does NOT have." -ForegroundColor Red
        Write-Host "    The user's reported 'freeze feeling' is measurably real." -ForegroundColor Red
        Write-Host "[FAIL] Typing benchmark: Stalls detected" -ForegroundColor Red
        exit 1
    } elseif ($pTotalStalls -eq 0 -and $dTotalStalls -eq 0) {
        Write-Host "`n    VERDICT: No stalls in either environment." -ForegroundColor Green
        if ($p90Overhead -gt 30) {
            Write-Host "    However, psmux P90 is ${p90Overhead}ms higher, which may feel sluggish." -ForegroundColor Yellow
            Write-Host "[PASS] Typing benchmark: No stalls (acceptable overhead)" -ForegroundColor Green
        } else {
            Write-Host "    Psmux overhead is minimal and should not be perceptible." -ForegroundColor Green
            Write-Host "[PASS] Typing benchmark: Minimal overhead" -ForegroundColor Green
        }
    } else {
        Write-Host "`n    VERDICT: Both environments show stalls (possible system load)." -ForegroundColor Yellow
        Write-Host "[PASS] Typing benchmark: Equal stall profiles" -ForegroundColor Green
    }
}

Write-Host ""