C# PerformanceCounterの使い方|CPU・メモリ使用率を取得する実装サンプル完全解説

はじめに

C#でCPU使用率やメモリ使用率を取得したい場合、Windows環境ではSystem.Diagnostics.PerformanceCounterを使う方法があります。PerformanceCounterを使うと、WindowsのパフォーマンスカウンターからCPU、メモリ、プロセス、ディスク、ネットワークなどの情報を取得できます。

たとえば、次のような用途で利用できます。

C#
// CPU使用率を取得する
// メモリの空き容量を取得する
// 特定プロセスのCPU使用率を監視する
// アプリケーションのリソース使用量をログに出力する

ただし、PerformanceCounterはWindows専用の仕組みであり、.NET Core.NET 5以降ではNuGetパッケージの追加が必要になる場合があります。また、NextValue()の初回値が0になる、プロセス名のインスタンス名が分かりにくい、管理者権限が必要になるケースがあるなど、実装時に注意すべき点もあります。

この記事では、C#でPerformanceCounterを使う基本から、CPU使用率・メモリ使用率・特定プロセスの監視コード、よくあるエラーの解決策、実用的な監視アプリへの組み込み方まで、実装サンプル付きで解説します。

1. C# PerformanceCounterとは?できることと用途を整理

1-1. PerformanceCounterの概要

PerformanceCounterは、WindowsのパフォーマンスカウンターにC#からアクセスするためのクラスです。名前空間はSystem.Diagnosticsで、CPU、メモリ、プロセス、ディスク、ネットワークなど、Windowsが管理している各種パフォーマンス情報を取得できます。

Microsoftのドキュメントでも、System.Diagnostics.PerformanceCounterはWindowsのパフォーマンスカウンター技術に対するマネージドAPIとして説明されています。また、.NETのメトリクスAPIの中では古いAPIに分類され、Windows専用である点が明記されています。Microsoft Learn+1

代表的には、次のような値を取得できます。

C#
// システム全体のCPU使用率
// 利用可能な物理メモリ
// 特定プロセスのCPU使用率
// 特定プロセスのメモリ使用量
// ディスク読み書き量
// ネットワーク送受信量

Windowsの「パフォーマンス モニター」で確認できる項目の多くは、C#のPerformanceCounterからも参照できます。

1-2. CPU使用率・メモリ使用率・プロセス監視で使われる理由

PerformanceCounterがCPU使用率やメモリ使用率の取得でよく使われる理由は、Windowsが標準で保持している監視情報をそのまま取得できるからです。

たとえば、CPU使用率を取得したい場合は、Processorカテゴリの% Processor Timeカウンターを使います。メモリの空き容量を取得したい場合は、MemoryカテゴリのAvailable MBytesカウンターを使います。特定プロセスを監視したい場合は、Processカテゴリの% Processor TimeWorking SetPrivate Bytesなどを使います。

自前でCPU時間やメモリ使用量を計算する方法もありますが、PerformanceCounterを使うとWindowsが提供する既存のカウンター値を取得できるため、簡潔なコードで監視処理を実装できます。

1-3. .NET Frameworkと.NET Core / .NET 5以降での違い

.NET Frameworkでは、System.Diagnostics.PerformanceCounterを比較的そのまま利用できます。一方で、.NET Core.NET 5以降では、プロジェクトによってはSystem.Diagnostics.PerformanceCounterパッケージをNuGetから追加する必要があります。

NuGetパッケージSystem.Diagnostics.PerformanceCounterは、Windowsのパフォーマンスカウンターにアクセスするための型を提供するパッケージです。NuGet+1

.NET 6.NET 7.NET 8.NET 9以降のプロジェクトで利用する場合は、次のようにパッケージを追加します。

Bash
dotnet add package System.Diagnostics.PerformanceCounter

また、Windows固有APIをまとめて利用したい場合は、Microsoft.Windows.Compatibilityパッケージを使う選択肢もあります。このパッケージは、.NET Frameworkから.NET Core 2.0以降や.NET 5以降へ移行する際に、Windows固有APIを利用しやすくするためのものです。NuGet

1-4. Windows専用APIとしての注意点

PerformanceCounterはWindows専用です。LinuxやmacOS上で同じコードを実行すると、PlatformNotSupportedExceptionが発生する可能性があります。

そのため、クロスプラットフォーム対応が必要なアプリケーションでは、PerformanceCounterを直接使う設計は避けたほうが安全です。Windows専用の業務アプリ、Windowsサービス、社内監視ツール、Windows Forms / WPFアプリなどでは有効ですが、Linuxコンテナやクラウド上のマルチプラットフォーム環境で動作させる場合は、EventCounterSystem.Diagnostics.Metrics、OpenTelemetry、WMI、またはOS別のAPIを検討しましょう。

.NETの新しいメトリクスAPIとしては、System.Diagnostics.Metricsがあり、OpenTelemetryとの連携も考慮されています。Microsoftの比較ドキュメントでも、新規開発では特別な理由がなければSystem.Diagnostics.Metricsが有力な選択肢として説明されています。Microsoft Learn+1

2. C#でPerformanceCounterを使うための準備

2-1. 必要な参照設定・NuGetパッケージ

.NET FrameworkのWindowsアプリケーションであれば、基本的にはSystem.Diagnosticsを参照することでPerformanceCounterを使えます。

.NET Core.NET 5以降.NET 6.NET 8などのプロジェクトでは、次のNuGetパッケージを追加します。

Bash
dotnet add package System.Diagnostics.PerformanceCounter

Visual Studioを使っている場合は、次の手順でも追加できます。

  1. プロジェクトを右クリック

  2. 「NuGet パッケージの管理」を選択

  3. System.Diagnostics.PerformanceCounterを検索

  4. インストール

.csprojには、次のような参照が追加されます。

XML
<ItemGroup>
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="10.0.0" />
</ItemGroup>

バージョン番号は利用時点の最新安定版に合わせてください。

2-2. using System.Diagnosticsの追加

C#コードでは、次のusingを追加します。

C#
using System.Diagnostics;

PerformanceCounterクラスはSystem.Diagnostics名前空間に含まれているため、このusingを追加しておくと、コード内でPerformanceCounterをそのまま記述できます。

C#
using System;
using System.Diagnostics;
using System.Threading;

class Program
{
static void Main()
{
using var cpuCounter = new PerformanceCounter(
"Processor",
"% Processor Time",
"_Total"
);

cpuCounter.NextValue();
Thread.Sleep(1000);

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

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

環境や取得するカウンターの種類によっては、管理者権限が必要になる場合があります。MicrosoftのNextValue()やコンストラクターのドキュメントでも、パフォーマンスカウンターへアクセスするコードを実行するには、標準ユーザーから管理者へ権限昇格が必要になる場合があると説明されています。Microsoft Learn+1

特に、次のような場合は権限に注意してください。

C#
// Windowsサービスから取得する
// IIS上のアプリケーションから取得する
// リモートコンピューターのカウンターを取得する
// カスタムパフォーマンスカウンターを作成・削除する
// 一部のシステム系カウンターへアクセスする

開発環境では動作しても、本番環境では権限不足で失敗するケースがあります。サービスアカウントやアプリケーションプールIDの権限も確認しておきましょう。

2-4. 実行環境で確認すべき前提条件

PerformanceCounterを使う前に、次の点を確認してください。

C#
// Windows上で実行しているか
// 必要なNuGetパッケージが追加されているか
// 取得対象のカテゴリ・カウンターが存在するか
// インスタンス名が正しいか
// 実行ユーザーに必要な権限があるか
// パフォーマンスカウンターが破損していないか

特にCategory does not existInstance does not existが出る場合は、カテゴリ名、カウンター名、インスタンス名の指定ミスが多いです。英語版Windowsと日本語版Windowsでも内部的なカウンター名は基本的に英語名を使うことが多いため、コードではProcessorMemoryProcessなどの英語名で指定します。

3. PerformanceCounterの基本的な使い方

3-1. CategoryName・CounterName・InstanceNameの意味

PerformanceCounterでは、主に次の3つの名前を指定します。

C#
CategoryName  // カテゴリ名
CounterName // カウンター名
InstanceName // インスタンス名

それぞれの意味は次のとおりです。

C#
CategoryName:
監視対象の分類です。
: "Processor", "Memory", "Process", "PhysicalDisk", "Network Interface"

CounterName:
取得したい具体的な値です。
: "% Processor Time", "Available MBytes", "Working Set"

InstanceName:
カテゴリ内の対象インスタンスです。
: "_Total", "chrome", "dotnet", "C:"

たとえば、システム全体のCPU使用率を取得する場合は、次のように指定します。

C#
new PerformanceCounter("Processor", "% Processor Time", "_Total");

これは、「Processorカテゴリ」の「% Processor Timeカウンター」を「_Totalインスタンス」に対して取得するという意味です。

Microsoftのドキュメントでも、パフォーマンスカウンターはコンピューター上のカテゴリまたはパフォーマンスオブジェクトの動作を監視し、カテゴリにはプロセッサ、ディスク、メモリ、プロセスなどが含まれると説明されています。Microsoft Learn+1

3-2. PerformanceCounterインスタンスの作成方法

PerformanceCounterの基本的な作成方法は次のとおりです。

C#
var counter = new PerformanceCounter(
categoryName: "Processor",
counterName: "% Processor Time",
instanceName: "_Total"
);

読み取り専用で利用する場合は、通常この指定で問題ありません。

usingを使って破棄まで含めると、次のようになります。

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

PerformanceCounterはアンマネージリソースにアクセスするため、使い終わったらDispose()されるようにしておきましょう。短いサンプルでは省略されがちですが、実用コードではusingまたはフィールドとして保持してアプリ終了時に破棄する設計がおすすめです。

3-3. NextValue()で値を取得する基本コード

値の取得にはNextValue()を使います。

C#
using System;
using System.Diagnostics;
using System.Threading;

class Program
{
static void Main()
{
using var cpuCounter = new PerformanceCounter(
"Processor",
"% Processor Time",
"_Total"
);

float value = cpuCounter.NextValue();

Console.WriteLine(value);
}
}

ただし、CPU使用率のように前回値との差分から計算されるカウンターでは、初回のNextValue()が正しい値にならないことがあります。そのため、実際には次のように1回目を捨てて、少し待ってから2回目の値を使います。

C#
cpuCounter.NextValue();
Thread.Sleep(1000);
float cpuUsage = cpuCounter.NextValue();

3-4. 初回取得値が0になる理由と対処法

NextValue()の初回値が0になるのは、多くのカウンターが前回取得値との差分を使って計算されるためです。

MicrosoftのPerformanceCounter.NextValue()ドキュメントでは、計算に2回の読み取りが必要なカウンターの場合、最初の読み取り操作は0.0を返し、NextValue()の呼び出し間隔として1秒程度の遅延が推奨されると説明されています。Microsoft Learn

対処法は、次のように初回値をウォームアップとして捨てることです。

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

// 初回は捨てる
cpuCounter.NextValue();

// 1秒待つ
Thread.Sleep(1000);

// 2回目以降を使う
float cpuUsage = cpuCounter.NextValue();

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

CPU使用率、ディスク使用率、ネットワーク送受信量など、時間差分が必要なカウンターではこの考え方が重要です。

4. C#でCPU使用率を取得する実装サンプル

4-1. システム全体のCPU使用率を取得するコード

システム全体のCPU使用率を取得するには、Processorカテゴリの% Processor Timeカウンターを使います。インスタンス名は_Totalです。

C#
using System;
using System.Diagnostics;
using System.Threading;

class Program
{
static void Main()
{
using var cpuCounter = new PerformanceCounter(
"Processor",
"% Processor Time",
"_Total"
);

// 初回値は0になりやすいため捨てる
cpuCounter.NextValue();
Thread.Sleep(1000);

float cpuUsage = cpuCounter.NextValue();

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

実行結果の例です。

CPU使用率: 18.72%

このコードは、Windows全体のCPU使用率を取得する基本形です。監視アプリやログ収集ツールでは、この処理を一定間隔で繰り返します。

4-2. 特定プロセスのCPU使用率を取得するコード

特定プロセスのCPU使用率を取得する場合は、Processカテゴリの% Processor Timeカウンターを使います。

たとえば、notepadプロセスを監視する場合は次のようにします。

C#
using System;
using System.Diagnostics;
using System.Threading;

class Program
{
static void Main()
{
string processName = "notepad";

using var processCpuCounter = new PerformanceCounter(
"Process",
"% Processor Time",
processName
);

processCpuCounter.NextValue();
Thread.Sleep(1000);

float cpuUsage = processCpuCounter.NextValue();

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

ただし、同じ名前のプロセスが複数存在する場合、インスタンス名はchromechrome#1chrome#2のようになることがあります。そのため、PIDから正しいパフォーマンスカウンターのインスタンス名を探す処理が必要になるケースがあります。

PIDからインスタンス名を探す実装例は次のとおりです。

C#
using System;
using System.Diagnostics;
using System.Linq;

class Program
{
static void Main()
{
int targetPid = Process.GetProcessesByName("notepad").First().Id;

string? instanceName = GetProcessInstanceName(targetPid);

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

using var cpuCounter = new PerformanceCounter(
"Process",
"% Processor Time",
instanceName
);

cpuCounter.NextValue();
Thread.Sleep(1000);

float cpuUsage = cpuCounter.NextValue();

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

static string? GetProcessInstanceName(int pid)
{
var category = new PerformanceCounterCategory("Process");
string[] instances = category.GetInstanceNames();

foreach (string instance in instances)
{
using var counter = new PerformanceCounter(
"Process",
"ID Process",
instance,
readOnly: true
);

try
{
if ((int)counter.RawValue == pid)
{
return instance;
}
}
catch
{
// プロセス終了などで取得できない場合は無視
}
}

return null;
}
}

この方法では、ProcessカテゴリのID Processカウンターを使って、PIDと一致するインスタンス名を探しています。

4-3. 複数コア環境でのCPU使用率の考え方

システム全体のCPU使用率を取得する場合、Processorカテゴリの_Totalを使えば、複数コアを含めた全体の使用率を取得できます。

一方、Processカテゴリの% Processor Timeでは、環境や取得方法によっては、複数コア分を合算した値が返ることがあります。たとえば、4コア環境で1プロセスが複数コアを大きく使っている場合、100%を超える値が出ることがあります。

プロセス単位のCPU使用率をタスクマネージャーに近い感覚で表示したい場合は、論理プロセッサ数で割る方法がよく使われます。

C#
float rawCpu = processCpuCounter.NextValue();
float normalizedCpu = rawCpu / Environment.ProcessorCount;

Console.WriteLine($"補正後CPU使用率: {normalizedCpu:F2}%");

実用上は、次のように考えると分かりやすいです。

C#
// システム全体のCPU使用率
// => Processor / % Processor Time / _Total

// プロセスのCPU使用率をざっくり監視
// => Process / % Processor Time / インスタンス名

// タスクマネージャーに近い表示
// => プロセスCPU使用率を Environment.ProcessorCount で割る

4-4. Timerを使って定期的にCPU使用率を取得する方法

監視アプリでは、CPU使用率を1回だけ取得するのではなく、定期的に取得することが多いです。コンソールアプリであれば、System.Threading.Timerを使って簡単に実装できます。

C#
using System;
using System.Diagnostics;
using System.Threading;

class Program
{
private static PerformanceCounter? _cpuCounter;
private static Timer? _timer;

static void Main()
{
_cpuCounter = new PerformanceCounter(
"Processor",
"% Processor Time",
"_Total"
);

// 初回値を捨てる
_cpuCounter.NextValue();

_timer = new Timer(_ =>
{
float cpuUsage = _cpuCounter.NextValue();
Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} CPU: {cpuUsage:F2}%");
}, null, 1000, 1000);

Console.WriteLine("Enterキーで終了します。");
Console.ReadLine();

_timer.Dispose();
_cpuCounter.Dispose();
}
}

このコードでは、1秒ごとにCPU使用率を取得してコンソールに出力しています。NextValue()の推奨間隔を考えると、CPU使用率の監視では1秒以上の間隔を空けるのが基本です。

5. C#でメモリ使用率を取得する実装サンプル

5-1. システム全体の利用可能メモリを取得するコード

システム全体の利用可能メモリを取得するには、MemoryカテゴリのAvailable MBytesカウンターを使います。

C#
using System;
using System.Diagnostics;

class Program
{
static void Main()
{
using var memoryCounter = new PerformanceCounter(
"Memory",
"Available MBytes"
);

float availableMemory = memoryCounter.NextValue();

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

実行結果の例です。

利用可能メモリ: 8452 MB

Available MBytesは、現在利用可能な物理メモリ量をMB単位で返します。CPU使用率のように差分計算が必要なカウンターではないため、初回から利用しやすい値です。

5-2. メモリ使用率をパーセントで算出する方法

PerformanceCounterで取得しやすいのは「利用可能メモリ」です。メモリ使用率をパーセントで表示するには、総物理メモリ量を別途取得し、次の式で計算します。

C#
メモリ使用率 = (総メモリ - 利用可能メモリ) / 総メモリ * 100

Windows環境では、Microsoft.VisualBasic.Devices.ComputerInfoを使って総物理メモリを取得できます。

C#
using System;
using System.Diagnostics;
using Microsoft.VisualBasic.Devices;

class Program
{
static void Main()
{
using var availableMemoryCounter = new PerformanceCounter(
"Memory",
"Available MBytes"
);

float availableMemoryMb = availableMemoryCounter.NextValue();

var computerInfo = new ComputerInfo();
ulong totalMemoryBytes = computerInfo.TotalPhysicalMemory;
double totalMemoryMb = totalMemoryBytes / 1024.0 / 1024.0;

double usedMemoryMb = totalMemoryMb - availableMemoryMb;
double memoryUsagePercent = usedMemoryMb / totalMemoryMb * 100.0;

Console.WriteLine($"総メモリ: {totalMemoryMb:F0} MB");
Console.WriteLine($"利用可能メモリ: {availableMemoryMb:F0} MB");
Console.WriteLine($"使用中メモリ: {usedMemoryMb:F0} MB");
Console.WriteLine($"メモリ使用率: {memoryUsagePercent:F2}%");
}
}

.NET Core.NET 5以降Microsoft.VisualBasic.Devices.ComputerInfoが使えない場合は、WMIやGlobalMemoryStatusExなどのWindows APIを利用する方法もあります。

5-3. 特定プロセスのメモリ使用量を取得するコード

特定プロセスのメモリ使用量を取得する場合は、ProcessカテゴリのWorking SetPrivate Bytesを使います。

C#
using System;
using System.Diagnostics;

class Program
{
static void Main()
{
string processName = "notepad";

using var workingSetCounter = new PerformanceCounter(
"Process",
"Working Set",
processName
);

float workingSetBytes = workingSetCounter.NextValue();
double workingSetMb = workingSetBytes / 1024.0 / 1024.0;

Console.WriteLine($"{processName} のWorking Set: {workingSetMb:F2} MB");
}
}

Private Bytesを取得する場合は、カウンター名を変更します。

C#
using var privateBytesCounter = new PerformanceCounter(
"Process",
"Private Bytes",
processName
);

float privateBytes = privateBytesCounter.NextValue();
double privateBytesMb = privateBytes / 1024.0 / 1024.0;

Console.WriteLine($"{processName} のPrivate Bytes: {privateBytesMb:F2} MB");

同名プロセスが複数ある場合は、CPU使用率の例と同じように、PIDからインスタンス名を特定する処理を使いましょう。

5-4. Working Set・Private Bytesの違い

プロセスのメモリ使用量を監視する場合、Working SetPrivate Bytesの違いを理解しておくことが重要です。

Working Setは、プロセスが現在物理メモリ上で使用しているメモリ量の目安です。タスクマネージャーで見えるメモリ使用量に近い値として扱われることがあります。

Private Bytesは、そのプロセス専用に割り当てられた仮想メモリ量です。他プロセスと共有されないメモリを把握したい場合に使います。

実用上は、次のように使い分けます。

C#
// 画面表示や簡易監視
// => Working Set

// メモリリーク調査やプロセス専有メモリの監視
// => Private Bytes

// .NETアプリ内部のGCやヒープ監視
// => EventCounter、dotnet-counters、OpenTelemetryなども検討

Working Setだけを見ていると、OSのメモリ管理の影響で増減することがあります。メモリリーク調査では、Private Bytesや.NETランタイムのメトリクスも併せて見ると判断しやすくなります。

6. よく使うPerformanceCounterのカテゴリ・カウンター一覧

6-1. Processorカテゴリで使う主なカウンター

CPU監視でよく使うカテゴリはProcessorです。

CategoryName: Processor

代表的なカウンターは次のとおりです。

% Processor Time
CPU使用率を取得する代表的なカウンターです。

% User Time
ユーザーモードで使用されたCPU時間の割合です。

% Privileged Time
カーネルモードで使用されたCPU時間の割合です。

% Interrupt Time
ハードウェア割り込み処理に使われたCPU時間の割合です。

システム全体のCPU使用率を取得する場合は、通常次の組み合わせを使います。

C#
new PerformanceCounter("Processor", "% Processor Time", "_Total");

6-2. Memoryカテゴリで使う主なカウンター

メモリ監視でよく使うカテゴリはMemoryです。

CategoryName: Memory

代表的なカウンターは次のとおりです。

Available MBytes
利用可能な物理メモリ量をMB単位で取得します。

Committed Bytes
コミット済みの仮想メモリ量を取得します。

% Committed Bytes In Use
コミット制限に対する使用割合を取得します。

Pages/sec
ページングの発生頻度を確認するために使います。

空きメモリを取得する基本コードは次のとおりです。

C#
new PerformanceCounter("Memory", "Available MBytes");

6-3. Processカテゴリで使う主なカウンター

プロセス監視でよく使うカテゴリはProcessです。

CategoryName: Process

代表的なカウンターは次のとおりです。

% Processor Time
プロセスのCPU使用率を取得します。

Working Set
プロセスが物理メモリ上で使用しているメモリ量を取得します。

Private Bytes
プロセス専用に割り当てられたメモリ量を取得します。

Thread Count
プロセス内のスレッド数を取得します。

Handle Count
プロセスが使用しているハンドル数を取得します。

ID Process
プロセスIDを取得します。

特定プロセスのCPU使用率を取得する場合は、次の組み合わせを使います。

C#
new PerformanceCounter("Process", "% Processor Time", "プロセスのインスタンス名");

6-4. Disk・Network監視で使えるカウンター例

ディスク監視では、PhysicalDiskカテゴリやLogicalDiskカテゴリを使います。

CategoryName: PhysicalDisk

代表的なカウンターは次のとおりです。

% Disk Time
Disk Reads/sec
Disk Writes/sec
Disk Read Bytes/sec
Disk Write Bytes/sec
Avg. Disk Queue Length

ネットワーク監視では、Network Interfaceカテゴリを使います。

CategoryName: Network Interface

代表的なカウンターは次のとおりです。

Bytes Received/sec
Bytes Sent/sec
Bytes Total/sec
Packets Received/sec
Packets Sent/sec

ネットワークインターフェース名は環境によって異なるため、まずインスタンス名を一覧表示すると便利です。

C#
using System;
using System.Diagnostics;

class Program
{
static void Main()
{
var category = new PerformanceCounterCategory("Network Interface");

foreach (string instance in category.GetInstanceNames())
{
Console.WriteLine(instance);
}
}
}

7. PerformanceCounterでよくあるエラーと解決策

7-1. Category does not existエラーの原因

Category does not existは、指定したカテゴリ名が存在しない場合に発生します。

よくある原因は次のとおりです。

C#
// カテゴリ名のスペルミス
// 日本語名で指定している
// 対象OSにそのカテゴリが存在しない
// 必要な機能やサービスが有効化されていない
// パフォーマンスカウンターが破損している

たとえば、CPU使用率を取得する場合は、カテゴリ名を"Processor"と指定します。

C#
new PerformanceCounter("Processor", "% Processor Time", "_Total");

存在するカテゴリを確認したい場合は、次のコードで一覧表示できます。

C#
using System;
using System.Diagnostics;

class Program
{
static void Main()
{
foreach (var category in PerformanceCounterCategory.GetCategories())
{
Console.WriteLine(category.CategoryName);
}
}
}

7-2. Instance does not existエラーの原因

Instance does not existは、指定したインスタンス名が存在しない場合に発生します。

よくある原因は次のとおりです。

C#
// プロセス名が間違っている
// 対象プロセスが起動していない
// 同名プロセスが複数あり、実際のインスタンス名が異なる
// インスタンス名に #1 や #2 が付いている
// 取得前にプロセスが終了した

インスタンス名を確認するには、次のコードを使います。

C#
using System;
using System.Diagnostics;

class Program
{
static void Main()
{
var category = new PerformanceCounterCategory("Process");

foreach (string instance in category.GetInstanceNames())
{
Console.WriteLine(instance);
}
}
}

プロセス監視では、プロセス名を直接指定するよりも、PIDからインスタンス名を探す方法のほうが安全です。

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

UnauthorizedAccessExceptionが発生する場合は、実行ユーザーに必要な権限がない可能性があります。

対処法は次のとおりです。

C#
// Visual Studioを管理者として実行する
// アプリケーションを管理者として実行する
// Windowsサービスの実行アカウントを確認する
// IISのアプリケーションプールIDを確認する
// 必要に応じてPerformance Monitor Usersグループへの追加を検討する

開発中だけ管理者実行で解決しても、本番環境ではサービスアカウントの権限が不足して失敗することがあります。デプロイ先の実行ユーザーで実際に動作確認することが重要です。

7-4. カウンター値が0または不正確になる場合

CPU使用率などで値が0になる場合は、初回のNextValue()をそのまま使っている可能性があります。

悪い例です。

C#
float cpu = cpuCounter.NextValue();
Console.WriteLine(cpu); // 初回は0になりやすい

改善例です。

C#
cpuCounter.NextValue();
Thread.Sleep(1000);
float cpu = cpuCounter.NextValue();
Console.WriteLine(cpu);

また、取得間隔が短すぎると値が安定しないことがあります。CPU使用率やネットワーク使用量など、時間差分が必要なカウンターでは、1秒程度の間隔を空けて取得するのが基本です。

7-5. パフォーマンスカウンターが壊れている場合の復旧方法

Windows環境によっては、パフォーマンスカウンター自体が破損していることがあります。その場合、正しいカテゴリ名やカウンター名を指定してもエラーが発生することがあります。

管理者権限のコマンドプロンプトで、次のコマンドを実行して復旧できる場合があります。

cmd
lodctr /R

その後、必要に応じてWMIの再同期を行います。

cmd
winmgmt /resyncperf

実行後は、アプリケーションやサービスを再起動して確認してください。サーバー環境では、事前に管理者や運用担当者と相談したうえで実行しましょう。

8. 実用的な監視アプリへの組み込み方

8-1. CPU・メモリ使用率をログ出力する実装例

CPU使用率と利用可能メモリを定期的に取得し、CSV形式でログに出力する例です。

C#
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;

class Program
{
static void Main()
{
using var cpuCounter = new PerformanceCounter(
"Processor",
"% Processor Time",
"_Total"
);

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

string logPath = "performance_log.csv";

if (!File.Exists(logPath))
{
File.WriteAllText(logPath, "Timestamp,CpuUsagePercent,AvailableMemoryMB" + Environment.NewLine);
}

cpuCounter.NextValue();
Thread.Sleep(1000);

Console.WriteLine("ログ出力を開始します。Ctrl+Cで終了してください。");

while (true)
{
float cpuUsage = cpuCounter.NextValue();
float availableMemory = memoryCounter.NextValue();

string line = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss},{cpuUsage:F2},{availableMemory:F0}";

File.AppendAllText(logPath, line + Environment.NewLine);

Console.WriteLine(line);

Thread.Sleep(5000);
}
}
}

このコードでは、5秒ごとにCPU使用率と利用可能メモリを取得し、performance_log.csvに追記します。簡易的な監視ログとして利用できます。

8-2. 閾値を超えた場合にアラートを出す実装例

CPU使用率が一定値を超えた場合にアラートを出す例です。

C#
using System;
using System.Diagnostics;
using System.Threading;

class Program
{
static void Main()
{
const float cpuThreshold = 80.0f;

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

cpuCounter.NextValue();
Thread.Sleep(1000);

while (true)
{
float cpuUsage = cpuCounter.NextValue();

Console.WriteLine($"{DateTime.Now:HH:mm:ss} CPU: {cpuUsage:F2}%");

if (cpuUsage >= cpuThreshold)
{
Console.WriteLine($"警告: CPU使用率が閾値 {cpuThreshold}% を超えました。");
}

Thread.Sleep(1000);
}
}
}

メール通知やSlack通知と組み合わせれば、簡易的なアラート機能を実装できます。ただし、本格的な監視基盤として使う場合は、ログのローテーション、通知の抑制、連続発生時の扱い、復旧通知なども設計する必要があります。

8-3. Windows Forms / WPFで画面表示する方法

Windows FormsやWPFアプリで表示する場合は、UIスレッドをブロックしないように注意します。System.Windows.Forms.TimerDispatcherTimerを使って、一定間隔で画面を更新するのが基本です。

Windows Formsのイメージは次のとおりです。

C#
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;

public partial class MainForm : Form
{
private readonly PerformanceCounter _cpuCounter;

public MainForm()
{
InitializeComponent();

_cpuCounter = new PerformanceCounter(
"Processor",
"% Processor Time",
"_Total"
);

_cpuCounter.NextValue();

var timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;
timer.Tick += Timer_Tick;
timer.Start();
}

private void Timer_Tick(object? sender, EventArgs e)
{
float cpuUsage = _cpuCounter.NextValue();
labelCpu.Text = $"CPU使用率: {cpuUsage:F2}%";
}

protected override void OnFormClosed(FormClosedEventArgs e)
{
_cpuCounter.Dispose();
base.OnFormClosed(e);
}
}

WPFの場合は、DispatcherTimerを使うとUI更新がしやすくなります。

C#
using System;
using System.Diagnostics;
using System.Windows.Threading;

public partial class MainWindow
{
private readonly PerformanceCounter _cpuCounter;

public MainWindow()
{
InitializeComponent();

_cpuCounter = new PerformanceCounter(
"Processor",
"% Processor Time",
"_Total"
);

_cpuCounter.NextValue();

var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += (_, _) =>
{
float cpuUsage = _cpuCounter.NextValue();
CpuTextBlock.Text = $"CPU使用率: {cpuUsage:F2}%";
};
timer.Start();
}
}

UIアプリでは、監視処理が重くなると画面が固まる原因になります。複数のカウンターを大量に取得する場合は、バックグラウンド処理とUI更新を分ける設計にしましょう。

8-4. バックグラウンドサービスで監視する場合の注意点

WindowsサービスやWorker ServiceでPerformanceCounterを使う場合は、次の点に注意します。

C#
// 実行アカウントの権限
// サービス起動直後にカウンターが取得できない可能性
// 例外発生時にサービス全体を落とさない設計
// ログ出力先の権限
// Disposeの徹底
// 監視間隔を短くしすぎない

Worker Serviceで使う場合のイメージです。

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

public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private PerformanceCounter? _cpuCounter;
private PerformanceCounter? _memoryCounter;

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

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_cpuCounter = new PerformanceCounter(
"Processor",
"% Processor Time",
"_Total"
);

_memoryCounter = new PerformanceCounter(
"Memory",
"Available MBytes"
);

_cpuCounter.NextValue();

await Task.Delay(1000, stoppingToken);

while (!stoppingToken.IsCancellationRequested)
{
try
{
float cpu = _cpuCounter.NextValue();
float memory = _memoryCounter.NextValue();

_logger.LogInformation(
"CPU: {Cpu:F2}%, Available Memory: {Memory:F0} MB",
cpu,
memory
);
}
catch (Exception ex)
{
_logger.LogError(ex, "パフォーマンスカウンターの取得に失敗しました。");
}

await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}

public override void Dispose()
{
_cpuCounter?.Dispose();
_memoryCounter?.Dispose();
base.Dispose();
}
}

サービスでは、例外が発生しても監視ループが停止しないように、try-catchでログに残す設計が重要です。

9. PerformanceCounterを使う際の注意点と代替手段

9-1. PerformanceCounterのメリット・デメリット

PerformanceCounterのメリットは、Windows標準のパフォーマンス情報をC#から簡単に取得できることです。

C#
// メリット
// ・Windows標準のカウンターを利用できる
// ・CPU、メモリ、プロセス、ディスク、ネットワークを監視できる
// ・Windows Forms、WPF、Windowsサービスと相性がよい
// ・既存のWindows監視項目と対応しやすい

一方で、デメリットもあります。

C#
// デメリット
// ・Windows専用
// ・初回値が0になるカウンターがある
// ・カテゴリ名やインスタンス名の指定が分かりにくい
// ・権限不足で失敗することがある
// ・同名プロセスの扱いが面倒
// ・新規のクロスプラットフォーム監視には向かない

Windows専用の社内ツールやレガシーシステムの監視では便利ですが、新規のクラウドネイティブアプリやLinuxコンテナで動かすアプリでは、別の方法を検討したほうがよいでしょう。

9-2. LinuxやmacOSでは使えない点に注意

PerformanceCounterはWindowsのパフォーマンスカウンターを前提としたAPIです。そのため、LinuxやmacOSでは同じように利用できません。

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

C#
// System.Diagnostics.Process
// System.Diagnostics.Metrics
// EventCounter
// dotnet-counters
// OpenTelemetry
// Prometheus exporter
// OS別API

Windowsだけで動かす前提ならPerformanceCounterは便利ですが、将来的にLinuxサーバーやコンテナ環境へ移行する可能性があるなら、最初から抽象化しておくのがおすすめです。

9-3. Processクラスで代替できるケース

単純に自分自身や特定プロセスのメモリ使用量を取得したいだけであれば、PerformanceCounterではなくProcessクラスで十分なケースがあります。

C#
using System;
using System.Diagnostics;

class Program
{
static void Main()
{
using var process = Process.GetCurrentProcess();

long workingSet = process.WorkingSet64;
long privateMemory = process.PrivateMemorySize64;

Console.WriteLine($"Working Set: {workingSet / 1024.0 / 1024.0:F2} MB");
Console.WriteLine($"Private Memory: {privateMemory / 1024.0 / 1024.0:F2} MB");
}
}

CPU使用率も、Process.TotalProcessorTimeを使って一定時間の差分から算出できます。

C#
using System;
using System.Diagnostics;
using System.Threading;

class Program
{
static void Main()
{
using var process = Process.GetCurrentProcess();

TimeSpan startCpu = process.TotalProcessorTime;
DateTime startTime = DateTime.UtcNow;

Thread.Sleep(1000);

process.Refresh();

TimeSpan endCpu = process.TotalProcessorTime;
DateTime endTime = DateTime.UtcNow;

double cpuUsedMs = (endCpu - startCpu).TotalMilliseconds;
double totalMsPassed = (endTime - startTime).TotalMilliseconds;
double cpuUsage = cpuUsedMs / (Environment.ProcessorCount * totalMsPassed) * 100.0;

Console.WriteLine($"現在プロセスのCPU使用率: {cpuUsage:F2}%");
}
}

この方法は、PerformanceCounterに依存せず、特定プロセスの情報を取得したい場合に便利です。

9-4. EventCounter・OpenTelemetry・WMIとの使い分け

PerformanceCounter以外にも、C#や.NETでパフォーマンス情報を取得・公開する方法はいくつかあります。

EventCounterは、.NETアプリケーションのランタイム情報や独自メトリクスを扱う場合に使われます。dotnet-countersと組み合わせると、実行中の.NETプロセスのメトリクスを確認できます。

System.Diagnostics.Metricsは、.NET 6以降で利用できる新しいメトリクスAPIです。OpenTelemetryとの連携を前提にした監視基盤を作る場合に向いています。

OpenTelemetryは、メトリクス、ログ、トレースを統一的に扱うための仕組みです。クラウド環境、マイクロサービス、Kubernetes、Prometheus、Grafanaなどと連携する場合に有力です。

WMIは、Windowsの管理情報を取得するための仕組みです。OS情報、ハードウェア情報、サービス情報など、PerformanceCounterとは違う範囲の情報を取得したい場合に使えます。

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

C#
// WindowsのCPU・メモリ・プロセスを簡単に監視したい
// => PerformanceCounter

// 自分の.NETアプリのメモリやGC、例外、リクエスト数を監視したい
// => EventCounter / dotnet-counters

// 新規開発でメトリクスを設計したい
// => System.Diagnostics.Metrics

// クラウド監視基盤と連携したい
// => OpenTelemetry

// Windowsの管理情報を幅広く取得したい
// => WMI

// 単純にプロセスのメモリやCPU時間を取得したい
// => Processクラス

まとめ

C#でCPU使用率やメモリ使用率を取得する場合、Windows環境ではPerformanceCounterが便利です。Processorカテゴリの% Processor Timeを使えばシステム全体のCPU使用率を取得でき、MemoryカテゴリのAvailable MBytesを使えば利用可能メモリを取得できます。特定プロセスを監視したい場合は、Processカテゴリの% Processor TimeWorking SetPrivate Bytesなどを利用します。

基本的なCPU使用率の取得コードは次のとおりです。

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

cpuCounter.NextValue();
Thread.Sleep(1000);

float cpuUsage = cpuCounter.NextValue();

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

メモリの空き容量を取得するコードは次のとおりです。

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

float availableMemory = memoryCounter.NextValue();

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

一方で、PerformanceCounterはWindows専用であり、LinuxやmacOSでは使えません。また、初回のNextValue()0になる、同名プロセスのインスタンス名に注意が必要、権限不足でエラーになることがあるなど、実装時の落とし穴もあります。

Windows専用の監視ツール、Windows Forms / WPFアプリ、WindowsサービスでCPUやメモリを監視したい場合は、PerformanceCounterは今でも実用的な選択肢です。クロスプラットフォーム対応やクラウド監視を重視する場合は、System.Diagnostics.Metrics、EventCounter、OpenTelemetry、Processクラスなどの代替手段も含めて検討するとよいでしょう。