编辑此页面

使用 Java Flight Recorder

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

本文档是 Quarkus 可观察性参考指南的一部分,该指南介绍了此组件和其他可观察性相关组件。

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

先决条件

要完成本指南,您需要

  • 大约 15 分钟

  • 一个 IDE

  • 已安装 JDK 17+ 并正确配置了 JAVA_HOME

  • Apache Maven 3.9.9

  • 如果您想使用它,可以选择 Quarkus CLI

  • 如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置

架构

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

创建 Maven 项目

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

CLI
quarkus create app org.acme:jfr-quickstart \
    --extension='quarkus-rest,quarkus-jfr' \
    --no-code
cd jfr-quickstart

要创建 Gradle 项目,请添加 --gradle--gradle-kotlin-dsl 选项。

有关如何安装和使用 Quarkus CLI 的更多信息,请参阅 Quarkus CLI 指南。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.24.4:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=jfr-quickstart \
    -Dextensions='quarkus-rest,quarkus-jfr' \
    -DnoCode
cd jfr-quickstart

要创建 Gradle 项目,请添加 -DbuildTool=gradle-DbuildTool=gradle-kotlin-dsl 选项。

对于 Windows 用户

  • 如果使用 cmd,(不要使用反斜杠 \ 并将所有内容放在同一行上)

  • 如果使用 Powershell,请将 -D 参数括在双引号中,例如 "-DprojectArtifactId=jfr-quickstart"

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

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

CLI
quarkus extension add quarkus-jfr
Maven
./mvnw quarkus:add-extension -Dextensions='quarkus-jfr'
Gradle
./gradlew addExtension --extensions='quarkus-jfr'

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

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-jfr</artifactId>
</dependency>
build.gradle
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

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

CLI
quarkus dev -Djvm.args="-XX:StartFlightRecording=name=quarkus,dumponexit=true,filename=myrecording.jfr"
Maven
./mvnw quarkus:dev -Djvm.args="-XX:StartFlightRecording=name=quarkus,dumponexit=true,filename=myrecording.jfr"
Gradle
./gradlew --console=plain quarkusDev -Djvm.args="-XX:StartFlightRecording=name=quarkus,dumponexit=true,filename=myrecording.jfr"

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

$ curl https://:8080/hello
hello

这就是将信息写入 JFR 所需的全部操作。

将 JFR 事件保存到文件

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

或者,我们也可以使用 jcmd 命令转储它。

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

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

打开 JFR 转储文件

我们可以使用两个工具打开 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。 某些 Java 发行版包含 JMC,但如果没有,您需要手动下载它。

要使用 JMC 查看事件列表,首先我们按如下方式在 JMC 中加载 JFR 文件。

jmc -open myrecording.jfr

打开 JFR 文件后,我们有两个选择。 一种是将事件视为表格列表,另一种是在事件发生的线程中按时间顺序查看事件。

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

JDK Mission Control Event Browser view

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

JDK Mission Control thread view

标准配置不显示 Quarkus 事件。 我们必须执行三个任务才能看到 Quarkus 事件。

  1. 右键单击并选择 Edit Thread Activity Lanes…​

  2. 选择加号按钮以在左侧添加一个新通道,然后选中以显示该通道。

  3. 选择 Quarkus 作为该通道将显示的事件类型,然后按 OK。

JDK Mission Control Edit Thread Activity Lanes

现在我们可以看到每个线程的 Quarkus 事件。

JDK Mission Control thread view

此扩展能够同时记录多个 JFR 事件(由不同线程发出或在反应式执行模型中从同一线程发出)。 因此,事件可能会在 JMC 中重叠。 这可能会使您难以看到想要看到的事件。 为了避免这种情况,我们建议使用 请求 ID 来筛选事件,以便您仅看到有关您想要查看的请求的信息。

事件

识别请求

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

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

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

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

事件实施策略

当 JFR 开始记录事件时,该事件尚未记录到 JFR,但是当提交该事件时,该事件将被记录。 因此,在转储时已开始记录但尚未提交的事件不会被转储。 由于 JFR 的设计,这是不可避免的。 这意味着如果存在长时间的处理,事件将不会被永久记录。 因此,您不会意识到长时间的处理。

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

REST API 事件

当启用 Quarkus REST 或 RESTEasy 扩展时,将记录此事件。 以下三个 JFR 事件将在 REST 服务器处理完成后立即记录。

REST

记录从 REST 服务器进程开始到 REST 服务器进程结束的时间段。

RestStart

记录 REST 服务器进程的开始。

RestEnd

记录 REST 服务器进程的结束。

这些事件具有以下信息。

  • HTTP 方法

  • URI

  • 资源类

  • 资源方法

  • 客户端

HTTP 方法记录客户端访问的 HTTP 方法。 这将记录 HTTP 方法的字符串,例如 GET、POST、DELETE 和其他常规 HTTP 方法。

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

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

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

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

Native Image

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

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

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

quarkus.native.monitoring=jfr

然后像往常一样构建您的 Native 可执行文件

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.native.enabled=true

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

CLI
quarkus build --native -Dquarkus.native.monitoring=jfr
Maven
./mvnw install -Dnative -Dquarkus.native.monitoring=jfr
Gradle
./gradlew build -Dquarkus.native.enabled=true -Dquarkus.native.monitoring=jfr

完成构建 Native 可执行文件后,您可以按如下方式运行带有 JFR 的 Native 应用程序

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

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

JFR 配置

我们可以使用 JFR CLI 配置 JFR 将记录的事件。 配置文件 JFC 文件为 XML 格式,因此我们可以使用文本编辑器修改它。 但是,建议使用 jfr configure,它默认包含在 OpenJDK 中。

在这里,我们创建一个配置文件,其中记录了 REST Start 和 REST End 事件(默认情况下未记录)

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

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

现在我们准备好使用新设置运行我们的应用程序了。 我们启动应用程序,并将 JFR 配置为从 Java 虚拟机的启动开始启用。

CLI
quarkus dev -Djvm.args="-XX:StartFlightRecording=name=quarkus,settings=./custom-rest.jfc,dumponexit=true,filename=myrecording.jfr"
Maven
./mvnw quarkus:dev -Djvm.args="-XX:StartFlightRecording=name=quarkus,settings=./custom-rest.jfc,dumponexit=true,filename=myrecording.jfr"
Gradle
./gradlew --console=plain quarkusDev -Djvm.args="-XX:StartFlightRecording=name=quarkus,settings=./custom-rest.jfc,dumponexit=true,filename=myrecording.jfr"

添加新事件

本节适用于希望向此扩展添加新事件的用户。

我们建议将新事件与现有事件相关联。 在查看长时间运行的进程的详细信息时,关联非常有用。 例如,通用的 REST 应用程序从数据存储中检索处理所需的数据。 如果 REST 事件未与数据存储事件相关联,则不可能知道每个 REST 请求中处理了哪些数据存储事件。 当两个事件相关联时,我们会立即知道每个 REST 请求中处理了哪些数据存储事件。

数据存储事件尚未实现。

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

在特定代码中,需要执行以下两个步骤。 首先,在新的事件上实现 traceIdspanId,如下所示:附加到这些事件的 @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();
        }
    }
}

配置参考

构建时固定的配置属性 - 所有其他配置属性都可以在运行时覆盖

配置属性

类型

默认

如果为 false,即使启用了 JFR,也不会记录 quarkus-jfr 事件。 在这种情况下,将根据设置记录 Java 标准 API 和虚拟机信息。 默认值为 true

环境变量:QUARKUS_JFR_ENABLED

显示更多

布尔值

true

如果为 false,即使启用了 JFR,也不会记录 quarkus-jfr 中的 REST 事件。 在这种情况下,将根据设置记录其他 quarkus-jfr、Java 标准 API 和虚拟机信息。 默认值为 true

环境变量:QUARKUS_JFR_REST_ENABLED

显示更多

布尔值

true

相关内容