スクリプトプローブと同様に、インジェクションプローブは選択されたメソッドのインターセプションハンドラーを定義します。 ただし、インジェクションプローブはJProfiler GUIの外部(IDE内)で開発され、JProfilerが提供するインジェクションプローブAPIに依存します。このAPIは寛容なApache License, Version 2.0の下でライセンス供与されているため、関連するアーティファクトの配布が容易です。
インジェクションプローブの使用を開始する最良の方法は、JProfilerインストールディレクトリのapi/samples/simple-injected-probeにあるサンプルを調べることです。
そのディレクトリで../gradlewを実行してコンパイルおよび実行します。Gradleビルドファイルbuild.gradleには、サンプルに関する詳細情報が含まれています。api/samples/advanced-injected-probeのサンプルは、制御オブジェクトを含むプローブシステムのより多くの機能を示しています。
開発環境
インジェクションプローブの開発に必要なプローブAPIは、以下のMaven座標を持つ単一のアーティファクトに含まれています。
group: com.jprofiler artifact: jprofiler-probe-injected version: <JProfiler version>
ここで、このマニュアルに対応するJProfilerバージョンは16.3です。
JAR、ソース、およびJavadocアーティファクトはMaven Centralリポジトリで公開されています。 GradleやMavenなどのビルドツールを使用して開発クラスパスにプローブAPIを追加するか、JProfilerインストール内のJARファイル
api/jprofiler-probe-injected.jar
を使用できます。
com.jprofiler.api.probe.injectedパッケージの概要は、APIを調べるための良い出発点となります。
プローブの構造
インジェクションプローブは、com.jprofiler.api.probe.injected.Probeでアノテーションが付けられたクラスです。そのアノテーションの属性により、プローブ全体の設定オプションが公開されます。たとえば、個別に調査する必要のないプローブイベントが大量に生成される場合、events属性を使用すると、プローブイベントビューを無効にしてオーバーヘッドを削減できます。
@Probe(name = "Foo", description = "Shows foo server requests", events = false)
public class FooProbe {
...
}
インターセプションハンドラーを定義するために、特別にアノテーションを付けたstaticメソッドをプローブクラスに追加します。
PayloadInterceptionアノテーションはペイロードを作成し、SplitInterceptionアノテーションは呼び出しツリーを分割します。アノテーションに応じて、ハンドラーの戻り値がペイロードまたは分割文字列として使用されます。スクリプトプローブと同様に、nullを返すとインターセプションの効果はありません。インターセプトされたメソッドのタイミング情報は自動的に計算されます。
@Probe(name = "FooBar")
public class FooProbe {
@PayloadInterception(
invokeOn = InvocationType.ENTER,
method = @MethodSpec(className = "com.bar.Database",
methodName = "processQuery",
parameterTypes = {"com.bar.Query"},
returnType = "void"))
public static String fooRequest(@Parameter(0) Query query) {
return query.getVerbose();
}
@SplitInterception(
method = @MethodSpec(className = "com.foo.Server",
methodName = "handleRequest",
parameterTypes = {"com.foo.Request"},
returnType = "void"))
public static String barQuery(@Parameter(0) Request request) {
return request.getPath();
}
}
上記の例でわかるように、どちらのアノテーションにも、インターセプトされるメソッドをMethodSpecで定義するためのmethod属性があります。スクリプトプローブとは異なり、MethodSpecには空のクラス名を設定できるため、クラス名に関係なく、特定のシグネチャを持つすべてのメソッドがインターセプトされます。あるいは、MethodSpecのsubtypes属性を使用して、クラス階層全体をインターセプトすることもできます。
すべてのパラメーターが自動的に使用可能になるスクリプトプローブとは異なり、ハンドラーメソッドでは関心のある値を要求するパラメーターを宣言します。各パラメーターにはcom.jprofiler.api.probe.injected.parameterパッケージのアノテーションが付加されるため、プロファイリングエージェントはどのオブジェクトまたはプリミティブ値をメソッドに渡す必要があるかを認識します。たとえば、ハンドラーメソッドのパラメーターに@Parameter(0)のアノテーションを付けると、インターセプトされたメソッドの最初のパラメーターがインジェクトされます。
インターセプトされたメソッドのメソッドパラメーターは、すべてのインターセプションタイプで使用できます。プロファイリングエージェントにメソッドのエントリではなく終了をインターセプトするように指示した場合、ペイロードインターセプションは@ReturnValueで戻り値に、または@ExceptionValueでスローされた例外にアクセスできます。これは、PayloadInterceptionアノテーションのinvokeOn属性で行います。
スクリプトプローブとは対照的に、インターセプションアノテーションのreentrant属性をtrueに設定すると、インジェクションプローブハンドラーはインターセプトされたメソッドの再帰的呼び出しに対しても呼び出すことができます。ハンドラーメソッドにProbeContext型のパラメーターを指定すると、ProbeContext.getOuterPayload()またはProbeContext.restartTiming()を呼び出すことで、ネストされた呼び出しに対するプローブの動作を制御できます。
高度なインターセプション
場合によっては、プローブ文字列を構築するために必要なすべての情報を収集するのに、単一のインターセプションでは不十分なことがあります。その目的のために、プローブにはペイロードや分割を作成しない、Interceptionでアノテーションが付けられた任意の数のインターセプションハンドラーを含めることができます。情報はプローブクラスのstaticフィールドに格納できます。マルチスレッド環境でのスレッドセーフティのために、参照型の格納にはThreadLocalインスタンスを使用し、カウンターの保持にはjava.util.concurrent.atomicパッケージのアトミック数値型を使用する必要があります。
状況によっては、メソッドのエントリとメソッドの終了の両方に対してインターセプションが必要になることがあります。よくあるケースとしては、別のインターセプションの動作を変更するinMethodCallのような状態変数を保持する場合です。デフォルトのインターセプションタイプであるエントリのインターセプションで、inMethodCallをtrueに設定できます。次に、そのインターセプションの直下に別のstaticメソッドを定義し、@AdditionalInterception(invokeOn
=
InvocationType.EXIT)アノテーションを付けます。インターセプトされるメソッドは上記のインターセプションハンドラーから引き継がれるため、再指定する必要はありません。メソッド本文で、inMethodCall変数をfalseに設定できます。
...
private static final ThreadLocal<Boolean> inMethodCall =
ThreadLocal.withInitial(() -> Boolean.FALSE);
@Interception(
invokeOn = InvocationType.ENTER,
method = @MethodSpec(className = "com.foo.Server",
methodName = "internalCall",
parameterTypes = {"com.foo.Request"},
returnType = "void"))
public static void guardEnter() {
inMethodCall.set(Boolean.TRUE);
}
@AdditionalInterception(InvocationType.EXIT)
public static void guardExit() {
inMethodCall.set(Boolean.FALSE);
}
@SplitInterception(
method = @MethodSpec(className = "com.foo.Server",
methodName = "handleRequest",
parameterTypes = {"com.foo.Request"},
returnType = "void"),
reentrant = true)
public static String splitRequest(@Parameter(0) Request request) {
if (!inMethodCall.get()) {
return request.getPath();
} else {
return null;
}
}
...
このユースケースの動作するサンプルは、api/samples/advanced-injected-probe/src/main/java/AdvancedAwtEventProbe.javaで確認できます。
制御オブジェクト
ProbeアノテーションのcontrolObjects属性がtrueに設定されていない限り、制御オブジェクトビューは表示されません。制御オブジェクトを操作するには、ハンドラーメソッドでその型のパラメーターを宣言してProbeContextを取得する必要があります。以下のサンプルコードは、制御オブジェクトを開いてプローブイベントに関連付ける方法を示しています。
@Probe(name = "Foo", controlObjects = true, customTypes = MyEventTypes.class)
public class FooProbe {
@Interception(
invokeOn = InvocationType.EXIT,
method = @MethodSpec(className = "com.foo.ConnectionPool",
methodName = "createConnection",
parameterTypes = {},
returnType = "com.foo.Connection"))
public static void openConnection(ProbeContext pc, @ReturnValue Connection c) {
pc.openControlObject(c, c.getId());
}
@PayloadInterception(
invokeOn = InvocationType.EXIT,
method = @MethodSpec(className = "com.foo.ConnectionPool",
methodName = "createConnection",
parameterTypes = {"com.foo.Query", "com.foo.Connection"},
returnType = "com.foo.Connection"))
public static Payload handleQuery(
ProbeContext pc, @Parameter(0) Query query, @Parameter(1) Connection c) {
return pc.createPayload(query.getVerbose(), c, MyEventTypes.QUERY);
}
...
}
制御オブジェクトには定義されたライフタイムがあり、プローブビューはタイムラインおよび制御オブジェクトビューでそれらのオープン時間とクローズ時間を記録します。可能であれば、ProbeContext.openControlObject()およびProbeContext.closeControlObject()を呼び出して、制御オブジェクトを明示的に開閉する必要があります。そうしない場合は、制御オブジェクトの表示名を解決する@ControlObjectNameでアノテーションが付けられたstaticメソッドを宣言する必要があります。
ハンドラーメソッドがStringではなくPayloadのインスタンスを返す場合、プローブイベントを制御オブジェクトに関連付けることができます。ProbeContext.createPayload()メソッドは、制御オブジェクトとプローブイベントタイプを受け取ります。許可されたイベントタイプを持つenumは、ProbeアノテーションのcustomTypes属性で登録する必要があります。
制御オブジェクトは、メソッドのエントリに対応する時間測定の開始時に指定する必要があります。場合によっては、ペイロード文字列の名前が戻り値や他のインターセプションに依存するため、メソッドの終了時にしか利用できないことがあります。その場合は、ProbeContext.createPayloadWithDeferredName()を使用して名前のないペイロードオブジェクトを作成できます。すぐ下に@AdditionalInterception(invokeOn
=
InvocationType.EXIT)でアノテーションが付けられたインターセプションハンドラーを定義し、そのメソッドからStringを返すと、それが自動的にペイロード文字列として使用されます。
スレッド状態のオーバーライド
データベースドライバーや外部リソースへのネイティブコネクタの実行時間を測定する場合、JProfilerに対して一部のメソッドを別のスレッド状態にするよう指示する必要が生じることがあります。たとえば、データベース呼び出しを「Net I/O」スレッド状態にすることが有用です。通信メカニズムが標準のJava I/O機能を使用せず、何らかのネイティブメカニズムを使用している場合、自動的にはそのようになりません。
ThreadState.NETIO.enter()とThreadState.exit()の呼び出しのペアを使用すると、プロファイリングエージェントはそれに応じてスレッド状態を調整します。
...
@Interception(invokeOn = InvocationType.ENTER, method = ...)
public static void enterMethod(ProbeContext probeContext, @ThisValue JComponent component) {
ThreadState.NETIO.enter();
}
@AdditionalInterception(InvocationType.EXIT)
public static void exitMethod() {
ThreadState.exit();
}
...
デプロイ
インジェクションプローブをデプロイするには、クラスパスに配置するかどうかに応じて2つの方法があります。 以下のVMパラメーターを使用すると、
-Djprofiler.probeClassPath=...
プロファイリングエージェントによって個別のプローブクラスパスが設定されます。プローブクラスパスには、Windowsでは「;」、その他のプラットフォームでは「:」で区切られたディレクトリやJARファイルを含めることができます。プロファイリングエージェントはプローブクラスパスをスキャンし、すべてのプローブ定義を検出します。
プローブクラスをクラスパスに配置する方が簡単な場合は、VMパラメーター
-Djprofiler.customProbes=...に完全修飾クラス名のカンマ区切りリストを設定できます。プロファイリングエージェントは、これらの各クラス名に対してインジェクションプローブのロードを試みます。