開発環境が整ったところで、ではまず「CPUの負荷を取得する」仕組みを実装してみることにしよう。この、CPUの負荷を取得するという作業、Windowsに用意されているPerformanceCounterと呼ばれるサービスを使うことで、簡単に実現できる。例えばコマンドラインで、

typeperf "Processor(_Total)\% Processor Time"

と入力すれば、毎秒あたりのシステム全体のCPU負荷率を表示してくれる(Photo01)し、

typeperf "Processor(1)\% Processor Time"

と入力すれば、特定のCPUを選んで表示させることも可能だ(Photo02)。余談ながら、typeperf -qを発行すれば、表示できる一覧が示される。これは要するにPerformance Counter(Photo03)で指定できるものが全部、typeperfでも表示できるという話である。ということで、このPerformance Counterの値をプログラムで取得してみようという話だ。

Photo01: システム全体の場合、プロセッサ番号に"_Total"を指定する。

Photo02: プロセッサ番号は0から始まるので、これは2番目のCPUということになる。

Photo03: Windows 7なら「コントロールパネル」→「管理ツール」→「コンピュータの管理」→「パフォーマンス」→「モニターツール」→「パフォーマンスモニター」を開き、カウンタの追加を選ぶとこのダイアログが表示される。

では具体的にどうするか? であるが、WindowsのPdhBrowseCounters()を使うことになる。サンプルソースはこちら。これは本家MSDNのサンプルである。このソースを元に、ちょっと書き換えたのがList 1である。まずはこれを説明しよう。

最初にいくつかの変数などを線言語、まずPdhOpenQuery()を呼び出して、hQueryというQueryHandleを登録する。以後、Performance Counterとのやり取りには、このhQueryを使うことになる。これに対して、まずPdhExpandWildCardPath()を一発実行する。この関数は、ワイルドカードを含んだ形でQueryを指定すると、これを展開してくれるというものだ。今回ターゲットにしたAtom 330の場合、CPUは見かけ上4つ(2Core+HT)あるから、カウンタとしては、

\\Processor(_Total)\\% Processor Time
\\Processor(0)\\% Processor Time
\\Processor(1)\\% Processor Time
\\Processor(2)\\% Processor Time
\\Processor(3)\\% Processor Time

の5つがある計算だが、これは「このシステムが4CPUである」と知っているから判ることで、当然環境が変わればCPUの数も変わる。そこでワイルドカード指定でカウンタに問い合わせを掛けることで、いくつのカウンタが用意されているか確認するわけだ。

ただこの問い合わせの結果を収めるバッファのサイズは事前に知りようがない。幸いなことにPdhExpandWildCardPath()は、バッファサイズ0のまま問い合わせを掛けると、結果を格納するのに必要なサイズを返してくれる。PathListSizeという変数に0を突っ込んだままPdhExpandWildCardPath()を呼び出し、必要なサイズを取得したらこれにあわせてバッファをcalloc()で確保し、そこに対して改めて2つ目のPdhExpandWildCardPath()を発行するわけだ。

2つ目のPdhExpandWildCardPath()が無事に戻ってきたら、今度は具体的に中身をチェックする。今回の場合、

\\Processor(_Total)\\% Processor Time

の値は要らないので、"_Total"が入っていない結果だけを抜き出し、これをPdhAddCounterで登録する。先のPhoto03で言えば、オブジェクトのインスタンスの0/1/2/3のみを登録し、_Totalを省く、という作業に相当する。

ここまでが終了したら、あとはPdhCollectQueryData()を定期的(この例では1秒毎)に実行し、それぞれのCPUの利用率を取得する。ただこの取得した値はそのままでは利用できない形なので、PdhGetFormattedCounterValue()を使って利用できる数字に変換してから表示を行うという形だ。

このプログラムはあくまでサンプルなので、calloc()で確保したPathListをfree()してないとか、PdhCloseQuery()を呼び出していないとか、色々問題はあるのだが、なにせ最後はwhile(TRUE)で無限ループを構成している形なので、そのあたりはご容赦いただきたい。

List 1:

// CounterTest.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#include <stdio.h>
#include <windows.h>
#include <tchar.h>
#include <winperf.h>
#include <pdh.h>
#include <pdhmsg.h>

int _tmain(int argc, _TCHAR* argv[])
{
    TCHAR       *cp;
    TCHAR       *PathList;
    DWORD       PathListSize;
    PDH_STATUS  status;
    int     numProcessor;
    int     lpCnt;

    HQUERY hQuery;
    HCOUNTER hCounter[100];
    PDH_FMT_COUNTERVALUE FmtValue;

    PdhOpenQuery(NULL, 0, &hQuery);

    PathList = NULL;
    PathListSize = 0;
    // ワイルドカード指定でPreocessor Timeを指定(1回目)
    status = PdhExpandWildCardPath(NULL, TEXT("\\Processor(*)\\% Processor Time"), PathList, &PathListSize, NULL);
    if (status == PDH_MORE_DATA)
    {
        // ワイルドカード展開に必要なサイズのバッファを確保
        PathList = (TCHAR *)calloc(PathListSize+1, sizeof(TCHAR));

        // ワイルドカード指定でPreocessor Timeを指定(2回目)
        status = PdhExpandWildCardPath(NULL, TEXT("\\Processor(*)\\% Processor Time"), PathList, &PathListSize, NULL);
        for(cp = PathList, numProcessor = 0; *cp; cp += _tcslen(cp)+1)
        {
            // _Totalの数字は要らないので抜き、その他のカウンタをAddCounterで登録
            if (!_tcsstr(cp, TEXT("_Total")))
            {
                status = PdhAddCounter(hQuery, cp, 0, &hCounter[numProcessor]);
                numProcessor++;
            }
        }

        while(TRUE)
        {
            // 1秒間待つ
            Sleep(1000);
            // データ取得
            PdhCollectQueryData(hQuery);
            for(lpCnt = 0; lpCnt < numProcessor; lpCnt++)
            {
                // データ表示
                status = PdhGetFormattedCounterValue( hCounter[lpCnt], PDH_FMT_DOUBLE, NULL, &FmtValue );
                _tprintf( TEXT("CPU%d: %f%%\n"), lpCnt, FmtValue.doubleValue);
            }
        }
    }

    return 0;
}

(続く)