<# .SYNOPSIS Logs connection quality to the local gateway and a public target at the same time, so you can tell a wifi problem from an ISP problem. .DESCRIPTION Dropped frames on stream are almost always one of three things: your own wifi, your ISP, or the ingest server. Pinging only 1.1.1.1 cannot tell the first two apart. This pings both the gateway and a public target every second and writes a CSV into the Diagnostics folder, which Claude can read. Run it during a stream that is misbehaving, or leave it going for an hour. Ctrl-C stops it and prints the summary. NOT a replacement for the standard tools, and not the first thing to reach for. OBS's own Stats dock tells you whether you even have a network problem; WinMTR or PingPlotter show per-hop loss; Twitch Inspector tests the actual path to ingest. This one covers what those do not: unattended logging over hours, in a CSV that Claude can read afterwards. Use it for intermittent problems the one-shot tests keep missing. Reading the result: gateway loss, internet fine -> your wifi or cable. Fixable by you. gateway fine, internet loss -> upstream. Your ISP, not your PC. both clean, stream still bad -> ingest server or encoder. Change server in OBS, or lower the bitrate. .PARAMETER Target Public host to test. Default 1.1.1.1. .PARAMETER Minutes How long to run. 0 (default) means until Ctrl-C. .EXAMPLE .\monitor-connection.ps1 .EXAMPLE .\monitor-connection.ps1 -Minutes 30 #> [CmdletBinding()] param( [string]$Target = "1.1.1.1", [int]$Minutes = 0, [int]$IntervalSeconds = 1, [string]$OutputRoot = "$env:USERPROFILE\Diagnostics" ) $ErrorActionPreference = "Stop" # --- Find the default gateway --- $gateway = (Get-NetRoute -DestinationPrefix "0.0.0.0/0" -ErrorAction SilentlyContinue | Sort-Object RouteMetric | Select-Object -First 1).NextHop if (-not $gateway -or $gateway -eq "0.0.0.0") { Write-Warning "Could not find a default gateway -- testing the public target only." $gateway = $null } $targets = @() if ($gateway) { $targets += [PSCustomObject]@{ Label = "gateway"; Address = $gateway } } $targets += [PSCustomObject]@{ Label = "internet"; Address = $Target } New-Item -Path $OutputRoot -ItemType Directory -Force | Out-Null $csv = Join-Path $OutputRoot "connection-$(Get-Date -Format 'yyyy-MM-dd_HHmmss').csv" "timestamp,label,address,success,latency_ms" | Set-Content -Path $csv -Encoding UTF8 Write-Host "" foreach ($t in $targets) { Write-Host " $($t.Label.PadRight(9)) $($t.Address)" } Write-Host "" Write-Host " Logging to $csv" Write-Host " Ctrl-C to stop$(if ($Minutes -gt 0) { " (or it stops after $Minutes min)" })." Write-Host "" $ping = New-Object System.Net.NetworkInformation.Ping $stats = @{} foreach ($t in $targets) { $stats[$t.Label] = [System.Collections.ArrayList]@() } $start = Get-Date $deadline = if ($Minutes -gt 0) { $start.AddMinutes($Minutes) } else { [datetime]::MaxValue } $lastReport = Get-Date function Show-Summary { Write-Host "" Write-Host " ---- summary ----" foreach ($label in $stats.Keys) { $all = $stats[$label] if ($all.Count -eq 0) { continue } $ok = @($all | Where-Object { $_ -ge 0 }) $lossPct = [math]::Round((($all.Count - $ok.Count) / $all.Count) * 100, 2) if ($ok.Count -gt 0) { $sorted = $ok | Sort-Object $p50 = $sorted[[int]($sorted.Count * 0.50)] $p95 = $sorted[[math]::Min([int]($sorted.Count * 0.95), $sorted.Count - 1)] $max = $sorted[-1] Write-Host (" {0,-9} loss {1,6}% p50 {2,4} ms p95 {3,4} ms max {4,5} ms n={5}" -f ` $label, $lossPct, $p50, $p95, $max, $all.Count) } else { Write-Host (" {0,-9} loss {1,6}% no successful replies n={2}" -f $label, $lossPct, $all.Count) } } Write-Host "" Write-Host " CSV: $csv" Write-Host " Ask Claude: 'Read the newest connection CSV in Diagnostics and tell me" Write-Host " whether my problem is local wifi or upstream.'" Write-Host "" } try { while ((Get-Date) -lt $deadline) { $now = Get-Date -Format "o" foreach ($t in $targets) { $latency = -1 try { $reply = $ping.Send($t.Address, 1000) if ($reply.Status -eq "Success") { $latency = [int]$reply.RoundtripTime } } catch { $latency = -1 } [void]$stats[$t.Label].Add($latency) "$now,$($t.Label),$($t.Address),$([int]($latency -ge 0)),$latency" | Add-Content -Path $csv -Encoding UTF8 } # Console line every 15s so it is readable rather than a waterfall. if (((Get-Date) - $lastReport).TotalSeconds -ge 15) { $lastReport = Get-Date $parts = foreach ($label in $stats.Keys) { $recent = @($stats[$label] | Select-Object -Last 15) $lost = @($recent | Where-Object { $_ -lt 0 }).Count $good = @($recent | Where-Object { $_ -ge 0 }) $avg = if ($good.Count) { [int](($good | Measure-Object -Average).Average) } else { 0 } "{0} {1} ms{2}" -f $label, $avg, $(if ($lost) { " ($lost lost)" } else { "" }) } Write-Host (" {0} {1}" -f (Get-Date -Format "HH:mm:ss"), ($parts -join " | ")) } Start-Sleep -Seconds $IntervalSeconds } } finally { Show-Summary }