PerformanceCounterでCPU使用率をゲッツ!

注意:
この文書は以前「.NETでいきまっしょい!」で公開していたものですが、公開以降メンテナンスされていません。 今や古い情報となった内容が記載されている場合があるのでご注意ください。

0.サンプルコードの動作環境について


 これから紹介するサンプルコードは、一部OSでしか動作しません。 一応、CPU使用率測定のサンプルはWindows 2000またはWindows XP、及びその双方で動作することをを確認し、Windows Meでは動作しないことをしました。 動作確認の結果は赤字で記述しているので注意してください。

1.PerformanceCounterでのCPU使用率測定

(このサンプルはWindows 2000, Windows XP上でのみ動作します)
 System.Diagnostics名前空間にあるPerformanceCounterクラスを使用すると、システムパフォーマンスに関する様々な情報を測定することができます。 まずは、その使用方法の説明を兼ねてCPU使用率の測定をしてみようと思います。 次のコードはそれを行うものです。
ソースコード
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
Option Strict On

Module PerformanceCounter

    Sub Main()

        ' PerformanceCounterクラスのインスタンスを作成
        Dim pc As New System.Diagnostics.PerformanceCounter()

        ' プロパティを設定
        With pc

            .CategoryName = "Processor"
            .CounterName = "% Processor Time"
            .InstanceName = "_Total"

        End With

        ' 1秒おきに測定
        Do

            ' 得られた値を百分率にする
            Dim value As Single = pc.NextValue() / 100.0F

            ' 表示
            Console.WriteLine("{0:P2}", value)

            System.Threading.Thread.Sleep(1000)

        Loop

    End Sub

End Module
出力結果
0.00%
59.63%
23.76%
28.00%
26.00%
31.00%
33.00%
19.61%
23.00%
26.00%
29.00%
26.00%
21.00%
19.00%
41.90%
47.00%
45.00%

 このコードは無限にループを繰り返すので、手動で停止してください。 この結果だけでは本当に測定できているかわからないので、実際にご自分のマシン上でこのコードを実行し、出力される値とシステムモニタで示される値とを比較すると良いかと思います。
 ソースコードの簡単な説明をすると、まず8行目でPerformanceCounterクラスのインスタンスを作成します。 続いて、いくつかのプロパティ、つまりCategoryName、CounterName、InstanceNameを指定します。 これらを簡単に説明すると、CategoryNameにはCPU、メモリ、プロセスなどを指定し、CounterNameにはカテゴリで指定したものに対して計測する量を指定し、InstanceNameにはカテゴリがインスタンスに分割されている場合に指定します。
 今回の場合は、計測する対象が「Processor(CPU)」、計測する値が「% Processor Time(プロセッサ時間[%])」、計測する対象のインスタンスは「Total(合計)」になります。 もしCPUを複数搭載したPC上で起動した場合、InstanceNameにCPUのインデックス、つまり"0"や"1"などを指定すれば、CPU毎の使用率を取得することもできます。
 NextValue()メソッドが実際に計測値を取得するメソッドですが、この結果で一番はじめの計測値が「0.00%」になっているのは、NextValue()メソッドが「現在の値と以前の値の差を計測間隔時間で割ったもの」を算出結果として返すためです。 バグでは無いので注意してください。

2.CPU使用率の別の測定法

(このサンプルはWindows XP上でのみ動作します)
 先ほどのコードでは直接CPU使用率を測定していましたが、アイドル時間(CPU非使用率)から逆算することができるので、次のコードでそれをやってみます。 比較のためにCPU使用率を直接取得した場合の結果も併記してみます。
ソースコード
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
Option Strict On

Module PerformanceCounter

    Sub Main()

        ' PerformanceCounterクラスのインスタンスを作成
        Dim pc1 As New System.Diagnostics.PerformanceCounter()
        Dim pc2 As New System.Diagnostics.PerformanceCounter()

        ' プロパティを設定
        With pc1

            .CategoryName = "Processor"
            .CounterName = "% Processor Time"
            .InstanceName = "_Total"

        End With

        With pc2

            .CategoryName = "Processor"
            .CounterName = "% Idle Time"
            .InstanceName = "_Total"

        End With

        ' 1秒おきに測定
        Do

            ' 得られた値を百分率にする
            Dim value1 As Single = pc1.NextValue() / 100.0F
            Dim value2 As Single = pc2.NextValue() / 100.0F

            ' 使用率[%] = 1.0 - 非使用率[%]
            value2 = 1.0F - value2

            ' 表示
            Console.WriteLine("{0:P2}, {1:P2}", value1, value2)

            System.Threading.Thread.Sleep(1000)

        Loop

    End Sub

End Module
出力結果
0.00%, 100.00%
54.13%, 52.38%
22.77%, 22.77%
19.00%, 19.00%
19.00%, 19.00%
23.00%, 23.00%
28.00%, 28.00%
26.00%, 26.00%
25.00%, 25.00%
19.00%, 19.00%
24.00%, 24.00%
38.00%, 38.00%
48.04%, 48.04%
37.00%, 37.00%
23.00%, 23.00%
28.00%, 28.71%
23.00%, 23.00%
31.68%, 31.00%

 ソースコードはPerformanceCounterのインスタンスを一つ増やしたことと、1(全体)からアイドル時間を引けば使用率になることをのぞけばすべて前のコードと一緒なので大丈夫かと思います。 実行結果で最初だけ100%になるのは先ほど説明したことが原因であるので、これも説明は省略します。

3.システム・ユーザー別のCPU使用率

(このサンプルはWindows XP上でのみ動作を確認しました。 それ以外のOSでの動作確認は行っていません。)
 CounterNameプロパティに「User(ユーザー)」を指定することで、ユーザー自身が実際に使用している分のCPUの使用率を取得できます。
ソースコード
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
Option Strict On

Module PerformanceCounter

    Sub Main()

        ' PerformanceCounterクラスのインスタンスを作成
        Dim pc1 As New System.Diagnostics.PerformanceCounter()
        Dim pc2 As New System.Diagnostics.PerformanceCounter()

        ' プロパティを設定
        With pc1

            .CategoryName = "Processor"
            .CounterName = "% Processor Time"
            .InstanceName = "0"

        End With

        With pc2

            .CategoryName = "Processor"
            .CounterName = "% User Time"
            .InstanceName = "0"

        End With

        ' 1秒おきに測定
        Do

            ' 得られた値を百分率にする
            Dim value1 As Single = pc1.NextValue() / 100.0F
            Dim value2 As Single = pc2.NextValue() / 100.0F

            ' 表示
            Console.WriteLine("{0:P2}, {1:P2}", value1, value2)

            System.Threading.Thread.Sleep(1000)

        Loop

    End Sub

End Module
出力結果
0.00%, 0.00%
19.42%, 10.58%
21.78%, 12.00%
21.00%, 12.00%
26.00%, 16.00%
31.00%, 24.00%
28.00%, 17.00%
24.00%, 14.00%
30.00%, 16.83%
59.41%, 33.00%
47.57%, 25.96%
44.55%, 19.00%
23.00%, 15.00%
24.00%, 13.00%
24.00%, 13.86%
48.51%, 29.63%
43.12%, 26.47%
28.71%, 14.00%

 左が合計の使用率、右がユーザーの使用率です。

4.その他の値の計測

(このサンプルはWindows XP上でのみ動作を確認しました。 それ以外のOSでの動作確認は行っていません。)
 CategoryName、CounterName、InstanceNameに適当な値を指定することで、様々な値を取得できるので、CPU使用率以外の値も計測することができます。 次のコードは、ハンドル、スレッド、プロセスの各合計値を取得するものです。 今回はプロパティではなくコンストラクタでカテゴリ等をしています。 コンストラクタで指定する場合は、左のパラメータから「カテゴリ」、「計測量」、「インスタンス」の順で指定していきます。
ソースコード
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
Option Strict On

Module PerformanceCounter

    Sub Main()

        ' PerformanceCounterクラス
        Dim handle As System.Diagnostics.PerformanceCounter
        Dim threads As System.Diagnostics.PerformanceCounter
        Dim processes As System.Diagnostics.PerformanceCounter

        ' インスタンスを作成
        handle = New System.Diagnostics.PerformanceCounter("Process", "Handle Count", "_Total")
        threads = New System.Diagnostics.PerformanceCounter("System", "Threads", "")
        processes = New System.Diagnostics.PerformanceCounter("System", "Processes", "")

        ' 項目名を表示
        Console.WriteLine("ハンドル スレッド プロセス")

        ' 1秒おきに測定
        Do

            Dim h As Integer = CInt(handle.NextValue())
            Dim th As Integer = CInt(threads.NextValue())
            Dim p As Integer = CInt(processes.NextValue())

            ' 表示
            Console.WriteLine(" {0:D5}    {1:D3}      {2:D3}", h, th, p)

            System.Threading.Thread.Sleep(1000)

        Loop

    End Sub

End Module
出力結果
ハンドル スレッド プロセス
 09911    498      044
 09938    500      045
 09980    499      045
 10028    500      046
 10041    499      046
 09924    498      045
 09928    498      045
 09929    497      044
 09947    498      045
 09899    497      044
 09903    497      044
 09907    497      044
 09911    497      044
 09915    497      044
 09919    497      044

 これらの値は「Windwosタスクマネージャ」で確認することができます。 実行結果と同じになるかを確認してみると良いでしょう。 さらにこのほかの値を取得する方法については次で説明します。

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

(このサンプルはWindows XP上でのみ動作を確認しました。 それ以外のOSでの動作確認は行っていません。
OSの種類により、カテゴリ、計測値のいくつかは取得できないものもあるかもしれません。)
 PerformanceCounterはフォームデザイナからも配置できます。 配置する場合は、ツールボックスの「コンポーネント」から「PerformanceCounter」を選択し、フォーム上の適当な場所に配置します。 PerformanceCounterはGUIを持たないので、配置してもフォーム上には現れません。
PerformanceCounter
PerformanceCounter

 次に、CategoryName、CounterName等のプロパティ値を指定します。 まず、カテゴリの一覧から適切なものをクリックすると、そのカテゴリに含まれる計測値がCounterNameの一覧として表示されます。 この中から計測したい値を選択します。
CategoryNameの指定
CategoryNameの指定
CounterNameの指定
CounterNameの指定

 これで計測を行う準備は整いました。 当然、カテゴリ、カウンタ名によってはインスタンスも指定する必要があるので忘れずに指定してください。 プロパティ設定が終わったら、後は適宜NextValue()メソッドを呼び出して値を取得するだけです。
 それでは早速サンプルを作ってみることにします。 次のコードでは一定間隔おきにフォーム上のラベルにCPU使用率を表示しています。 performanceCounterはPerformanceCounter型の変数で、CategoryNameには「Processor」、CounterNameには「% Processor Time」、InstanceNameには「_Total」を指定しています。
ソースコード
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
Option Strict On

Public Class Form1
    Inherits System.Windows.Forms.Form

#Region " Windows フォーム デザイナで生成されたコード "
#End Region

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        Dim timer As New Timer()

        timer.Interval = 1000
        AddHandler timer.Tick, AddressOf timer_Tick

        timer.Start()

    End Sub

    Private Sub timer_Tick(ByVal sender As Object, ByVal e As EventArgs)

        Dim value As Double = performanceCounter.NextValue / 100.0F

        labelPerformance.Text = "CPU使用率(合計): " + value.ToString("P2")

    End Sub

End Class
実行結果
実行結果

 なお、CategoryNameプロパティの選択項目を見ているとわかると思いますが、.NET Frameworkの言語共通ランタイム(CLR)のパフォーマンスも取得できることがわかると思います。