Private/Probe.ps1
|
# How Gatherers reach the network. # # Both of these are Gatherer machinery: they talk to something outside this machine and # decide nothing about what comes back. They are shared because the Network Kind and the # Server connectivity Kind measure the same two things - can I reach it, and how long does # it take - and a Technician comparing a gateway against a database server is comparing # numbers that must have been produced the same way. # # Deliberately untested, like every Gatherer: faking a ping tests the fake. function Measure-Latency { <# .SYNOPSIS Pings a target repeatedly and reports what came back. Judges none of it. .DESCRIPTION Loss matters more than latency for the problems Gutcheck exists to find - a client that loses one packet in fifty hangs rather than slows - so a window of pings is sent rather than one, and the loss count is reported alongside the timings. #> [CmdletBinding()] [OutputType([psobject])] param( [Parameter(Mandatory)][string]$Target, [int]$Count = 20, [int]$TimeoutMs = 1000 ) $ping = New-Object System.Net.NetworkInformation.Ping try { $replies = for ($i = 0; $i -lt $Count; $i++) { # Collected as the replies, so the progress call must emit nothing - it doesn't. Write-CheckProgress -Status ((Get-Text 'Console.Progress.Ping') -f $Target) -Step $i -Of $Count try { $ping.Send($Target, $TimeoutMs) } catch { $null } Start-Sleep -Milliseconds 100 } } finally { $ping.Dispose() } $ok = @($replies | Where-Object { $_ -and $_.Status -eq 'Success' }) [pscustomobject]@{ PSTypeName = 'Gutcheck.Data.Latency' Target = $Target Sent = $Count Received = $ok.Count Lost = $Count - $ok.Count LossPercent = [math]::Round((1 - $ok.Count / [double]$Count) * 100) AverageMs = $(if ($ok.Count) { [math]::Round(($ok | Measure-Object RoundtripTime -Average).Average, 1) } else { $null }) MaximumMs = $(if ($ok.Count) { ($ok | Measure-Object RoundtripTime -Maximum).Maximum } else { $null }) } } function Measure-TcpConnect { <# .SYNOPSIS Opens a TCP connection and reports how long it took, or $null when it did not open. .DESCRIPTION A single connect is not evidence: a firewall that drops one connection in five is the thing that makes a business application feel unreliable, and one successful probe would report it as healthy. Callers connect repeatedly and judge the spread. #> [CmdletBinding()] [OutputType([double])] param( [Parameter(Mandatory)][string]$HostName, [Parameter(Mandatory)][int]$Port, [int]$TimeoutMs = 3000 ) $client = New-Object System.Net.Sockets.TcpClient try { $watch = [Diagnostics.Stopwatch]::StartNew() $task = $client.ConnectAsync($HostName, $Port) if ($task.Wait($TimeoutMs) -and $client.Connected) { return [math]::Round($watch.Elapsed.TotalMilliseconds, 1) } $null } catch { $null } finally { $client.Dispose() } } function Resolve-HostAddress { <# .SYNOPSIS Resolves a host name and reports how long it took. Reports failure; judges nothing. .DESCRIPTION Slow name resolution delays every connection an application makes, so the duration is as much of the answer as the address is. #> [CmdletBinding()] [OutputType([psobject])] param([Parameter(Mandatory)][string]$HostName) $watch = [Diagnostics.Stopwatch]::StartNew() try { $address = @([Net.Dns]::GetHostAddresses($HostName))[0] $watch.Stop() [pscustomobject]@{ PSTypeName = 'Gutcheck.Data.Resolution' HostName = $HostName Address = "$address" Resolved = $true Milliseconds = $watch.Elapsed.TotalMilliseconds } } catch { $watch.Stop() [pscustomobject]@{ PSTypeName = 'Gutcheck.Data.Resolution' HostName = $HostName Address = $null Resolved = $false Milliseconds = $null } } } |