◆パフォーマンスモニタ - VC++ 2005編 その3

[ ホーム ] [ 上へ ]

・パフォーマンスオブジェクト・カウンタを指定してモニタしてみる

では実際にリソースの活動状況をモニタしてみますが、すでにPDH系APIで オブジェクト名・カウンタ名・インスタンス名を取得する こちら のGetPdhCounterExplain関数で途中までやり方は書いています。

手順としては以下のようになります。

1. PdhOpenQueryで問合せを作成&初期化します。
2. PdhAddCounterでカウンタを問合せに追加してカウンタハンドルを取得します。
この関数を実行するにはカウンタパス(カウンタを一意に特定する文字列) が必要でこれには書式があり
コンピュータ名\オブジェクト名(親インスタンス名/インスタンス名#インデックス)\カウンタ名
という形式です。GetPdhCounterExplain関数では文字列操作でこれを作成しましたが、 PdhMakeCounterPathというAPIがちゃんと用意されているので、今回はオブジェクト名・ カウンタ名・インスタンス名からこれを作成してPdhAddCounterを実行します。
3. カウンタへのハンドルを取得できたら、PdhGetCounterInfoでPDH_COUNTER_INFO構造体から
データのサイズ(長整数・倍精度浮動小数点 など)や、数値なのか文字列なのかカウンタ値なのか などの情報を 得ます。この種類は最後にパフォーマンス値を出力する際に使います。
4. いよいよ監視ですが、その種類は2種類あります。
ひとつはある瞬間の活動状況をするだけでいいもの、もうひとつは一定期間の平均値を出力するものです。 前者はProcessオブジェクトのThread Countカウンタなどがあり、 後者はMemoryオブジェクトのPages/secカウンタなどがあります。 後者は2点のデータが必要なので、最初にPdhCollectQueryDataでデータを収集しておいて、 一定時間後に再度この関数を呼び出し計算をします。
5. 一度、または二度のカウンタの値を得たら、PdhGetFormattedCounterValue APIで出力します。
このとき、3.で得たカウンタの種類によって形式を変えます。 (今回は倍精度浮動小数点と長整数のみに分けただけですが) もし、ログの形式にしたいのであれば、PdhOpenLogやPdhUpdateLog APIなどを使いますが、 今回は割愛します。

今回はカウンタ名一覧取得の時のようではなく、 後で使い回しができるようにクラスにしてみます。今まで使っていた Win32コンソールのプロジェクトにクラスを追加します。 ツールバーのプロジェクト(P)→クラスの追加(C)で表示されるダイアログボックスるの左ペインの カテゴリ(C)でC++を選択して追加ボタンを押してください。任意のクラス名を 入力(以下CPerfMonitorとして話を進めます)して完了をするとヘッダと実装ファイルができるので 以下のように記述してください。

//PerfMonitor.h ヘッダファイル//////////////////////
//
#pragma once

#include "pdh.h"

enum ENUM_CNTR_TYPE
{
    DBL,
    LNG
};

class CPerfMonitor
{
    //メンバ変数////////////////////////////////////////////////////
protected:
    HQUERY      m_hQuery;   //問い合わせハンドル
    HCOUNTER    m_hCounter; //カウンタハンドル
    DWORD       m_dwType;   //カウンタのタイプ

    //メンバ関数//////////////////////////////////////////////////////
private:
    CPerfMonitor(const CPerfMonitor & obj); //コピーコンストラクタ

public:
    CPerfMonitor(); //コンストラクタ
    CPerfMonitor(LPTSTR pszObject, LPTSTR pszCounter, //引数付きコンストラクタ
                 LPTSTR pszInstance = NULL, DWORD dwInstanceIdx = -1,
                 LPTSTR pszParentInstance = NULL);
    virtual ~CPerfMonitor(); //デストラクタ

    BOOL AddCounter(LPTSTR pszObject, LPTSTR pszCounter, //カウンタ追加
                    LPTSTR pszInstance = NULL,
                    LPTSTR pszParentInstance = NULL,
                    DWORD dwInstanceIdx = -1);

    BOOL GetCounterVal(PDH_FMT_COUNTERVALUE *pFmtCntrVal,
                       ENUM_CNTR_TYPE *pCntrType); //カウンタの現在値を取得
};

//PerfMonitor.cpp実装ファイル//////////////////////////////
//

#include "StdAfx.h"
#include "PerfMonitor.h"

#include "PDHMSG.H"

//コンストラクタ
CPerfMonitor::CPerfMonitor() : m_hQuery(NULL)
                             , m_hCounter(NULL)
{
    ::PdhOpenQuery(NULL, 0, &m_hQuery);
}

//引数付きコンストラクタ
//第一引数: Performance Object名
//第二引数: Performance Counter名
//第三引数: Performance Instance名
//第四引数: Performance Instanceのインデックス
//第五引数: Performance Instanceの親インスタンス
CPerfMonitor::CPerfMonitor(LPTSTR pszObject,
                           LPTSTR pszCounter,
                           LPTSTR pszInstance /*=NULL*/,
                           DWORD dwInstanceIdx /*=-1*/,
                           LPTSTR pszParentInstance /*=NULL*/) : m_hQuery(NULL)
                                                               , m_hCounter(NULL)
                                                               , m_dwType(0)
{
    ::PdhOpenQuery(NULL, 0, &m_hQuery);
    AddCounter(pszObject, pszCounter, pszInstance, pszParentInstance, dwInstanceIdx);
}

//コピーコンストラクタ
CPerfMonitor::CPerfMonitor(const CPerfMonitor & obj) : m_hQuery(NULL)
                                                         , m_hCounter(NULL)
                                                         , m_dwType(0)
{
}

//デストラクタ
CPerfMonitor::~CPerfMonitor()
{
    if (m_hCounter)
        ::PdhRemoveCounter(m_hCounter);

    if (m_hQuery)
        ::PdhCloseQuery(m_hQuery);
}

//カウンタ追加
BOOL CPerfMonitor::AddCounter(LPTSTR pszObject, LPTSTR pszCounter,
                              LPTSTR pszInstance /*= NULL*/,
                              LPTSTR pszParentInstance /*= NULL*/,
                              DWORD dwInstanceIdx /*= -1*/)
{
    PDH_COUNTER_PATH_ELEMENTS   cpe;
    DWORD                       dwSize = 0;
    LPTSTR                      pCounterPath = NULL;
    HCOUNTER                    hOldCounter = m_hCounter;

    PDH_COUNTER_INFO            *pCounterInfo = NULL;

    //PDH_COUNTEr_PATH_ELEMENTS構造体セット
    cpe.szMachineName = NULL;
    cpe.szObjectName = pszObject;
    cpe.szCounterName = pszCounter;
    cpe.szInstanceName = pszInstance;
    cpe.dwInstanceIndex = dwInstanceIdx;
    cpe.szParentInstance = pszParentInstance;

    //カウンタパスの文字列サイズ取得
    if (::PdhMakeCounterPath(&cpe, NULL, &dwSize, 0) == PDH_INVALID_ARGUMENT)
        return FALSE;

    //カウンタパス用の文字列のメモリ確保
    pCounterPath = new TCHAR[dwSize];
    //カウンタパス作成
    if (::PdhMakeCounterPath(&cpe, pCounterPath, &dwSize, 0) != ERROR_SUCCESS)
    {
        delete [] pCounterPath;     return FALSE;
    }

    //問い合わせにパフォーマンスカウンタ(監視項目)を追加
    if (::PdhAddCounter(m_hQuery, pCounterPath, 0, &m_hCounter) != ERROR_SUCCESS)
    {
        delete [] pCounterPath;     return FALSE;
    }

    while (TRUE)
    {
        //カウンタ情報の取得
        dwSize = 0;
        ::PdhGetCounterInfo(m_hCounter, FALSE, &dwSize, NULL);

        pCounterInfo = (PDH_COUNTER_INFO *) new BYTE[dwSize];
        if (::PdhGetCounterInfo(m_hCounter, FALSE, &dwSize, pCounterInfo)
                                                                != ERROR_SUCCESS)
            break;

        if ((pCounterInfo->CStatus == PDH_CSTATUS_VALID_DATA) ||
                                    (pCounterInfo->CStatus == PDH_CSTATUS_NEW_DATA))
        {
            m_dwType = pCounterInfo->dwType;
        } else
        {
            break;
        }

        if (::PdhCollectQueryData(m_hQuery) != ERROR_SUCCESS)
            break;

        //前カウンタの削除
        if (hOldCounter)
            ::PdhRemoveCounter(hOldCounter);

        delete [] pCounterPath;
        delete [] pCounterInfo;     return TRUE;
    }
    //元のカウンタに戻す
    ::PdhRemoveCounter(m_hCounter);
    m_hCounter = hOldCounter;

    delete [] pCounterPath;
    delete [] pCounterInfo;     return FALSE;
}

//カウンタの現在値(又は最も最近の値と計算値)取得
//第一引数: 取得したデータ値
//第二引数: Counterの種類(DBL:倍精度浮動小数点, LNG:長整数)
//戻値    : Performanceデータ取得成功時TRUE
BOOL CPerfMonitor::GetCounterVal(PDH_FMT_COUNTERVALUE *pFmtCntrVal,
                                 ENUM_CNTR_TYPE *pCntrType)
{
    PDH_FMT_COUNTERVALUE fmtval;

    //m_hQuery内のカウンタの未処理データの収集
    if (::PdhCollectQueryData(m_hQuery) != ERROR_SUCCESS)
        return FALSE;

    //カウンタの現在の値取得
    if (::PdhGetFormattedCounterValue(m_hCounter,
                            m_dwType & PERF_SIZE_LARGE ? PDH_FMT_DOUBLE : PDH_FMT_LONG,
                            NULL,
                            &fmtval
                            ) != ERROR_SUCCESS)
        return FALSE;

    //PDH_FMT_COUNTERVALUEのCStatusメンバにより値の有効性確認
    if ((fmtval.CStatus != PDH_CSTATUS_VALID_DATA) &&
                                        (fmtval.CStatus != PDH_CSTATUS_NEW_DATA))
        return FALSE;

    //第二引数に返却
    if (m_dwType & PERF_SIZE_LARGE)
        *pCntrType = DBL;
    else
        *pCntrType = LNG;

    //第一引数に返却
    *pFmtCntrVal = fmtval;

    return TRUE;
}

使い方はCPerfMonitorクラスの引数付コンストラクタもしくは、 デフォルトコンストラクタを読んだ後CPerfMonitor::AddCounterを呼んでカウンタを追加します。 あとは、CPerfMonitor::GetCounterValを呼び出せばパフォーマンスデータを取得できます。 この時、カウンダが2点間のデータが必要な種類でしたら第二引数をTRUEで実行すればいいです。 継続的にモニタするにはタイマがあるスレッドでCPerfMonitorクラスオブジェクトを 使えば可能になります。

これまでで使ったWin32コンソールプロジェクトの_tmain関数で試してみましょう。

// xxxxxxxx.cpp : コンソールアプリケーションのエントリポイントを定義します。
//
#include "stdafx.h"
#include "pdh.h"

#include "PerfMonitor.h"

#pragma comment(lib, "pdh.lib")

int _tmain(int argc, _TCHAR* argv[])
{
    CPerfMonitor PerfMoni;

    if (PerfMoni.AddCounter(_T("Processor"), _T("% Processor Time"), _T("_Total")))
    {
        for (int i=0; i<20; i++)
        {
            PDH_FMT_COUNTERVALUE val;
            ENUM_CNTR_TYPE type;

            ::Sleep(1000);

            if (PerfMoni.GetCounterVal(&val, &type))
            {
                if (type == DBL)
                    _tprintf(_T("Value%d : %f\n"), i, val.doubleValue);
                else
                    _tprintf(_T("Value%d : %d\n"), i, val.longValue);
            }
        }
    }
    return 0;
}

上記の例はProcessorオブジェクトの_Totalインスタンスの非アイドルスレッドに 対するプロセッサ使用の経過時間の割合を1秒おきに20回モニタしています。 次回はVBAで実装してみたいと思います。

参考文献 Using the PDH Functions to Consume Counter Data

Using the Registry Functions to Consume Counter Data

パフォーマンスオブジェクトとカウンタ