EternalWindows
PDH / パフォーマンスオブジェクト

パフォーマンスデータの取得にあたってまず決めておくべきことは、 どのオブジェクトをモニタ対象とするかです。 たとえば、アプリケーションがプロセスに関するデータを必要とするなら モニタ対象のオブジェクトはプロセスという具合になりますが、 これはプログラム内ではどのように表すことになるのでしょうか。 実は、PDHではオブジェクトを単純な文字列で表すため、 オブジェクトのハンドルを取得するといったような作業は一切行うことはありません。 つまり、そのオブジェクトを表す文字列を適切に指定するだけで、 直ちにパフォーマンスデータ取得の作業に入ることができるのです。

しかし、オブジェクトを表す文字列といっても、 実際にはこれを完全に把握するのは難しいものがあります。 幸いにも文字列はオブジェクトの名前に即した形になっているため、 たとえばプロセスならTEXT("Process")という具合になるのですが、 全てのオブジェクトがこのような例に当てはまるとは限りませんし、 そもそも、オブジェクトがどれくらい存在するのかを知らなければ、 文字列を指定するにも検討が尽かないというが実際のところでしょう。 そういった意味でも、まずはオブジェクトの列挙を通じて その数と名前を把握しておくことは重要であるといえます。 次に示すPdhEnumObjectsは、オブジェクトのリストを返します。

PDH_STATUS PdhEnumObjects(
  LPCTSTR szDataSource,
  LPCTSTR szMachineName,
  LPTSTR mszObjectList,
  LPDWORD pcchBufferLength,
  DWORD dwDetailLevel,
  BOOL bRefresh
);

szDataSourceは、データソースを表す文字列を指定します。 基本的にはこの引数はNULLに指定し、オブジェクト(パフォーマンスDLL)のデータを照会します。 szMachineNameは、オブジェクトを列挙するコンピュータ名を指定します。 NULLを指定した場合、ローカルコンピュータが参照されることになります。 mszObjectListは、列挙されたオブジェクトのリストを格納するバッファを指定します。 個々のオブジェクトはNULL文字で区切られ、NULL文字が2つ続いた場合は、 それ以上オブジェクトが存在しないことを意味しています。 pcchBufferLengthは、mszObjectListを格納するバッファのサイズを指定します。 関数から制御が返るとリストのサイズが格納されることになっているため、 このサイズを基に動的にバッファを確保するようなことも可能です。 dwDetailLevelは、PERF_DETAIL_XXXという専用の定数を指定することになるのですが、 この定数が効果を発揮するのは次節で取り上げるPdhEnumObjectItemsであるため、 PdhEnumObjectの呼び出しでは、重要な意味は持たないと思われます。 bRefreshは、常に最新のオブジェクトのリストを取得する場合はTRUEを指定し、 キャッシュ(ローカルに保存)されたリストを参照したい場合はFALSEを指定します。 複数回PdhEnumObjectsを呼び出す必要がある場合は、キャッシュを参照することで 関数の処理時間を短くすることができます。 PDHの関数は成功時に、ERROR_SUCCESSを返します。

今回のプログラムは、PdhEnumObjectsで取得したオブジェクトのリストから 個々のオブジェクトを取り出し、それをリストボックスに追加します。 PdhEnumObjectsの呼び出しには、多少の時間がかかります。 PDHを利用するには、pdh.hのインクルードとpdh.libへのリンクが必要です。

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

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

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndListBox = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		TCHAR  szObjectList[1024];
		DWORD  dwObjectSize;
		LPTSTR lp;

		hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);

		dwObjectSize = sizeof(szObjectList);
		PdhEnumObjects(NULL, NULL, szObjectList, &dwObjectSize, PERF_DETAIL_WIZARD, TRUE);

		lp = szObjectList;
		while (*lp != '\0') {
			SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)lp);
			lp += lstrlen(lp) + 1;
		}

		return 0;
	}
	
	case WM_SIZE:
		MoveWindow(hwndListBox, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

プログラムを実行してみれば分かるように、非常に多くのオブジェクトが リストボックスに追加されています。 個々のオブジェクトが管理するデータは相互に異なるものの、 それらは全てPDHによって同じように取得することができますから、 普段聞き慣れないオブジェクトの照会も容易です。 次のコードは、PdhEnumObjectsの呼び出し部分です。

dwObjectSize = sizeof(szObjectList);
PdhEnumObjects(NULL, NULL, szObjectList, &dwObjectSize, PERF_DETAIL_WIZARD, TRUE);

PdhEnumObjectsは、オブジェクトのリストを格納するバッファのサイズを 第4引数に要求するので、まずこれをdwObjectSizeに代入します。 関数が成功すると第3引数のszObjectListに全てのオブジェクトの名前を格納されるため、 次はそれを1つずつ走査することになります。

lp = szObjectList;
while (*lp != '\0') {
	SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)lp);
	lp += lstrlen(lp) + 1;
}

既に述べてきたように、PDHでのオブジェクトの表現は文字列ですから、 単純にLPTSTR型変数を使って表現することができます。 予めszObjectListの先頭を指していれば、その文字列のNULL文字を考慮した長さ分、 ポインタを進めるだけで次の文字列を指すことができ、 その指した文字列がNULLであれば、NULL文字が2つ続いたということで、 リストの終端に到達したと判断することができます。 NULL文字を考慮した文字列の長さは、lstrlenの戻り値に+1することで取得できます。

データソースについて

PDHの世界におけるデータソースとは、パフォーマンスデータをどこから取得、 または、どのように取得するかという意味合いを含んでいます。 リアルタイム形式と呼ばれるデータソースは、そこで得られるデータが 常にシステムやアプリケーションの現在の状態を反映しており、 データソースの引数にNULLを指定した場合は、 このリアルタイム形式が採用されることになっています。 一方、ログファイルと呼ばれるデータソースは、 カウンタとそのデータを記述しているバイナリあるいはテキストファイルであり、 データソースの引数にそのファイル名を指定することによって、 パフォーマンスデータ照会の対象がログファイルとなります。 PdhIsRealTimeQueryを呼び出せば、照会するデータソースが リアルタイム形式であるかを調べることができます。

WindowsXPから登場したPdhBindInputDataSourceという関数は、 データソースを文字列ではなくハンドルで表すことを可能にしました。 これに併せて、データソースを要求する関数には、 その関数名の後ろにHを付けたハンドル版の関数が用意されており、 たとえば、PdhEnumObjectsにはPdhEnumObjectsHという別の関数があります。 ハンドル指向がもたらす1つの効果は、個々のデータソースを要求する関数が 文字列の解析を行わずに済むことであり、複数のログファイルをリストとして データソースに指定するような場合は、これは大きな最適化に繋がるといえます。 なお、PdhBindInputDataSourceは、リアルタイム形式の内部的な照会方法となる レジストリ関数、もしくはWMIのどちらかを選択することができますが、 基本的にこれはどちらを選んでも大差はないかと思われます。 これはデータを実際に維持、または公開するのがパフォーマンスDLLであるからであり、 WMIを使うにしてもそれはレジストリプロバイダを介すだけのことですから、 得られるデータの値はどちらも同じになると思われます。 WindowsXP以前でリアルタイム形式の照会方法を選択したい場合は、 PdhSetDefaultRealTimeDataSourceを呼び出します。



戻る