Micrometer Metrics

Micrometer fournit une couche d’abstraction pour la collecte des métriques. Il définit une API pour les types de compteurs de base, tels que les compteurs, les jauges, les minuteries et les résumés de distribution, ainsi qu’une API MeterRegistry qui généralise la collecte et la propagation des métriques pour différents systèmes de surveillance principaux.

Micrometer est l’approche recommandée pour les métriques pour Quarkus.

Par défaut, les métriques sont exposées sur le serveur HTTP principal. Si vous souhaitez afficher les métriques à partir d’un port de gestion distinct, consultez la section Managed interface .

Micrometer and monitoring system extensions

Les extensions Micrometer de Quarkus sont structurées de la même manière que le projet Micrometer. L’extension quarkus-micrometer fournit le support Micrometer de base et l’intégration du runtime. D’autres extensions Quarkus et Quarkiverse utilisent l’extension Quarkus Micrometer pour fournir une prise en charge d’autres systèmes de surveillance.

Extensions Quarkus :

  • micrometer

  • micrometer-registry-prometheus

Quarkiverse extensions (peut être incomplet) :

  • micrometer-registry-azure-monitor

  • micrometer-registry-datadog

  • micrometer-registry-graphite

  • micrometer-registry-influx

  • micrometer-registry-jmx

  • micrometer-registry-newrelic-telemetry

  • micrometer-registry-otlp

  • micrometer-registry-signalfx

  • micrometer-registry-stackdriver

  • micrometer-registry-statsd

Pour ajouter la prise en charge des métriques Prometheus à votre application, par exemple, utilisez l’extension micrometer-registry-prometheus . Il intègrera l’extension Quarkus Micrometer et les librairies de base Micrometer en tant que dépendances.

Ajoutez l’extension à votre projet à l’aide de la commande suivante (à partir du répertoire de votre projet) :

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

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

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

Et vous êtes prêts !

Un processus similaire s’applique aux autres extensions de registre de compteur. Pour utiliser Micrometer StackDriver MeterRegistry, par exemple, vous utiliseriez l’extension quarkus-micrometer-registry-stackdriver de Quarkiverse :

CLI
quarkus extension add {add-extension-extensions}
Maven
./mvnw quarkus:add-extension -Dextensions='{add-extension-extensions}'
Gradle
./gradlew addExtension --extensions='{add-extension-extensions}'
pom.xml
<dependency>
    <groupId>io.quarkiverse.micrometer.registry</groupId>
    <artifactId>quarkus-micrometer-registry-stackdriver</artifactId>
</dependency>
build.gradle
implementation("io.quarkiverse.micrometer.registry:quarkus-micrometer-registry-stackdriver")

Other registry implementations

Si le registre Micrometer que vous souhaitez utiliser n’a pas encore d’extension associée, utilisez l’extension quarkus-micrometer et ajoutez directement la dépendance du registre de compteur Micrometer :

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-micrometer</artifactId>
</dependency>
<dependency>
    <groupId>com.acme</groupId>
    <artifactId>custom-micrometer-registry</artifactId>
    <version>...</version>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-micrometer")
implementation("com.acme:custom-micrometer-registry")

Vous devrez ensuite spécifier votre propre fournisseur pour configurer et initialiser MeterRegistry, comme indiqué dans la section suivante.

Create a customized MeterRegistry

如果您需要,可以使用自定义的 @Produces 方法来创建和配置自己的 MeterRegistry

以下示例自定义了 StatsD 所用的行格式:

@Produces
@Singleton (1)
public StatsdMeterRegistry createStatsdMeterRegistry(StatsdConfig statsdConfig, Clock clock) { (2)
    // define what to do with lines
    Consumer<String> lineLogger = line -> logger.info(line);

    // inject a configuration object, and then customize the line builder
    return StatsdMeterRegistry.builder(statsdConfig)
          .clock(clock)
          .lineSink(lineLogger)
          .build();
}
1 此方法返回 @Singleton
2 此方法返回特定类型的 MeterRegistry

此示例对应于 Micrometer 文档中的以下说明: Micrometer StatsD: Customizing the Metrics Sink

使用 MicroProfile Config 注入配置注册表所需的任何配置属性。大多数 Micrometer 注册表扩展(如 quarkus-micrometer-registry-statsd),提供与 Quarkus 配置模型集成的注册表特定配置对象。 Quarkiverse GitHub Repository 可以作为有用的实现参考。

Create your own metrics

指标数据被用于聚合中,以观察数据在一段时间内的变化情况。此数据用于趋势分析、异常检测和告警。数据由后端监控系统存储在时序数据库中,新值追加到序列的末尾。

指标是惰性构建的。在执行用于创建指标的动作(例如,访问端点)之前,您可能看不到要查找的指标的任何数据。

Naming conventions

指标名称应使用点分隔段,a.name.like.this。Micrometer 应用命名约定将已注册的指标名称转换成与后端监控系统期望相符的形式。

给定计时器的以下声明: registry.timer("http.server.requests"),应用的命名约定将针对不同的监控系统发出以下指标:

  • Prometheus: http_server_requests_duration_seconds

  • Atlas: httpServerRequests

  • Graphite: http.server.requests

  • InfluxDB: http_server_requests

Define dimensions for aggregation

指标(单个数字测量值)通常还有其他与之捕获的数据。这些辅助数据用于对指标进行分组或聚合以供分析。Micrometer API 将这些维度数据称为标签,但您可能会在其他文档来源中看到它们被称为“标签”或“属性”。

Micrometer 主要构建用于支持维度数据(使用键/值对丰富指标名称)的后端监控系统。针对仅支持平面指标名称的分层系统,Micrometer 将将键/值对集(按键排序)展平,并将其添加到该名称中。

当使用 MeterRegistry 注册指标或使用 Meter Filter 注册指标时,可以指定标记。

请参阅 Micrometer 文档以获取有关 tag naming 的其他建议。

指标名称和维度的每个唯一组合都会生成唯一的时间序列。使用无界维度数据集会导致“基数爆炸”,即新时序的创建呈指数级增加。

Obtain a reference to a MeterRegistry

要注册指标,您需要一个对 MeterRegistry 的引用,它是由 Micrometer 扩展配置和维护的。

使用以下方法之一获取对 MeterRegistry 的引用:

  1. Use CDI Constructor injection:[source, java]

package org.acme.micrometer;

import io.micrometer.core.instrument.MeterRegistry;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;

@Path("/example")
@Produces("text/plain")
public class ExampleResource {

    private final MeterRegistry registry;

    ExampleResource(MeterRegistry registry) {
        this.registry = registry;
    }
}
  1. 使用 MeterRegistry 成员变量并使用 @Inject:[source, java]

    @Inject
    MeterRegistry registry;
  1. Use the global MeterRegistry:[source, java]

    MeterRegistry registry = Metrics.globalRegistry;

Gauges

计量器测量一个可以随时间增大或减小的值,就像汽车上的速度计。监控缓存或集合的统计信息时,计量器非常有用。

仪表值是采样而非设置的;没有记录与测量值之间仪表相关值如何变化的记录。

微计提供几种机制用于创建仪表:

  1. 用集合构建包装以监视其大小:[source, java]

List<String> list = registry.gaugeCollectionSize("fantastic.list", (1)
        Tags.of("key", "value") // optional 2
        new ArrayList<>());  (3)
1 用分隔点的习惯,创立一个新仪表,list.size
2 tags 与仪表关联。仪表标签值是常量,并且必须在构建时间指派。
3 构建应观察个数组列。
  1. 用生成器创立要求功能的仪表:[source, java]

Gauge.builder("jvm.threads.peak", threadBean, ThreadMXBean::getPeakThreadCount) (1)
    .baseUnit(BaseUnits.THREADS) // optional 2
    .description("The peak live thread count...") // optional 3
    .tags("key", "value") // optional 4
    .register(registry); (5)
1 创立一个新仪表,称为 jvm.threads.peak,其将在 threadBean 上要求 getPeakThreadCount,它是一个 ThreadMXBean 实例。
2 定义基本单位,参见 BaseUnits.java 所述预定义值。
3 提供仪表描述
4 tags 与仪表关联
5 在 MeterRegistry 中注册仪表

更多信息和示例,请见微计档中的 Gauges。需要注意的是两个特殊情况:TimeGauge,用于测量时间,和 MultiGauge,用于共同汇报多个标准。

微计默认不建立强烈引用的待观察对象。根据注册表,微计要么完全忽略观察垃圾收集对象的仪表,要么使用 NaN(不是数字)作为观察值。

何时应使用仪表?只有在没有其他办法使用时才使用仪表。仪表可能不如其他仪表易于使用。如果要测量内容是可计数的(因为值始终递增),则使用计数器。

Counters

计数器测量仅增加的值。使用以下方法之一创建计数器。

  1. MeterRegistry 上使用简化方法:[source, java]

registry.counter("example.prime.number", "type", "prime"); // <1> 2
1 example.prime.number 是计数器名称。
2 type 是具有值 prime 的维度标签。
  1. 使用 Counter.builder 提供说明和单位:[source, java]

Counter.builder("count.me") (1)
    .baseUnit("beans")            // optional 2
    .description("a description") // optional 3
    .tags("region", "test")       // optional 4
    .register(registry);
1 创建名为 count.me 的新计数器
2 定义自定义基本单位。参见 BaseUnits.java 了解预定义值
3 为计数器提供说明
4 tags 与计数器关联
  1. annotations a method[source, java]

@Counted(value = "counted.method", extraTags = { "extra", "annotated" }) // <1> 2
void countThisMethod(){
    ...
}
1 CDI 拦截器将创建一个名为 counted.method 的计数器并进行注册
2 拦截器创建的计数器具有带有值“annotated”的“extra”维度标记

请参阅 Micrometer 文档中的 Counters 了解更多信息和示例,包括不太常见的 FunctionCounter,可用于测量总是增加函数返回的结果

您何时应使用计数器?如果您正在执行无法计时或总结的操作,请使用计数器。如果您想更多地了解一个值是怎样发生改变的,计时器(当基本测量单位是时间时)或分布摘要可能更加合适

Summaries and Timers

Micrometer 中的计时器和分布摘要非常相似。这两个度量记录数据,并且可以捕捉额外的直方图或百分位数数据。虽然分布摘要可用于任意类型的数据,但计时器经过优化用于测量时间和持续时间

计时器和分布摘要在内部至少存储三个值:

  • 作为总和的已记录所有值的聚合

  • 已记录的值的数量(一个计数器)

  • 在衰减时间窗口内看到的最高值(一个测量值)

Create a distribution summary

使用分布摘要记录值,而非时间。使用以下方法之一创建分布摘要

  1. MeterRegistry 上使用简化方法:[source, java]

registry.summary("bytes.written", "protocol", "http"); // <1> 2
1 bytes.written 是摘要名称
2 protocol 是带有值 http 的维度标记
  1. 使用 DistributionSummary.builder 提供说明和单位:[source, java]

DistributionSummary.builder("response.size") (1)
    .baseUnit("bytes")            // optional 2
    .description("a description") // optional 3
    .tags("protocol", "http")     // optional 4
    .register(registry);
1 创建一个名为 response.size 的新分布摘要
2 使用 `bytes`作为基本单位,参见 BaseUnits.java以了解预定义的值。
3 为分布摘要提供一个描述
4 tags与分布摘要关联起来

Create a timer

计时器测量短时延和它们发生的频率。不支持负值,并且较长的持续时间可能导致总时间溢出(Long.MAX_VALUE 纳秒(292.3 年))。

使用以下方法之一构造计时器。

  1. MeterRegistry 上使用简化方法:[source, java]

registry.timer("fabric.selection", "primary", "blue"); // <1> 2
1 `fabric.selection`是摘要名称
2 `primary`是一个带值 `blue`的维度标记。
  1. 使用 `Timer.builder`提供描述和单位:[source, java]

Timer.builder("my.timer")        // <1> 2
    .description("description ") // optional 3
    .tags("region", "test")      // optional 4
    .register(registry);
1 创建一个名为 `my.timer`的新计时器
2 计时器测量时间,并将时间转换为监控后端所需的单位
3 为分布摘要提供一个描述
4 tags与计时器关联起来
  1. annotations a method[source, java]

@Timed(value = "call", extraTags = {"region", "test"}) // <1> 2
1 一个 CDI 拦截器将创建一个名为 `call`的计时器并对其进行注册
2 拦截器创建的计时器将具有值为“test”的“区域”维度标记

Measure durations with Timers

Micrometer 提供以下便利机制用于记录持续时间:

  1. 封装 `Runnable`的调用:[source, java]

timer.record(() -> noReturnValue());
  1. 封装 `Callable`的调用:[source, java]

timer.recordCallable(() -> returnValue());
  1. 创建一个封装的 `Runnable`用于重复调用:[source, java]

Runnable r = timer.wrap(() -> noReturnValue());
  1. 创建一个封装的 `Callable`用于重复调用:[source, java]

Callable c = timer.wrap(() -> returnValue());
  1. 对于更复杂的代码路径,使用 Sample:[source, java]

Sample sample = Timer.start(registry); (1)

doStuff; (2)

sample.stop(registry.timer("my.timer", "response", response.status())); (3)
1 我们创建一个记录计时器开始时间的样例。
2 该样例可以作为上下文传递 along。
3 我们在样例停止时可以选择计时器。此例使用响应状态作为识别计时器的标签,该状态在处理完成之前无法得知。

Histograms and percentiles

计时器和分布摘要都可以配置为发出附加统计信息,如直方图数据、预计算百分比或服务水平目标 (SLO) 边界。有关更多信息和示例,请参阅 Micrometer 文档中的 TimersDistribution Summaries,包括对这两种类型进行内存占用估计。

计时器和分布摘要关联的计数、总和和直方图数据可以跨维度(或跨一系列实例)重新聚合。 预计算百分比值不可行。百分比对于每个数据集都是唯一的(此测量集合的 90th 百分位)。

Automatically generated metrics

Micrometer 扩展程序自动计时 HTTP 服务器请求。遵循 Prometheus 计时器的命名惯例,查找 http_server_requests_seconds_counthttp_server_requests_seconds_sum`和 `http_server_requests_seconds_max。已为请求的 uri、HTTP 方法(GET、POST 等)、状态代码(200、302、404 等)和更通用的结果字段添加了维度标记。

Ignoring endpoints

你可以使用 quarkus.micrometer.binder.http-server.ignore-patterns`属性禁用 HTTP 端点的测量。此属性接受一个逗号分隔的简单正则表达式匹配模式列表,用于识别应该忽略的 URI 路径。例如,设置 `quarkus.micrometer.binder.http-server.ignore-patterns=/example/prime/[0-9]+ 将忽略对 http://localhost:8080/example/prime/7919 的请求。对 http://localhost:8080/example/gauge/7919 的请求仍会被测量。

URI templates

micrometer 扩展程序将在模板形式中尽力表示包含路径参数的 URI。使用以上示例,对 http://localhost:8080/example/prime/7919 的请求应显示为 http_server_requests_seconds_* 度量中的一个属性,其值为 uri=/example/prime/{number}

如果无法确定正确的 URL,请使用 quarkus.micrometer.binder.http-server.match-patterns 属性。此属性接受一个逗号分隔的列表,定义简单的正则表达式匹配模式和替换字符串之间的关联。例如,设置 quarkus.micrometer.binder.http-server.match-patterns=/example/prime/[role="0-9"][.0-9]=/example/{jellybeans} 将对任何时候匹配 /example/prime/[0-9] 的请求 uri 使用 /example/{jellybeans} 值作为 uri 属性。

Exported metrics format

默认情况下,度量使用 Prometheus 格式 application/openmetrics-text`导出,你可以通过将 `Accept 请求头指定为 text/plain (curl -H "Accept: text/plain" localhost:8080/q/metrics/) 来恢复为以前的形式。

Customizing Micrometer

Quarkus 提供了多种自定义 Micrometer 的方式。

Use MeterFilter to customize emitted tags and metrics

Micrometer 使用 MeterFilter 实例来自定义由 MeterRegistry 实例发出的度量。Micrometer 扩展程序将检测 MeterFilter CDI Bean,并在初始化 `MeterRegistry`实例时使用它们。

@Singleton
public class CustomConfiguration {

    @ConfigProperty(name = "deployment.env")
    String deploymentEnv;

    /** Define common tags that apply only to a Prometheus Registry */
    @Produces
    @Singleton
    @MeterFilterConstraint(applyTo = PrometheusMeterRegistry.class)
    public MeterFilter configurePrometheusRegistries() {
        return MeterFilter.commonTags(Arrays.asList(
                Tag.of("registry", "prometheus")));
    }

    /** Define common tags that apply globally */
    @Produces
    @Singleton
    public MeterFilter configureAllRegistries() {
        return MeterFilter.commonTags(Arrays.asList(
                Tag.of("env", deploymentEnv)));
    }

    /** Enable histogram buckets for a specific timer */
    @Produces
    @Singleton
    public MeterFilter enableHistogram() {
        return new MeterFilter() {
            @Override
            public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) {
                if(id.getName().startsWith("myservice")) {
                    return DistributionStatisticConfig.builder()
                        .percentiles(0.5, 0.95)     // median and 95th percentile, not aggregable
                        .percentilesHistogram(true) // histogram buckets (e.g. prometheus histogram_quantile)
                        .build()
                        .merge(config);
                }
                return config;
            }
        };
    }
}

在此示例中,一个单例 CDI Bean 将产生两个不同的 MeterFilter Bean。一个仅应用于 Prometheus MeterRegistry 实例(使用 @MeterFilterConstraint 限定符),另一个将应用于所有 MeterRegistry 实例。一个应用程序配置属性也会被注入并用作标签值。可以在 official documentation 中找到 MeterFilters 的其他示例。

Use HttpServerMetricsTagsContributor for server HTTP requests

通过提供实现 io.quarkus.micrometer.runtime.HttpServerMetricsTagsContributor 的 CDI Bean,用户代码可以根据 HTTP 请求的详细信息提供任意标签。

Use MeterRegistryCustomizer for arbitrary customizations to meter registries

通过提供实现 io.quarkus.micrometer.runtime.MeterRegistryCustomizer 的 CDI Bean,用户代码可以有机会更改任何已激活的 MeterRegistry 的配置。除非一个实现带 @io.quarkus.micrometer.runtime.MeterRegistryCustomizerConstraint 注释,否则自定义将适用于所有 MeterRegistry 实例。

Does Micrometer support annotations?

Micrometer 确实定义了两个注释,@Counted@Timed,它们可以添加到方法中。@Timed 注释将包装方法的执行,并将发出以下标签作为补充除了注释本身定义的任何标签:类、方法和异常(“none” 或检测到的异常的简单类名)。

@Counted@Timed 的参数可以使用 @MeterTag 注释动态分配有意义的标签值。

可使用 MeterTag.resolver 从方法参数提取标记,方法是创建一个实现 io.micrometer.common.annotation.ValueResolver 的 bean,并引用此类: @MeterTag(resolver=CustomResolver.class)

还支持 MeterTag.expression,但您必须实现表达式的计算,方法是创建一个实现 io.micrometer.common.annotation.ValueExpressionResolver 的 bean,它可以计算表达式。

enum Currency { USD, EUR }

@Singleton
class EnumOrdinalResolver implements ValueResolver {
    @Override
    public String resolve(Object parameter) {
        if(parameter instanceof Enum) {
            return String.valueOf(((Enum<?>) parameter).ordinal());
        }
        return null;
    }
}

@Singleton
public class MyExpressionResolver implements ValueExpressionResolver {
    @Override
    public String resolve(String expression, Object parameter) {
        return someParser.parse(expression).evaluate(parameter);
    }
}

// tags = type=with_enum, currency=${currency.toString()}
@Timed(value="time_something", extraTags = {"type", "with_enum"})
public Something calculateSomething(@MeterTag Currency currency) { ... }

// tags = type=with_enum, the_currency=${currency.toString()}
@Timed(value="time_something", extraTags = {"type", "with_enum"})
public Something calculateSomething(@MeterTag(key="the_currency") Currency currency) { ... }

// tags = type=with_enum, currency=${currency.ordinal()}
@Timed(value="time_something", extraTags = {"type", "with_enum"})
public Something calculateSomething(@MeterTag(resolver=EnumOrdinalResolver.class) Currency currency) { ... }

// tags = type=with_enum, currency=${currency.ordinal()}
@Timed(value="time_something", extraTags = {"type", "with_enum"})
public Something calculateSomething(@MeterTag(expression="currency.ordinal()") Currency currency) { ... }

提供的标记值必须是低基数的。高基数值可能导致指标后端的性能和存储问题(“基数爆炸”)。标记值不应使用最终用户数据,因为这些数据可能是高基数的。

微计扩展开箱即用地对许多方法(例如 REST 端点方法或 Vert.x 路由)进行计数和计时。

Support for the MicroProfile Metrics API

如果您在应用程序中使用 MicroProfile 指标 API, 微计扩展将创建一个自适应层,以将这些指标映射到微计注册表中。请注意,两个系统之间的命名约定不同,因此在使用 MP 指标和微计时发出的指标将会发生变化。

使用 MeterFilter 根据您的约定重新映射名称或标记。

@Produces
@Singleton
public MeterFilter renameApplicationMeters() {
    final String targetMetric = MPResourceClass.class.getName() + ".mpAnnotatedMethodName";

    return MeterFilter() {
        @Override
        public Meter.Id map(Meter.Id id) {
            if (id.getName().equals(targetMetric)) {
                // Drop the scope tag (MP Registry type: application, vendor, base)
                List<Tag> tags = id.getTags().stream().filter(x -> !"scope".equals(x.getKey()))
                        .collect(Collectors.toList());
                // rename the metric
                return id.withName("my.metric.name").replaceTags(tags);
            }
            return id;
        }
    };
}

如果您需要 MicroProfile 指标 API,请确保存在以下依赖关系:

pom.xml
<dependency>
    <groupId>org.eclipse.microprofile.metrics</groupId>
    <artifactId>microprofile-metrics-api</artifactId>
</dependency>
build.gradle
implementation("org.eclipse.microprofile.metrics:microprofile-metrics-api")

将来,MP 指标 API 兼容层可能会移至其他扩展。

Management interface

默认情况下,指标在主 HTTP 服务器上公开。

您可以通过在应用程序配置中设置 quarkus.management.enabled=true 来在单独的网络接口和端口上公开它们。注意,此属性是构建时间属性。此值在运行时不可重写。

如果您在未自定义管理网络接口和端口的情况下启用管理界面,则指标在以下位置公开: http://0.0.0.0:9000/q/metrics

您可以使用以下配置路径配置每个公开格式的路径:

quarkus.micrometer.export.json.enabled=true # Enable json metrics
quarkus.micrometer.export.json.path=metrics/json
quarkus.micrometer.export.prometheus.path=metrics/prometheus

通过此类配置,可以从 http://0.0.0.0:9000/q/metrics/json 获得 json 指标。可以从 http://0.0.0.0:9000/q/metrics/prometheus 获得 prometheus 指标。

请参阅 management interface reference 了解更多信息。

Configuration Reference

Unresolved include directive in modules/ROOT/pages/telemetry-micrometer.adoc - include::../../../target/quarkus-generated-doc/config/quarkus-micrometer.adoc[]