param( [int]$Port = 8789 ) $ErrorActionPreference = "Stop" function Get-Percent { param([double]$Value) if ([double]::IsNaN($Value) -or [double]::IsInfinity($Value)) { return 0 } $rounded = [math]::Round($Value) if ($rounded -lt 0) { return 0 } if ($rounded -gt 100) { return 100 } return [int]$rounded } function Get-SystemMetrics { $cpu = Get-CimInstance Win32_PerfFormattedData_PerfOS_Processor | Where-Object { $_.Name -eq "_Total" } | Select-Object -First 1 $os = Get-CimInstance Win32_OperatingSystem $totalMemory = [double]$os.TotalVisibleMemorySize $freeMemory = [double]$os.FreePhysicalMemory $memoryPercent = if ($totalMemory -gt 0) { (($totalMemory - $freeMemory) / $totalMemory) * 100 } else { 0 } $disk = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'" $diskPercent = if ($disk -and [double]$disk.Size -gt 0) { (([double]$disk.Size - [double]$disk.FreeSpace) / [double]$disk.Size) * 100 } else { 0 } $network = Get-CimInstance Win32_PerfFormattedData_Tcpip_NetworkInterface | Where-Object { $_.Name -notmatch "Loopback|isatap|Teredo" -and ([double]$_.BytesReceivedPersec -gt 0 -or [double]$_.BytesSentPersec -gt 0) } $rxBytes = 0.0 $txBytes = 0.0 foreach ($adapter in $network) { $rxBytes += [double]$adapter.BytesReceivedPersec $txBytes += [double]$adapter.BytesSentPersec } return [ordered]@{ cpu = Get-Percent ([double]$cpu.PercentProcessorTime) mem = Get-Percent $memoryPercent disk = Get-Percent $diskPercent rx = [int][math]::Max(0, [math]::Round($rxBytes / 1024)) tx = [int][math]::Max(0, [math]::Round($txBytes / 1024)) } } function Get-MetricsLine { $m = Get-SystemMetrics return "SYS,$($m.cpu),$($m.mem),$($m.disk),$($m.rx),$($m.tx)" } function Send-Response { param( [System.Net.Sockets.TcpClient]$Client, [string]$Status, [string]$ContentType, [string]$Body ) $bodyBytes = [System.Text.Encoding]::UTF8.GetBytes($Body) $header = "HTTP/1.1 $Status`r`nContent-Type: $ContentType; charset=utf-8`r`nContent-Length: $($bodyBytes.Length)`r`nConnection: close`r`nCache-Control: no-store`r`n`r`n" $headerBytes = [System.Text.Encoding]::ASCII.GetBytes($header) $stream = $Client.GetStream() $stream.Write($headerBytes, 0, $headerBytes.Length) $stream.Write($bodyBytes, 0, $bodyBytes.Length) $stream.Flush() } $listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Any, $Port) $listener.Start() Write-Host "PC metrics HTTP server listening on 0.0.0.0:$Port" Write-Host "Text endpoint: /metrics.txt" Write-Host "JSON endpoint: /metrics.json" try { while ($true) { $client = $listener.AcceptTcpClient() try { $stream = $client.GetStream() $buffer = New-Object byte[] 2048 $read = $stream.Read($buffer, 0, $buffer.Length) $request = [System.Text.Encoding]::ASCII.GetString($buffer, 0, $read) $firstLine = ($request -split "`r?`n")[0] $path = "/" if ($firstLine -match "^[A-Z]+\\s+([^\\s]+)") { $path = $Matches[1] } if ($path -eq "/health") { Send-Response $client "200 OK" "text/plain" "ok`n" } elseif ($path -eq "/metrics.json") { $metrics = Get-SystemMetrics Send-Response $client "200 OK" "application/json" (($metrics | ConvertTo-Json -Compress) + "`n") } elseif ($path -eq "/" -or $path -eq "/metrics.txt") { Send-Response $client "200 OK" "text/plain" ((Get-MetricsLine) + "`n") } else { Send-Response $client "404 Not Found" "text/plain" "not found`n" } } catch { try { Send-Response $client "500 Internal Server Error" "text/plain" "error`n" } catch { } } finally { $client.Close() } } } finally { $listener.Stop() }