【実務・中級編】【高負荷プロセス検知】WMI パフォーマンスデータ(Win32_PerfFormattedData)を活用したリソース監視 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【高負荷プロセス検知】WMIパフォーマンスデータ(Win32_PerfFormattedData)を活用したリソース監視ロジックの極意

開発現場において、常駐バッチやレガシーシステムが突然暴走し、サーバーのCPU使用率が100%に張り付くインシデントほど冷や汗をかくものはない。
「何がリソースを食いつぶしているのか分からないままサーバーが応答しなくなった」——そんな悪夢を防ぐため、今回はVBScriptとWMI(Windows Management Instrumentation)を駆使し、高負荷プロセスをピンポイントで自動検知・特定する堅牢な監視システムの構築手法を伝授する。

ネット上の散見されるサンプルコードの多くは、動的生成の罠やエラーハンドリングの欠如により、実運用の負荷に耐えられずスクリプト自体がメモリリークを起こす。
本記事では、プロのアーキテクトが現場で実践している「落ちない・詰まらない・正確な」VBScript設計の全貌を公開する。

1. なぜ「Win32_PerfFormattedData」なのか? 設計思想の核心

プロセス監視において、よく見かけるのが `Win32_Process` から情報を取得するアプローチだ。しかし、これには致命的な欠点がある。`Win32_Process` はプロセスの「存在」と「基本情報」しか持っておらず、CPU使用率の経時変化を取得できない

生データ(RawData)と整形済みデータ(FormattedData)の罠

WMIのパフォーマンスカウンタには、以下の2種類が存在する。

  • `Win32_PerfRawData_PerfProc_Process` (生データ)
  • `Win32_PerfFormattedData_PerfProc_Process` (整形済みデータ)

生データ(RawData)を扱う場合、CPU使用率を算出するためには「前回取得時のカウンタ値」と「現在のカウンタ値」、さらに「タイムスタンプの差分」を自前で計算しなくてはならない。VBScriptの貧弱な演算能力と浮動小数点処理の癖を考慮すると、これはバグの温床となる。

一方、`Win32_PerfFormattedData_PerfProc_Process` を利用すれば、OS側(WMIプロバイダ)がすでに計算を終えたパーセンテージデータを直接取得できる。我々はただ、閾値を超えているか判定するだけでよいのだ。

2. 堅牢な監視ロジック設計の3大原則

実運用に耐えうるスクリプトを書くためには、以下の3点を徹底しなければならない。

1. 動的クエリの排除と適切なオブジェクト開放(`Set obj = Nothing`)
VBScriptのCOMコンポーネントはGC(ガベージコレクション)が非力である。ループ内でWMIクエリを乱発すると、確実にメモリリークを引き起こす。
2. アクセス拒否(Access Denied)やプロセス消滅への耐性
監視対象のプロセスは、スクリプトが実行されている最中にも起動・終了を繰り返す。存在しないプロセスへのアクセスでスクリプトが異常終了(クラッシュ)しては監視システムとして失格である。
3. ログ出力とトレーサビリティ
単に検知して終了するのではなく、「いつ、どのプロセスが、どの程度の負荷をかけたのか」をファイルシステムやイベントログに正確に残すアーキテクチャが必須である。

3. 【プロダクションコード】高負荷プロセス検知・通知スクリプト

以下のコードは、指定したCPU使用率の閾値(例: 80%)を一定時間(または特定回数のポーリング)超え続けたプロセスを特定し、ログ出力を行う実用スクリプトである。そのまま `.vbs` ファイルとして保存して利用できる。

‘ ==============================================================================
‘ Script Name: ProcessResourceMonitor.vbs
‘ Description: WMIを使用した高負荷プロセス検知・特定システム
‘ Author : Enterprise Architecture Team
‘ ==============================================================================
Option Explicit

‘ — 定数定義 —
Const THRESHOLD_CPU = 80 ‘ CPU使用率の閾値 (%)
Const LOG_FILE_PATH = “C:\Logs\ProcessMonitor_Alert.log”
Const POLLING_INTERVAL = 5000 ‘ 監視インターバル (ミリ秒: 5秒)

‘ メイン処理の実行
Call Main()

Sub Main()
Dim objWMIService, colProcesses, objProcess
Dim strComputer, wmiQuery
Dim fso, logFile

strComputer = “.”

‘ ファイルシステムオブジェクトの初期化(ログ出力用)
Set fso = CreateObject(“Scripting.FileSystemObject”)
Call EnsureLogDirectory(fso, LOG_FILE_PATH)

‘ WMI接続の確立(ネームスペースの指定)
On Error Resume Next
Set objWMIService = GetObject(“winmgmts:\\” & strComputer & “\root\cimv2”)
If Err.Number <> 0 Then
Call WriteLog(fso, “CRITICAL: WMIサービスへの接続に失敗しました. Error: ” & Err.Description)
WScript.Quit 1
End If
On Error GoTo 0

‘ 無限ループによる常時監視(実運用ではタスクスケジューラやサービス化を推奨)
Do
On Error Resume Next

‘ 暴走プロセスを特定するためのWMIクエリ
‘ ※ IdleやSystemプロセスなど、CPUを大量消費する特権プロセスを除外する場合は WHERE 句を調整
wmiQuery = “SELECT IDProcess, Name, PercentProcessorTime, WorkingSetPrivate FROM Win32_PerfFormattedData_PerfProc_Process WHERE PercentProcessorTime > ” & THRESHOLD_CPU & ” AND Name <> ‘_Total’ AND Name <> ‘Idle'”

Set colProcesses = objWMIService.ExecQuery(wmiQuery, , 48)

If Err.Number <> 0 Then
‘ クエリ実行時の一時的なエラーはスルーして次回のポーリングへ
Err.Clear
Else
If colProcesses.Count > 0 Then
For Each objProcess in colProcesses
‘ 検出時の詳細情報をログに記録
Dim logMessage
logMessage = “WARNING [High CPU Detected] ” & _
“Process: ” & objProcess.Name & ” ” & _
“(PID: ” & objProcess.IDProcess & “) | ” & _
“CPU Usage: ” & objProcess.PercentProcessorTime & “% | ” & _
“Memory (WorkingSet): ” & FormatNumber(objProcess.WorkingSetPrivate / 1024 / 1024, 2) & ” MB”

Call WriteLog(fso, logMessage)

‘ 【拡張ポイント】
‘ ここにメール通知ロジックや、強制終了ロジック(objProcess.Terminate など)を組み込むことが可能
Next
End If
End If

‘ コレクションの明示的な解放
Set colProcesses = Nothing

On Error GoTo 0

‘ 指定インターバル待機
WScript.Sleep POLLING_INTERVAL
Loop

‘ クリーンアップ(通常は無限ループのため到達しないが作法として記述)
Set objWMIService = Nothing
Set fso = Nothing
End Sub

‘ ——————————————————————————
‘ ログディレクトリの存在確認と自動作成
‘ ——————————————————————————
Sub EnsureLogDirectory(fso, filePath)
Dim parentPath
parentPath = fso.GetParentFolderName(filePath)
If Not fso.FolderExists(parentPath) Then
fso.CreateFolder(parentPath)
End If
End Sub

‘ ——————————————————————————
‘ ログファイルへの書き込み関数(排他制御・追記モード)
‘ ——————————————————————————
Sub WriteLog(fso, message)
Dim logFile, formattedMessage
formattedMessage = “[” & Now & “] ” & message

On Error Resume Next
‘ ForAppending = 8, Create = True
Set logFile = fso.OpenTextFile(LOG_FILE_PATH, 8, True)
If Err.Number = 0 Then
logFile.WriteLine formattedMessage
logFile.Close
End If
Set logFile = Nothing
On Error GoTo 0
End Sub

4. コードの解説とアーキテクトからの助言

1. 例外処理(`On Error Resume Next`)の正しい配置

WMIを扱うスクリプトにおいて、対象プロセスが突如消滅する「競合状態(Race Condition)」は日常茶飯事である。ループ全体を雑に囲むのではなく、WMIクエリの実行やログ書き込みなど、外部リソースに依存する不安定なブロック単位でエラーをハンドリングしている点に注目してほしい。これにより、一過性のエラーで監視システム自体が停止するリスクを排除している。

2. メモリリークを防ぐためのオブジェクト管理

VBScriptの `For Each` ループ内で取得したCOMオブジェクトは、スコープを抜けるまでメモリ上に残り続けることがある。特に長期間稼働させる監視スクリプトでは、ループの最後で `Set colProcesses = Nothing` を明示的に呼び出し、WMIプロバイダとのセッションやメモリをこまめに解放することが極めて重要である。

3. 実運用におけるデプロイの注意点

  • 権限の問題: `Win32_PerfFormattedData` から正確なパフォーマンスデータを取得するためには、スクリプトを管理者権限(Administrator)で実行する必要がある。
  • 実行環境: このスクリプトを常時走らせる場合、CUI環境であれば `cscript.exe` を用いてバックグラウンド実行し、Windowsサービス化ツール(NSSMなど)やタスクスケジューラを活用してシステム起動時に自動フックさせるのがプロの現場の定石である。

総括

VBScriptはレガシーな言語と揶揄されることもあるが、OSの深部(WMIやCOM)へダイレクトにアクセスできるその身軽さは、Windows環境におけるインフラ自動化において今なお強力な武器となる。
今回紹介したパフォーマンスデータ監視ロジックをベースに、アラートメール送信機能や、暴走プロセスの自動セーフキル機能を拡張していけば、現場の運用コストを劇的に削減する強固な自製監視ツージェントが完成するはずだ。

設計の美しさと堅牢性を両立させ、トラブルに強いシステム基盤をあなたの手で構築してほしい。

タイトルとURLをコピーしました