・パフォーマンスオブジェクト・カウンタを指定してモニタしてみる
では実際にリソースの活動状況をモニタしてみますが、すでに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