パフォーマンスカウンタの作成方法 ~ パフォーマンス拡張 DLL とその利用方法
別の資料でパフォーマンスモニタの仕組みを説明しました。
この資料ではパフォーマンスオブジェクト及びカウンターを作成するのに必要な、パフォーマンス拡張 DLL (Performance Extension DLL) とそれを利用するプログラムを作成してみましょう。
パフォーマンス拡張 DLL
パフォーマンス拡張 DLL は次の関数をエクスポートします。これがパフォーマンスモニタとのインターフェイスになります。
Open | DWORD CALLBACK OpenPerformanceData ( LPWSTR lpDeviceNames ); |
Close | DWORD WINAPI ClosePerformanceData(); |
Collect | DWORD WINAPI CollectPerformanceData( LPWSTR lpwszValue, LPVOID *lppData, LPDWORD lpcbBytes, ← パフォーマンスデータブロックを設定する LPDWORD lpcObjectTypes ); ← 書き込んだデータブロック数だけポインタを進める |
関数は上の通りですが、データはどうなるでしょうか。
データはインスタンスがある場合とない場合で異なり、次のようなデータに値をセットします。
PERF_OBJECT_TYPE |
PERF_COUNTER_DEFINITION |
PERF_COUNTER_BLOCK |
PERF_INSTANCE_DEFINITION |
データの使い方は下記に実際にコードを示しますので、MSDN とサンプルコードを見てください
パフォーマンスデータテストプログラムの作成
ここではパフォーマンスデータを公開する perftest.exe と、perftest.exe のパフォーマンスデータを収集する、 パフォーマンス拡張 DLL を作成します。
テストプログラムの作成
次のコードを perftest.cpp として保存します。
#include "resource.h" #include <windows.h> #include <windowsx.h> #include <tchar.h> /////////////////////////////////////////////////////////////////////////////// LONG g_lInstanceCount = 0; /////////////////////////////////////////////////////////////////////////////// HMODULE g_hCounterDLL; typedef LONG (__stdcall *pfnGetInstanceCount)(void); typedef LONG (__stdcall *pfnGetPerf1)(void); typedef LONG (__stdcall *pfnChangeInstanceCount)(INT n); typedef LONG (__stdcall *pfnChangePerf1)(INT n); pfnGetInstanceCount GetInstanceCount; pfnGetPerf1 GetPerf1; pfnChangeInstanceCount ChangeInstanceCount; pfnChangePerf1 ChangePerf1; /////////////////////////////////////////////////////////////////////////////// BOOL OnInitDialog(HWND hWnd, HWND hWndFocus, LPARAM lParam) { // Load Counter DLL which exposes performance counters in the shared memory. g_hCounterDLL = LoadLibrary (TEXT("perfdll")); if (!g_hCounterDLL) { ::MessageBox ( hWnd, TEXT("Failed to load the performance dll."), TEXT("Error"), MB_OK | MB_ICONERROR); return FALSE; } GetInstanceCount = (pfnGetInstanceCount) GetProcAddress (g_hCounterDLL, "GetInstanceCount"); GetPerf1 = (pfnGetPerf1) GetProcAddress (g_hCounterDLL, "GetPerf1"); ChangeInstanceCount = (pfnChangeInstanceCount) GetProcAddress (g_hCounterDLL, "ChangeInstanceCount"); ChangePerf1 = (pfnChangePerf1) GetProcAddress (g_hCounterDLL, "ChangePerf1"); if (!GetInstanceCount || !GetPerf1 || !ChangeInstanceCount || !GetPerf1) { ::MessageBox (hWnd, TEXT("Failed to get one or more function addresses."), TEXT("ERROR"), MB_OK | MB_ICONERROR); return FALSE; } TCHAR szInstanceNumber[32]; g_lInstanceCount = ChangeInstanceCount (1); Static_SetText (GetDlgItem(hWnd, IDC_INSTANCE_NUMBER), _ltot(g_lInstanceCount, szInstanceNumber, 10)); return TRUE; } /////////////////////////////////////////////////////////////////////////////// void OnCommand(HWND hWnd, int nID, HWND hWndCtl, UINT codeNotify) { switch(nID) { case IDC_PERF1_UP: ChangePerf1 (1); break; case IDC_PERF1_DOWN: ChangePerf1 (-1); break; case IDCANCEL: ChangeInstanceCount (-1); EndDialog(hWnd, 0); break; } } /////////////////////////////////////////////////////////////////////////////// BOOL CALLBACK MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { HANDLE_MSG(hWnd, WM_INITDIALOG, OnInitDialog); HANDLE_MSG(hWnd, WM_COMMAND, OnCommand); return TRUE; } return FALSE; } /////////////////////////////////////////////////////////////////////////////// int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { ::DialogBox(hInstance, MAKEINTRESOURCE(IDD_MAINWND), NULL, MainWndProc); return 0; }
次の内容を perftest.rc として保存します。
#include "resource.h" IDD_MAINWND DIALOGEX 0, 0, 245, 85 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Performance Test Program" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN LTEXT "Instance Number:",IDC_STATIC,7,7,66,10 LTEXT "Static",IDC_INSTANCE_NUMBER,75,7,63,12 GROUPBOX "Perf1",IDC_STATIC,7,22,231,34 PUSHBUTTON "Up",IDC_PERF1_UP,15,34,70,15,BS_FLAT PUSHBUTTON "Down",IDC_PERF1_DOWN,89,34,66,15,BS_FLAT DEFPUSHBUTTON "Close",IDCANCEL,167,62,71,15 END
次の内容を resource.h として保存します。
#include <windows.h> #define IDD_MAINWND 101 #define IDC_STATIC -1 #define IDC_EDIT1 1000 #define IDC_EDIT_MESSAGE 1000 #define IDC_SHOW 1001 #define IDC_PERF1_UP 1002 #define IDC_PERF1_DOWN 1003 #define IDC_INSTANCE_NUMBER 1005
次の内容を makefile として保存します。
CPP=cl.exe RSC=rc.exe LINK32=link.exe TARGETNAME=perftest OUTDIR=.\chk ALL : "$(OUTDIR)\$(TARGETNAME).exe" "$(OUTDIR)" : @if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" CPP_PROJ=\ /MT\ /W4\ /Fo"$(OUTDIR)\\"\ /Fd"$(OUTDIR)\\"\ /c\ /Zi\ /DWIN32\ /DUNICODE\ /D_UNICODE\ RSC_PROJ=/l 0x411 /fo"$(OUTDIR)\$(TARGETNAME).res" /d "_DEBUG" LINK32_FLAGS=\ user32.lib\ /subsystem:windows\ /pdb:"$(OUTDIR)\$(TARGETNAME).pdb"\ /machine:I386\ /out:"$(OUTDIR)\$(TARGETNAME).exe"\ /DEBUG LINK32_OBJS= \ "$(OUTDIR)\$(TARGETNAME).obj"\ "$(OUTDIR)\$(TARGETNAME).res" "$(OUTDIR)\$(TARGETNAME).exe" : "$(OUTDIR)" $(LINK32_OBJS) $(LINK32) $(LINK32_FLAGS) $(LINK32_OBJS) .cpp{$(OUTDIR)}.obj: $(CPP) $(CPP_PROJ) $< .rc{$(OUTDIR)}.res: $(RSC) $(RSC_PROJ) $<
以上をひとつのディレクトリの置き、nmake すると chk サブディレクトリ内に perftest.exe が出来上がります。
これが 前回の資料の foo.exe に相当します。
パフォーマンス拡張 DLL の作成
こちらは当サイトでよく使う debug.cpp です。
#include <windows.h> #include <tchar.h> #include <stdio.h> #include <stdarg.h> #include "debug.h" #define BUFF_SIZE (511) /////////////////////////////////////////////////////////////////////////////// VOID DebugPrint (LPTSTR szFormat, ... ) { TCHAR szBuffer[BUFF_SIZE + 1]; INT nWritten; va_list args; ::ZeroMemory(szBuffer, sizeof(szBuffer)); // Format error message like printf() va_start( args, szFormat ); nWritten = _vsntprintf( szBuffer, BUFF_SIZE, szFormat, args ); va_end( args ); // Output debug string ::OutputDebugString( szBuffer ); }
debug.h は次の内容。
#pragma once #include <windows.h> VOID DebugPrint (LPTSTR szFormat, ... );
次の内容を perfdll.h として保存します。
#pragma once #include <windows.h> #include <WinPerf.h> typedef struct _MYPERF_DATA { PERF_OBJECT_TYPE pot; PERF_COUNTER_DEFINITION pcd1; PERF_COUNTER_DEFINITION pcd2; PERF_COUNTER_BLOCK pcb; LONG lInstanceCount; LONG lPerf1; } MYPERF_DATA, *PMYPERF_DATA;
次の内容を perfdll.cpp として保存します。
#include <windows.h> #include "perfdll.h" #include "debug.h" #include "Perfdll_test.h" /////////////////////////////////////////////////////////////////////////////// #pragma data_seg("Shared") LONG g_lInstanceCount = 0; LONG g_lPerf1 = 50; #pragma data_seg() #pragma comment(linker, "/SECTION:Shared,RWS") /////////////////////////////////////////////////////////////////////////////// PMYPERF_DATA pPerfData = NULL; DWORD g_dwObjectNameTitleIndex = 0; /////////////////////////////////////////////////////////////////////////////// BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { return TRUE; } /////////////////////////////////////////////////////////////////////////////// LONG GetInstanceCount () { return g_lInstanceCount; } /////////////////////////////////////////////////////////////////////////////// LONG GetPerf1 () { return g_lPerf1; } /////////////////////////////////////////////////////////////////////////////// LONG ChangeInstanceCount (INT n) { g_lInstanceCount += n; return g_lInstanceCount; } /////////////////////////////////////////////////////////////////////////////// LONG ChangePerf1 (INT n) { g_lPerf1 += n; return g_lPerf1; } /////////////////////////////////////////////////////////////////////////////// BOOL GetNumFromRegistry( HKEY hHKey, LPCTSTR lpszSubKey, LPCTSTR lpszValueName, DWORD* lpszData) { LONG lngRet; HKEY hKey; DWORD dwType; DWORD dwResult; DWORD dwSize = sizeof(DWORD); lngRet = RegOpenKeyEx(hHKey, lpszSubKey, 0, KEY_READ, &hKey); if(lngRet != ERROR_SUCCESS) { return FALSE; } lngRet = RegQueryValueEx(hKey, lpszValueName, 0, &dwType, (BYTE*) &dwResult, &dwSize); if(ERROR_SUCCESS != lngRet || REG_DWORD != dwType) { RegCloseKey(hKey); return FALSE; } RegCloseKey(hKey); *lpszData = dwResult; return TRUE; } /////////////////////////////////////////////////////////////////////////////// DWORD CALLBACK PerftestOpen (PWSTR pwStr) { // Read Registry Setting if (!GetNumFromRegistry ( HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Services\\PerfTest\\Performance"), TEXT("First Counter"), &g_dwObjectNameTitleIndex)) { return GetLastError (); } // Performance Data pPerfData = (PMYPERF_DATA) malloc (sizeof (MYPERF_DATA)); if (!pPerfData) { return ERROR_OUTOFMEMORY; } ::ZeroMemory (pPerfData, sizeof (MYPERF_DATA)); pPerfData->pot.TotalByteLength = sizeof (MYPERF_DATA); pPerfData->pot.DefinitionLength = sizeof (PERF_OBJECT_TYPE) + 2 * sizeof (PERF_COUNTER_DEFINITION); pPerfData->pot.HeaderLength = sizeof (PERF_OBJECT_TYPE); pPerfData->pot.ObjectNameTitleIndex = g_dwObjectNameTitleIndex; pPerfData->pot.ObjectNameTitle = NULL; pPerfData->pot.ObjectHelpTitleIndex = g_dwObjectNameTitleIndex + 1; pPerfData->pot.ObjectHelpTitle = NULL; pPerfData->pot.DetailLevel = PERF_DETAIL_NOVICE; pPerfData->pot.NumCounters = 2; pPerfData->pot.DefaultCounter = 0; pPerfData->pot.NumInstances = PERF_NO_INSTANCES; pPerfData->pot.CodePage = 0; pPerfData->pcd1.ByteLength = sizeof (PERF_COUNTER_DEFINITION); pPerfData->pcd1.CounterNameTitleIndex = g_dwObjectNameTitleIndex + TESTPROGRAM_INSTANCE_COUNTER; pPerfData->pcd1.CounterNameTitle = NULL; pPerfData->pcd1.CounterHelpTitleIndex = g_dwObjectNameTitleIndex + TESTPROGRAM_INSTANCE_COUNTER +1; pPerfData->pcd1.CounterHelpTitle = NULL; pPerfData->pcd1.DefaultScale = 0; pPerfData->pcd1.DetailLevel = PERF_DETAIL_NOVICE; pPerfData->pcd1.CounterType = PERF_COUNTER_RAWCOUNT; pPerfData->pcd1.CounterSize = sizeof (LONG); pPerfData->pcd1.CounterOffset = sizeof (PERF_COUNTER_BLOCK); pPerfData->pcd2.ByteLength = sizeof (PERF_COUNTER_DEFINITION); pPerfData->pcd2.CounterNameTitleIndex = g_dwObjectNameTitleIndex + TESTPROGRAM_PERF1_COUNTER; pPerfData->pcd2.CounterNameTitle = NULL; pPerfData->pcd2.CounterHelpTitleIndex = g_dwObjectNameTitleIndex + TESTPROGRAM_PERF1_COUNTER + 1; pPerfData->pcd2.CounterHelpTitle = NULL; pPerfData->pcd2.DefaultScale = 0; pPerfData->pcd2.DetailLevel = PERF_DETAIL_NOVICE; pPerfData->pcd2.CounterType = PERF_COUNTER_RAWCOUNT; pPerfData->pcd2.CounterSize = sizeof (LONG); pPerfData->pcd2.CounterOffset = sizeof (PERF_COUNTER_BLOCK) + sizeof (LONG); pPerfData->pcb.ByteLength = sizeof (PERF_COUNTER_BLOCK) + 2 * sizeof (LONG); return ERROR_SUCCESS; } /////////////////////////////////////////////////////////////////////////////// DWORD CALLBACK PerftestClose () { if (pPerfData) { free (pPerfData); pPerfData = NULL; } return ERROR_SUCCESS; } /////////////////////////////////////////////////////////////////////////////// DWORD CALLBACK PerftestCollect( PWSTR pszValueName, PVOID* ppvData, PDWORD pcbTotalBytes, PDWORD pdwNumObjectTypes) { pPerfData->lInstanceCount = g_lInstanceCount; pPerfData->lPerf1 = g_lPerf1; ::CopyMemory (*ppvData, pPerfData, sizeof (MYPERF_DATA)); *ppvData = (char*)(*ppvData) + sizeof (MYPERF_DATA); *pcbTotalBytes = sizeof (MYPERF_DATA); *pdwNumObjectTypes = 1; return ERROR_SUCCESS; }
次は perfdll_test.h です。
#define TESTPROGRAM_COUNTER_OBJECT 0 #define TESTPROGRAM_INSTANCE_COUNTER 2 #define TESTPROGRAM_PERF1_COUNTER 4
さらに次は perfdll_test.ini です。
[info] drivername=PerfTest symbolfile=Perfdll_test.h [languages] 009=English 011=Japanese [text] TESTPROGRAM_COUNTER_OBJECT_009_NAME=PerfDLL Test TESTPROGRAM_COUNTER_OBJECT_009_HELP=The object includes two counters. TESTPROGRAM_COUNTER_OBJECT_011_NAME=PerfDLL Test TESTPROGRAM_COUNTER_OBJECT_011_HELP=このオブジェクトは二つカウンタを含みます。 TESTPROGRAM_INSTANCE_COUNTER_009_NAME=Total Instances TESTPROGRAM_INSTANCE_COUNTER_009_HELP=A number of total instances TESTPROGRAM_INSTANCE_COUNTER_011_NAME=Total Instances TESTPROGRAM_INSTANCE_COUNTER_011_HELP=アプリケーションのインスタンス数 TESTPROGRAM_PERF1_COUNTER_009_NAME=Perf1 TESTPROGRAM_PERF1_COUNTER_009_HELP=This is a test counter. TESTPROGRAM_PERF1_COUNTER_011_NAME=Perf1 TESTPROGRAM_PERF1_COUNTER_011_HELP=テスト用のカウンター
上記をひとつのディレクトリにいれて、nmake すると chk サブディレクトリに perfdll.dllが出来上がります。
実験開始
実験開始
作業の前にレジストリのバックアップを取って置いてください。
インストール
1. ファイルをそれぞれ以下のフォルダに配置します。
ファイル名 | 配置場所 |
perftest.exe | 任意 (例: C:\Temp) |
perfdll_test.h | 任意 (例: C:\Temp) |
perfdll_test.ini | 任意 (perfdll_test.h と同一のフォルダ) |
perfdll.dll | %SYSTEMROOT%\System32 |
2. Registry_Install.reg を登録
次の内容を Registry_Install.reg としてレジストリを作成
Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\PerfTest\Performance] "Library"="perfdll.dll" "Open"="PerftestOpen" "Collect"="PerftestCollect" "Close"="PerftestClose"
3. カウンタを登録します。
> lodctr perfdll_test.ini
実験
4. perfmon を開きます。
Performance object に "PerfDLL Test" が追加されていることを確認してください。
"PerfDLL Test" パフォーマンスオブジェクトは次のカウンタを実装します。
Perf1 - 50 が初期値で、perftest.exe のボタンによって値が変動します。
Total Instances - perftest.exe の起動数を示します。
5. "Perf1" と "Total Instances" を追加します。
6. C:\Temp\perftest.exe を複数起動して、[Up][Down] ボタンを押すなどします。
perftest.exe の Up, Down をクリックすると、パフォーマンスカウンタ Perf1 が上下するのが確認できたでしょうか? また、perftest.exe を起動した数が Total Instances カウンタに現れます。
アンインストール
7. カウンタの削除
パフォーマンスカウンタは unlodctr コマンドで削除できます。
> unlodctr PerfTest
Removing counter names and explain text for PerfTest
Updating text for language 009
>
8. レジストリ HKLM\SYSTEM\CurrentControlSet\Services\PerfTest 以下を削除します。
レジストリの削除は間違ったキーを削除しないように十分気をつけてください。