パフォーマンスカウンタの作成方法 ~ パフォーマンス拡張 DLL とその利用方法

別の資料でパフォーマンスモニタの仕組みを説明しました。

この資料ではパフォーマンスオブジェクト及びカウンターを作成するのに必要な、パフォーマンス拡張 DLL (Performance Extension DLL) とそれを利用するプログラムを作成してみましょう。

パフォーマンス拡張 DLL

パフォーマンス拡張 DLL は次の関数をエクスポートします。これがパフォーマンスモニタとのインターフェイスになります。

OpenDWORD CALLBACK OpenPerformanceData ( LPWSTR lpDeviceNames );
CloseDWORD WINAPI ClosePerformanceData();
CollectDWORD 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 以下を削除します。

レジストリの削除は間違ったキーを削除しないように十分気をつけてください。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説