スクリプトプローブと同様に、インジェクトされたプローブは選択されたメソッドのインターセプションハンドラを定義します。 しかし、インジェクトされたプローブは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のバージョンは15.0.3です。
Jar、ソース、およびjavadocアーティファクトは、次のリポジトリに公開されています。
https://maven.ej-technologies.com/repository
GradleやMavenのようなビルドツールを使用して開発クラスパスにプローブAPIを追加するか、 JARファイルを使用することができます。
api/jprofiler-probe-injected.jar
JProfilerインストール内で。
Javadocを閲覧するには、次に移動します。
api/javadoc/index.html
そのjavadocは、JProfilerによって公開されているすべてのAPIのjavadocを組み合わせたものです。
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 {
...
}
プローブクラスには、インターセプションハンドラを定義するために特別に注釈された静的メソッドを追加します。
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で注釈された任意の数のインターセプションハンドラを含めることができます。
情報はプローブクラスの静的フィールドに保存できます。マルチスレッド環境でのスレッドセーフティのために、参照型を保存するためにThreadLocalインスタンスを使用し、
カウンタを維持するためにjava.util.concurrent.atomicパッケージのアトミック数値型を使用するべきです。
ある状況下では、メソッドのエントリとメソッドの出口の両方に対してインターセプションが必要です。
一般的なケースは、inMethodCallのような状態変数を維持し、別のインターセプションの動作を変更する場合です。
デフォルトのインターセプションタイプであるエントリインターセプションでinMethodCallをtrueに設定できます。
そのインターセプションの直下に別の静的メソッドを定義し、@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で注釈された静的メソッドを宣言する必要があります。
プローブイベントは、ハンドラメソッドが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では';'、他のプラットフォームでは':'で区切ります。 プロファイリングエージェントはプローブクラスパスをスキャンし、すべてのプローブ定義を見つけます。
プローブクラスをクラスパスに配置する方が簡単な場合は、VMパラメータを設定できます。
-Djprofiler.customProbes=...
完全修飾クラス名のカンマ区切りリストにします。これらのクラス名のそれぞれについて、プロファイリングエージェントはインジェクトされたプローブをロードしようとします。