Windowsでは管理ツールにシステムのパフォーマンスを計測・収集するツールとしてパフォーマンスモニタ(perfmon.exe)が用意されていますが、.NET FrameworkのSystem.Diagnostics.PerformanceCounterクラスを使うことにより、CPU使用率やメモリの使用状況など、システムやプロセス・スレッドのパフォーマンス情報をコード上で収集することができるようになります。

注意:Windows 9x系では、PerformanceCounterを使ったデータの収集はサポートされていません。 また、Monoではカテゴリ・カウンタの種類やプラットフォームによってはサポートされていない、もしくは未実装のものがあるようです。 カテゴリ・カウンタがサポートされているか調べる方法についてはパフォーマンスカウンタのカテゴリで解説しています。

PerformanceCounterクラス

パフォーマンス情報の計測

まずはPerformanceCounterクラスを使った簡単な例として、CPU使用率の測定を行ってみます。 次の例では、1秒ごとにCPU使用率を取得し、表示しています。

using System;
using System.Diagnostics;
using System.Threading;

class Sample {
  static void Main()
  {
    // カテゴリ名・カウンタ名・インスタンス名を指定してPerformanceCounterのインスタンスを作成
    var pc = new PerformanceCounter("Processor", "% Processor Time", "_Total");

    for (;;) {
      // カウンタの値を取得して、値を0.0~1.0の範囲にする
      var percent = pc.NextValue() / 100.0f;

      // パーセント値として表示
      Console.WriteLine("{0,8:P2}", percent);

      Thread.Sleep(1000);
    }
  }
}
Imports System
Imports System.Diagnostics
Imports System.Threading

Class Sample
  Shared Sub Main()
    ' カテゴリ名・カウンタ名・インスタンス名を指定してPerformanceCounterのインスタンスを作成
    Dim pc As New PerformanceCounter("Processor", "% Processor Time", "_Total")

    Do
      ' カウンタの値を取得して、値を0.0~1.0の範囲にする
      Dim usage As Single = pc.NextValue() / 100.0

      ' パーセント値として表示
      Console.WriteLine("{0,8:P2}", usage)

      Thread.Sleep(1000)
    Loop
  End Sub
End Class
実行結果
   0.00%
  13.72%
   8.53%
  10.68%
   0.88%
   4.69%
   7.03%
  24.06%
  17.97%
  10.68%
  11.80%
^C

実行すると上記のような結果が得られます。 システムモニタのグラフと合わせて確認すると、結果が比較しやすいと思います。

上記のコードを簡単に解説すると、まずPerformanceCounterのインスタンス作成のところで、コンストラクタに計測する値のカテゴリ名・カウンタ名・インスタンス名を指定しています。 具体的には追って説明しますが、この部分を変えることで様々な値を計測することができます。 この例では、カテゴリ名が"Processor"でプロセッサ(CPU)、カウンタ名が"% Processor Time"でプロセッサ時間(%)、インスタンス名が"_Total"で全CPUの合計時間を計測するPerformanceCounterを作成していることになります。

計測した値を取得するためのメソッドがNextValueです。 CPU使用率の場合は戻り値は0.0から100.0までの値となりますが、計測する値の種類によって異なります。 この例では0.0から1.0までの値にスケーリングしなおしてから表示しています。

なお、実行結果では一番はじめの計測値が0.00%となっているのは、NextValueメソッドが「現在の値と以前の値の差を計測間隔時間で割ったもの」を算出結果として返すためです。 そのため、最初の呼び出しでは計算に使うための値がないため、0が返されます。 NextValueメソッドの戻り値の意味はカウンタが計測する値の種類によって変わりますが、CounterTypeプロパティを参照することでカウンタの値の種類を知ることができます。

また、リファレンスによると、NextValueメソッドの呼び出しは1秒間隔で行うことが推奨されています。 これより短い間隔で更新した場合は、値が更新されない可能性があります。 最初のNextValueの呼び出しの際、環境によっては結果が得られるまでに時間がかかる場合もあるようです。

フォームデザイナでの配置

PerformanceCounterはフォームデザイナからフォーム上に配置できるコンポーネントになっています。 配置する場合は、ツールボックスの「コンポーネント」から「PerformanceCounter」を選択します。 なお、PerformanceCounterはGUIを持たないので、配置してもフォーム上には現れません。

ツールボックス中のPerformanceCounter

次に、CategoryName、CounterName、InstanceName等のプロパティ値を指定します。 まず、CategoryNameの一覧からどれか一つ選択すると、そのカテゴリに含まれる計測値がCounterNameの一覧に表示されます。 この中から計測したい値を選択します。 さらに、必要に応じてInstanceNameも選択します。

PerformanceCounterのプロパティの設定

あとは、NextValueメソッドを呼び出し、結果を表示するようにするなど、必要なコードを記述します。 呼び出し方は、すでに説明した例の場合と同様です。

実行結果

カテゴリ・カウンタ名・インスタンス名

カテゴリ名・カウンタ名・インスタンス名にそれぞれ適切な値を指定することで、様々な値を計測することができるようになります。

CategoryName, CounterName, InstanceName

先の例ではコンストラクタでカテゴリ名等を指定していますが、指定した値はプロパティで参照でき、また設定をすることもできます。 それぞれ対応するプロパティはCategoryNameCounterNameInstanceNameで、次の例のようにコンストラクタに何も指定せずインスタンスを作成し、後からカテゴリ等のプロパティを個別に設定することもできます。 次の例は、先のコードと同じ動作となります。

using System;
using System.Diagnostics;
using System.Threading;

class Sample {
  static void Main()
  {
    // PerformanceCounterのインスタンスを作成
    var pc = new PerformanceCounter();

    pc.CategoryName = "Processor"; // カテゴリ名
    pc.CounterName  = "% Processor Time"; // カウンタ名
    pc.InstanceName = "_Total"; // インスタンス名

    // 以下略
  }
}
Imports System
Imports System.Diagnostics
Imports System.Threading

Class Sample
  Shared Sub Main()
    ' PerformanceCounterのインスタンスを作成
    Dim pc As New PerformanceCounter()

    pc.CategoryName = "Processor" ' カテゴリ名
    pc.CounterName  = "% Processor Time" ' カウンタ名
    pc.InstanceName = "_Total" ' インスタンス名

    ' 以下略
  End Sub
End Class

InstanceNameの例:CPUコア毎の使用率の取得

インスタンス名(InstanceName)は、カテゴリ・カウンタの種類は同じでも、特定の対象を指定して値を計測したい場合に指定します。 例えば、マルチコアCPUを使用している環境の場合は、プロセッサそれぞれの使用率を取得することができます。 この場合、インスタンス名には"_Total"ではなくプロセッサのインデックス、つまり"0"や"1"などを指定することでプロセッサそれぞれの使用率を取得することができます。

次の例ではEnvironment.ProcessorCountを参照してプロセッサ数を取得し、それぞれのプロセッサ毎の使用率を計測するPerformanceCounterを作成して、値を計測しています。

using System;
using System.Diagnostics;
using System.Threading;

class Sample {
  static void Main()
  {
    // プロセッサ数分のPerformanceCounterを格納する配列
    var pcs = new PerformanceCounter[Environment.ProcessorCount];

    for (var index = 0; index < pcs.Length; index++) {
      // プロセッサ毎の使用率を計測するPerformanceCounterを作成
      pcs[index] = new PerformanceCounter("Processor", "% Processor Time", index.ToString());
    }

    for (;;) {
      foreach (var pc in pcs) {
        Console.Write("{0,8:P2} ", pc.NextValue() / 100.0f);
      }

      Console.WriteLine();

      Thread.Sleep(1000);
    }
  }
}
Imports System
Imports System.Diagnostics
Imports System.Threading

Class Sample
  Shared Sub Main()
    ' プロセッサ数分のPerformanceCounterを格納する配列
    Dim pcs As PerformanceCounter() = New PerformanceCounter(Environment.ProcessorCount - 1) {}

    For index As Integer = 0 To pcs.Length - 1
      ' プロセッサ毎の使用率を計測するPerformanceCounterを作成
      pcs(index) = New PerformanceCounter("Processor", "% Processor Time", index.ToString())
    Next

    Do
      For Each pc As PerformanceCounter In pcs
        Console.Write("{0,8:P2} ", pc.NextValue() / 100.0)
      Next

      Console.WriteLine()

      Thread.Sleep(1000)
    Loop
  End Sub
End Class
実行結果
   0.00%    0.00%
  29.89%   22.56%
  24.03%   22.63%
  53.58%   35.38%
  21.61%   22.03%
   3.22%    6.52%
  33.01%   12.59%
   1.66%    4.78%
  17.27%    4.00%
  60.38%   31.85%
  55.08%    8.66%
  49.81%   25.44%
  61.17%   28.45%
^C

CounterNameの例:ユーザモード・特権モードの使用率・アイドル率の取得

カウンタ名(CounterName)では、計測する値の種類を指定します。 例えばカテゴリがCPU情報("Processor")の場合は、ユーザモード・特権モードの使用率・アイドル率などを計測することができます。 次の例では、それぞれの使用率を取得するPerformanceCounterを作成し、値を計測しています。 また、100%からアイドル率を差し引くことで全体の使用率も求めています。

この例ではインスタンス名(InstanceName)に"_Total"を指定していますが、既に解説したとおり、"0"や"1"などを指定することでプロセッサ毎に計測することもできます。

using System;
using System.Diagnostics;
using System.Threading;

class Sample {
  static void Main()
  {
    var pcCpuPriv = new PerformanceCounter("Processor", "% Privileged Time", "_Total"); // 特権モードの使用率
    var pcCpuUser = new PerformanceCounter("Processor", "% User Time", "_Total"); // ユーザモードの使用率
    var pcCpuIdle = new PerformanceCounter("Processor", "% Idle Time", "_Total"); // アイドル率

    Console.WriteLine("  Priv[%]  User[%]  Idle[%] Total[%]");

    for (;;) {
      var usagePriv = pcCpuPriv.NextValue() / 100.0;
      var usageUser = pcCpuUser.NextValue() / 100.0;
      var usageIdle = pcCpuIdle.NextValue() / 100.0;

      Console.WriteLine("{0,8:P2} {1,8:P2} {2,8:P2} {3,8:P2}",
                        usagePriv,
                        usageUser,
                        usageIdle,
                        1.0f - usageIdle);

      Thread.Sleep(1000);
    }
  }
}
Imports System
Imports System.Diagnostics
Imports System.Threading

Class Sample
  Shared Sub Main()
    Dim pcCpuPriv As New PerformanceCounter("Processor", "% Privileged Time", "_Total") ' 特権モードの使用率
    Dim pcCpuUser As New PerformanceCounter("Processor", "% User Time", "_Total") ' ユーザモードの使用率
    Dim pcCpuIdle As New PerformanceCounter("Processor", "% Idle Time", "_Total") ' アイドル率

    Console.WriteLine("  Priv[%]  User[%]  Idle[%] Total[%]")

    Do
      Dim usagePriv As Single = pcCpuPriv.NextValue() / 100.0
      Dim usageUser As Single = pcCpuUser.NextValue() / 100.0
      Dim usageIdle As Single = pcCpuIdle.NextValue() / 100.0

      Console.WriteLine("{0,8:P2} {1,8:P2} {2,8:P2} {3,8:P2}", _
                        usagePriv, _
                        usageUser, _
                        usageIdle, _
                        1.0 - usageIdle)

      Thread.Sleep(1000)
    Loop
  End Sub
End Class
実行結果
  Priv[%]  User[%]  Idle[%] Total[%]
   0.00%    0.00%    0.00%  100.00%
   1.56%    0.00%   98.34%    1.66%
   1.56%    0.78%   98.55%    1.45%
   0.78%    0.00%   99.03%    0.97%
   2.34%    0.78%   96.88%    3.13%
   3.90%    1.56%   94.35%    5.65%
   3.12%    0.78%   95.63%    4.37%
   5.45%    0.78%   93.75%    6.25%
   3.12%    1.56%   95.22%    4.78%
   0.00%    0.78%   99.12%    0.88%
   2.34%    0.78%   96.69%    3.31%
   3.12%    0.78%   96.00%    4.00%
^C

CategoryNameの例・CPU以外のパフォーマンス情報の収集

カテゴリ名(CategoryName)を変えることで、CPU情報("Processor"カテゴリ)以外にも、様々なパフォーマンス情報の収集を行うことができます。 次の例では、システムの全プロセス数、使用可能なメモリ、ディスクからの秒間読み込みバイト数を計測・表示しています。

using System;
using System.Diagnostics;
using System.Threading;

class Sample {
  static void Main()
  {
    // システムの全プロセス数
    var pcProcesses = new PerformanceCounter("System", "Processes", string.Empty);
    // 使用可能なメモリ (MBytes)
    var pcMemoryAvailable = new PerformanceCounter("Memory", "Available MBytes", string.Empty);
    // Cドライブからの秒間読み込みバイト数
    var pcDiskReadBps = new PerformanceCounter("PhysicalDisk", "Disk Read Bytes/sec", "0 C:");

    for (;;) {
      Console.WriteLine("{0,8} {1,8}[MB] {2,8}[Bytes/sec]",
                        pcProcesses.NextValue(),
                        pcMemoryAvailable.NextValue(),
                        pcDiskReadBps.NextValue());

      Thread.Sleep(1000);
    }
  }
}
Imports System
Imports System.Diagnostics
Imports System.Threading

Class Sample
  Shared Sub Main()
    ' システムの全プロセス数
    Dim pcProcesses As New PerformanceCounter("System", "Processes", String.Empty)
    ' 使用可能なメモリ (MBytes)
    Dim pcMemoryAvailable As New PerformanceCounter("Memory", "Available MBytes", String.Empty)
    ' Cドライブからの秒間読み込みバイト数
    Dim pcDiskReadBps As New PerformanceCounter("PhysicalDisk", "Disk Read Bytes/sec", "0 C:")

    Do
      Console.WriteLine("{0,8} {1,8}[MB] {2,8}[Bytes/sec]", _
                        pcProcesses.NextValue(), _
                        pcMemoryAvailable.NextValue(), _
                        pcDiskReadBps.NextValue())

      Thread.Sleep(1000)
    Loop
  End Sub
End Class
実行結果
      67     2557[MB]        0[Bytes/sec]
      67     2554[MB]  1113750[Bytes/sec]
      67     2553[MB] 829922.4[Bytes/sec]
      67     2552[MB] 417267.6[Bytes/sec]
      67     2551[MB] 623574.4[Bytes/sec]
      67     2551[MB] 493810.3[Bytes/sec]
      67     2550[MB] 490272.9[Bytes/sec]
      67     2551[MB] 461774.7[Bytes/sec]
      67     2550[MB] 692728.8[Bytes/sec]
      67     2551[MB] 391742.1[Bytes/sec]
      67     2550[MB] 380394.1[Bytes/sec]
      64     2553[MB] 539733.5[Bytes/sec]
      64     2553[MB] 519753.1[Bytes/sec]
      64     2554[MB]   730525[Bytes/sec]
^C

CPU使用率の場合では、NextValueメソッドからパーセント値が返されますが、この例のようにカウンタの種類に応じて返される値の単位が変わるため、表示する場合などは適宜対応する必要があります。 値の単位や種類についてはCounterTypeで解説します。

CategoryName, CounterName, InstanceNameに指定できる値

パフォーマンスカウンタで計測できる値は、プロセス数や空きメモリなどどの環境でも共通して計測できるもののほか、ドライブ毎のディスクIOなどマシンによって計測できる対象が変わるものがあります。 そのため、カテゴリ名・カウンタ名・インスタンス名に指定できる値は、環境によって変わってきます。
具体的にどの値が指定できるかどうかを調べるには、パフォーマンスモニタ(perfmon.exe)を使う他にも、コード上からはPerformanceCounterCategoryクラスのメンバを使うことができます。 詳しくはパフォーマンスカウンタのカテゴリで解説します。

計測値の種類 (CounterType)

カウンタが計測する値の種類を調べるには、CounterTypeプロパティを参照します。 このプロパティはPerformanceCounterType列挙体の値となっていて、計測される生データの種類とNextValueの戻り値の算出方法を表すものです。 このプロパティから、NextValueメソッドが現在の値と以前の値の差を元にした値を返すのか、NextValueメソッドを呼び出した時点の計測値が返されるのかなどを知ることができます。

PerformanceCounterTypeの代表的なものとして以下のようなものがあります。 なお、生データから計算される値が整数値となる場合でも、NextValueメソッドでは浮動小数点型に変換された値が返されます。

CounterTypeの値と計測値
CounterTypeの値 計測値とNextValueの戻り値
Timer100Ns
Timer100NsInverse
直前の呼び出し時との計測値の差を基に求めた、100%に対する割合。 パーセント値(0~100)。
CountPerTimeInterval32
CountPerTimeInterval64
RateOfCountsPerSecond32
RateOfCountsPerSecond64
直前の呼び出し時との計測値の差を基に求めた、単位時間あたりの量。 実数値。
CounterDelta32
CounterDelta64
直前の呼び出し時との計測値の差。 整数値または実数値。
NumberOfItems32
NumberOfItems64
NumberOfItemsHEX32
NumberOfItemsHEX64
呼び出しを行った時点での計測値。 整数値。

次の例では、いくつかのカウンタを作成し、それぞれのCounterTypeを表示しています。

using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    foreach (var pc in new[] {
      new PerformanceCounter("Processor", "% Processor Time", "_Total"),
      new PerformanceCounter("System", "Processes", string.Empty),
      new PerformanceCounter("Memory", "Available MBytes", string.Empty),
      new PerformanceCounter("PhysicalDisk", "Disk Read Bytes/sec", "0 C:"),
    }) {
      Console.WriteLine("{0,-15} {1,-20} {2,-10} : {3}",
                        pc.CategoryName,
                        pc.CounterName,
                        pc.InstanceName,
                        pc.CounterType);
    }
  }
}
Imports System
Imports System.Diagnostics

Class Sample
  Shared Sub Main()
    For Each pc As PerformanceCounter In New PerformanceCounter() { _
      New PerformanceCounter("Processor", "% Processor Time", "_Total"), _
      New PerformanceCounter("System", "Processes", String.Empty), _
      New PerformanceCounter("Memory", "Available MBytes", String.Empty), _
      New PerformanceCounter("PhysicalDisk", "Disk Read Bytes/sec", "0 C:") _
    }
      Console.WriteLine("{0,-15} {1,-20} {2,-10} : {3}", _
                        pc.CategoryName, _
                        pc.CounterName, _
                        pc.InstanceName, _
                        pc.CounterType)
    Next
  End Sub
End Class
実行結果
Processor       % Processor Time     _Total     : Timer100NsInverse
System          Processes                       : NumberOfItems32
Memory          Available MBytes                : NumberOfItems64
PhysicalDisk    Disk Read Bytes/sec  0 C:       : RateOfCountsPerSecond64

計測値の説明 (CounterHelp)

CounterHelpプロパティを参照すると、パフォーマンスカウンタの計測値についての説明を取得することができます。

次の例では、先の例で使用したカウンタのCounterHelpプロパティを参照し、説明を表示しています。

using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    foreach (var pc in new[] {
      new PerformanceCounter("Processor", "% Processor Time", "_Total"),
      new PerformanceCounter("System", "Processes", string.Empty),
      new PerformanceCounter("Memory", "Available MBytes", string.Empty),
      new PerformanceCounter("PhysicalDisk", "Disk Read Bytes/sec", "0 C:"),
    }) {
      Console.WriteLine("[{0,-15} {1,-20} {2,-10}]",
                        pc.CategoryName,
                        pc.CounterName,
                        pc.InstanceName);
      Console.WriteLine(pc.CounterHelp);
      Console.WriteLine();
    }
  }
}
Imports System
Imports System.Diagnostics

Class Sample
  Shared Sub Main()
    For Each pc As PerformanceCounter In New PerformanceCounter() { _
      New PerformanceCounter("Processor", "% Processor Time", "_Total"), _
      New PerformanceCounter("System", "Processes", String.Empty), _
      New PerformanceCounter("Memory", "Available MBytes", String.Empty), _
      New PerformanceCounter("PhysicalDisk", "Disk Read Bytes/sec", "0 C:") _
    }
      Console.WriteLine("[{0,-15} {1,-20} {2,-10}]", _
                        pc.CategoryName, _
                        pc.CounterName, _
                        pc.InstanceName)
      Console.WriteLine(pc.CounterHelp)
      Console.WriteLine()
    Next
  End Sub
End Class
実行結果
[Processor       % Processor Time     _Total    ]
% Processor Time は、プロセッサがアイドル以外のスレッドを実行するために使用した経過時間の割合をパーセントで表示します。プロセッサがアイドル スレッドの実行に使用する時間の割合を計測し、その値を 100% から引いて算出します (各プロセッサには、実行するスレッドが他にない場合にサイクルを消費するアイドル スレッドがあります)。このカウンターはプロセッサの処理状況を示す主な指標で、サンプリング間隔で計測されたビジー時間の平均割合をパーセントで表示します。プロセッサがアイドル状態かどうかの判断は、システム時計の内部サンプリング間隔 (10 ミリ秒) で実行されます。そのため、現在の高速プロセッサでは、システム時計のサンプリング間隔の間に、プロセッサがスレッド処理に多くの時間を費やしている可能性があり、% Processor Time でプロセッサ使用量が少なく見積もられる場合があります。処理負荷に基づくタイマー アプリケーションは、サンプルが取得された直後にタイマーが通知されるため正確に計測されない可能性の高いアプリケーションの一例です。

[System          Processes                      ]
データの収集時にコンピューター上にあるプロセス数です。この値はある時点でのカウントで、ある時間間隔での平均値ではありません。各プロセスは任意のプログラムの実行状態を表します。

[Memory          Available MBytes               ]
プロセスへの割り当て、またはシステムの使用にすぐに利用可能な物理メモリのサイズをメガバイト数で表示します。スタンバイ (キャッシュ済み)、空き、ゼロ ページの一覧に署名されたメモリの合計です。空きメモリは使用可能なメモリです。メモリ マネージャーについての詳細は、MSDN または『Windows Server 2003 リソース キット』の「システム パフォーマンスとトラブルシューティング ガイド」を参照してください。

[PhysicalDisk    Disk Read Bytes/sec  1 C:      ]
読み取り操作中にディスクからバイトが転送される速度です。