プローブの対象となるソフトウェアコンポーネントのソースコードを自身で管理できる場合は、インジェクトプローブの代わりに埋め込みプローブを作成することをお勧めします。
インジェクトプローブを作成する際、初期作業の大半はインターセプトするメソッドの指定や、ハンドラーメソッドのメソッドパラメーターとしてインジェクトされるオブジェクトの選択に費やされます。埋め込みプローブでは、monitor対象のメソッドから直接埋め込みプローブAPIを呼び出すことができるため、これは不要です。埋め込みプローブのもう1つの利点は、デプロイが自動化されることです。プローブはソフトウェアと共に出荷され、アプリケーションがプロファイルされた際にJProfiler UIに表示されます。出荷が必要な依存関係は、主にプロファイリングエージェントのフックとして機能する空のメソッド本体で構成された、Apache 2.0ライセンスの小さなJARファイルのみです。
開発環境
開発環境はインジェクトプローブの場合と同じですが、アーティファクト名がjprofiler-probe-injectedではなくjprofiler-probe-embeddedである点、およびプローブを別のプロジェクトで開発するのではなくJARファイルをアプリケーションと共に出荷する点が異なります。ソフトウェアコンポーネントに埋め込みプローブを追加するために必要なプローブAPIは、単一のJARアーティファクトに含まれています。APIを調べる際は、Javadocの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(FooPayloadProbe.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 MyEventTypes.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は、1回の呼び出しで分割を実行するRunnableおよびCallable引数を取るexecute()メソッドも提供しています。
テレメトリー
埋め込みプローブではテレメトリーのパブリッシュが特に便利です。同一のクラスパス上にあるため、アプリケーション内のすべての静的メソッドに直接アクセスできるからです。インジェクトプローブと同様に、プローブ設定クラスのpublic
staticメソッドに@Telemetryでアノテーションを付け、数値を返します。詳細については、プローブの概念の章を参照してください。埋め込みプローブAPIとインジェクトプローブAPIの@Telemetryアノテーションは同等であり、異なるパッケージに存在しているだけです。
埋め込みプローブAPIとインジェクトプローブAPIの間で共通するもう1つの機能は、ThreadStateクラスを使用してスレッド状態を変更できることです。これも同様に、クラスは異なるパッケージで両方のAPIに存在します。
デプロイ
JProfiler
UIでプロファイリングを行う際、埋め込みプローブを有効にするための特別な手順は必要ありません。ただし、プローブが登録されるのは、PayloadまたはSplitへの最初の呼び出しが行われたときのみです。その時点でのみ、関連付けられたプローブビューがJProfilerで作成されます。組み込みプローブやインジェクトプローブの場合のように、最初からプローブビューを表示させたい場合は、ペイロードプローブに対して
Payload.register(FooPayloadProbe.class);
を、スプリットプローブに対しては
Split.register(FooSplitProbe.class);
を呼び出すことができます。
オーバーヘッドを最小限に抑えるために、コマンドラインスイッチなどで制御してPayloadやSplitのメソッドを条件付きで呼び出すことを検討しているかもしれません。しかし、メソッド本体は空であるため、通常は不要です。プロファイリングエージェントがattachされていない状態では、ペイロード文字列の構築を除いてオーバーヘッドは発生しません。プローブイベントは極小のスケールで生成されるべきではないことを考慮すると、比較的低頻度で作成されるため、ペイロード文字列の構築にかかる負荷は比較的小さいはずです。
コンテナに関するもう1つの懸念事項として、クラスパス上に外部依存関係を公開したくないという点が挙げられます。コンテナのユーザーも埋め込みプローブAPIを使用している可能性があり、競合が発生するおそれがあります。そのような場合は、埋め込みプローブAPIを独自のパッケージにシェードすることができます。JProfilerはシェードされたパッケージを引き続き認識し、APIクラスを正しくインスツルメントします。ビルド時のシェーディングが現実的でない場合は、ソースアーカイブを展開してクラスをプロジェクトの一部に含めることができます。