EternalWindows
PDH / カウンタパス

モニタしたいオブジェクトとカウンタを決定したら、 いよいよ実際にそれらの文字列を指定することになりますが、 これにはカウンタパスと呼ばれる仕組みを理解しておく必要があります。 カウンタパスというのは、オブジェクトやカウンタ、インスタンスといった 実際にデータを取得する際に必要な情報を1つにまとめた文字列のことで、 その形式はファイルシステムのファイルパスと似ている部分がいくつかあります。 次に、カウンタパスの形式を示します。

\\Machine\PerfObject(ParentInstance/ObjectInstance#InstanceIndex)\Counter

これが最も長いカウンタパスの例となります。 察しの通り、MachineやPerfObjectなどが書かれた位置に 該当する情報の文字列を指定することになるのですが、 実際にはこれらの一部が不要な場合は省略可能となっています。 これまで説明してきたように、パフォーマンスデータの取得に最低限 必要なのはオブジェクトとカウンタですから、これらの情報だけで十分に カウンタパスを構成することができるようになっているのです。 次にその例を示します。

\Memory\Available Bytes

このカウンタパスはPerfObjectにMemoryを指定し、 CounterにAvailable Bytes(使用可能メモリ量)を指定した形となっています。 正規の形式と見比べた場合、このカウンタパスはまず\\Machineの部分を省略していますが、 この部分はモニタするオブジェクトがローカルマシンに存在するならば 省略してよいことになっているため、その点を利用することにしています。 また、上記の形式は\PerfObjectから\Counterまでの括弧の中身を全て省略していますが、 これはオブジェクトがインスタンスを持たない場合は成立します。 次に、インスタンスを必要とするカウンタパスの例を示します。

\Process(notepad)\% Processor Time

前節で述べたように、プロセスはインスタンスを持つオブジェクトですから、 オブジェクト名の後にインスタンス情報の括弧を指定することになります。 この括弧には、ParentInstanceとObjectInstance、InstanceIndexの3つがありましたが、 上記の例で該当するのはObjectInstanceです。 この情報は、インスタンスを持つオブジェクトでは必須となり、 ParentInstanceは一部のオブジェクト、InstanceIndexは状況によって必要となります。 InstanceIndexを指定する例を次に示します。

\Process(notepad#1)\% Processor Time

インスタンスのインデックスという概念は、 同じ名前を持ったインスタンスが複数存在し得ることからきています。 このような場合、単純にインスタンスを指定するだけでは、 常に複数内の同一インスタンスをモニタすることになってしまいますが、 #1というように数字で区切れば、別のインスタンスを対象とすることができるようになります。 これは、ObjectInstanceというのがObjectInstance#0と同じ意味を持つからであり、 インデックス順に対象となるインスタンスが異なるためです。

最後に、ParentInstanceを利用する例を次に示します。

\Thread(notepad/0)\% Processor Time

スレッドのようなオブジェクトは、プロセスという親の下で実行していると考えられるため、 インスタンスの指定では、まず親のインスタンスから指定することになっています。 上記の例の場合、メモ帳プロセスの0番目(つまり最初に作成された)のスレッドが モニタされるインスタンスということになり、さらに後ろにInstanceIndexが続く場合は、 また別のメモ帳プロセスの0番目のスレッドがモニタ対象となります。

さて、カウンタパスの使い方を見てきたところで、 いよいよこれをPDHに指定する手順について見ていきたいと思います。 単純に考えれば、カウンタパスからカウンタの値を初期化する関数を 呼び出すことになるのではないかと想像しますが、 厳密にはPDHにはカウンタの値を初期化する関数は存在しません。 存在するのは、クエリーと呼ばれる構造体の内部を初期化する関数であり、 この構造体に予めカウンタを追加しておくことで、 結果としてカウンタが初期化されるという仕組みになっているのです。 この機能により、開発者は複数のカウンタをまとめて初期化できることになります。

PDH_STATUS PdhOpenQuery(
  LPCTSTR szDataSource,
  DWORD_PTR dwUserData,
  PDH_HQUERY *phQuery
);

szDataSourceは、データソースを表す文字列を指定します。 dwUserDataは、クエリーに関連付けたいユーザーデータの値を指定します。 phQueryは、作成されたクエリーのハンドルを受け取るアドレスを指定します。 関数のプロトタイプでは、この型はPDH_HQUERYとなっていますが、 typedef定義されているHQUERYという型を利用することもできます。

クエリーを作成したら、次はそれにカウンタを追加することになります。 ここで述べているカウンタとは、% Processor Timeというような単一の文字列のことを 意味するのではなく、どちらかというと、そのカウンタ文字列についての詳細を 格納する構造体であると考えると分かりやすいでしょう。 次に示すPdhAddCounterは、引数に指定されたカウンタパスを基にその構造体を作成します。

PDH_STATUS PdhAddCounter(
  PDH_HQUERY hQuery,
  LPCTSTR szFullCounterPath,
  DWORD_PTR dwUserData,
  PDH_HCOUNTER* phCounter
);

hQueryは、カウンタを追加するクエリーのハンドルを指定します。 szFullCounterPathは、カウンタパスを表す文字列を指定します。 dwUserDataは、クエリーの中に作成されたカウンタ構造体に 関連付けたいユーザーデータの値を指定します。 phCounterは、作成されたカウンタを受け取るハンドルを指定します。

PdhOpenQueryで取得したクエリーのハンドルは、 不要になったらPdhCloseQueryで閉じることになります。 これにより、クエリーに追加されていたハンドルも閉じられ、 クエリーに関連したメモリが開放されることになります。

PDH_STATUS PdhCloseQuery(
  PDH_HQUERY hQuery  
);

hQueryは、クエリーのハンドルを指定します。 指定しているのはクエリーのハンドルだけですが、 同時にカウンタのハンドルも使えなくなるという点に注意してください。

今回のプログラムは、クエリーの作成とカウンタの追加の手順を示し、 追加されたカウンタに含まれる情報について考察します。

#include <windows.h>
#include <pdh.h>

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR             szBuf[256];
	DWORD             dwInfoSize;
	PDH_HQUERY        hQuery;
	PDH_HCOUNTER      hCounter;	
	PPDH_COUNTER_INFO pCounterInfo;
	
	PdhOpenQuery(NULL, 0, &hQuery);

	if (PdhAddCounter(hQuery, TEXT("\\Process(notepad)\\% Processor Time"), 0, &hCounter) != ERROR_SUCCESS)
		return 0;

	dwInfoSize = 0;
	PdhGetCounterInfo(hCounter, FALSE, &dwInfoSize, NULL);
	pCounterInfo = (PPDH_COUNTER_INFO)HeapAlloc(GetProcessHeap(), 0, dwInfoSize);
	PdhGetCounterInfo(hCounter, FALSE, &dwInfoSize, pCounterInfo);
	
	PdhCloseQuery(hQuery);

	wsprintf(szBuf, TEXT("オブジェクト %s, カウンタ %s"), pCounterInfo->szObjectName, pCounterInfo->szCounterName);
	MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
	
	HeapFree(GetProcessHeap(), 0, pCounterInfo);

	return 0;
}

PdhOpenQueryの呼び出しは特に問題はないでしょう。 PdhAddCounterで指定しているカウンタパスはオブジェクトがプロセスとなっているため、 インスタンス用の括弧が必要だということは忘れてはいけません。 また、カウンタパスに含まれるオブジェクト名やカウンタ名に誤りがある場合は、 PdhAddCounterはエラーを返すことになっていますが、 インスタンス名の誤りに関しては考慮していないということも重要です。 たとえば、今回の呼び出しではインスタンス名はnotepadとなっていますが、 PdhAddCounterの呼び出し時に実際にメモ帳が起動されていなくても、 関数は成功することになります。

PdhAddCounterで取得できるカウンタのハンドルは、 主としてカウンタの値を取得する際に利用されますが、 今回のようにカウンタの基本的な情報を取得するために利用することもできます。 PdhGetCounterInfoは、第4引数に指定されたPDH_COUNTER_INFO構造体を初期化し、 そのメンバにはカウンタの名前や対象としているインスタンス名、 他にカウンタの説明テキストなどが含まれています。 また、PdhOpenQueryやPdhAddCounterで指定したユーザーデータの値を 取得するのもこの関数となっています。 次のコードは、PdhGetCounterInfoの呼び出し部分です。

dwInfoSize = 0;
PdhGetCounterInfo(hCounter, FALSE, &dwInfoSize, NULL);
pCounterInfo = (PPDH_COUNTER_INFO)HeapAlloc(GetProcessHeap(), 0, dwInfoSize);
PdhGetCounterInfo(hCounter, FALSE, &dwInfoSize, pCounterInfo);

PdhGetCounterInfoが必要とするPDH_COUNTER_INFO構造体は サイズが可変であることを想定して設計されているため、 まずは1回目の呼び出しで必要なメモリのサイズを第3引数で取得しています。 その後、そのサイズを基にメモリを確保し、2回目の呼び出しでデータを取得します。 szExplainTextメンバ(カウンタの説明テキスト)を参照する場合は、 第2引数をTRUEにしてPdhGetCounterInfoを呼び出します。

カウンタパスの作成

カウンタパスの各文字列の配置順序を覚え、適切に整形するというのは、 開発者にとって煩わしいことです。 幸いにもPDHには、オブジェクト名やカウンタ名などのカウンタパスの構成要素から カウンタパスを作成する関数が用意されているため、その使い方を確認してみることにします。

TCHAR                     szCounterPath[256];
DWORD                     dwCounterPathSize;
PDH_COUNTER_PATH_ELEMENTS elements;

elements.szMachineName    = NULL;
elements.szObjectName     = TEXT("Process");
elements.szParentInstance = NULL
elements.szInstanceName   = TEXT("notepad");
elements.dwInstanceIndex  = 0;
elements.szCounterName    = TEXT("% Processor Time");

dwCounterPathSize = sizeof(szCounterPath);
PdhMakeCounterPath(&elements, szCounterPath, &dwCounterPathSize, 0);

PdhMakeCounterPathは、第1引数のPDH_COUNTER_PATH_ELEMENTS構造体で表される カウンタパスの構成要素からカウンタパスを作成し、それを第2引数に格納します。 第3引数はカウンタパスのサイズを格納する変数のアドレスを指定し、第4引数は0にします。 szMachineName、szParentInstance、dwInstanceIndexがそれぞれNULL、0で済ませられるのは、 既に述べてきたように、これらがカウンタパスの必須要素でないからであり、 インスタンスを必要としないオブジェクトではszInstanceNameもNULLにできるでしょう。 上記の方法は、静的に宣言したカウンタパスを扱うよりかは手順を踏むことになりますが、 モニタするオブジェクトやカウンタが動的に変更するようなアプリケーションでは、 PdhMakeCounterPathは便利な関数になると思われます。 PDH_COUNTER_PATH_ELEMENTS構造体を利用する他の関数としては、 カウンタパスから構成要素を分解するPdhParseCounterPathがあります。



戻る