JDK Flight Recorder を使う
このガイドでは、https://openjdk.org/jeps/328[JDK Flight Recorder] (JFR) を拡張して、みなさんのQuarkusアプリケーションに対する洞察を提供する方法について説明します。 JFRは、Java標準APIやJVMからの様々な情報をイベントとして記録します。 この拡張機能を追加することで、みなさんは独自のQuarkusイベントをJFRに追加できます。これは、みなさんがアプリケーションの問題を解決するのに役立つでしょう。
JFRはファイルをダンプするように事前に設定でき、アプリケーションが終了すると、JFRはそのファイルを出力します。 このファイルには、Quarkusの独自イベントも追加されたJFRイベントストリームの内容が含まれます。 もちろん、アプリケーションが予期せず終了した場合であっても、あなたは必要なときにこのファイルを取得できます。
前提条件
このガイドを完成させるには、以下が必要です:
-
約15分
-
IDE
-
JDK 17+がインストールされ、
JAVA_HOME
が適切に設定されていること -
Apache Maven 3.9.7
-
使用したい場合は、 Quarkus CLI
-
ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること
Mavenプロジェクトの作成
まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します。
Windowsユーザーの場合:
-
cmdを使用する場合、(バックスラッシュ
\
を使用せず、すべてを同じ行に書かないでください)。 -
Powershellを使用する場合は、
-D
パラメータを二重引用符で囲んでください。例:"-DprojectArtifactId=jfr-quickstart"
このコマンドはMavenプロジェクトを生成し、標準のJFRサポートを含む quarkus-jfr
エクステンションをインポートします。
もし既にQuarkusプロジェクトが存在していれば、あなたのプロジェクトのベースディレクトリで以下のコマンドを実行することで、 quarkus-jfr
エクステンションをプロジェクトに追加で きます:
quarkus extension add quarkus-jfr
./mvnw quarkus:add-extension -Dextensions='quarkus-jfr'
./gradlew addExtension --extensions='quarkus-jfr'
これにより、ビルドファイルに以下が追加されます:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jfr</artifactId>
</dependency>
implementation("io.quarkus:quarkus-jfr")
Jakarta REST リソースの調査
src/main/java/org/acme/jfr/JfrResource.java
ファイルを以下の内容で作成します:
package org.acme.jfr;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/hello")
public class JfrResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello";
}
}
アプリケーションにはJFR固有のコードが含まれていないことに注目していただきたい。デフォルトでは、このエンドポイントに送られたリクエストは、コードを変更することなくJFRに記録されます。
QuarkusアプリケーションとJFRの実行
これで、アプリケーションを実行する準備ができました。 Java仮想マシンの起動時にJFRが有効になるように設定すれば、 アプリケーションをJFRと一緒に起動できます。
quarkus dev -Djvm.args="-XX:StartFlightRecording=name=quarkus,dumponexit=true,filename=myrecording.jfr"
./mvnw quarkus:dev -Djvm.args="-XX:StartFlightRecording=name=quarkus,dumponexit=true,filename=myrecording.jfr"
./gradlew --console=plain quarkusDev -Djvm.args="-XX:StartFlightRecording=name=quarkus,dumponexit=true,filename=myrecording.jfr"
JDK Flight Recorderとアプリケーションを実行した状態で、提供されたエンドポイントにリクエストできます:
$ curl http://localhost:8080/hello
hello
これだけでJFRに情報を書き込めます。
JFRダンプファイルを開く
2つのツールを使ってJFRダンプを開くことがでます: jfr CLI と JDK Mission Control(JMC)です。 JFRのAPIを使ってダンプを読む方法もあるが、ここでは触れません。
jfr CLI
jfr CLIはOpenJDKに含まれているツールです。
その実行ファイルは $JAVA_HOME/bin/jfr
です。
jfr CLIを使って、以下のようにすれば、ダンプ・ファイル内のQuarkusに関連するイベントに限定したイベントの一覧を表示できます。
jfr print --categories quarkus myrecording.jfr
JDK Mission Control
JMCは基本的にJFRのGUIビューワーです。 OpenJDKのバイナリにJMCが含まれているディストリビューションもありますが、そうでない場合は手動でダウンロードする必要があります。 JMCを使ってイベントの一覧を見るには、まずJFRファイルを以下のようにJMCに読み込ます。
jmc -open myrecording.jfr
JFRファイルを開くと、2つの選択肢があります。 ひとつは、イベントを表形式の一覧として表示する方法、もうひとつは、イベントが発生したスレッド上で時系列順に表示する方法です。
Quarkusのイベントを表形式で表示するには、JMCの左側にあるイベントブラウザを選択し、JMCの右側にあるQuarkusイベントタイプツリーを開きます。
![JDK Mission Control Event Browser view](/guides/images/jfr-event-browser.png)
Quarkusのイベントをスレッド上で時系列順に表示するには、JMCの左側にある Java application
と Threads
を選択します。
![JDK Mission Control thread view](/guides/images/jfr-java-ap-thread.png)
標準の設定では、Quarkusのイベントは表示されません。 Quarkusのイベントを表示するには、3つの手順を実行する必要があります。
-
右クリックして、
Edit Thread Activity Lanes…
を選択します。 -
左側にある新しいレーンを追加する+ボタンを選択し、チェックするとそのレーンが表示されます。
-
レーンに表示さ せたいイベントタイプとしてQuarkusを選択し、OKを押します。
![JDK Mission Control Edit Thread Activity Lanes](/guides/images/jfr-edit-thread-activity-lanes.png)
これで、スレッドごとのQuarkusイベントを表示できるようになりました。
![JDK Mission Control thread view](/guides/images/jfr-thread.png)
ノンブロッキングでは、1つのスレッドで複数の処理が見かけ上同時に処理されます。 そのため、このエクステンションでは複数の JFR イベントが同時に記録され、JMC 上でいくつものイベントが重なるかもしれません。 このため、見たいイベントを見ることが難しくなることがあるでしょう。 これを避けるには、リクエストID を使ってイベントを絞り込み、見たいリクエストの情報だけを表示させることをお勧めします。 |
イベント
リクエストの特定
このエクステンションは OpenTelemetry エクステンションと連携します。 このエクステンションによって記録されるイベントには、トレース ID とスパン ID を持ちます。これらはそれぞれ OpenTelemetry の ID で記録されます。
つまり、OpenTelemetry により提供される UI から、対象のトレース ID とスパン ID を特定した後、その ID を使って JFR ですぐに詳細にジャンプできます。
もし OpenTelemetry エクステンションを有効にしていない場合、このエクステンションはリクエストごとに ID を作成し、それを traceId として JFR イベントに紐付けます。 この場合、スパン ID は null になります。
今のところ、QuarkusにはRESTイベントしかありませんが、将来的にイベントを追加する際には、このIDを使用して各イベントを互いに紐付ける予定です。
イベントの実装方針
JFRがイベントを記録し始めた時点では、そのイベントはまだJFRに記録されていませんが、そのイベントをコミットした時点で、そのイベントは記録されます。 そのため、ダンプ時に記録を開始しているが、まだコミットしていないイベントはダンプされません。 これはJFRの設計上避けられません。 これは、処理が長引いた場合、イベントが永遠に記録されないことを意味します。 そのため、処理が長引いたことを知ることはできません。
この問題を解決するために、Quarkusは処理の開始時と終了時にそれぞれ開始イベントと終了イベントを記録できます。 これらのイベントはデフォルトでは無効です。 しかし、JFRでこれらのイベントを有効にできます(後述)。
REST API イベント
このイベントは quarkus-rest
または resteasy-classic
エクステンションが有効な場合に記録されます。
REST サーバの処理が完了すると、次の 3 つの JFR イベントが記録されます。
-
REST
-
REST Start
-
REST End
REST イベントは、REST 処理の開始から その終了までの期間を記録します。
REST Start イベントは、REST サーバ処理の開始を記録します。
REST End イベントは、REST サーバ処理の終了を記録します。
これらのイベントには以下の情報があります。
-
HTTPメソッド
-
URI
-
リソースクラス
-
リソースメソッド
-
クライアント
HTTP メソッドは、クライアントがアクセスした HTTP メソッドを記録します。これは、GET、POST、DELETE などの一般的な HTTP メソッドの文字列を記録します。
URIはクライアントがアクセスしたURIパスを記録します。これにはホスト名やポート番号は含まれません。
リソースクラスは実行されたクラスを記録します。HTTPメソッドとURIによって、リソースクラスが期待通りに実行されたかどうかを確認できます。
リソースメソッドは実行されたメソッドを記録します。 HTTP メソッドと URI によって、リソースメソッドが期待通りに実行されたかどうかを確認できます。
クライアントは、アクセスしたクライアントに関する情報を記録します。
ネイティブ・イメージ
ネイティブイメージはJDK Flight Recorderをサポートしています。
このエクステンションはネイティブイメージにも対応しています。
ネイティブイメージでJFRを有効にするには、通常、 --enable-monitoring
を用いてビルドします。
しかし、 quarkus.native.monitoring
の設定に jfr
を追加することで、Quarkus のネイティブイメージでJFRを有効にできます。
この設定を行うには2つの方法があります。 application.properties
に含める方法と、ビルド時に指定する方法です。
最初の方法は、まず application.properties
で設定を行います。
application.properties
quarkus.native.monitoring=jfr
次に、 ./mvnw package -Dnative
としてビルドします。
2つ目の方法は、ビルド時に -Dquarkus.native.monitoring=jfr
を与え、 ./mvnw package -Dnative -Dquarkus.native.monitoring=jfr
としてビルドします。
ネイティブ・イメージのビルドが完了したら、次のように JFR とともにネイティブ・アプリケーションを実行します。
target/your-application-runner -XX:StartFlightRecording=name=quarkus,dumponexit=true,filename=myrecording.jfr
現時点では、GraalVM は Windows ネイティブ・イメージ上で JFR を記録できません。 |
JFRの設定
JFR CLI を使用して、JFR が記録するイベントを設定できます。
その設定ファイルである JFC ファイルは XML 形式なので、テキストエディタで変更できます。
しかし、OpenJDKに標準で含まれている jfr configure
を使うべきです。
ここでは、デフォルトでは記録されないRestStartとRestEndイベントが記録される設定ファイルを作成します。
jfr configure --input default.jfc +quarkus.RestStart#enabled=true +quarkus.RestEnd#enabled=true --output custom-rest.jfc
これは、RestStartとRestEndを有効にした設定ファイルとして custom-rest.jfc
を作成します。
これで、新しい設定でアプリケーションを実行する準備ができました。Java 仮想マシンの起動時から JFR が有効になるように設定して、アプリケーションを起動します。
quarkus dev -Djvm.args="-XX:StartFlightRecording=name=quarkus,settings=./custom-rest.jfc,dumponexit=true,filename=myrecording.jfr"
./mvnw quarkus:dev -Djvm.args="-XX:StartFlightRecording=name=quarkus,settings=./custom-rest.jfc,dumponexit=true,filename=myrecording.jfr"
./gradlew --console=plain quarkusDev -Djvm.args="-XX:StartFlightRecording=name=quarkus,settings=./custom-rest.jfc,dumponexit=true,filename=myrecording.jfr"
quarkus-jfrエクステンションへ新しいイベントを導入
このセクションは、このエクステンションに新しいイベントを追加したい方のためのセクションです。
新しいイベントは既存のイベントと紐付けることを推奨します。 紐付けは、時間がかかっている処理の詳細を見るときに便利です。 例えば、一般的な REST アプリケーションは、データストアから処理に必要なデータを取得します。 REST イベントがデータストア・イベントと関連付けられていない場合、各 REST リクエストでどのデータストア・イベントが処理されたかを知ることはできません。 2 つのイベントが紐付けられていれば、各 REST 要求でどのデータストア・イベントが処理されたかを即座に把握できます。
データストアのイベントはまだ実装されていません。 |
quarkus-jfr エクステンションは、イベントの紐付けのためにリクエスト ID を提供します。 リクエストIDの詳細については、リクエストの特定を参照してください。
具体的なコードとしては、以下の2ステップが必要です。
まず、新しいイベントに traceId
と spanId
を次のように実装します。
これらのイベントに付けられた @TraceIdRelational
と @SpanIdRelational
が紐付けを行います。
import io.quarkus.jfr.runtime.SpanIdRelational;
import io.quarkus.jfr.runtime.TraceIdRelational;
import jdk.jfr.Description;
import jdk.jfr.Event;
import jdk.jfr.Label;
public class NewEvent extends Event {
@Label("Trace ID")
@Description("Trace ID to identify the request")
@TraceIdRelational
protected String traceId;
@Label("Span ID")
@Description("Span ID to identify the request if necessary")
@SpanIdRelational
protected String spanId;
// other properties which you want to add
// setters and getters
}
そして、 IdProducer
オブジェクトの getTraceId()
と getSpanId()
から、それらに格納する情報を取得します。
import io.quarkus.jfr.runtime.IdProducer;
public class NewInterceptor {
@Inject
IdProducer idProducer;
private void recordedInvoke() {
NewEvent event = new NewEvent();
event.begin();
// The process you want to measure
event.end();
if (endEvent.shouldCommit()) {
event.setTraceId(idProducer.getTraceId());
event.setSpanId(idProducer.getSpanId());
// call other setters which you want to add
endEvent.commit();
}
}
}
quarkus-jfr 設定リファレンス
ビルド時に固定される構成プロパティ - 他のすべての構成プロパティは実行時にオーバーライド可能
タイプ |
デフォルト |
|
---|---|---|
If false, only quarkus-jfr events are not recorded even if JFR is enabled. In this case, Java standard API and virtual machine information will be recorded according to the setting. Default value is Environment variable: Show more |
ブーリアン |
|
If false, only REST events in quarkus-jfr are not recorded even if JFR is enabled. In this case, other quarkus-jfr, Java standard API and virtual machine information will be recorded according to the setting. Default value is Environment variable: Show more |
ブーリアン |
|