かつて山市良と呼ばれたおじさんのブログ

セイテクエンジニアのブログ  かつて山市良と呼ばれたおじさんのブログ  メモ. パフォーマンスログを毎日集計してCSVに蓄積する|セミナーフォローアップ

 

 

メモ. パフォーマンスログを毎日集計してCSVに蓄積する|セミナーフォローアップ

2026年06月05日配信
執筆者:山内 和朗

 2026年5月20日(水)に開催された「セイテク・シス管道場 第7回 Windows Serverパフォーマンス監視の基本と新常識」のフォローアップ記事です。セミナー中はチラ見せしかしなかったパフォーマンスログの集計のCSVファイル出力について。また、ご質問をいただいたパフォーマンス監視のパフォーマンスへの影響についても。

※本セミナーのアーカイブビデオの視聴が可能になりました。
【アーカイブ】セイテク・シス管道場 第7回 Windows Server パフォーマンス監視の基本と新常識~正しく測る、正しく判断するための実践ポイント~

 

データコレクターセットのテンプレートと集計用スクリプト

 

 連載のvol.205vol.207では、それぞれサーバーで監視するべき一般的なパフォーマンスカウンターと、Hyper-Vホストで監視するべきパフォーマンスカウンターのパフォーマンスデータの収集に利用できる、データコレクターセットのテンプレート「dcs_HostLog.xml」および「dcs_HyperVHostLog.xml」と、1日分のログデータを集計するスクリプト「samplescript_HostLog.ps1」および「samplescript_Hyper-VHostLog.ps1」(スクリプトのファイル名は今回付けました)を紹介しました。

 もう一度振り返ると、各テンプレートはそれぞれPowerShellで次のコマンドラインを実行することでシステムにインポートし、開始できます(.xmlがカレントディレクトリにあることを想定)。これらのテンプレートは、パフォーマンスカウンターを15秒間隔で収集し、24時間ごとにログファイルを切り替え、各ログファイルは30日後に削除する設定が含まれます。3つ目のコマンドライン(Set-ScheduledTask)は、OSの再起動後にデータコレクターセットを自動開始するための設定です。データコレクターセットを作成すると、タスクスケジューラの「¥Microsoft¥Windows¥PLA¥」にタスクが作成され、タスクによって実行が管理されます。そのタスクに「システム起動時」トリガーを設定することで、システム起動時に自動開始するようにするのです(画面1)。

一般的なパフォーマンスカウンター:

logman import -name "HostLog" -xml ".\dcs_HostLog.xml"
logman start "HostLog"
Set-ScheduledTask -TaskPath "\Microsoft\Windows\PLA\" -TaskName "HostLog" `
  -Trigger (New-ScheduledTaskTrigger -AtStartup) 


Hyper-V向けパフォーマンスカウンター:

logman import -name "Hyper-VHostLog" -xml ".\dcs_Hyper-VHostLog.xml"
logman start "Hyper-VHostLog"
Set-ScheduledTask -TaskPath "\Microsoft\Windows\PLA\" -TaskName "Hyper-VHostLog" ` 
  -Trigger (New-ScheduledTaskTrigger -AtStartup) 

 

画面1 データコレクターセットをインポートし、開始する。再起動しても自動開始するようにタスクのトリガーを設定する
画面1 データコレクターセットをインポートし、開始する。再起動しても自動開始するようにタスクのトリガーを設定する

 1日分のログデータを集計するスクリプト「samplescript_HostLog.ps1」および「samplescript_Hyper-VHostLog.ps1」は、結果をコンソールに順番に出力します(画面2)。

 

画面2 過去1日のログデータを集計して、パフォーマンスカウンターごと、インスタンスごと(インスタンスがある場合)に、平均、最大、最小値を出力
画面2 過去1日のログデータを集計して、パフォーマンスカウンターごと、インスタンスごと(インスタンスがある場合)に、平均、最大、最小値を出力

 [dcs_HostLog.xml]プレーンテキスト《UTF-8 BOM付き》で表示)
 [dcs_Hyper-VHostLog.xml] プレーンテキスト《UTF-8 BOM付き》で表示)
 [samplescript_HostLog.ps1] プレーンテキスト《UTF-8 BOM付き》で表示)
 [samplescript_Hyper-VHostLog.ps1] プレーンテキスト《UTF-8 BOM付き》で表示)

 

集計用スクリプトのCSV出力版

 

 次の2つのスクリプト「samplescript_HostLogToCSV.ps1」および「samplescript_HostLogToCSV.ps1」は、コンソールではなく、CSVファイルに出力します(コンソールには出力先のパスを表示します)。先日のセミナーでお見せしたCSVファイルを出力するために使用したスクリプトです。ログファイルは、パフォーマンスカウンターごとに、パフォーマンスカウンターの*、¥、/、スペースを_に置き換えたもので、ログファイルと同じ場所にExport-CSVコマンドレットを使用してCSVファイルとして出力します(既に存在する場合は追記《-Append》します)。

 1日分のログデータの集計は実行に少し時間がかかります。これらのスクリプトをタスクスケジューラなどで1日1回実行するようにスケジュールしておけば、毎日の集計データをログに蓄積することができます(画面3、画面4)。

[samplescript_HostLogToCSV.ps1]プレーンテキスト《UTF-8 BOM付き》で表示)

#●ホストパフォーマンスログの集計(CSV出力版)

#ログファイルのパスとインターバルは適宜変更してください。
$logpath = "C:\perflogs\Admin\HostLog\*.blg"
$intervalhr = 24

$since = (Get-Date).AddHours(-1*$intervalhr)
$logs = Get-ChildItem $logpath |
    Where-Object { $_.LastWriteTime -ge $since } |
    Sort-Object LastWriteTime -Descending

$counters = @()
foreach ($log in $logs) {
  $ret = $null
  if((get-item($log)).Length -gt 131072) {
    $ret = Import-Counter -Path $log.FullName -ErrorAction SilentlyContinue | Where-Object { $_.TimeStamp -ge $since } | Select-Object -Expand CounterSamples
    if ($ret -and $ret.Count -gt 0) {
      $counters += $ret
    }
  }
}

$perfcounters = @(
  "\Processor Information(*)\% Processor Time",
  "\Memory\Available MBytes",
  "\Memory\Pages/sec",
  "\Memory\Pool Nonpaged Bytes",
  "PhysicalDisk(*)\% Idle Time",
  "PhysicalDisk(*)\Avg. Disk sec/Read",
  "PhysicalDisk(*)\Avg. Disk sec/Write",
  "PhysicalDisk(*)\Disk Read Bytes/sec",
  "PhysicalDisk(*)\Disk Write Bytes/sec",
  "PhysicalDisk(*)\Disk Reads/sec",
  "PhysicalDisk(*)\Disk Writes/sec",
  "LogicalDisk(*)\% Idle Time",
  "LogicalDisk(*)\Avg. Disk sec/Read",
  "LogicalDisk(*)\Avg. Disk sec/Write",
  "LogicalDisk(*)\Disk Read Bytes/sec",
  "LogicalDisk(*)\Disk Write Bytes/sec",
  "LogicalDisk(*)\Disk Reads/sec",
  "LogicalDisk(*)\Disk Writes/sec",
  "Network Interface(*)\Bytes Total/sec",
  "Network Interface(*)\Output Queue Length",
  "Network Interface(*)\Packets Outbound Errors",
  "Network Interface(*)\Packets Received Errors"
)

foreach ($perfcounter in $perfcounters) {
  $usage = $counters | Where-Object {$_.Path -like "*$perfcounter*"
  } |
  ForEach-Object {
    if ($_.TimeStamp -ge $since ) {
      [PSCustomObject]@{
        Time = $_.TimeStamp
        Instance = $_.InstanceName
        Value  = $_.CookedValue
      }
    }
  }
  $result = $usage | Group-Object { $_.Time.ToString("yyyy-MM-dd HH:00-59") },Instance |
  ForEach-Object {
    $values = $_.Group.Value
    [PSCustomObject]@{
        Hour = $_.Name
        Avg  = [math]::Round(($values | Measure-Object -Average).Average,2)
        Max  = [math]::Round(($values | Measure-Object -Maximum).Maximum,2)
        Min  = [math]::Round(($values | Measure-Object -Minimum).Minimum,2)
    }
  }
  #$perfcounter
  #$result | Sort-Object Hour | Format-Table -AutoSize
  if ($result.Count -gt 0 ) {
    $outDir = Split-Path $logpath
    $filename = $perfcounter `
      -replace '\(\*\)', '(_)' `
      -replace '[\\/]', '_' `
      -replace '\s+', '_'
    $filename = $filename.Trim('_')
    $outFile = Join-Path $outDir "$filename.csv"
    $result | Export-Csv -Path $outFile -NoTypeInformation -Append:$(Test-Path $outFile)
    Write-Host "Export-CSV to $($outFile)" 
  }
}
[samplescript_Hyper-VHostLogToCSV.ps1]プレーンテキスト《UTF-8 BOM付き》で表示)
#●Hyper-Vホストパフォーマンスログの集計(CSV出力版)

#ログファイルのパスとインターバルは適宜変更してください。
$logpath = "C:\perflogs\Admin\Hyper-VHostLog\*.blg"
$intervalhr = 24

$since = (Get-Date).AddHours(-1*$intervalhr)
$logs = Get-ChildItem $logpath |
    Where-Object { $_.LastWriteTime -ge $since } |
    Sort-Object LastWriteTime -Descending

$counters = @()
foreach ($log in $logs) {
  $ret = $null
  if((get-item($log)).Length -gt 131072) {
    $ret = Import-Counter -Path $log.FullName -ErrorAction SilentlyContinue | Where-Object { $_.TimeStamp -ge $since } | Select-Object -Expand CounterSamples
    if ($ret -and $ret.Count -gt 0) {
      $counters += $ret
    }
  }
}

$perfcounters = @(
  "\Hyper-V Hypervisor Logical Processor(*)\% Total Run Time",
  "\Hyper-V Hypervisor Virtual Processor(*)\% Total Run Time",
  "\Hyper-V Hypervisor Root Virtual Processor(*)\% Total Run Time"
  "\Processor(*)\% DPC Time",
  "\Processor(*)\% Interrupt Time",
  "\Memory\Available MBytes",
  "\Hyper-V Dynamic Memory Balancer(*)\Available Memory",
  "\Network Interface(*)\Bytes Total/sec",
  "\Network Interface(*)\Output Queue Length",
  "\Hyper-V Virtual Network Adapter(*)\Bytes/sec",
  "\PhysicalDisk(*)\Avg. Disk sec/Read",
  "\PhysicalDisk(*)\Avg. Disk sec/Write",
  "\PhysicalDisk(*)\Avg. Disk Read Queue length",
  "\PhysicalDisk(*)\Avg. Disk Write Queue Length",
  "\LogicalDisk(*)\Avg. Disk sec/Read",
  "\LogicalDisk(*)\Avg. Disk sec/Write",
  "\LogicalDisk(*)\Avg. Disk Read Queue length",
  "\LogicalDisk(*)\Avg. Disk Write Queue Length"  
)

foreach ($perfcounter in $perfcounters) {
  $usage = $counters | Where-Object {$_.Path -like "*$perfcounter*"
  } |
  ForEach-Object {
    if ($_.TimeStamp -ge $since ) {
      [PSCustomObject]@{
        Time = $_.TimeStamp
        Instance = $_.InstanceName
        Value  = $_.CookedValue
      }
    }
  }
  $result = $usage | Group-Object { $_.Time.ToString("yyyy-MM-dd HH:00-59") },Instance |
  ForEach-Object {
    $values = $_.Group.Value
    [PSCustomObject]@{
        Hour = $_.Name
        Avg  = [math]::Round(($values | Measure-Object -Average).Average,2)
        Max  = [math]::Round(($values | Measure-Object -Maximum).Maximum,2)
        Min  = [math]::Round(($values | Measure-Object -Minimum).Minimum,2)
    }
  }
  #$perfcounter
  #$result | Sort-Object Hour | Format-Table -AutoSize
  if ($result.Count -gt 0 ) {
    $outDir = Split-Path $logpath
    $filename = $perfcounter `
      -replace '\(\*\)', '(_)' `
      -replace '[\\/]', '_' `
      -replace '\s+', '_'
    $filename = $filename.Trim('_')
    $outFile = Join-Path $outDir "$filename.csv"
    $result | Export-Csv -Path $outFile -NoTypeInformation -Append:$(Test-Path $outFile)
    Write-Host "Export-CSV to $($outFile)" 
  }
}

 

 画面3 CSV出力用のスクリプトを1日に1回のスケジュールタスクで自動実行させる
画面3 CSV出力用のスクリプトを1日に1回のスケジュールタスクで自動実行させる

 

画面4 出力されたCSVファイルをExcelで加工したもの。この方法で1週間や1か月、3か月など長期間のパフォーマンスデータの分析が可能に
画面4 出力されたCSVファイルをExcelで加工したもの。この方法で1週間や1か月、3か月など長期間のパフォーマンスデータの分析が可能に

 

パフォーマンス収集のパフォーマンスへの影響は?

 

 セミナーでいただいたご質問への回答を改めて残しておきます。パフォーマンスを監視することの、システム負荷への影響についてです。

 

 Windowsパフォーマンス カウンターは、管理/診断データの検出と収集向けに最適化されており、通常の運用監視レベルでは非常に軽量です。一般的な15~60秒間隔でプロセッサ、メモリ、ディスク、ネットワークの主要なカウンターを監視し、バイナリ形式のログ(.blg)に保存する程度なら、システム負荷への影響は無視できるレベルであり、まったく心配しなくていいでしょう(画面5)。ただし、バイナリ形式のログ取得は通常軽量ですが、大量のカウンターを高頻度で収集する場合は、プロセッサ使用率やディスクI/Oに影響を与える可能性があります。

 

画面5 試しに、CPU、メモリ、ディスク、ネットワークの主要なカウンターを15秒間隔で収集するデータコレクターセットの停止時の実行時の、CPU使用率やディスク転送を計測してみたが、両者にデータコレクターセットの実行を理由とした差はまったく見られなかった

画面5 試しに、CPU、メモリ、ディスク、ネットワークの主要なカウンターを15秒間隔でログ(.blg)に収集するデータコレクターセット実行時と停止時の、CPU使用率やディスク転送を計測してみたが、両者にデータコレクターセットの実行を理由とした差はまったく見られなかった

 プロセッサやメモリのカウンターは、カーネル内部カウンターを参照しているため軽量であり短い間隔で収集しても影響は無視できます。ディスクやネットワーク、プロセスのカウンターは、監視対象のインスタンス数やカウンターが多く、監視間隔が短いと、システムの負荷に影響する可能性があります。また、例えば¥LogicalDisk(*)や¥Network Interface(*)、¥Process(*)のカウンターを短い間隔で収集する場合、毎回インスタンス列挙が発生し、負荷が高くなるおそれがあります。

 なお、これはパフォーマンスカウンターをパフォーマンスモニターやTYPEPERFコマンド、Get-Counterコマンドレット(Perfmon、PDH API)で収集する場合の話です。パフォーマンスカウンターはWMIを使用して取得することもできますが(例、Get-CimInstance Win32_PerfFormattedData_* )、WMI経由の取得(既に、非推奨)は管理情報モデル経由で抽象化して取得することになるため、処理が重くなります。監視ツールの監視エージェントを使用してパフォーマンスカウンターを収集する場合、監視エージェント自身の負荷の影響が大きいと思います。

 

関連:

お知らせ. 5月20日(水)開催! 第7回 セイテク・シス管道場 ーパフォーマンス監視の基本と新常識

 

blog_yamanxworld_subscribe

blog_yamanxworld_comment

blog_yamanxworld_WP_ws2025

最新記事