C#でPerformance Countersを使う方法|取得・作成・監視の実装例と注意点

はじめに

C#でWindowsサーバーや業務アプリケーションのCPU使用率、メモリ使用量、プロセス単位の負荷、独自アプリケーションの処理件数などを監視したい場合、Performance Countersは今でも有効な選択肢です。特に、Windowsのパフォーマンスモニター、既存の監視基盤、.NET Frameworkアプリケーションと連携したい場面では、C#からPerformanceCounterクラスを使うことで、OSやアプリケーションの状態をコード上から取得・作成・監視できます。

ただし、Performance CountersはWindowsに依存する古い仕組みであり、.NET 6/7/8以降の新規開発ではSystem.Diagnostics.Metrics、EventCounters、OpenTelemetryなどの代替手段も検討する必要があります。PerformanceCounter APIはWindows専用で、主に互換性目的で提供されており、新規開発ではWindows Performance Counterツールを使いたい明確な理由がある場合に適しています。Microsoft Learn

1. C#のPerformance Countersとは

Performance Countersとは、Windowsが提供するパフォーマンス情報の収集機能です。CPU、メモリ、ディスク、ネットワーク、プロセス、アプリケーション固有の指標などを「カウンター」として公開し、WindowsのパフォーマンスモニターやC#プログラムから値を取得できます。

C#では、主にSystem.Diagnostics.PerformanceCounterSystem.Diagnostics.PerformanceCounterCategoryを使います。PerformanceCounterは個別のカウンター値の読み取りや書き込みに使い、PerformanceCounterCategoryはカテゴリの一覧取得、カスタムカテゴリの作成、削除、存在確認などに使います。Microsoft Learn+1

1-1. Performance Countersで取得できる情報

Performance Countersで取得できる代表的な情報には、次のようなものがあります。

種類代表的なカテゴリ代表的なカウンター
CPU使用率Processor% Processor Time
メモリ空き容量MemoryAvailable MBytes
プロセスCPU使用率Process% Processor Time
プロセスメモリ使用量ProcessWorking Set - Private
ディスクI/OPhysicalDiskDisk Reads/sec, Disk Writes/sec
独自アプリ指標任意のカスタムカテゴリ処理件数、キュー数、エラー数など

Windows Performance Countersは、CPU、メモリ、ディスク使用量などのシステムデータを収集するための一貫したインターフェースを提供し、OS標準のカウンターだけでなくアプリケーション独自のカウンターも扱えます。nuget

1-2. PerformanceCounterクラスの基本

PerformanceCounterクラスは、カテゴリ名、カウンター名、必要に応じてインスタンス名を指定して使用します。

C#
using System.Diagnostics;

using var counter = new PerformanceCounter(
categoryName: "Processor",
counterName: "% Processor Time",
instanceName: "_Total",
readOnly: true);

float value = counter.NextValue();
Console.WriteLine($"CPU使用率: {value}%");

Performance Counterは、次の3要素で対象を指定するのが基本です。

要素説明
カテゴリカウンターの分類Processor, Memory, Process
カウンター取得したい値% Processor Time, Available MBytes
インスタンス対象の個体_Total, chrome, dotnet

インスタンスが不要なカテゴリもあります。たとえばMemoryカテゴリのAvailable MBytesはシステム全体の値なので、インスタンス名を指定しません。一方、Processカテゴリはプロセスごとの値を扱うため、プロセスのインスタンス名が必要です。

1-3. 利用できる環境とWindows依存の注意点

Performance CountersはWindowsの機能です。そのため、LinuxやmacOSで同じコードを実行すると、PlatformNotSupportedExceptionや関連する実行時エラーが発生する可能性があります。

.NET 6/7/8以降でクロスプラットフォーム対応のアプリケーションを作る場合は、実行前にOSを判定しておくと安全です。

C#
if (!OperatingSystem.IsWindows())
{
Console.WriteLine("Performance CountersはWindowsでのみ利用できます。");
return;
}

PerformanceCounter APIはWindows OSのPerformance Counter技術に対するマネージド ラッパーであり、Windowsでのみサポートされています。Microsoft Learn

1-4. .NET Frameworkと.NET 6/7/8以降での違い

.NET Frameworkでは、System.Diagnostics名前空間のPerformanceCounterをそのまま利用するケースが一般的でした。一方、.NET Core、.NET 5以降、.NET 6/7/8以降では、System.Diagnostics.PerformanceCounterパッケージを追加して使うのが基本です。

Bash
dotnet add package System.Diagnostics.PerformanceCounter

このNuGetパッケージは、Windows performance countersと対話するための型を提供し、既存の定義済みカウンターやカスタムカウンターの読み取り、カスタムカウンターへの書き込み、ローカルまたはリモートマシンからの収集に対応しています。nuget

.NET 6/7/8以降で新規にメトリクス基盤を設計する場合は、Performance Countersだけでなく、System.Diagnostics.MetricsやOpenTelemetryも検討すべきです。System.Diagnostics.Metricsは.NET 6以降で利用できる新しいクロスプラットフォームAPIで、OpenTelemetryとの連携も前提に設計されています。Microsoft Learn

2. C#でPerformance Countersを使う準備

C#でPerformance Countersを使うには、対象環境、パッケージ、権限、カテゴリ名やカウンター名を事前に確認しておく必要があります。特に本番環境では、開発PCでは動くのにサーバーでは権限やカウンター名の違いで失敗するケースがあります。

2-1. 必要な名前空間とNuGetパッケージ

.NET Frameworkの場合は、次の名前空間を使います。

C#
using System.Diagnostics;

.NET 6/7/8以降のプロジェクトでは、必要に応じてNuGetパッケージを追加します。

Bash
dotnet add package System.Diagnostics.PerformanceCounter

プロジェクトファイルでWindows対象を明確にしたい場合は、次のようにnet8.0-windowsを指定する方法もあります。

XML
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

クロスプラットフォーム対象のプロジェクトに含める場合は、OperatingSystem.IsWindows()で分岐するか、Windows専用の処理として分離しておくと保守しやすくなります。

2-2. 管理者権限が必要になるケース

Performance Countersでは、次のような操作で管理者権限が必要になることがあります。

操作管理者権限の必要性
既存カウンターの読み取り環境や対象カウンターにより必要
カスタムカテゴリの作成多くの場合必要
カスタムカテゴリの削除多くの場合必要
リモートマシンのカウンター取得権限設定が必要
Windowsサービスからの監視実行アカウントの権限設計が必要

Microsoftのドキュメントでは、Performance Counterへアクセスするコードを実行するには、標準ユーザーから管理者へ昇格する必要がある旨が説明されています。特にUACが有効なWindowsでは、Administratorsグループのユーザーであっても、管理者として明示的に実行しないと権限不足になる場合があります。Microsoft Learn

本番環境では、安易に管理者権限で常時実行するのではなく、監視専用ユーザーを用意し、必要な権限だけを付与する設計が重要です。

2-3. カテゴリ・カウンター・インスタンスの関係

Performance Countersを理解するうえで重要なのが、カテゴリ、カウンター、インスタンスの関係です。

カテゴリ
└─ カウンター
└─ インスタンス

たとえば、CPU全体の使用率を取得する場合は次のようになります。

カテゴリ: Processor
カウンター: % Processor Time
インスタンス: _Total

プロセスごとのCPU使用率を取得する場合は、次のようになります。

カテゴリ: Process
カウンター: % Processor Time
インスタンス: dotnet

ただし、同じ名前のプロセスが複数存在する場合、インスタンス名はdotnetdotnet#1dotnet#2のようになることがあります。そのため、プロセス名だけでなくプロセスIDと照合する実装が必要になる場合があります。

2-4. 事前に確認しておきたい実行環境

実装前に、次の点を確認しておくとトラブルを防ぎやすくなります。

確認項目内容
OSWindowsで実行するか
.NETバージョン.NET Frameworkか.NET 6/7/8以降か
実行権限管理者権限または監視に必要な権限があるか
カウンター名対象環境に存在するカテゴリ・カウンターか
プロセス構成同名プロセスが複数存在しないか
監視間隔高頻度すぎないか
保存先ログ、CSV、DB、監視基盤のどこに保存するか

OSの言語設定やインストール済みコンポーネントによって、利用できるカテゴリやカウンターが異なることがあります。実装前に、Windowsの「パフォーマンス モニター」または後述する一覧取得コードで確認しておくと安全です。

3. 既存のPerformance Countersから値を取得する方法

既存のPerformance Countersから値を取得する基本は、PerformanceCounterを作成してNextValue()を呼び出すだけです。ただし、CPU使用率のように差分計算が必要なカウンターでは、初回値が0になる点に注意が必要です。

3-1. CPU使用率を取得する実装例

システム全体のCPU使用率を取得する例です。

C#
using System.Diagnostics;

if (!OperatingSystem.IsWindows())
{
Console.WriteLine("このサンプルはWindows専用です。");
return;
}

using var cpuCounter = new PerformanceCounter(
categoryName: "Processor",
counterName: "% Processor Time",
instanceName: "_Total",
readOnly: true);

// 初回値は0になることがあるため、いったん読み捨てる
cpuCounter.NextValue();

await Task.Delay(1000);

float cpuUsage = cpuCounter.NextValue();

Console.WriteLine($"CPU使用率: {cpuUsage:F2}%");

Processorカテゴリの% Processor Timeカウンターは、指定したプロセッサインスタンスの使用率を取得する代表的なカウンターです。_Totalを指定すると、システム全体のCPU使用率を取得できます。

3-2. メモリ使用量を取得する実装例

空きメモリを取得するには、MemoryカテゴリのAvailable MBytesを使います。

C#
using System.Diagnostics;

if (!OperatingSystem.IsWindows())
{
Console.WriteLine("このサンプルはWindows専用です。");
return;
}

using var availableMemoryCounter = new PerformanceCounter(
categoryName: "Memory",
counterName: "Available MBytes",
readOnly: true);

float availableMBytes = availableMemoryCounter.NextValue();

Console.WriteLine($"利用可能メモリ: {availableMBytes:F0} MB");

この値は「空きメモリがどの程度残っているか」を見るのに便利です。メモリ使用率として扱いたい場合は、総メモリ量と組み合わせて計算するか、監視基盤側で割合に変換します。

3-3. プロセス別のカウンター値を取得する実装例

プロセス単位の値を取得する場合は、Processカテゴリを使います。次の例では、指定したプロセスIDに対応するPerformance Counterのインスタンス名を探し、そのプロセスのCPU使用率を取得します。

C#
using System.Diagnostics;

static string? GetProcessInstanceName(int processId)
{
var category = new PerformanceCounterCategory("Process");

foreach (string instanceName in category.GetInstanceNames())
{
using var idCounter = new PerformanceCounter(
categoryName: "Process",
counterName: "ID Process",
instanceName: instanceName,
readOnly: true);

try
{
if ((int)idCounter.RawValue == processId)
{
return instanceName;
}
}
catch (InvalidOperationException)
{
// プロセス終了などで取得できない場合はスキップ
}
}

return null;
}

if (!OperatingSystem.IsWindows())
{
Console.WriteLine("このサンプルはWindows専用です。");
return;
}

int pid = Environment.ProcessId;
string? instanceName = GetProcessInstanceName(pid);

if (instanceName is null)
{
Console.WriteLine("対象プロセスのインスタンス名が見つかりません。");
return;
}

using var processCpuCounter = new PerformanceCounter(
categoryName: "Process",
counterName: "% Processor Time",
instanceName: instanceName,
readOnly: true);

processCpuCounter.NextValue();
await Task.Delay(1000);

float rawCpu = processCpuCounter.NextValue();

// Process\% Processor Timeはマルチコア環境で100%を超える場合があるため、
// システム全体に対する割合にしたい場合は論理プロセッサ数で割る。
float normalizedCpu = rawCpu / Environment.ProcessorCount;

Console.WriteLine($"PID: {pid}");
Console.WriteLine($"Instance: {instanceName}");
Console.WriteLine($"Process CPU: {normalizedCpu:F2}%");

プロセス監視では、プロセス名だけでインスタンスを決め打ちしないことが重要です。同じアプリケーションが複数起動していると、インスタンス名に#1#2が付与され、想定と異なるプロセスの値を取得してしまうことがあります。

3-4. NextValueメソッド使用時の注意点

NextValue()は、カウンターの種類によっては2回分の読み取り値から差分を計算します。そのため、最初の読み取りが0.0になることがあります。

C#
counter.NextValue();       // 初回は読み捨て
await Task.Delay(1000); // 推奨される間隔
float value = counter.NextValue();

Microsoftのドキュメントでも、計算値が2つの読み取りに依存するカウンターでは最初の読み取りが0.0を返し、NextValue()呼び出し間の推奨遅延は1秒と説明されています。Microsoft Learn

特に次のようなカウンターでは、初回値をそのまま使わないようにしましょう。

  • % Processor Time

  • Disk Reads/sec

  • Disk Writes/sec

  • Requests/sec

  • RateOfCountsPerSecond64系のカスタムカウンター

3-5. 一定間隔で継続取得するサンプルコード

CPU使用率と空きメモリを1秒ごとに取得するコンソールアプリの例です。

C#
using System.Diagnostics;

if (!OperatingSystem.IsWindows())
{
Console.WriteLine("このサンプルはWindows専用です。");
return;
}

using var cts = new CancellationTokenSource();

Console.CancelKeyPress += (_, e) =>
{
e.Cancel = true;
cts.Cancel();
};

using var cpuCounter = new PerformanceCounter(
"Processor",
"% Processor Time",
"_Total",
true);

using var memoryCounter = new PerformanceCounter(
"Memory",
"Available MBytes",
true);

cpuCounter.NextValue();

Console.WriteLine("監視を開始します。終了するには Ctrl+C を押してください。");

while (!cts.Token.IsCancellationRequested)
{
try
{
await Task.Delay(1000, cts.Token);

float cpu = cpuCounter.NextValue();
float availableMemory = memoryCounter.NextValue();

Console.WriteLine(
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss}, CPU={cpu:F2}%, AvailableMemory={availableMemory:F0}MB");
}
catch (TaskCanceledException)
{
break;
}
}

継続監視では、PerformanceCounterをループのたびに作成し直さず、使い回すのが基本です。毎回インスタンスを作ると無駄な負荷やハンドル消費につながります。

4. 利用可能なカテゴリ・カウンターを一覧取得する方法

対象環境でどのカテゴリやカウンターが使えるか分からない場合は、PerformanceCounterCategoryを使って一覧取得できます。カテゴリやカウンター名を決め打ちする前に、実行環境で存在確認しておくと例外を防げます。

4-1. PerformanceCounterCategoryでカテゴリ一覧を取得する

カテゴリ一覧を取得する例です。

C#
using System.Diagnostics;

if (!OperatingSystem.IsWindows())
{
Console.WriteLine("このサンプルはWindows専用です。");
return;
}

PerformanceCounterCategory[] categories =
PerformanceCounterCategory.GetCategories();

foreach (var category in categories.OrderBy(c => c.CategoryName))
{
Console.WriteLine(category.CategoryName);
}

PerformanceCounterCategory.GetCategories()を使うと、コンピューター上のカテゴリ一覧を取得できます。PerformanceCounterCategoryには、カテゴリ作成、削除、一覧取得、カテゴリ内データ取得など、カテゴリやカウンターを操作するためのメソッドが用意されています。Microsoft Learn

4-2. カテゴリ内のカウンター一覧を取得する

特定カテゴリ内のカウンター一覧を取得する例です。

C#
using System.Diagnostics;

string categoryName = "Processor";

if (!PerformanceCounterCategory.Exists(categoryName))
{
Console.WriteLine($"カテゴリが存在しません: {categoryName}");
return;
}

var category = new PerformanceCounterCategory(categoryName);

PerformanceCounter[] counters = category.GetCounters("_Total");

foreach (var counter in counters.OrderBy(c => c.CounterName))
{
Console.WriteLine(counter.CounterName);
counter.Dispose();
}

Processorカテゴリのように複数インスタンスを持つカテゴリでは、GetCounters(instanceName)のようにインスタンス名を指定します。インスタンスを持たないカテゴリでは、GetCounters()を使います。

C#
var memoryCategory = new PerformanceCounterCategory("Memory");

foreach (var counter in memoryCategory.GetCounters())
{
Console.WriteLine(counter.CounterName);
counter.Dispose();
}

4-3. インスタンス名を取得する

カテゴリ内のインスタンス名を取得する例です。

C#
using System.Diagnostics;

string categoryName = "Process";

if (!PerformanceCounterCategory.Exists(categoryName))
{
Console.WriteLine($"カテゴリが存在しません: {categoryName}");
return;
}

var category = new PerformanceCounterCategory(categoryName);

foreach (string instanceName in category.GetInstanceNames().OrderBy(x => x))
{
Console.WriteLine(instanceName);
}

プロセス別、ディスク別、ネットワークインターフェース別のように、対象が複数存在するカテゴリではインスタンス名の取得が重要です。Processカテゴリでは、同名プロセスが複数ある場合にchrome#1のようなインスタンス名になるため、ID ProcessカウンターでPIDと照合すると確実です。

4-4. 存在確認をして例外を防ぐ方法

カテゴリ、カウンター、インスタンスの存在を確認してからPerformanceCounterを作成すると、実行時エラーを防ぎやすくなります。

C#
using System.Diagnostics;

string categoryName = "Processor";
string counterName = "% Processor Time";
string instanceName = "_Total";

if (!PerformanceCounterCategory.Exists(categoryName))
{
Console.WriteLine($"カテゴリが存在しません: {categoryName}");
return;
}

if (!PerformanceCounterCategory.CounterExists(counterName, categoryName))
{
Console.WriteLine($"カウンターが存在しません: {counterName}");
return;
}

if (!PerformanceCounterCategory.InstanceExists(instanceName, categoryName))
{
Console.WriteLine($"インスタンスが存在しません: {instanceName}");
return;
}

using var counter = new PerformanceCounter(
categoryName,
counterName,
instanceName,
true);

counter.NextValue();
await Task.Delay(1000);

Console.WriteLine(counter.NextValue());

ただし、存在確認後に対象プロセスが終了するなど、実行時に状態が変わる可能性はあります。そのため、存在確認だけでなく、InvalidOperationExceptionUnauthorizedAccessExceptionWin32Exceptionなどの例外処理も組み合わせることが重要です。

5. カスタムPerformance Counterを作成する方法

Performance Countersでは、Windows標準のカウンターを読むだけでなく、アプリケーション独自のカウンターを作成して値を書き込むこともできます。たとえば、処理件数、キュー滞留数、エラー数、リクエスト数/秒などをWindowsのパフォーマンスモニターで見える形にできます。

5-1. カスタムカテゴリを作成する流れ

カスタムPerformance Counterを作成する流れは次のとおりです。

  1. カテゴリ名を決める

  2. カウンター名と種類を決める

  3. CounterCreationDataCollectionにカウンター定義を追加する

  4. PerformanceCounterCategory.Create()でカテゴリを作成する

  5. アプリケーションからRawValueIncrement()IncrementBy()などで値を書き込む

カスタムカテゴリの作成や削除はシステム設定を変更するため、通常は管理者権限で実行します。また、カテゴリ作成直後にすぐ利用すると反映が間に合わない場合があります。Microsoftのサンプルにも、Performance Counterは作成後すぐに使用すべきではなく、有効化までに遅延があるため、利用するアプリケーション実行前に作成しておくべきと説明されています。Microsoft Learn

5-2. CounterCreationDataを使った作成例

次の例では、MyApp Countersというカテゴリに、処理件数/秒とキュー件数を表す2つのカウンターを作成します。

C#
using System.Diagnostics;

const string CategoryName = "MyApp Counters";

if (!OperatingSystem.IsWindows())
{
Console.WriteLine("このサンプルはWindows専用です。");
return;
}

if (PerformanceCounterCategory.Exists(CategoryName))
{
Console.WriteLine("カテゴリは既に存在します。");
return;
}

var counters = new CounterCreationDataCollection
{
new CounterCreationData
{
CounterName = "Requests/sec",
CounterHelp = "1秒あたりのリクエスト処理件数",
CounterType = PerformanceCounterType.RateOfCountsPerSecond64
},
new CounterCreationData
{
CounterName = "Queue Length",
CounterHelp = "現在のキュー滞留件数",
CounterType = PerformanceCounterType.NumberOfItems64
}
};

PerformanceCounterCategory.Create(
categoryName: CategoryName,
categoryHelp: "MyApp application performance counters",
categoryType: PerformanceCounterCategoryType.SingleInstance,
counterData: counters);

Console.WriteLine("カスタムPerformance Counterカテゴリを作成しました。");
Console.WriteLine("反映後にアプリケーションを再実行してください。");

RateOfCountsPerSecond64は「1秒あたりの増加数」を見るのに適しており、リクエスト数/秒や処理件数/秒に使えます。NumberOfItems64は、キュー件数や現在接続数のような、その時点の数量を表す値に向いています。

5-3. カウンター値を書き込む実装例

作成済みのカスタムカウンターに値を書き込む例です。

C#
using System.Diagnostics;

const string CategoryName = "MyApp Counters";

if (!OperatingSystem.IsWindows())
{
Console.WriteLine("このサンプルはWindows専用です。");
return;
}

if (!PerformanceCounterCategory.Exists(CategoryName))
{
Console.WriteLine("カテゴリが存在しません。先に作成してください。");
return;
}

using var requestsPerSec = new PerformanceCounter(
categoryName: CategoryName,
counterName: "Requests/sec",
readOnly: false);

using var queueLength = new PerformanceCounter(
categoryName: CategoryName,
counterName: "Queue Length",
readOnly: false);

queueLength.RawValue = 0;

for (int i = 0; i < 10; i++)
{
requestsPerSec.Increment();
queueLength.RawValue = Random.Shared.Next(0, 100);

Console.WriteLine("カウンター値を書き込みました。");
await Task.Delay(1000);
}

読み取り専用ではなく書き込みを行うため、readOnly: falseを指定します。Requests/secのような増分系カウンターではIncrement()IncrementBy()を使い、Queue Lengthのような現在値系カウンターではRawValueに値を設定します。

5-4. 作成済みカテゴリを削除・再作成する方法

カウンター定義を変更したい場合、既存カテゴリを削除して再作成することがあります。

C#
using System.Diagnostics;

const string CategoryName = "MyApp Counters";

if (!OperatingSystem.IsWindows())
{
Console.WriteLine("このサンプルはWindows専用です。");
return;
}

if (PerformanceCounterCategory.Exists(CategoryName))
{
PerformanceCounterCategory.Delete(CategoryName);
Console.WriteLine("既存カテゴリを削除しました。");
}

Console.WriteLine("必要に応じて再作成処理を実行してください。");

カテゴリ削除は影響が大きい操作です。本番環境で削除・再作成する場合は、監視ツール側の設定、ダッシュボード、アラート定義への影響を事前に確認してください。

5-5. カスタムカウンター作成時の権限と反映タイミング

カスタムカウンター作成時は、次の点に注意します。

注意点内容
管理者権限カテゴリ作成・削除では管理者権限が必要になりやすい
即時利用しない作成直後は反映に時間がかかる場合がある
インストール処理で作成アプリ起動時ではなく、セットアップやデプロイ時に作成する
カウンター名変更変更時は削除・再作成が必要になることが多い
本番反映監視停止時間や権限を考慮する

アプリケーション起動のたびにカテゴリ作成処理を実行する設計は避けたほうが安全です。インストーラー、初期化スクリプト、管理者向けセットアップコマンドなどに分離しておくと、運用トラブルを減らせます。

6. Performance Countersを監視に活用する実装例

Performance Countersは、単発で値を取得するだけでなく、一定間隔で監視してログ出力、アラート、CSV保存、DB保存などに活用できます。

6-1. コンソールアプリで定期監視する例

CPU使用率が80%を超えたら警告を表示するコンソールアプリの例です。

C#
using System.Diagnostics;

if (!OperatingSystem.IsWindows())
{
Console.WriteLine("このサンプルはWindows専用です。");
return;
}

using var cpuCounter = new PerformanceCounter(
"Processor",
"% Processor Time",
"_Total",
true);

cpuCounter.NextValue();

while (true)
{
await Task.Delay(1000);

float cpu = cpuCounter.NextValue();

if (cpu >= 80)
{
Console.WriteLine($"[WARN] {DateTime.Now:HH:mm:ss} CPU使用率が高いです: {cpu:F2}%");
}
else
{
Console.WriteLine($"[INFO] {DateTime.Now:HH:mm:ss} CPU={cpu:F2}%");
}
}

簡易監視ツールや検証用ツールであれば、このようなコンソールアプリでも十分です。ただし、本番運用ではログローテーション、例外処理、サービス化、監視プロセス自体の死活監視も考慮する必要があります。

6-2. Windowsサービスで監視する例

.NETのWorker Serviceを使ってWindowsサービスとして監視する場合は、BackgroundService内でPerformance Counterを定期取得します。

C#
using System.Diagnostics;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

public sealed class CounterMonitorWorker : BackgroundService
{
private readonly ILogger<CounterMonitorWorker> _logger;

public CounterMonitorWorker(ILogger<CounterMonitorWorker> logger)
{
_logger = logger;
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
if (!OperatingSystem.IsWindows())
{
_logger.LogWarning("Performance CountersはWindows専用です。");
return;
}

using var cpuCounter = new PerformanceCounter(
"Processor",
"% Processor Time",
"_Total",
true);

cpuCounter.NextValue();

while (!stoppingToken.IsCancellationRequested)
{
try
{
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);

float cpu = cpuCounter.NextValue();

_logger.LogInformation("CPU使用率: {Cpu:F2}%", cpu);
}
catch (TaskCanceledException)
{
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Performance Counterの取得に失敗しました。");
}
}
}
}

Windowsサービスとして実行する場合は、サービスアカウントにPerformance Countersへのアクセス権限があるかを確認してください。ローカルシステム、ドメインユーザー、専用監視ユーザーのどれで実行するかによって、アクセスできる範囲が変わります。

6-3. しきい値を超えた場合にログ出力する例

CPU、メモリ、キュー長などを監視し、しきい値超過時にログを出す例です。

C#
using System.Diagnostics;

const float CpuThreshold = 80.0f;
const float AvailableMemoryThresholdMb = 1024.0f;

using var cpuCounter = new PerformanceCounter(
"Processor",
"% Processor Time",
"_Total",
true);

using var memoryCounter = new PerformanceCounter(
"Memory",
"Available MBytes",
true);

cpuCounter.NextValue();

while (true)
{
await Task.Delay(1000);

float cpu = cpuCounter.NextValue();
float availableMemory = memoryCounter.NextValue();

if (cpu >= CpuThreshold)
{
Console.WriteLine($"[WARN] CPU使用率がしきい値を超えました: {cpu:F2}%");
}

if (availableMemory <= AvailableMemoryThresholdMb)
{
Console.WriteLine($"[WARN] 空きメモリが少なくなっています: {availableMemory:F0}MB");
}
}

しきい値は固定値ではなく、設定ファイルや環境変数から変更できるようにしておくと、本番環境で調整しやすくなります。また、一度超えただけで通知するのではなく、「3回連続で超えた場合に通知する」「復旧時にもログを出す」などの制御を入れると、誤検知を減らせます。

6-4. 監視データをファイルやDBに保存する例

CSVファイルに保存する例です。

C#
using System.Diagnostics;
using System.Text;

string filePath = "performance-log.csv";

if (!File.Exists(filePath))
{
await File.WriteAllTextAsync(
filePath,
"Timestamp,Cpu,AvailableMemoryMB" + Environment.NewLine,
Encoding.UTF8);
}

using var cpuCounter = new PerformanceCounter(
"Processor",
"% Processor Time",
"_Total",
true);

using var memoryCounter = new PerformanceCounter(
"Memory",
"Available MBytes",
true);

cpuCounter.NextValue();

while (true)
{
await Task.Delay(5000);

float cpu = cpuCounter.NextValue();
float memory = memoryCounter.NextValue();

string line = $"{DateTimeOffset.Now:O},{cpu:F2},{memory:F0}{Environment.NewLine}";

await File.AppendAllTextAsync(filePath, line, Encoding.UTF8);

Console.WriteLine(line.TrimEnd());
}

DBに保存する場合は、1件ずつ同期的に書き込むと監視処理自体がボトルネックになる場合があります。一定件数をまとめてバルクインサートする、キューに入れて別スレッドで保存する、保存失敗時のリトライや破棄ポリシーを決めるなど、監視処理がアプリケーション本体に悪影響を与えない設計にしましょう。

7. よくあるエラーとトラブルシューティング

Performance Countersでは、環境差、権限、インスタンス名、初回値、カウンター破損などによるトラブルがよく発生します。原因を切り分けるには、コードだけでなくWindowsのパフォーマンスモニターやコマンドラインツールも併用します。

7-1. カテゴリやカウンターが見つからない場合

InvalidOperationExceptionや「カテゴリが存在しない」「カウンターが存在しない」といったエラーが出る場合は、次を確認します。

  • カテゴリ名が正しいか

  • カウンター名が正しいか

  • 対象機能や役割がWindowsにインストールされているか

  • OSの言語や環境によって名称が異なっていないか

  • 32bit/64bitや実行環境の違いがないか

  • カスタムカウンターが事前に作成されているか

実装では、PerformanceCounterCategory.Exists()CounterExists()で存在確認してから利用します。

C#
if (!PerformanceCounterCategory.Exists("Processor"))
{
Console.WriteLine("Processorカテゴリが存在しません。");
return;
}

if (!PerformanceCounterCategory.CounterExists("% Processor Time", "Processor"))
{
Console.WriteLine("% Processor Timeカウンターが存在しません。");
return;
}

7-2. UnauthorizedAccessExceptionが発生する場合

UnauthorizedAccessExceptionが発生する場合は、実行ユーザーにPerformance Countersへアクセスする権限がありません。次を確認してください。

  • Visual Studioやコンソールを「管理者として実行」しているか

  • Windowsサービスの実行アカウントに権限があるか

  • カスタムカテゴリ作成・削除を通常ユーザーで実行していないか

  • リモートマシンへアクセスする場合、対象マシン側の権限があるか

特に開発環境では管理者として実行していたが、本番のWindowsサービスでは一般ユーザー権限で動いて失敗するケースがあります。開発時から本番と同じ権限に近いユーザーで動作確認しておくと安全です。

7-3. 初回取得値が0になる場合

CPU使用率や秒間処理数のような差分計算型のカウンターでは、初回のNextValue()0になることがあります。これは異常ではありません。

C#
counter.NextValue();    // 初回は読み捨て
Thread.Sleep(1000);
float value = counter.NextValue();

NextValue()はカウンターの種類によって2回の読み取り値を使うため、最初の読み取りが0.0になる場合があります。推奨される呼び出し間隔は1秒です。Microsoft Learn

7-4. インスタンス名が一致しない場合

Processカテゴリでよくあるのが、プロセス名とインスタンス名が一致しない問題です。同じプロセスが複数あると、インスタンス名は次のようになります。

dotnet
dotnet#1
dotnet#2

この場合、プロセス名だけで監視対象を決めると誤ったプロセスを監視する可能性があります。ID Processカウンターを使ってPIDと照合する実装にすると確実です。

C#
using var idCounter = new PerformanceCounter(
"Process",
"ID Process",
instanceName,
true);

int pid = (int)idCounter.RawValue;

また、対象プロセスが起動直後または終了直後の場合、一覧取得時点と値取得時点で状態が変わることがあります。プロセス監視では、例外を握りつぶすのではなく、ログに残して再試行できる設計にしておくと運用しやすくなります。

7-5. カウンターが壊れている場合の確認方法

Windows側のPerformance Counter情報が壊れている場合、カテゴリが表示されない、値が取得できない、パフォーマンスモニターでもエラーになるといった症状が出ます。

確認手順の例です。

1. Windowsの「パフォーマンス モニター」で対象カウンターが見えるか確認
2. 管理者権限のコマンドプロンプトで lodctr /q を実行
3. 必要に応じて lodctr /R で再構築
4. サーバーを再起動
5. カスタムカウンターや.NET関連カウンターを再登録

MicrosoftのWindows Server向けトラブルシューティングでは、Performance Counterライブラリが破損した場合にlodctr /Rでカウンターを再構築する手順が案内されています。実行には管理者権限が必要です。Microsoft Learn

cmd
cd %SystemRoot%\System32
lodctr /R

本番サーバーで実行する場合は、影響範囲を確認し、メンテナンス時間帯に実施してください。

8. Performance Countersを使う際の注意点

Performance Countersは便利ですが、現代の.NETアプリケーションでは注意点も多い機能です。Windows専用であること、権限が必要になること、初回値やインスタンス名の扱いが独特であることを理解して使う必要があります。

8-1. Windows以外では利用できない点

Performance CountersはWindows専用です。Linuxコンテナ、Kubernetes、macOS開発環境、クロスプラットフォームの.NETアプリでは、そのまま利用できません。

クロスプラットフォーム対応が必要な場合は、次のような代替を検討します。

要件推奨候補
Windows既存監視と連携Performance Counters
.NET 6以降の新規メトリクスSystem.Diagnostics.Metrics
.NET Core系の軽量監視EventCounters
一時的な調査dotnet-counters
クラウドネイティブ監視OpenTelemetry, Prometheus

.NETでは、.NET Core以降でEventCountersやSystem.Diagnostics.Metricsといったクロスプラットフォームの代替手段が提供されています。Microsoft Learn

8-2. 高頻度取得による負荷への注意

Performance Counterの取得自体は比較的軽量ですが、数百〜数千のカウンターを高頻度で取得すると負荷になります。特に次のような実装は避けましょう。

  • 100ミリ秒ごとに多数のカウンターを読む

  • ループのたびにPerformanceCounterを作成・破棄する

  • 取得値を毎回同期的にDBへ書き込む

  • 例外発生時に短間隔で無限リトライする

  • ログを無制限に出力する

通常の監視であれば、1秒、5秒、10秒、60秒など、用途に応じた間隔で十分です。アラート用途なら短め、傾向分析なら長めの間隔にするなど、目的に合わせて設計します。

8-3. 例外処理と存在確認の重要性

Performance Countersでは、対象プロセスの終了、権限不足、カテゴリ削除、OS環境差などにより例外が発生します。監視処理で例外が未処理になると、監視アプリケーション自体が停止してしまいます。

C#
try
{
float value = counter.NextValue();
Console.WriteLine(value);
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine($"権限不足です: {ex.Message}");
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"カウンターの状態が不正です: {ex.Message}");
}
catch (System.ComponentModel.Win32Exception ex)
{
Console.WriteLine($"Windows APIエラーです: {ex.Message}");
}

監視処理では、例外を単に無視するのではなく、原因調査できる情報をログに残しましょう。カテゴリ名、カウンター名、インスタンス名、実行ユーザー、マシン名を含めると、トラブルシューティングしやすくなります。

8-4. 本番環境での権限設計

本番環境では、管理者権限で常時監視アプリケーションを動かす設計は避けるべきです。必要最小限の権限を持つ専用アカウントを使い、次のように役割を分けると安全です。

処理推奨設計
カスタムカテゴリ作成管理者がデプロイ時に実行
カウンター読み取り監視専用ユーザーで実行
カウンター書き込みアプリケーション実行ユーザーに必要権限を付与
カテゴリ削除運用手順に限定
リモート監視専用アカウントとファイアウォール設定を管理

権限不足の問題は、開発環境では見逃されがちです。Windowsサービス、IIS、タスクスケジューラ、コンテナ、リモート監視など、実際の実行形態で検証することが重要です。

8-5. 長期運用時のログ肥大化対策

監視データをファイルに保存する場合、ログ肥大化に注意が必要です。1秒ごとのCSV出力でも、長期間運用すれば大きなファイルになります。

対策としては、次のような方法があります。

  • 日付単位でログファイルを分割する

  • 一定期間を過ぎたログを削除する

  • 圧縮してアーカイブする

  • 必要な粒度に集計して保存する

  • DB保存時はパーティションや保持期間を設定する

  • 監視基盤に送信した後、ローカルログは最小限にする

監視データは「細かく取ればよい」というものではありません。障害調査、傾向分析、アラート判定に必要な粒度を決め、それ以上のデータを無駄に保存しない設計が重要です。

9. Performance Countersの代替手段

Performance CountersはWindows環境では有用ですが、現代の.NETアプリケーション、とくにクラウド、Linux、コンテナ、Kubernetesを前提にする場合は、別の監視方法を選ぶほうが適している場合があります。

9-1. EventCountersとの違い

EventCountersは、.NET Core以降で使える軽量なメトリクス収集APIです。Performance CountersがWindows中心なのに対して、EventCountersはクロスプラットフォームの近リアルタイムなメトリクス収集を目的としています。

EventCountersは、.NET FrameworkのWindows Performance Countersに対するクロスプラットフォーム代替として追加され、軽量で近リアルタイムなパフォーマンスメトリクス収集に使われます。Microsoft Learn

比較項目Performance CountersEventCounters
対応OSWindowsクロスプラットフォーム
主な用途Windows監視、既存資産連携.NET Core以降の軽量監視
取得ツールパフォーマンスモニター、C#dotnet-counters、EventListener
新規開発向き限定的Metrics APIのほうが推奨される場合あり

EventCountersは便利ですが、Microsoftは新しい.NETプロジェクトでは、より新しいSystem.Diagnostics.Metrics APIの使用を推奨しています。Microsoft Learn

9-2. dotnet-countersとの使い分け

dotnet-countersは、.NETアプリケーションのメトリクスをコマンドラインから監視するための診断ツールです。アプリケーションに本格的な監視機能を組み込む前の調査や、障害時の一次切り分けに向いています。

Bash
dotnet-counters monitor --process-id 1234

dotnet-countersは、EventCounter APIまたはMeter APIで公開された値を監視でき、CPU使用率や例外発生率などを素早く確認するためのアドホックな正常性監視・一次調査ツールとして使えます。Microsoft Learn

使い分けの目安は次のとおりです。

用途選択肢
一時的に.NETプロセスを調査dotnet-counters
アプリにメトリクスを実装System.Diagnostics.Metrics
Windowsの既存監視基盤に載せるPerformance Counters
継続的な本番監視OpenTelemetry、Prometheus、APM

9-3. OpenTelemetryやPrometheusとの連携

クラウドネイティブな監視では、OpenTelemetryやPrometheusとの連携が一般的です。.NETではSystem.Diagnostics.Metricsを使ってアプリケーション内のメトリクスを定義し、OpenTelemetry経由でPrometheusや各種APMに送信できます。

.NETのメトリクス収集チュートリアルでは、System.Diagnostics.Metricsで作成したメトリクスをdotnet-countersで確認したり、OpenTelemetryのPrometheusエクスポーターを使って公開したりする流れが紹介されています。Microsoft Learn

簡単なMetrics APIの例です。

C#
using System.Diagnostics.Metrics;

var meter = new Meter("MyApp", "1.0.0");
var requestCounter = meter.CreateCounter<int>("myapp.requests");

requestCounter.Add(1);

Performance CountersのようにWindowsへカウンターを登録する必要がなく、LinuxコンテナやKubernetesでも扱いやすい点が大きなメリットです。

9-4. どの監視方法を選ぶべきか

監視方法は、アプリケーションの実行環境と運用要件で選びます。

状況推奨
.NET FrameworkのWindowsアプリを保守しているPerformance Counters
Windowsのパフォーマンスモニターで見たいPerformance Counters
.NET 6/7/8以降で新規開発するSystem.Diagnostics.Metrics
一時的にプロセス状態を調べたいdotnet-counters
Linuxやコンテナで動かすSystem.Diagnostics.Metrics、OpenTelemetry
Prometheusで収集したいOpenTelemetry + Prometheus exporter
既存監視製品と統合したい製品対応に合わせて選択

Windows専用の既存システムでは、C#のPerformance Countersは今でも実用的です。一方、これから新しく監視基盤を作るなら、System.Diagnostics.Metricsを中心に設計し、必要に応じてOpenTelemetryやPrometheusへ連携するほうが将来性があります。

まとめ

C#でPerformance Countersを使うと、WindowsのCPU使用率、メモリ使用量、プロセス単位の負荷、カスタムアプリケーション指標などを取得・作成・監視できます。基本的にはPerformanceCounterで値を読み書きし、PerformanceCounterCategoryでカテゴリやカウンターの一覧取得、存在確認、作成、削除を行います。

実装時は、次のポイントを押さえておくことが重要です。

  • .NET 6/7/8以降ではSystem.Diagnostics.PerformanceCounterパッケージを追加する

  • Windows専用機能であるため、OS判定を入れる

  • CPU使用率などは初回NextValue()が0になるため、1回読み捨てる

  • プロセス別監視ではインスタンス名とPIDを照合する

  • カスタムカウンターの作成・削除には管理者権限が必要になりやすい

  • カスタムカテゴリ作成直後はすぐに使わず、反映タイミングを考慮する

  • 本番環境では権限、例外処理、ログ肥大化、監視間隔を設計する

  • 新規開発ではSystem.Diagnostics.Metrics、EventCounters、OpenTelemetryも検討する

Performance Countersは古い技術ですが、Windows環境や.NET Framework資産、既存の監視基盤と連携する場面ではまだ有効です。C#で正しく扱うには、カテゴリ・カウンター・インスタンスの関係、権限、初回取得値、Windows依存の制約を理解したうえで、運用を見据えた実装にすることが大切です。