プローブのターゲットとなるソフトウェアコンポーネントのソースコードを制御できる場合は、注入プローブではなく埋め込みプローブを書くべきです。
注入プローブを書く際の最初の作業のほとんどは、インターセプトするメソッドを指定し、ハンドラーメソッドのメソッドパラメータとして注入オブジェクトを選択することに費やされます。埋め込みプローブでは、監視対象のメソッドから直接埋め込みプローブAPIを呼び出すことができるため、これが不要です。埋め込みプローブのもう一つの利点は、デプロイが自動であることです。プローブはソフトウェアと一緒に出荷され、アプリケーションがプロファイルされたときにJProfiler UIに表示されます。出荷する必要がある唯一の依存関係は、プロファイリングエージェントのフックとして機能する空のメソッドボディを主に含む、Apache 2.0ライセンスの小さなJARファイルです。
開発環境
開発環境は注入プローブと同じですが、アーティファクト名が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句で行うべきです。
測定されたコードブロックが値を生成しない場合は、ペイロード文字列とRunnableを取るPayload.executeメソッドを呼び出すことができます。Java
8+では、ラムダやメソッド参照を使用してこのメソッド呼び出しを非常に簡潔にすることができます。
public void measuredCall(String query) {
Payload.execute(FooPayloadProbe.class, query, this::performWork);
}
その場合、ペイロード文字列は事前に知られている必要があります。Callableを取るexecuteのバージョンもあります。
public QueryResult measuredCall(String query) throws Exception {
return Payload.execute(PayloadProbe.class, query, () -> query.execute());
}
Callableを取るシグネチャの問題は、Callable.call()がチェックされたExceptionをスローするため、それをキャッチするか、含むメソッドで宣言する必要があることです。
制御オブジェクト
ペイロードプローブは、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 {}
分割プローブの重要な構成の1つは、それが再入可能であるべきかどうかです。デフォルトでは、トップレベルの呼び出しのみが分割されます。再帰呼び出しも分割したい場合は、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()メソッドを提供し、単一の呼び出しで分割を実行します。
テレメトリー
埋め込みプローブのテレメトリーを公開することは特に便利です。同じクラスパスにあるため、アプリケーション内のすべての静的メソッドに直接アクセスできます。注入プローブと同様に、プローブ構成クラスの静的なパブリックメソッドに@Telemetryを注釈し、数値を返します。詳細については、プローブの概念の章を参照してください。埋め込みプローブAPIと注入プローブAPIの@Telemetryアノテーションは同等であり、異なるパッケージにあるだけです。
埋め込みプローブAPIと注入プローブAPIのもう一つの並行機能は、ThreadStateクラスを使用してスレッド状態を変更する機能です。再び、クラスは異なるパッケージで両方のAPIに存在します。
デプロイメント
JProfiler
UIでプロファイリングする際に埋め込みプローブを有効にするための特別な手順は必要ありません。ただし、最初のPayloadまたはSplitへの呼び出しが行われたときにのみプローブが登録されます。その時点でのみ、関連するプローブビューがJProfilerに作成されます。組み込みおよび注入プローブの場合のように、最初からプローブビューが表示されることを好む場合は、次のように呼び出すことができます。
PayloadProbe.register(FooPayloadProbe.class);
ペイロードプローブの場合
SplitProbe.register(FooSplitProbe.class);
分割プローブの場合。
PayloadおよびSplitのメソッドを条件付きで呼び出すかどうかを検討しているかもしれません。オーバーヘッドを最小限に抑えるためにコマンドラインスイッチで制御するかもしれません。しかし、メソッドボディが空であるため、これは一般的には必要ありません。プロファイリングエージェントがアタッチされていない場合、ペイロード文字列の構築を除いてオーバーヘッドは発生しません。プローブイベントは微視的なスケールで生成されるべきではないことを考慮すると、それらは比較的まれに作成されるため、ペイロード文字列の構築は比較的無視できる努力であるべきです。
コンテナにとってのもう一つの懸念は、クラスパス上の外部依存関係を公開したくないかもしれないということです。コンテナのユーザーが埋め込みプローブAPIを使用することもでき、それが競合を引き起こす可能性があります。その場合、埋め込みプローブAPIを独自のパッケージにシェードすることができます。JProfilerはシェードされたパッケージを認識し、APIクラスを正しくインストゥルメントします。ビルド時のシェードが実用的でない場合は、ソースアーカイブを抽出し、クラスをプロジェクトの一部にすることができます。