プローブの対象となるソフトウェアコンポーネントのソースコードを管理している場合は、インジェクトプローブではなく、組み込みプローブを作成することを推奨します。
インジェクトプローブを作成する際の初期作業の多くは、インターセプトするメソッドの指定や、ハンドラーメソッドのメソッドパラメータとしてインジェクトされるオブジェクトの選択に費やされます。組み込みプローブの場合、監視対象メソッドから直接組み込みプローブAPIを呼び出せるため、この作業は不要です。さらに、組み込みプローブの利点として、デプロイが自動化されている点が挙げられます。プローブはソフトウェアと一緒に配布され、アプリケーションがプロファイルされた際にJProfiler UIに表示されます。配布が必要な依存関係は、主にプロファイリングエージェントのフックとして空のメソッド本体から構成される、Apache 2.0 Licenseの下でライセンスされた小さなJARファイルのみです。
開発環境
開発環境はインジェクトプローブの場合と同じですが、アーティファクト名がjprofiler-probe-injectedではなくjprofiler-probe-embeddedとなり、プローブを別プロジェクトで開発するのではなく、アプリケーションと一緒にJARファイルを配布します。組み込みプローブをソフトウェアコンポーネントに追加するために必要なプローブAPIは、単一のJARアーティファクトに含まれています。javadocでは、APIを調査する際にパッケージ概要(com.jprofiler.api.probe.embedded)から始めてください。
インジェクトプローブと同様に、組み込みプローブにも2つのサンプルがあります。api/samples/simple-embedded-probeには、組み込みプローブの作成を始めるためのサンプルがあります。そのディレクトリで../gradlewを実行してコンパイル・実行し、gradleビルドファイルbuild.gradleを確認して実行環境を理解してください。より多くの機能(制御オブジェクトなど)については、api/samples/advanced-embedded-probeのサンプルを参照してください。
ペイロードプローブ
インジェクトプローブと同様に、設定用のプローブクラスが必要です。プローブクラスは、プローブがペイロードを収集するか呼び出しツリーを分割するかに応じて、com.jprofiler.api.probe.embedded.PayloadProbeまたはcom.jprofiler.api.probe.embedded.SplitProbeを継承する必要があります。インジェクトプローブAPIでは、ハンドラーメソッドにペイロード収集や分割用の異なるアノテーションを使用しますが、組み込みプローブAPIではハンドラーメソッドが存在せず、設定はプローブクラス自体で行います。
public class FooPayloadProbe extends PayloadProbe {
@Override
public String getName() {
return "Foo queries";
}
@Override
public String getDescription() {
return "Records foo queries";
}
}
インジェクトプローブがアノテーションで設定を行うのに対し、組み込みプローブはプローブの基底クラスのメソッドをオーバーライドして設定します。ペイロードプローブの場合、唯一の抽象メソッドはgetName()で、他のメソッドは必要に応じてオーバーライド可能なデフォルト実装があります。例えば、オーバーヘッドを減らすためにイベントビューを無効化したい場合は、isEvents()をオーバーライドしてfalseを返すことができます。
ペイロードの収集とそのタイミング計測には、Payload.enter()とPayload.exit()のペア呼び出しを使用します。
public void measuredCall(String query) {
Payload.enter(FooPayloadProbe.class);
try {
performWork();
} finally {
Payload.exit(query);
}
}
Payload.enter()呼び出しにはプローブクラスを引数として渡すため、プロファイリングエージェントはどのプローブが対象かを認識できます。Payload.exit()呼び出しは自動的に同じプローブに関連付けられ、ペイロード文字列を引数として受け取ります。exit呼び出しを忘れると呼び出しツリーが壊れるため、必ずtryブロックのfinally節で実行してください。
計測対象のコードブロックが値を生成しない場合は、代わりにPayload.executeメソッドを使用できます。このメソッドはペイロード文字列とRunnableを受け取ります。Java
8以降では、ラムダ式やメソッド参照を使うことで、呼び出しが非常に簡潔になります。
public void measuredCall(String query) {
Payload.execute(FooPayloadProbe.class, query, this::performWork);
}
この場合、ペイロード文字列は事前に分かっている必要があります。executeにはCallableを受け取るバージョンも存在します。
public QueryResult measuredCall(String query) throws Exception {
return Payload.execute(PayloadProbe.class, query, () -> query.execute());
}
Callableを受け取るシグネチャの問題点は、Callable.call()がチェック例外Exceptionをスローするため、catchするか、呼び出し元メソッドでthrows宣言する必要があることです。
制御オブジェクト
ペイロードプローブは、Payloadクラスの適切なメソッドを呼び出すことで、制御オブジェクトを開閉できます。これらは、制御オブジェクトとカスタムイベントタイプを引数に取るPayload.enter()やPayload.execute()のバージョンを使って、プローブイベントに関連付けられます。
public void measuredCall(String query, Connection connection) {
Payload.enter(FooPayloadProbe.class, connection, MyEventTypes.QUERY);
try {
performWork();
} finally {
Payload.exit(query);
}
}
制御オブジェクトビューはプローブ設定で明示的に有効化する必要があり、カスタムイベントタイプもプローブクラスで登録する必要があります。
public class FooPayloadProbe extends PayloadProbe {
@Override
public String getName() {
return "Foo queries";
}
@Override
public String getDescription() {
return "Records foo queries";
}
@Override
public boolean isControlObjects() {
return true;
}
@Override
public Class<? extends Enum> getCustomTypes() {
return Connection.class;
}
}
制御オブジェクトを明示的に開閉しない場合は、getControlObjectNameをオーバーライドして、すべての制御オブジェクトの表示名を解決する必要があります。
分割プローブ
分割プローブの基底クラスには抽象メソッドがありません。これは、プローブビューを追加せずに単に呼び出しツリーを分割する用途にも使えるためです。その場合、最小限のプローブ定義は以下のようになります。
public class FooSplitProbe extends SplitProbe {}
分割プローブの重要な設定の一つは、再入可能(reentrant)かどうかです。デフォルトでは、最上位の呼び出しのみが分割されます。再帰呼び出しも分割したい場合は、isReentrant()をオーバーライドしてtrueを返してください。分割プローブは、isPayloads()をオーバーライドしてtrueを返すことで、プローブビューを作成し、分割文字列をペイロードとして公開することもできます。
分割を実行するには、Split.enter()とSplit.exit()のペア呼び出しを行います。
public void splitMethod(String parameter) {
Split.enter(FooSplitProbe.class, parameter);
try {
performWork(parameter);
} finally {
Split.exit();
}
}
ペイロード収集と異なり、分割文字列はプローブクラスと一緒にSplit.enter()に渡す必要があります。ここでもSplit.exit()を確実に呼び出すことが重要なので、tryブロックのfinally節で実行してください。Splitは、RunnableやCallableを引数に取るexecute()メソッドも提供しており、1回の呼び出しで分割を実行できます。
テレメトリー
組み込みプローブでは、同じクラスパス上にあるため、アプリケーション内のすべてのstaticメソッドに直接アクセスでき、テレメトリーの公開が特に便利です。インジェクトプローブと同様に、プローブ設定クラスのstatic
publicメソッドに@Telemetryアノテーションを付与し、数値を返します。詳細はプローブの概念の章を参照してください。組み込みプローブAPIとインジェクトプローブAPIの@Telemetryアノテーションは同等ですが、パッケージが異なります。
組み込みプローブAPIとインジェクトプローブAPIのもう一つの共通機能として、ThreadStateクラスによるスレッド状態の変更があります。このクラスも両APIに存在しますが、パッケージが異なります。
デプロイ
JProfiler
UIでプロファイリングする際、組み込みプローブを有効化するための特別な手順は必要ありません。ただし、最初にPayloadまたはSplitへの呼び出しが行われた時点でのみプローブが登録されます。その時点で関連するプローブビューがJProfilerに作成されます。組み込みプローブビューを最初から表示したい場合(組み込み・インジェクトプローブと同様)、以下のように呼び出してください。
PayloadProbe.register(FooPayloadProbe.class);
ペイロードプローブの場合は上記、
SplitProbe.register(FooSplitProbe.class);
分割プローブの場合は上記のようになります。
PayloadやSplitのメソッドを、オーバーヘッドを最小化するためにコマンドラインスイッチなどで条件付きで呼び出すべきか検討するかもしれません。しかし、通常はその必要はありません。なぜなら、メソッド本体は空であり、プロファイリングエージェントがattachされていなければ、ペイロード文字列の構築以外にオーバーヘッドは発生しません。プローブイベントはミクロなスケールで生成すべきではないため、比較的まれに作成され、ペイロード文字列の構築はごくわずかな負荷となります。
コンテナ向けのもう一つの懸念点として、クラスパス上の外部依存関係を公開したくない場合があります。コンテナのユーザーが組み込みプローブAPIを利用すると、競合が発生する可能性があります。その場合、組み込みプローブAPIを自身のパッケージにシャドウイング(shading)することができます。JProfilerはシャドウイングされたパッケージも認識し、APIクラスを正しくインストゥルメントします。ビルド時のshadingが難しい場合は、ソースアーカイブを展開してクラスをプロジェクトに組み込むことも可能です。