Browse Source

feat(windows-ops): Refactor crash-triage and boot-perf onto design system

crash-triage.ps1:
- Panel with crash timestamp as right indicator
- Summary line carries the BugCheck name decoded
- Three sections: parameters, pre-crash timeline, smoking guns
- Timeline leaves show T-{m:s} relative offset to crash (more
  useful than absolute clock time when correlating cascade)
- Cause hint surfaces as warning alert under parameters
- Smoking-guns section colored FAILING (red) with widened
  name column to fit full attribution text
- Footer health: ⬤ cascade (busted) when smoking guns found

boot-perf.ps1:
- Panel with 'full data' or 'fallback mode' right indicator
- Summary line: N boots · median Xs · avg Ys
- Admin hint surfaces when running non-elevated (kernel-event
  fallback path)
- Boot timeline with capacity pip bars relative to 20s ceiling
  — pips fill more as boot gets slower (anything ≥80% = red)
- Slowest boot in window gets an inline alert with comparison
  to median
- Slow components section (when elevated and components flagged)
  with remediation hint pointing to safe-disable-startup.ps1
- Footer health: • full data (green) or • fallback (yellow)

Both dogfooded successfully:
- crash-triage: most-recent Y-drive crash decoded with T-2m01s
  storahci 129 smoking gun
- boot-perf: all 10 last boots rendered with pip bars, May 11
  13.9s pre-crash boot correctly flagged as slowest

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
0xDarkMatter 2 weeks ago
parent
commit
6e18af5ffb
2 changed files with 112 additions and 65 deletions
  1. 55 45
      skills/windows-ops/scripts/boot-perf.ps1
  2. 57 20
      skills/windows-ops/scripts/crash-triage.ps1

+ 55 - 45
skills/windows-ops/scripts/boot-perf.ps1

@@ -50,6 +50,8 @@ param(
 
 
 $ErrorActionPreference = 'Stop'
 $ErrorActionPreference = 'Stop'
 . "$PSScriptRoot\_lib\common.ps1"
 . "$PSScriptRoot\_lib\common.ps1"
+. (Join-Path $PSScriptRoot '..\..\_lib\term.ps1')
+Initialize-Term
 
 
 $elevated = Test-IsElevated
 $elevated = Test-IsElevated
 $boots = New-Object System.Collections.Generic.List[hashtable]
 $boots = New-Object System.Collections.Generic.List[hashtable]
@@ -168,63 +170,71 @@ if ($Json) {
     exit $script:EXIT_OK
     exit $script:EXIT_OK
 }
 }
 
 
-Write-Section "Boot performance — last $LastN boots ($source)"
-
 if (-not $boots) {
 if (-not $boots) {
     Write-Log -Level FAIL -Message "No boot events found"
     Write-Log -Level FAIL -Message "No boot events found"
     exit $script:EXIT_PRECONDITION
     exit $script:EXIT_PRECONDITION
 }
 }
 
 
-if ($source -eq 'diagnostics-perf') {
-    [Console]::Out.WriteLine("")
-    [Console]::Out.WriteLine(("  {0,-20}  {1,8}  {2,8}  {3,8}  {4}" -f 'Time', 'Total', 'Main', 'PostBoot', 'Status'))
-    [Console]::Out.WriteLine(("  {0,-20}  {1,8}  {2,8}  {3,8}  {4}" -f ('-' * 20), ('-' * 8), ('-' * 8), ('-' * 8), ('-' * 12)))
-    foreach ($b in $boots) {
-        $t = ([DateTime]$b.time).ToString('yyyy-MM-dd HH:mm')
-        $tot = if ($b.bootTotalSec -gt 0) { "$($b.bootTotalSec)s" } else { '?' }
-        $main = if ($b.bootMainSec -gt 0) { "$($b.bootMainSec)s" } else { '?' }
-        $post = if ($b.bootPostSec -gt 0) { "$($b.bootPostSec)s" } else { '?' }
-        $status = if ($b.degraded) { '[DEGRADED]' } else { '[OK]' }
-        [Console]::Out.WriteLine(("  {0,-20}  {1,8}  {2,8}  {3,8}  {4}" -f $t, $tot, $main, $post, $status))
-    }
-
-    # Average + median calc on healthy boots
-    $healthy = $boots | Where-Object { -not $_.degraded -and $_.bootTotalSec -gt 0 }
-    if ($healthy.Count -ge 3) {
-        $avg = [math]::Round(($healthy | Measure-Object bootTotalSec -Average).Average, 1)
-        $sorted = $healthy | Sort-Object bootTotalSec
-        $median = $sorted[[math]::Floor($sorted.Count / 2)].bootTotalSec
-        [Console]::Out.WriteLine("")
-        [Console]::Out.WriteLine("  Healthy-boot average: ${avg}s    median: ${median}s    ($($healthy.Count) of $($boots.Count) boots)")
-    }
-} else {
-    [Console]::Out.WriteLine("")
-    [Console]::Out.WriteLine("  Note: $($boots[0].note)")
-    [Console]::Out.WriteLine("")
-    [Console]::Out.WriteLine(("  {0,-20}  {1,12}" -f 'Time', 'Kernel→LogSvc'))
-    [Console]::Out.WriteLine(("  {0,-20}  {1,12}" -f ('-' * 20), ('-' * 12)))
-    foreach ($b in $boots) {
-        $t = ([DateTime]$b.time).ToString('yyyy-MM-dd HH:mm')
-        [Console]::Out.WriteLine(("  {0,-20}  {1,12}" -f $t, "$($b.bootMainSec)s"))
+# Median + average for the summary line (whichever data we have)
+$mainSecs = $boots | ForEach-Object { $_.bootMainSec } | Where-Object { $_ -gt 0 }
+$median = if ($mainSecs.Count -ge 1) {
+    $sorted = $mainSecs | Sort-Object
+    $sorted[[math]::Floor($sorted.Count / 2)]
+} else { 0 }
+$avg = if ($mainSecs.Count -ge 1) {
+    [math]::Round(($mainSecs | Measure-Object -Average).Average, 1)
+} else { 0 }
+
+$sourceLabel = if ($source -eq 'diagnostics-perf') { 'full data' } else { 'fallback mode' }
+Write-TermLine (New-TermPanelOpen -Brand 'windows-ops' -Name 'windows-ops' -Subtitle 'boot-perf' -Indicator $sourceLabel)
+Write-TermLine (New-TermPanelVert)
+Write-TermLine (New-TermSummary -Text "$($boots.Count) boots · median ${median}s · avg ${avg}s")
+if (-not $elevated -and $source -ne 'diagnostics-perf') {
+    Write-TermLine (New-TermHint -Text 'run as Administrator for full Diagnostics-Performance log (boot phases + slow-component flags)')
+}
+Write-TermLine (New-TermPanelVert)
+
+Write-TermLine (New-TermSection -State 'INFO' -Label 'boot timeline' -Count $boots.Count)
+# Find slowest in window for highlighting
+$slowestSec = ($mainSecs | Measure-Object -Maximum).Maximum
+$idxLast = $boots.Count - 1
+for ($i = 0; $i -lt $boots.Count; $i++) {
+    $b = $boots[$i]
+    $t = ([DateTime]$b.time).ToString('yyyy-MM-dd HH:mm')
+    $secVal = if ($b.bootMainSec -gt 0) { $b.bootMainSec } else { 0 }
+    # Capacity pip bar: relative to 20-second ceiling (anything ≥80% = red)
+    $bar = New-TermPipBar -Type capacity -Filled ([int]($secVal * 5)) -Total 100
+    $meta = "${secVal}s"
+    if ($b.degraded) { $meta += ' [DEGRADED]' }
+    Write-TermLine (New-TermLeaf -Name $t -Rail $bar -Meta $meta -IsLast:($i -eq $idxLast) -NameColWidth 20)
+    if ($secVal -eq $slowestSec -and $boots.Count -gt 3 -and $secVal -gt 0) {
+        Write-TermLine (New-TermAlert -Severity warning -Text "slowest in window · ${secVal}s vs median ${median}s")
     }
     }
-    [Console]::Out.WriteLine("")
-    [Console]::Out.WriteLine("  For full boot timing including BootMainPath + BootPostBoot phases,")
-    [Console]::Out.WriteLine("  re-run as Administrator. The Diagnostics-Performance log requires elevation.")
 }
 }
+Write-TermLine (New-TermPanelVert)
 
 
-# Slow components
+# Slow components section
 $recentSlow = $slowComponents | Sort-Object { [DateTime]$_.time } -Descending | Select-Object -First 10
 $recentSlow = $slowComponents | Sort-Object { [DateTime]$_.time } -Descending | Select-Object -First 10
 if ($recentSlow) {
 if ($recentSlow) {
-    Write-Section "Slow components flagged at recent boots"
-    foreach ($s in $recentSlow) {
-        $t = ([DateTime]$s.time).ToString('yyyy-MM-dd HH:mm')
+    Write-TermLine (New-TermSection -State 'WARN' -Label 'slow components' -Count $recentSlow.Count)
+    $idxLast = $recentSlow.Count - 1
+    for ($i = 0; $i -lt $recentSlow.Count; $i++) {
+        $s = $recentSlow[$i]
         $delay = if ($s.delaySec) { "$($s.delaySec)s" } else { '?' }
         $delay = if ($s.delaySec) { "$($s.delaySec)s" } else { '?' }
-        [Console]::Out.WriteLine(("  {0}  [{1,-7}]  {2,-8}  {3}" -f $t, $s.kind, $delay, $s.name))
+        $when = ([DateTime]$s.time).ToString('MM-dd HH:mm')
+        Write-TermLine (New-TermLeaf -Name "[$($s.kind)] $($s.name)" -Meta $delay -Age $when -IsLast:($i -eq $idxLast) -NameColWidth 36)
     }
     }
-    [Console]::Out.WriteLine("")
-    [Console]::Out.WriteLine("  These components exceeded the system's 'fast boot' threshold at the boots shown.")
-    [Console]::Out.WriteLine("  Repeat offenders are prime candidates for disabling via safe-disable-startup.ps1")
-    [Console]::Out.WriteLine("  (apps) or Set-Service -StartupType Manual (services) or Disable-ScheduledTask.")
+    Write-TermLine (New-TermAlert -Severity warning -Text 'repeat offenders → safe-disable-startup.ps1 (apps) or Set-Service -StartupType Manual (services)')
+    Write-TermLine (New-TermPanelVert)
+}
+
+# Footer
+$health = if ($elevated -and $source -eq 'diagnostics-perf') {
+    New-TermHealth -State 'healthy' -Text 'full data'
+} else {
+    New-TermHealth -State 'pending' -Text 'fallback'
 }
 }
+$hk = (New-TermHotkey -Key '?' -Verb 'help')
+Write-TermLine (New-TermPanelClose -Hotkeys $hk -Healths $health)
 
 
 exit $script:EXIT_OK
 exit $script:EXIT_OK

+ 57 - 20
skills/windows-ops/scripts/crash-triage.ps1

@@ -65,6 +65,8 @@ param(
 
 
 $ErrorActionPreference = 'Stop'
 $ErrorActionPreference = 'Stop'
 . "$PSScriptRoot\_lib\common.ps1"
 . "$PSScriptRoot\_lib\common.ps1"
+. (Join-Path $PSScriptRoot '..\..\_lib\term.ps1')
+Initialize-Term
 
 
 # BugCheck quick-lookup (most common codes; full catalog in references/bugcheck-codes.md)
 # BugCheck quick-lookup (most common codes; full catalog in references/bugcheck-codes.md)
 $bugCheckNames = @{
 $bugCheckNames = @{
@@ -191,35 +193,70 @@ if ($Json) {
         }
         }
     } | ConvertTo-Json -Depth 5 | ForEach-Object { [Console]::Out.WriteLine($_) }
     } | ConvertTo-Json -Depth 5 | ForEach-Object { [Console]::Out.WriteLine($_) }
 } else {
 } else {
-    Write-Section "Crash record: $($CrashTime.ToString('yyyy-MM-dd HH:mm:ss'))"
-    [Console]::Out.WriteLine("  BugCheck:  $bcHex  $bcName")
-    [Console]::Out.WriteLine("  Param1:    0x{0:X}" -f $param1)
-    [Console]::Out.WriteLine("  Param2:    0x{0:X}" -f $param2)
-    [Console]::Out.WriteLine("  Param3:    0x{0:X}" -f $param3)
-    [Console]::Out.WriteLine("  Param4:    0x{0:X}" -f $param4)
-    [Console]::Out.WriteLine("  PowerBtn:  $(if ($pwrBtn -ne 0) {'held (forced shutdown)'} else {'not pressed'})")
+    $indicator = $CrashTime.ToString('yyyy-MM-dd HH:mm:ss')
+    Write-TermLine (New-TermPanelOpen -Brand 'windows-ops' -Name 'windows-ops' -Subtitle 'crash-triage' -Indicator $indicator)
+    Write-TermLine (New-TermPanelVert)
+    Write-TermLine (New-TermSummary -Text "BugCheck $bcHex · $bcName")
+    Write-TermLine (New-TermPanelVert)
+
+    # PARAMETERS section
+    Write-TermLine (New-TermSection -State 'INFO' -Label 'parameters' -Count -1)
+    Write-TermLine (New-TermLeaf -Name 'Param1' -Meta ('0x{0:X}' -f $param1))
+    Write-TermLine (New-TermLeaf -Name 'Param2' -Meta ('0x{0:X}' -f $param2))
+    Write-TermLine (New-TermLeaf -Name 'Param3' -Meta ('0x{0:X}' -f $param3))
+    Write-TermLine (New-TermLeaf -Name 'Param4' -Meta ('0x{0:X}' -f $param4))
+    $pwrText = if ($pwrBtn -ne 0) { 'held (forced shutdown)' } else { 'not pressed' }
+    Write-TermLine (New-TermLeaf -Name 'PowerButton' -Meta $pwrText -IsLast)
     if ($causeHint) {
     if ($causeHint) {
-        [Console]::Out.WriteLine("")
-        [Console]::Out.WriteLine("  Cause hint:  $causeHint")
+        Write-TermLine (New-TermAlert -Severity warning -Text $causeHint)
     }
     }
+    Write-TermLine (New-TermPanelVert)
 
 
-    Write-Section "Pre-crash timeline ($WindowMinutes min before crash)"
-    if (-not $preEvents) {
-        [Console]::Out.WriteLine("  (no warning/error/critical events in window — sudden hang or instant fault)")
-    } else {
-        foreach ($e in $preEvents) {
-            $tStr = $e.TimeCreated.ToString('HH:mm:ss')
-            $msg  = Format-EventMessage -Message $e.Message -MaxLength 90
-            [Console]::Out.WriteLine(("  {0}  [{1,-3}] {2,-32} Id={3,-5} {4}" -f $tStr, $e.LevelDisplayName.Substring(0,3), $e.ProviderName.Substring(0,[Math]::Min(32,$e.ProviderName.Length)), $e.Id, $msg))
+    # TIMELINE section
+    if ($preEvents) {
+        Write-TermLine (New-TermSection -State 'WARN' -Label "pre-crash timeline" -Count $preEvents.Count)
+        $idxLast = $preEvents.Count - 1
+        for ($i = 0; $i -lt $preEvents.Count; $i++) {
+            $e = $preEvents[$i]
+            $deltaSec = [int]($CrashTime - $e.TimeCreated).TotalSeconds
+            $deltaStr = if ($deltaSec -ge 60) {
+                "T-{0}m{1:00}s" -f ([math]::Floor($deltaSec/60)), ($deltaSec % 60)
+            } else {
+                "T-{0}s" -f $deltaSec
+            }
+            $msg = Format-EventMessage -Message $e.Message -MaxLength 50
+            Write-TermLine (New-TermLeaf -Name "$($e.ProviderName) $($e.Id)" -Meta $msg -Age $deltaStr -IsLast:($i -eq $idxLast) -NameColWidth 24 -MetaColWidth 50)
         }
         }
+        Write-TermLine (New-TermPanelVert)
+    } else {
+        Write-TermLine (New-TermSection -State 'WARN' -Label "pre-crash timeline" -Count 0)
+        Write-TermLine (New-TermHint -Text 'no warning/error events in window — sudden hang or instant fault')
+        Write-TermLine (New-TermPanelVert)
     }
     }
 
 
+    # SMOKING GUNS section
     if ($smokingGuns) {
     if ($smokingGuns) {
-        Write-Section "SMOKING GUNS"
-        foreach ($g in $smokingGuns) {
-            [Console]::Out.WriteLine("  - $g")
+        Write-TermLine (New-TermSection -State 'FAILING' -Label 'smoking guns' -Count $smokingGuns.Count)
+        $idxLast = $smokingGuns.Count - 1
+        for ($i = 0; $i -lt $smokingGuns.Count; $i++) {
+            Write-TermLine (New-TermLeaf -Name $smokingGuns[$i] -IsLast:($i -eq $idxLast) -NameColWidth 80 -RailColWidth 0 -MetaColWidth 0)
         }
         }
+        Write-TermLine (New-TermPanelVert)
+    }
+
+    # Footer
+    $health = if ($smokingGuns) {
+        New-TermHealth -State 'busted' -Text 'cascade'
+    } elseif ($bcCode -eq 0) {
+        New-TermHealth -State 'critical' -Text 'no bugcheck'
+    } else {
+        New-TermHealth -State 'warning' -Text 'decoded'
     }
     }
+    $hk = @(
+        (New-TermHotkey -Key 'D' -Verb 'drill')
+        (New-TermHotkey -Key '?' -Verb 'help')
+    ) | Join-TermHotkeys
+    Write-TermLine (New-TermPanelClose -Hotkeys $hk -Healths $health)
 }
 }
 
 
 exit $script:EXIT_OK
 exit $script:EXIT_OK