サイトやVPSの稼働を自分で見張る|PowerShellでHTTPの応答をCSVに記録する

VPSやWordPressのサイトが「今つながっているか」「どのくらいの時間で返ってくるか」は、監視サービスに登録しなくても、PowerShellのスクリプトを1つ書けば、自分のPCで記録できます。この記事では、URLに一定間隔でGETを送り、結果をCSVに残して、稼働率と応答時間の中央値・最大値を出すスクリプトを、実際に動かした結果とあわせて載せます。

(確認:筆者のWindows 11で、Windows PowerShell 5.1とPowerShell 7の両方で実行。対象は筆者のサイトのトップページに、5秒間隔で12回のGETです。短時間の試験なので、長期の稼働率を示すものではありません)

何を記録するか

  • 時刻・URL・HTTPステータス・応答時間(ミリ秒)を、1回ごとに1行のCSVに追記する
  • ステータスが200〜399なら「成功」、0は接続できない・名前が引けない・タイムアウト、404や500はそのまま記録する
  • 最後に、回数・成功数・稼働率・応答時間の中央値と最大値を表示する

スクリプト(Watch-Http.ps1)

# 指定したURLに一定間隔でGETを送り、結果をCSVに追記して、最後に稼働率と応答時間の中央値・最大値を出す
# 日本語のコメントを入れるので、Windows PowerShell 5.1 で動かすには UTF-8(BOM付き)で保存する
param(
    [string]$Url = "https://realine.net/",
    [int]$Count = 12,          # 回数
    [int]$IntervalSec = 5,     # 間隔(秒)
    [int]$TimeoutSec = 10,     # 1回あたりのタイムアウト
    [string]$Csv = "$PSScriptRoot\watch_http.csv"
)
Add-Type -AssemblyName System.Net.Http
$client = New-Object System.Net.Http.HttpClient
$client.Timeout = [TimeSpan]::FromSeconds($TimeoutSec)
$client.DefaultRequestHeaders.CacheControl = New-Object System.Net.Http.Headers.CacheControlHeaderValue
$client.DefaultRequestHeaders.CacheControl.NoCache = $true
if (-not (Test-Path $Csv)) { "time,url,status,ms" | Out-File -FilePath $Csv -Encoding utf8 }
$results = @()
for ($i = 1; $i -le $Count; $i++) {
    $sw = [System.Diagnostics.Stopwatch]::StartNew()
    try {
        $resp = $client.GetAsync($Url).GetAwaiter().GetResult()   # 404や500でも例外にならず、ステータスが返る
        $status = [int]$resp.StatusCode
        $null = $resp.Content.ReadAsStringAsync().GetAwaiter().GetResult()
        $resp.Dispose()
    } catch {
        $status = 0            # 0 = 接続できない・名前が引けない・タイムアウト
    }
    $sw.Stop()
    $ms = [int]$sw.ElapsedMilliseconds
    $now = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    "$now,$Url,$status,$ms" | Out-File -FilePath $Csv -Append -Encoding utf8
    $results += [pscustomobject]@{ Status = $status; Ms = $ms }
    if ($i -lt $Count) { Start-Sleep -Seconds $IntervalSec }
}
$client.Dispose()
$ok = @($results | Where-Object { $_.Status -ge 200 -and $_.Status -lt 400 }).Count
$sorted = @($results.Ms | Sort-Object)
$median = $sorted[[math]::Floor(($sorted.Count - 1) / 2)]
"回数: $Count  成功: $ok  稼働率: {0:P1}  中央値: {1}ms  最大: {2}ms" -f ($ok / $Count), $median, ($sorted | Select-Object -Last 1)

日本語のコメントを入れたファイルは、UTF-8(BOM付き)で保存してください。BOMがないと、Windows PowerShell 5.1では日本語が文字化けして、スクリプトが動きません(筆者の環境で実際に構文エラーになりました)。

実行例

.\Watch-Http.ps1 -Url "https://example.com/" -Count 12 -IntervalSec 5

筆者のサイトで実行した結果は、次のとおりです。

実行環境 回数 成功 稼働率 中央値 最大
Windows PowerShell 5.1 12 12 100.0% 298ミリ秒 788ミリ秒
PowerShell 7 12 12 100.0% 297ミリ秒 473ミリ秒

Windows PowerShell 5.1では1回目だけ788ミリ秒と遅く、ほかの回は264〜514ミリ秒でした。PowerShell 7は最大473ミリ秒です。1回目が遅い原因は調べていません。ここでの応答時間は、ヘッダーだけでなく、本文を最後まで読み終えるまでの時間です。ヘッダーが返るまでの時間(TTFB)だけを測る方法はcurlでTTFBを10回測る方法にあります。

中央値は、回数が偶数の時は、小さい方から数えて真ん中の2つのうち下側を使っています(通常の中央値とは、0.5〜1ミリ秒ずれる場合があります)。

失敗した時の記録も確かめた

対象 記録されたステータス
存在しないページ(404) 404(応答時間は約300ミリ秒)
存在しないドメイン 0(名前が引けず、約0〜120ミリ秒で失敗)

Invoke-WebRequestを使わなかった理由

最初は Invoke-WebRequest で書きました。ところが、筆者の環境のWindows PowerShell 5.1では、存在しないページ(404)を取得した時に、例外にレスポンスが入らず、ステータスが取れませんでした(「接続が予期せずに閉じられました」という例外になりました)。JSONを返すAPIの404では取れたため、サーバーの応答の形に依存するようです。原因までは調べていません。そこで、404や500でも例外にならずステータスを返す HttpClient に変えました。

使う時の注意

  • 間隔を短くしすぎない。他人のサイトに連続してアクセスするのは迷惑になります。自分のサイトか、許可されたサイトだけにします
  • ブラウザのキャッシュは使わないので、毎回サーバーに取りに行く(Cache-Control: no-cacheを付けています)。CDNやサーバーのキャッシュは残るため、キャッシュを通さない速度は別に測ります
  • 自宅のPCから測る値には、自宅の回線の状態も含まれる。サーバーだけの問題かどうかは、別の場所からも測って確かめる
  • PCが止まっている間は記録されない。常時の監視が必要なら、別のサーバーや監視サービスを使う

この記事で確かめていないこと

  • 数時間〜数日の長期の記録(今回は12回・約1分間のみ)
  • 他のVPS・サーバー会社のサイトでの動作
  • HTTPSの証明書の期限の確認(別の方法が必要です)

VPSの契約期間ごとの料金はXServer VPSの契約期間別の総額、鍵でのログインはSSH鍵の作り方にまとめています。

まとめ

  • PowerShellの HttpClient で、時刻・ステータス・応答時間をCSVに残し、稼働率と中央値・最大値を出せる
  • 筆者のサイトで12回測ると、稼働率100.0%・中央値は約300ミリ秒だった(短時間の試験)
  • Windows PowerShell 5.1の Invoke-WebRequest は、一部の404でステータスが取れなかったため、HttpClient を使った
  • 日本語のコメントを入れたスクリプトは、UTF-8(BOM付き)で保存する

本記事は記録の取り方の説明です。他人のサイトへ連続してアクセスしないでください。