Using Java Flight Recorder

本指南说明如何扩展 Java Flight Recorder (JFR),以便为 Quarkus 应用程序提供更多洞察力。JFR 将来自 Java 标准 API 和 JVM 的各种信息记录为事件。通过添加 Quarkus JFR 扩展,你可以向 JFR 添加自定义 Quarkus 事件。这将帮助你解决应用程序中潜在的问题。

This guide explains how Java Flight Recorder (JFR) can be extended to provide additional insight into your Quarkus application. JFR records various information from the Java standard API and JVM as events. By adding the Quarkus JFR extension, you can add custom Quarkus events to JFR. This will help you solve potential problems in your application.

JFR 可以预先配置为转储文件,当应用程序退出时,JFR 将输出此文件。该文件将包含 JFR 事件流的内容,其中还添加了 Quarkus 自定义事件。当然,你可以在任何时候获取此文件,即使应用程序意外退出。

JFR can be preconfigured to dump a file, and when the application exits, JFR will output the file. The file will contain the content of the JFR event stream to which Quarkus custom events have also been added. You can, of course, get this file at any time you want, even if your application exits unexpectedly.

Prerequisites

Unresolved directive in jfr.adoc - include::{includes}/prerequisites.adoc[]

Architecture

在本指南中,我们创建一个简单的 REST 应用程序来演示 JFR 使用。

In this guide, we create a straightforward REST application to demonstrate JFR usage.

Creating the Maven project

首先,我们需要一个新项目。使用以下命令创建一个新项目:

First, we need a new project. Create a new project with the following command:

Unresolved directive in jfr.adoc - include::{includes}/devtools/create-app.adoc[]

此命令生成 Maven 项目并导入 Quarkus JFR 扩展,其中包含默认的 JFR 支持。

This command generates the Maven project and imports the Quarkus JFR extension, which includes the default JFR support.

如果你已经配置了 Quarkus 项目,可以通过在项目基目录中运行以下命令,将 JFR 扩展添加到项目:

If you already have your Quarkus project configured, you can add the JFR extension to your project by running the following command in your project base directory:

Unresolved directive in jfr.adoc - include::{includes}/devtools/extension-add.adoc[]

这会将以下内容添加到构建文件中:

This will add the following to your build file:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-jfr</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-jfr")

Examine the Jakarta REST resource

使用以下内容创建一个 src/main/java/org/acme/jfr/JfrResource.java 文件:

Create a src/main/java/org/acme/jfr/JfrResource.java file with the following content:

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 中,无需进行任何必需的代码更改。

Notice that there is no JFR specific code included in the application. By default, requests sent to this endpoint will be recorded into JFR without any required code changes.

Running Quarkus applications and JFR

现在,我们已准备好运行此应用程序。我们可以启动应用程序,并配置 JFR 从 Java 虚拟机的启动开始启用。

Now we are ready to run our application. We can launch the application with JFR configured to be enabled from the startup of the Java Virtual Machine.

Unresolved directive in jfr.adoc - include::{includes}/devtools/dev.adoc[]

在 Java Flight Recorder 和应用程序运行的情况下,我们可以向提供的端点发送请求:

With the Java Flight Recorder and the application running, we can make a request to the provided endpoint:

$ curl http://localhost:8080/hello
hello

这便是向 JFR 写入信息所需的全部内容。

This is all that is needed to write the information to JFR.

Save the JFR events to a file

如上所述,Quarkus 应用程序已配置为在启动时也启动 JFR,并在终止时将其转储到 myrecording.jfr。因此,当我们按 CTRL+C 或键入 q 来停止应用程序时,我们便可获取此文件。

As mentioned above, the Quarkus application was configured to also start JFR at startup and dump it to a myrecording.jfr when it terminates. So we can get the file when we hit CTRL+C or type q to stop the application.

或者,我们还可以使用 jcmd 命令将其转储。

Or, we can also dump it with the jcmd command.

jcmd <PID> JFR.dump name=quarkus filename=myrecording.jfr

运行 jcmd 命令会向我们提供正在运行的 Java 进程以及每个进程的 PID 的列表。

Running the jcmd command give us a list of running Java processes and the PID of each process.

Open JFR dump file

我们可以使用两个工具打开一个 JFR 转储: jfr CLI 和 JDK Mission Control (JMC)。也可以使用 JFR API 阅读它们,但我们不会在本指南中涉及这一点。

We can open a JFR dump using two tools: the jfr CLI and JDK Mission Control (JMC). It is also possible to read them using JFR APIs, but we won’t go into that in this guide.

jfr CLI

jfr CLI 是 OpenJDK 中包含的一个工具。可执行文件是 $JAVA_HOME/bin/jfr。我们可以使用 jfr CLI 查看转储文件中仅限于与 Quarkus 相关的事件列表,方法是运行以下命令:

The jfr CLI is a tool included in OpenJDK. The executable file is $JAVA_HOME/bin/jfr. We can use the jfr CLI to see a list of events limited to those related to Quarkus in the dump file by running the following command:

jfr print --categories quarkus myrecording.jfr

JDK Mission Control

JMC 实际上是 JFR 的一个 GUI。某些 Java 发行版包含 JMC,但如果没有,你需要手动下载它。

JMC is essentially a GUI for JFR. Some Java distributions include JMC, but if not, you need to download it manually.

要使用 JMC 查看事件列表,我们首先按照以下步骤在 JMC 中加载 JFR 文件。

To see a list of events using JMC, first we load the JFR file in JMC as follows.

jmc -open myrecording.jfr

打开 JFR 文件后,我们有两个选项。一个是以表格列表的形式查看事件,另一个是按时间顺序查看事件在其发生的线程上。

After opening the JFR file, we have two options. One is to view the events as a tabular list, and the other is to view the events on the threads in which they occurred, in chronological order.

要以表格样式查看 Quarkus 事件,请选择 JMC 左侧的事件浏览器,然后打开 JMC 右侧的 Quarkus 事件类型树。

To view Quarkus events in tabular style, select the Event Browser on the left side of JMC, then open the Quarkus event type tree on the right side of JMC.

jfr event browser

要在线程上按时间顺序查看 Quarkus 事件,请选择 JMC 左侧的 Java applicationThreads

To see Quarkus events in chronological order on a thread, select the Java application and Threads on the left side of JMC.

jfr java ap thread

标准配置不显示 Quarkus 事件。我们要完成三个任务才能看到 Quarkus 事件。

The standard configuration does not show Quarkus events. We have to do three tasks to see the Quarkus events.

  1. Right-click and select Edit Thread Activity Lanes…​.

  2. Select the plus button to add a new lane on the left side, then check to display that lane.

  3. Select Quarkus as the event type that lane will display and press OK.

jfr edit thread activity lanes

现在我们可以按线程查看 Quarkus 事件了。

Now we can see the Quarkus events per thread.

jfr thread

此扩展能够同时记录多个 JFR 事件(由不同的线程或在响应式执行模型的情况下由同一个线程发出)。因此,事件可能在 JMC 中重叠。这可能会让你难以看到你想要看到的事件。为了避免这种情况,我们建议使用 Request IDs 过滤事件,以便你只能看到你想要看到的有关请求的信息。

This extension is able to records multiple JFR events concurrently (emitted by different threads or from the same thread in the case of the reactive execution model). Thus, events might overlap in JMC. This could make it difficult for you to see the events you want to see. To avoid this, we recommend to use identifying-requests to filter events so that you only see the information about the requests you want to see.

Events

Identifying Requests

此扩展与 OpenTelemetry 扩展协作。此扩展记录的事件具有跟踪 ID 和跨度 ID。这些分别使用 OpenTelemetry ID 记录。

This extension collaborates with the OpenTelemetry extension. The events recorded by this extension have a trace ID and a span ID. These are recorded with the OpenTelemetry IDs respectively.

这意味着在从 OpenTelemetry 实现提供的 UI 识别出感兴趣的跟踪和跨度 ID 后,我们可以使用这些 ID 立即跳转到 JFR 中的详细信息。

This means that after we identify the trace and span IDs of interest from the UI provided by the OpenTelemetry implementation, we can immediately jump to the details in JFR using those IDs.

如果我们尚未启用 OpenTelemetry 扩展,此扩展会为每个请求创建一个 ID,并将其作为 traceId 链接到 JFR 事件。在这种情况下,跨度 ID 将为 null。

If we have not enabled the OpenTelemetry extension, this extension creates an ID for each request and links it to JFR events as a traceId. In this case, the span ID will be null.

目前,Quarkus 仅记录 REST 事件,但我们计划随着我们将来添加更多事件,使用此 ID 将每个事件相互链接。

For now, Quarkus only records REST events, but we plan to use this ID to link each event to each other as we add more events in the future.

Event Implementation Policy

当 JFR 开始记录事件时,该事件尚未记录到 JFR,但当它提交该事件时,该事件就被记录了。因此,在转储时间开始记录但尚未提交的事件不会被转储。这是由 JFR 的设计决定的,无法避免。这意味着如果长时间处理,事件不会永远被记录。因此,你不会意识到长时间处理。

When JFR starts recording an event, the event does not record to JFR yet, but when it commits that event, the event is recorded. Therefore, events that have started recording at dump time but have not yet been committed are not dumped. This is unavoidable due to the design of JFR. This means that events are not recorded forever if there are prolonged processing. Therefore, you will not be aware of prolonged processing.

为了解决此问题,Quarkus 还可以分别在处理的开始和结束时,记录开始和结束事件。这些事件在默认情况下处于禁用状态。但是,我们可以按照以下说明在 JFR 中启用这些事件。

To solve this problem, Quarkus can also record start and end events at the beginning and end of processing. These events are disabled by default. However, we can enable these events in JFR, as described below.

REST API Event

记录此事件是在启用 Quarkus REST 或 RESTEasy 扩展时。REST 服务器处理结束后会立即记录以下三个 JFR 事件。

This event is recorded when either the Quarkus REST or RESTEasy extension is enabled. The following three JFR events are recorded as soon as REST server processing is complete.

REST

Records the time period from the start of the REST server process to the end of the REST server process.

RestStart

Records the start of the REST server process.

RestEnd

Records the end of the REST server process.

这些事件包含以下信息。

These events have the following information.

  • HTTP Method

  • URI

  • Resource Class

  • Resource Method

  • Client

HTTP 方法会记录客户端访问的 HTTP 方法。这会记录一个字符串,包含 GET、POST、DELETE 等 HTTP 方法以及其他通用 HTTP 方法。

HTTP Method records the HTTP Method accessed by the client. This will record a string of HTTP methods such as GET, POST, DELETE, and other general HTTP methods.

URI 记录客户端访问的 URI 路径。这并不包含主机名或端口号。

URI records the URI path accessed by the client. This does not include host names or port numbers.

资源类会记录已执行的类。我们可以检查按预期通过 HTTP 方法和 URI 执行了资源类。

Resource Class records the class that was executed. We can check whether the Resource Class was executed as expected by the HTTP Method and URI.

资源方法会记录已执行的方法。我们可以检查按预期通过 HTTP 方法和 URI 执行了资源方法。

Resource Method records the method that was executed. We can check if the Resource Method was executed as expected by the HTTP Method and URI.

客户端会记录有关访问客户端的信息。

Client records information about the accessing client.

Native Image

本机可执行文件支持 Java Flight Recorder。此扩展也支持本机可执行文件。

Native executables supports Java Flight Recorder. This extension also supports native executables.

在要为本机可执行文件启用 JFR 时,通常是用 --enable-monitoring 构建的。但是,我们可以通过将 jfr 添加到配置文件属性 quarkus.native.monitoring 中来为 Quarkus 本机可执行文件启用 JFR。有两种方法可以设置此配置文件:通过将其包含在 application.properties 中或在构建时指定它。

To enable JFR on native executables, it is usually built with --enable-monitoring. However, we can enable JFR in Quarkus native executables by adding jfr to the configuration property quarkus.native.monitoring. There are two ways to set up this configuration: by including it in the application.properties or by specifying it at build time.

第一种方法是首先在 application.properties 中设置配置文件。

The first method is to first configure settings in application.properties.

quarkus.native.monitoring=jfr

然后,像往常一样构建你的本机可执行文件:

Then build your native executable as usual:

Unresolved directive in jfr.adoc - include::{includes}/devtools/build-native.adoc[]

第二种方法是构建时传递 -Dquarkus.native.monitoring=jfr

The second way is to pass -Dquarkus.native.monitoring=jfr at build time:

include::{includes}/devtools/build-native.adoc[] :构建-附加-参数:

Unresolved directive in jfr.adoc - include::{includes}/devtools/build-native.adoc[] :build-additional-parameters:

在构建完本机可执行文件后,你可以通过以下方式使用 JFR 运行本机应用程序:

Once you have finished building the native executable, you can run the native application with JFR as follows:

target/your-application-runner -XX:StartFlightRecording=name=quarkus,dumponexit=true,filename=myrecording.jfr

请注意,此时 Mandrel 和 GraalVM 无法为 Windows 本机可执行文件记录 JFR。

Note that at this time, Mandrel and GraalVM cannot record JFR for Windows native executables.

JFR configuration

我们可以使用 JFR CLI 来配置 JFR 将要记录的事件。配置文件 JFC 文件采用 XML 格式,所以我们可以使用文本编辑器对其进行修改。但是,建议使用 jfr configure,它默认包含在 OpenJDK 中。

We can use the JFR CLI to configure the events that JFR will record. The configuration file, JFC file, is in XML format, so we can modify it with a text editor. However, it is recommended to use jfr configure, which is included in OpenJDK by default.

这里我们创建一个配置文件,其中记录了 REST Start 和 REST End 事件(它们默认不会记录):

Here we create a configuration file in which REST Start and REST End events are recorded (they are not recorded by default):

jfr configure --input default.jfc +quarkus.RestStart#enabled=true +quarkus.RestEnd#enabled=true --output custom-rest.jfc

这会创建 custom-rest.jfc 作为配置文件,并启用对 RestStart 和 RestEnd 的记录。

This creates custom-rest.jfc as a configuration file with recording for RestStart and RestEnd enabled.

现在,我们准备用新设置运行我们的应用程序。我们使用 JFR 配置来启动应用程序,以便在启动 Java 虚拟机时启用它。

Now we are ready to run our application with the new settings. We launch the application with JFR configured so that it is enabled from the startup of the Java Virtual Machine.

include::{includes}/devtools/dev.adoc[]:dev-additional-parameters:

Unresolved directive in jfr.adoc - include::{includes}/devtools/dev.adoc[] :dev-additional-parameters:

Adding new events

本部分针对希望向此扩展添加新事件的用户。

This section is for those who would like to add new events to this extension.

我们建议将新事件与现有事件进行关联。对于查看一个耗时较长的进程的详细信息而言,关联非常有用。例如,一个常规 REST 应用程序从数据存储获取处理所需的数据。如果 REST 事件未与数据存储事件关联,则不可能知道每个 REST 请求处理了哪些数据存储事件。当这两个事件关联时,我们就能立即知道每个 REST 请求处理了哪个数据存储事件。

We recommend that new events be associated with existing events. Associations are useful when looking at the details of a process that is taking a long time. For example, a general REST application retrieves the data needed for processing from a datastore. If REST events are not associated with datastore events, it is impossible to know which datastore events were processed in each REST request. When the two events are associated, we know immediately which datastore event was processed in each REST request.

数据存储事件尚未实现。

Datastore events are not implemented yet.

Quarkus JFR 扩展为事件关联提供一个请求 ID。有关请求 ID 的更多信息,请参见 Identifying Requests

The Quarkus JFR extension provides a Request ID for event association. See Identifying Requests for more information on Request IDs.

在具体代码中,需要以下两个步骤。首先,在新事件上按如下所示实现 traceId`和 `spanId。附加到这些事件上的 `@TraceIdRelational`和 `@SpanIdRelational`将提供关联。

In specific code, the following two steps are required. First, implement traceId and spanId on the new event as follows The @TraceIdRelational and @SpanIdRelational attached to these events will provide the association.

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()`获取信息并存储在事件中。

Then you get the information to store in the event from the IdProducer object’s getTraceId() and 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();
        }
    }
}

Configuration Reference

Unresolved directive in jfr.adoc - include::{generated-dir}/config/quarkus-jfr.adoc[]