◆パフォーマンスモニタ returns 〜 カスタムパフォーマンスモニタの作成 その1
[ ホーム ] [ 上へ ] [ パフォーマンスモニタ returns 〜 カスタムパフォーマンスモニタの作成 その2 ]
前回までに
をPDH関数群とレジストリデータを使った方法で取得してきました
(VBAではレジストリデータを使ったモニタリングは書いてません)。パフォーマンスモニタで書いてない
最後の項目が独自のパフォーマンスデータの作成です。
今まではWindowsに組み込み済みのパフォーマンスオブジェクト(CPUやメモリなど)の各カウンタデータを
モニタリングしてきましたが、自作のアプリケーションで、その動作のボトルネックやアプリケーションの
状態をカウンタデータとして知りたくなることがあります。典型例がDBMSで、通信トラフィックや
使用メモリ量などの状態をモニタする必要が多々でてきます。実際、例えばSQL Serverを
インストールすると様々なオブジェクト・カウンタが追加されています。
そこで、今回は自作アプリケーションの各データをWindowsシステムの パフォーマンスモニタでモニタリングしてみます。その方法はパフォーマンスモニタシステムが 要求する形式でレジストリデータを作成することによって行います。従ってこれまでに書いてきたこと、特に レジストリデータでの一覧表示、 レジストリデータを使ってのモニタ はカスタムパフォーマンスデータの作成に必須の条件になります。 逆にここを理解していればあと一息といえます。
・概要
レジストリデータを使ってのモニタ で書いたようにカウンタデータをモニタリングするには、RegQueryValueExの第二引数に 目的のカウンタオブジェクトのインデックスを指定して、PERF_DATA_BLOCKを先頭とするPERF_OBJECT_TYPE, PERF_COUNTER_DEFINITIONなどのメモリブロックを取得することによって実現できます。 PDH系の関数を使った場合でも多分?内部的にはこれらのメモリブロックを取得していると 思われるので同じ事だと思います。
RegQueryValueExが呼ばれてからPERF_DATA_BLOCKを先頭とするメモリブロックがどのようにして パフォーマンスモニタシステムによって返されているかを理解すれば、カスタムパフォーマンスモニタの 作成の流れを掴むことができ、この返却されるメモリブロックの作成が今回のテーマの中心 となります。ということで、まずこの流れを書いてきます。
上記は後の実践で作成するレジストリエントリのスナップショットですが、 RegQueryValueExが最初に呼ばれるとパフォーマンスシステムは HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\xxxxx\Performanceの "Librarvnエントリ"に登録されているDLLをロードして"Openエントリ" で登録されている関数(以後Open関数と呼びます)を呼び出します。
Open関数で様々な初期化をした後、システムは"Collectエントリ"で 登録された関数(以後Collect関数と呼びます)を実行します。この関数内で RegQueryValueExに返すべきパフォーマンスデータを含めてPERF_OBJECT_TYPE、PERF_COUNTER_DEFINITION, PERF_INSTANCE_DEFINITIONの構造体にデータをセットしてシステムに返却します。このデータをシステムが RegQueryValueExの第五引数に返却します。以後、RegQueryValueExが呼ばれるたびにCollect関数が 呼ばれてその都度カウンタデータがセットされてシステムに戻されます。
パフォーマンスデータ収集の必要がなくなったら、RegQueryValueExを呼んでいる パフォーマンスデータ収集アプリケーション( パフォーマンスモニタユーティリティ など)を終了します。このとき、"Closeエントリ"に登録された関数 (以後Close関数と呼びます)が最後の関数として呼び出されます。 ここで様々な終了処理を行います。
注意しないといけない点は、呼ばれるOpen, Collect, Close関数は
DLLに実装された関数で、複数のスレッドから呼ばれる可能性があるということです。
Open関数とClose関数の処理内容に関してはDLLがロードされた後、アンロードされる前に
それぞれ1度実行されればよいということ(DLLにアクセスする2つ目以降のスレッドは
初期化処理の必要がなく、DLLにアクセスする最後のスレッド以外は終了処理は必要がない)、
DLLのインスタンスは1つということ、Open、Close関数はパフォーマンスデータ収集
アプリケーションごとに1度だけ実行されるということ、を念頭において初期化、
終了処理以外に、アクセススレッド数の管理もしなければなりません。
Collect関数に関しては、あるスレッド(アプリケーション)がモニタリング対象から データを取得してCollect関数内の変数に格納したときに、別のスレッドがその変数を初期化して しまうこともありえるので、システムから与えられた結果格納領域に直接結果をコピーして いくようにする必要がでてくると思います。
これまでの特定のアプリケーションの独自のパフォーマンスモニタの流れを図にすると以下のようになります。
A | RegQueryValueExでモニタリング対象アプリのデータをモニタするように要求。 |
B | パフォーマンスモニタシステムはレジストリの HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services以下のPerformance キーに登録されている"Libraryエントリ"のDLLを検索し、"Openエントリ", "Collectエントリ"に登録されている関数名を取得 |
C | Bのレジストリデータに従ってDLLをロードし、Open関数を呼び出して返却データの初期化などをして、 続いてCollect関数を呼び出します。また、パフォーマンスデータ収集アプリの終了時にClose関数を 呼び出して後処理をします。 |
D | Collect関数内でモニタリング対象のアプリケーションと通信して目的のデータを取得して、 返却用データ(PERF_OBJECT_TYPE以下のメモリブロック)を作成します。 |
E、F | Dで収集したカウンタデータを含むメモリブロックを受け取ったら、 RegQueryValueExの発行元の収集アプリに結果を返却 |
上記のパフォーマンスデータ収集のしくみから、 実際のカスタムパフォーマンスモニタの作成は以下の作業が必要になります。
前回の
レジストリデータでのモニタリングで、
「あと少しでカスタムパフォーマンスモニタができる」と書きました。事実、パフォーマンスモニタに
関する部分は2.の部分で内容としては大変なボリュームとはいえません。
ところが、実際にモニタリングしようとすると最初に問題になるのは3.のモニタ対象の
アプリケーションとの通信です。パフォーマンスDLLからみれば、対象のアプリケーションは
外部のプログラムで基本的にはそのアドレス空間にはアクセスできないのでプロセス間通信
の理解が必要です。これはこれで1つのテーマとなりパフォーマンスモニタよりも内容は濃いくらいです。
しかし、これがわからないとカスタムパフォーマンスモニタは作成できないので
プロセス間通信について少し書いてますので理解してください。
さらに、Open,Collect,Close関数でエラーが発生した場合、普通でしたら例外処理等で処理しますが、
パフォーマンスモニタに関しては、同時にメモリやスレッドなど他のシステムモニタも同時に行っていて
全体に影響がでることもあって、イベントログで報告することが唯一の方法になります。
デバック時や正常にデータが取得できない場合はイベントビューアでどこでエラーになったかを
確認できます。イベントログもパフォーマンスモニタとは異なるテーマで、また別機会に書きますが
今回はコードの掲載だけに内容には触れません。
<0.オブジェクト・カウンタの設計>
レジストリデータでの一覧表示 で取得したPERF_DATA_BLOCK以下のPERF_OBJECT_TYPE, PERF_COUNTER_DEFINITIONをみて、 モニタ対象アプリケーションに対してカウンタオブジェクト、カウンタ定義を設計します。 後の例で掲載しますが、例えばあるダイアログアプリケーションをカウンタオブジェクトとして、 そのダイアログ内のコントロールに表示される色と、そのダイアログが表示されてからの経過時間の 2つの項目をカウンタとして設計します。
<1.レジストリエントリ・キーの作成>
先に示したレジストリエディタのスナップショット
のようなキーとエントリをレジストリに追加します。パフォーマンスモニタシステムがDLL名などを
検索するレジストリの位置は決まっていて
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\application_name\Performance
です。application_nameはカスタムパフォーマンスアプリケーション名でDLL名を指定することが
多いです。この名前は後に使用する重要な名前なので慎重に命名したほうがいいかと思います。
レジストリへの追加はregeditを使いますが、 これで追加できるのは"Library", "Open", "Collect", "Close" の4つだけです。Wbem〜の4つのエントリは自動で加えられるので無視して
ですが、これらの値はそれぞれ作成するオブジェクトの最初のインデックス値・
説明インデックス値と、最後カウンタのインデックス値・説明インデックス、オブジェクトの
インデックスのリストです。レジストリでは最初と最後のインデックスですが、結局0.で
設計したオブジェクトとカウンタのインデックスをすべて割り振らないといけないということです。
ところが、組み込みのオブジェクト/カウンタを含めてどれだけのインデックスが使用されているかは
個々のPCによってまちまちです。これをインストールするPCごとにいちいち調べてレジストリを
さわるのはなかなかたいへんです。
さらに、もう忘れたかもしれませんが、
レジストリデータでの一覧表示のデータ構造1
にオブジェクト/カウンタ名とその説明を追加しなければなりません。つまり、
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\011
の位置にMULTI_SZ形式で追加する必要もあるということです。
これを手動で行うのはなかなか難しいですが、正確・確実にやってくれるツールがあります。
(lodctrユーティリティ)
個々のPCに異なるパフォーマンス関係のインデックスとオブジェクト/カウンタ名、 説明をレジストリに登録してくれるユーティリティがlodctrです。 インデックスのレジストリへの登録を自動でやってくれるといっても、あなたが勝手に作った オブジェクト・カウンタをわかるわけがないのでその情報は与えなければなりません。 その仕組みは次のようです。
C++ ヘッダファイル (.hファイル) - ex. headerfile.h |
#define OBJ1 0 #define CNTR1 2 #define CNTR2 4 |
C++のヘッダファイルに オブジェクト・カウンタインデックスのオフセット値、
つまり相対位置を0をスタート値として偶数値を割り振っていきます。C++ヘッダに記述するということは
プログラム時にこのヘッダを使うということです。Collect関数でカスタムパフォーマンスデータを
作成する際にPERF_OBJECT_TYPE, PERF_COUNTER_DEFINITIONにインデックスを設定しますが、
前もってレジストリから取得しておいたFirst Counter値、First Help値にこのオフセット値を
加えるという仕組みになっています。
さらに、このヘッダファイルは実際にレジストリにインデックスを登録するlodctrが直接使用する
下記の.iniファイルにも使われます。
lodctrが使用する .ini ファイル |
[info] drivername=application_name symbolfile=headerfile.h [languages] 009=English [objects] OBJ1_009_NAME=object_name [text] OBJ1_009_NAME=object_name OBJ1_009_HELP=content of object CNTR1_009_NAME=counter1_name CNTR1_009_HELP=content of counter1 CNTR2_009_NAME=counter2_name CNTR2_009_HELP=content of counte2 |
C++ヘッダファイル、iniファイルができたら、
lodctr xxxxx.ini
を実行すると、
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\application_name\Performance
の各エントリにはレジストリエディタのスナップショットで示した
ようなFirst Counter、First Help, Last Counter, Last Help , Object Listの値が加えられ、
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\009
にはインデックスと名称のペアが追加されます。(言語設定が011)の場合はレジストリキーが
"011"に変わります。