diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..acbf18f --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,10 @@ +# Bolt's Performance Journal ⚡ + +## 2025-05-14 - [Webhook Latency Optimization & PowerShell Efficiency] +**Learning:** In event-driven systems like webhook listeners, the "Early Response" pattern is critical for minimizing sender-side latency. By closing the HTTP response immediately after reading the payload, we decouple the processing/logging time from the network turnaround time. Additionally, in PowerShell, cmdlets like `Get-Date` and the use of the pipeline (`|`) introduce significant overhead compared to direct .NET methods and parameter passing (`-InputObject`). + +**Action:** +1. Move `$response.Close()` to immediately follow the request body read. +2. Replace `Get-Date` with `[DateTime]::Now` for timestamping. +3. Replace pipeline operations with direct parameter passing in high-frequency loops. +4. Pre-allocate static buffers (like response bytes) outside the main request loop. diff --git a/tests/Project.Tests.ps1 b/tests/Project.Tests.ps1 index 3e27506..a4f48a6 100644 --- a/tests/Project.Tests.ps1 +++ b/tests/Project.Tests.ps1 @@ -1,10 +1,10 @@ -Describe "System Automation Hub Structure" { +Describe "System Automation Hub Structure" { It "Should have the main entry point (start-automation.ps1)" { - Test-Path "./start-automation.ps1" | Should -Be \$true + Test-Path "./start-automation.ps1" | Should -Be $true } It "Should have the webhooks/listener.ps1 script" { - Test-Path "./webhooks/listener.ps1" | Should -Be \$true + Test-Path "./webhooks/listener.ps1" | Should -Be $true } It "Should have at least one script in the scripts/ directory" { @@ -14,15 +14,19 @@ Describe "PowerShell Script Syntax Verification" { Context "Checking all .ps1 files" { - \$psFiles = Get-ChildItem -Path . -Include *.ps1 -Recurse + $psFiles = Get-ChildItem -Path . -Include *.ps1 -Recurse - foreach (\$file in \$psFiles) { - It "Should have valid syntax for \$(\$file.Name)" { - \$errorActionPreference = "Stop" - Get-Command -ErrorAction SilentlyContinue -Name Out-Null # Ensure we can run commands + foreach ($file in $psFiles) { + It "Should have valid syntax for $($file.Name)" { + $errors = $null + $tokens = $null + # Use the built-in Parser to verify syntax without external module dependencies + [System.Management.Automation.Language.Parser]::ParseFile($file.FullName, [ref]$tokens, [ref]$errors) | Out-Null - # Check for syntax errors by parsing the script - { [Microsoft.PowerShell.Commands.ScriptAnalyzer.Helper]::GetTokens(\$file.FullName, [ref]\$null, [ref]\$null) } | Should -Not -Throw + if ($errors) { + $errorMessages = $errors | ForEach-Object { "$($_.Message) at line $($_.Extent.StartLineNumber):$($_.Extent.StartColumnNumber)" } + throw "Syntax errors found in $($file.Name):`n$($errorMessages -join "`n")" + } } } } diff --git a/webhooks/listener.ps1 b/webhooks/listener.ps1 index 8d8f929..0cb6a15 100644 --- a/webhooks/listener.ps1 +++ b/webhooks/listener.ps1 @@ -7,6 +7,10 @@ param() $port = 9000 $endpoint = "http://localhost:$port/" +# Performance: Pre-calculate encoding and response buffer to avoid redundant allocations in the loop +$utf8 = [System.Text.Encoding]::UTF8 +$responseBytes = $utf8.GetBytes("System Automation Hub: Event Received") + # Ensure we don't try to start another listener if one is already running in this session if ($null -ne $listener) { try { $listener.Stop() } catch { Write-Verbose "Listener already stopped." } @@ -29,7 +33,8 @@ try { $request = $context.Request $response = $context.Response - $timestamp = Get-Date -Format 'HH:mm:ss' + # Performance: Use .NET [DateTime]::Now for faster timestamp generation than Get-Date cmdlet + $timestamp = [DateTime]::Now.ToString('HH:mm:ss') $method = $request.HttpMethod $remote = $request.RemoteEndPoint $userAgent = $request.UserAgent @@ -37,21 +42,34 @@ try { $sourceIcon = if ($isGitHub) { "🐙 GitHub " } else { "🔗 Web " } + # Read body if available + $body = $null + if ($request.HasEntityBody) { + # Performance: Use constructor directly and ensure proper disposal of the stream reader + $reader = [System.IO.StreamReader]::new($request.InputStream, $utf8) + $body = $reader.ReadToEnd() + $reader.Dispose() + } + + # ⚡ BOLT OPTIMIZATION: Early Response + # We send the response IMMEDIATELY after reading the body to minimize latency for the sender (e.g. GitHub). + # Expensive operations like JSON pretty-printing and console logging happen AFTER the connection is closed. + $response.ContentLength64 = $responseBytes.Length + $response.OutputStream.Write($responseBytes, 0, $responseBytes.Length) + $response.Close() + Write-Host "[$timestamp] " -ForegroundColor Gray -NoNewline Write-Host "$sourceIcon" -ForegroundColor Magenta -NoNewline Write-Host "$method " -ForegroundColor Yellow -NoNewline Write-Host "from " -ForegroundColor Gray -NoNewline Write-Host "$remote" -ForegroundColor White - # Read body if available - if ($request.HasEntityBody) { - $reader = New-Object System.IO.StreamReader($request.InputStream, [System.Text.Encoding]::UTF8) - $body = $reader.ReadToEnd() - + if ($null -ne $body) { try { if ($request.ContentType -match "application/json") { - $jsonObj = $body | ConvertFrom-Json - $prettyBody = $jsonObj | ConvertTo-Json -Depth 10 + # Performance: Use -InputObject parameter instead of pipeline for faster processing + $jsonObj = ConvertFrom-Json -InputObject $body + $prettyBody = ConvertTo-Json -InputObject $jsonObj -Depth 10 Write-Host "Payload (JSON):" -ForegroundColor Cyan Write-Host $prettyBody -ForegroundColor DarkGray } else { @@ -64,11 +82,6 @@ try { } } - # Simple response - $buffer = [System.Text.Encoding]::UTF8.GetBytes("System Automation Hub: Event Received") - $response.ContentLength64 = $buffer.Length - $response.OutputStream.Write($buffer, 0, $buffer.Length) - $response.Close() Write-Host "Done.`n" -ForegroundColor DarkGray } } catch {