diff --git a/core/build.gradle b/core/build.gradle index 4741aa3b4f2..18a23cbf366 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -105,9 +105,9 @@ dependencies { // Micrometer and other metric-related stuff api libs.micrometer.core - optionalApi libs.micrometer.prometheus + optionalApi libs.micrometer.prometheus.legacy optionalApi libs.dropwizard.metrics.core - optionalApi libs.prometheus + optionalApi libs.prometheus.legacy // Netty api libs.netty.transport @@ -154,6 +154,8 @@ dependencies { // Jetty, for testing interoperability with other servers. testImplementation libs.jetty94.webapp + testImplementation project(':prometheus1') + // Brotli implementation libs.brotli4j optionalImplementation libs.brotli4j.linux diff --git a/core/src/main/java/com/linecorp/armeria/common/metric/PrometheusMeterRegistries.java b/core/src/main/java/com/linecorp/armeria/common/metric/PrometheusMeterRegistries.java index 54ec2e0588f..07b62340472 100644 --- a/core/src/main/java/com/linecorp/armeria/common/metric/PrometheusMeterRegistries.java +++ b/core/src/main/java/com/linecorp/armeria/common/metric/PrometheusMeterRegistries.java @@ -27,7 +27,10 @@ /** * Provides the convenient factory methods for {@link PrometheusMeterRegistry} with more sensible defaults for * {@link NamingConvention}. + * + * @deprecated Use {@code PrometheusMeterRegistries} in {@code armeria-prometheus1} module instead. */ +@Deprecated public final class PrometheusMeterRegistries { private static final PrometheusMeterRegistry defaultRegistry = diff --git a/core/src/main/java/com/linecorp/armeria/server/metric/PrometheusExpositionService.java b/core/src/main/java/com/linecorp/armeria/server/metric/PrometheusExpositionService.java index 311acd83841..93c911eff76 100644 --- a/core/src/main/java/com/linecorp/armeria/server/metric/PrometheusExpositionService.java +++ b/core/src/main/java/com/linecorp/armeria/server/metric/PrometheusExpositionService.java @@ -42,7 +42,10 @@ /** * Exposes Prometheus metrics in text * format 0.0.4 or OpenMetrics format. + * + * @deprecated Use {@code PrometheusExpositionService} in {@code armeria-prometheus1} module instead. */ +@Deprecated public final class PrometheusExpositionService extends AbstractHttpService implements TransientHttpService { /** @@ -106,7 +109,7 @@ protected HttpResponse doGet(ServiceRequestContext ctx, HttpRequest req) throws final ByteBuf buffer = ctx.alloc().buffer(); boolean success = false; try (ByteBufOutputStream byteBufOutputStream = new ByteBufOutputStream(buffer); - OutputStreamWriter writer = new OutputStreamWriter(byteBufOutputStream)) { + OutputStreamWriter writer = new OutputStreamWriter(byteBufOutputStream)) { TextFormat.writeFormat(format, writer, collectorRegistry.metricFamilySamples()); success = true; } finally { diff --git a/core/src/main/java/com/linecorp/armeria/server/metric/PrometheusExpositionServiceBuilder.java b/core/src/main/java/com/linecorp/armeria/server/metric/PrometheusExpositionServiceBuilder.java index 2335e843502..d4ac6c1c8fa 100644 --- a/core/src/main/java/com/linecorp/armeria/server/metric/PrometheusExpositionServiceBuilder.java +++ b/core/src/main/java/com/linecorp/armeria/server/metric/PrometheusExpositionServiceBuilder.java @@ -25,7 +25,10 @@ /** * Builds a {@link PrometheusExpositionService}. + * + * @deprecated Use {@code PrometheusExpositionServiceBuilder} in {@code armeria-prometheus1} module instead. */ +@Deprecated public final class PrometheusExpositionServiceBuilder implements TransientServiceBuilder { private final CollectorRegistry collectorRegistry; diff --git a/core/src/test/java/com/linecorp/armeria/client/ConnectionPoolCollectingMetricTest.java b/core/src/test/java/com/linecorp/armeria/client/ConnectionPoolCollectingMetricTest.java index ce0e33e7b4a..ffd06347c00 100644 --- a/core/src/test/java/com/linecorp/armeria/client/ConnectionPoolCollectingMetricTest.java +++ b/core/src/test/java/com/linecorp/armeria/client/ConnectionPoolCollectingMetricTest.java @@ -24,7 +24,7 @@ import com.linecorp.armeria.common.SessionProtocol; import com.linecorp.armeria.common.metric.MoreMeters; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import io.micrometer.core.instrument.MeterRegistry; import io.netty.util.AttributeMap; diff --git a/core/src/test/java/com/linecorp/armeria/client/RefreshingAddressResolverTest.java b/core/src/test/java/com/linecorp/armeria/client/RefreshingAddressResolverTest.java index ef705d04d68..3a524b90d2d 100644 --- a/core/src/test/java/com/linecorp/armeria/client/RefreshingAddressResolverTest.java +++ b/core/src/test/java/com/linecorp/armeria/client/RefreshingAddressResolverTest.java @@ -52,7 +52,7 @@ import com.linecorp.armeria.client.endpoint.dns.TestDnsServer; import com.linecorp.armeria.client.retry.Backoff; import com.linecorp.armeria.common.annotation.Nullable; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import com.linecorp.armeria.internal.client.dns.ByteArrayDnsRecord; import com.linecorp.armeria.internal.client.dns.DnsQuestionWithoutTrailingDot; import com.linecorp.armeria.testing.junit5.common.EventLoopExtension; diff --git a/core/src/test/java/com/linecorp/armeria/client/circuitbreaker/MetricCollectingCircuitBreakerListenerTest.java b/core/src/test/java/com/linecorp/armeria/client/circuitbreaker/MetricCollectingCircuitBreakerListenerTest.java index b6c9e14f1cb..33af877c7f9 100644 --- a/core/src/test/java/com/linecorp/armeria/client/circuitbreaker/MetricCollectingCircuitBreakerListenerTest.java +++ b/core/src/test/java/com/linecorp/armeria/client/circuitbreaker/MetricCollectingCircuitBreakerListenerTest.java @@ -21,7 +21,7 @@ import org.junit.jupiter.api.Test; import com.linecorp.armeria.common.metric.MoreMeters; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import io.micrometer.core.instrument.MeterRegistry; diff --git a/core/src/test/java/com/linecorp/armeria/client/endpoint/healthcheck/HealthCheckedEndpointGroupIntegrationTest.java b/core/src/test/java/com/linecorp/armeria/client/endpoint/healthcheck/HealthCheckedEndpointGroupIntegrationTest.java index 0785b55ec83..bab65928ada 100644 --- a/core/src/test/java/com/linecorp/armeria/client/endpoint/healthcheck/HealthCheckedEndpointGroupIntegrationTest.java +++ b/core/src/test/java/com/linecorp/armeria/client/endpoint/healthcheck/HealthCheckedEndpointGroupIntegrationTest.java @@ -36,7 +36,7 @@ import com.linecorp.armeria.client.logging.LoggingClient; import com.linecorp.armeria.common.SessionProtocol; import com.linecorp.armeria.common.metric.MoreMeters; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.healthcheck.HealthCheckService; import com.linecorp.armeria.testing.junit5.server.ServerExtension; diff --git a/core/src/test/java/com/linecorp/armeria/common/metric/DropwizardMeterRegistriesTest.java b/core/src/test/java/com/linecorp/armeria/common/metric/DropwizardMeterRegistriesTest.java index 0bdf4d28ea4..8727bf0d824 100644 --- a/core/src/test/java/com/linecorp/armeria/common/metric/DropwizardMeterRegistriesTest.java +++ b/core/src/test/java/com/linecorp/armeria/common/metric/DropwizardMeterRegistriesTest.java @@ -21,23 +21,26 @@ import static org.assertj.core.api.Assertions.fail; import java.time.Duration; -import java.util.Enumeration; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; import com.codahale.metrics.MetricRegistry; -import com.google.common.collect.ImmutableList; + +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.DistributionSummary; import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.composite.CompositeMeterRegistry; import io.micrometer.core.instrument.dropwizard.DropwizardMeterRegistry; -import io.micrometer.prometheus.PrometheusMeterRegistry; -import io.prometheus.client.Collector.MetricFamilySamples; -import io.prometheus.client.Collector.MetricFamilySamples.Sample; +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; +import io.prometheus.metrics.model.snapshots.DataPointSnapshot; +import io.prometheus.metrics.model.snapshots.MetricMetadata; +import io.prometheus.metrics.model.snapshots.MetricSnapshot; +import io.prometheus.metrics.model.snapshots.SummarySnapshot.SummaryDataPointSnapshot; class DropwizardMeterRegistriesTest { @Test @@ -131,20 +134,28 @@ void filteredGaugesDoNotAffectOthers() { assertThat(dropwizard.getDropwizardRegistry().getMetrics()).containsOnlyKeys("summary"); // Make sure Prometheus registry collects all samples. - final MetricFamilySamples prometheusSamples = findPrometheusSample(prometheus, "summary"); - assertThat(prometheusSamples.samples).containsExactly( - new Sample("summary", ImmutableList.of("quantile"), ImmutableList.of("0.5"), 42), - new Sample("summary", ImmutableList.of("quantile"), ImmutableList.of("0.99"), 42), - new Sample("summary_count", ImmutableList.of(), ImmutableList.of(), 1), - new Sample("summary_sum", ImmutableList.of(), ImmutableList.of(), 42)); + final List dataPointSnapshots = + findPrometheusDataPointSnapshot(prometheus, "summary"); + assertThat(dataPointSnapshots.size()).isOne(); + final DataPointSnapshot snapshot = dataPointSnapshots.get(0); + assertThat(snapshot).isInstanceOf(SummaryDataPointSnapshot.class); + // SummaryDataPointSnapshot and its values do not override equals(). + final SummaryDataPointSnapshot summarySnapshot = (SummaryDataPointSnapshot) snapshot; + assertThat(summarySnapshot.getCount()).isOne(); + assertThat(summarySnapshot.getSum()).isEqualTo(42.0); + assertThat(summarySnapshot.getQuantiles().size()).isEqualTo(2); + assertThat(summarySnapshot.getQuantiles().get(0).getQuantile()).isEqualTo(0.5); + assertThat(summarySnapshot.getQuantiles().get(0).getValue()).isEqualTo(42.0); + assertThat(summarySnapshot.getQuantiles().get(1).getQuantile()).isEqualTo(0.99); + assertThat(summarySnapshot.getQuantiles().get(1).getValue()).isEqualTo(42.0); } - private static MetricFamilySamples findPrometheusSample(PrometheusMeterRegistry registry, String name) { - for (final Enumeration e = registry.getPrometheusRegistry().metricFamilySamples(); - e.hasMoreElements();) { - final MetricFamilySamples samples = e.nextElement(); - if (name.equals(samples.name)) { - return samples; + private static List findPrometheusDataPointSnapshot( + PrometheusMeterRegistry registry, String name) { + for (final MetricSnapshot snapshot : registry.getPrometheusRegistry().scrape()) { + final MetricMetadata metadata = snapshot.getMetadata(); + if (name.equals(metadata.getName())) { + return snapshot.getDataPoints(); } } @@ -152,3 +163,4 @@ private static MetricFamilySamples findPrometheusSample(PrometheusMeterRegistry throw new Error(); // Never reaches here. } } + diff --git a/core/src/test/java/com/linecorp/armeria/common/metric/MeterIdPrefixFunctionTest.java b/core/src/test/java/com/linecorp/armeria/common/metric/MeterIdPrefixFunctionTest.java index 916e2e0c382..96ec3f8c47d 100644 --- a/core/src/test/java/com/linecorp/armeria/common/metric/MeterIdPrefixFunctionTest.java +++ b/core/src/test/java/com/linecorp/armeria/common/metric/MeterIdPrefixFunctionTest.java @@ -31,6 +31,7 @@ import com.linecorp.armeria.common.annotation.Nullable; import com.linecorp.armeria.common.logging.RequestLog; import com.linecorp.armeria.common.logging.RequestOnlyLog; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import com.linecorp.armeria.server.ServiceRequestContext; import io.micrometer.core.instrument.MeterRegistry; diff --git a/core/src/test/java/com/linecorp/armeria/internal/common/metric/CaffeineMetricSupportTest.java b/core/src/test/java/com/linecorp/armeria/internal/common/metric/CaffeineMetricSupportTest.java index e7de85d054c..7d7df459466 100644 --- a/core/src/test/java/com/linecorp/armeria/internal/common/metric/CaffeineMetricSupportTest.java +++ b/core/src/test/java/com/linecorp/armeria/internal/common/metric/CaffeineMetricSupportTest.java @@ -39,7 +39,7 @@ import com.linecorp.armeria.common.metric.MeterIdPrefix; import com.linecorp.armeria.common.metric.MoreMeters; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import io.micrometer.core.instrument.MeterRegistry; diff --git a/core/src/test/java/com/linecorp/armeria/internal/common/metric/MicrometerUtilTest.java b/core/src/test/java/com/linecorp/armeria/internal/common/metric/MicrometerUtilTest.java index 021e57bb1f3..6b376cfb930 100644 --- a/core/src/test/java/com/linecorp/armeria/internal/common/metric/MicrometerUtilTest.java +++ b/core/src/test/java/com/linecorp/armeria/internal/common/metric/MicrometerUtilTest.java @@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test; import com.linecorp.armeria.common.metric.MeterIdPrefix; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import io.micrometer.core.instrument.MeterRegistry; diff --git a/core/src/test/java/com/linecorp/armeria/internal/common/metric/RequestMetricSupportTest.java b/core/src/test/java/com/linecorp/armeria/internal/common/metric/RequestMetricSupportTest.java index 19a7557b1d6..5c8c840ca80 100644 --- a/core/src/test/java/com/linecorp/armeria/internal/common/metric/RequestMetricSupportTest.java +++ b/core/src/test/java/com/linecorp/armeria/internal/common/metric/RequestMetricSupportTest.java @@ -33,7 +33,7 @@ import com.linecorp.armeria.common.SuccessFunction; import com.linecorp.armeria.common.logging.ClientConnectionTimings; import com.linecorp.armeria.common.metric.MeterIdPrefixFunction; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import com.linecorp.armeria.common.util.SafeCloseable; import com.linecorp.armeria.internal.testing.ImmediateEventLoop; import com.linecorp.armeria.server.RequestTimeoutException; diff --git a/core/src/test/java/com/linecorp/armeria/server/ServerBuilderTest.java b/core/src/test/java/com/linecorp/armeria/server/ServerBuilderTest.java index 6b17f7d6b50..fba4af0b045 100644 --- a/core/src/test/java/com/linecorp/armeria/server/ServerBuilderTest.java +++ b/core/src/test/java/com/linecorp/armeria/server/ServerBuilderTest.java @@ -52,7 +52,7 @@ import com.linecorp.armeria.common.HttpResponse; import com.linecorp.armeria.common.HttpStatus; import com.linecorp.armeria.common.SessionProtocol; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import com.linecorp.armeria.common.util.DomainSocketAddress; import com.linecorp.armeria.common.util.TransportType; import com.linecorp.armeria.internal.common.util.MinifiedBouncyCastleProvider; @@ -61,7 +61,7 @@ import com.linecorp.armeria.testing.junit5.server.ServerExtension; import io.micrometer.core.instrument.Metrics; -import io.micrometer.prometheus.PrometheusMeterRegistry; +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; import io.netty.channel.ChannelOption; import io.netty.handler.ssl.SslContextBuilder; import reactor.core.scheduler.Schedulers; diff --git a/core/src/test/java/com/linecorp/armeria/server/ServerListenerTest.java b/core/src/test/java/com/linecorp/armeria/server/ServerListenerTest.java index 4fb22ecfe91..dc6949a12d7 100644 --- a/core/src/test/java/com/linecorp/armeria/server/ServerListenerTest.java +++ b/core/src/test/java/com/linecorp/armeria/server/ServerListenerTest.java @@ -34,7 +34,7 @@ import org.junit.jupiter.params.provider.ArgumentsSource; import com.linecorp.armeria.common.HttpResponse; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import com.linecorp.armeria.testing.junit5.server.ServerExtension; class ServerListenerTest { diff --git a/core/src/test/java/com/linecorp/armeria/server/ServerTest.java b/core/src/test/java/com/linecorp/armeria/server/ServerTest.java index 4c0b7814fbe..54886bec62d 100644 --- a/core/src/test/java/com/linecorp/armeria/server/ServerTest.java +++ b/core/src/test/java/com/linecorp/armeria/server/ServerTest.java @@ -69,7 +69,7 @@ import com.linecorp.armeria.common.ResponseHeaders; import com.linecorp.armeria.common.SessionProtocol; import com.linecorp.armeria.common.metric.MeterIdPrefix; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import com.linecorp.armeria.common.util.CompletionActions; import com.linecorp.armeria.common.util.Exceptions; import com.linecorp.armeria.common.util.ThreadFactories; diff --git a/core/src/test/java/com/linecorp/armeria/server/ServerTlsCertificateMetricsTest.java b/core/src/test/java/com/linecorp/armeria/server/ServerTlsCertificateMetricsTest.java index 3c2827ce166..d78ab0e4002 100644 --- a/core/src/test/java/com/linecorp/armeria/server/ServerTlsCertificateMetricsTest.java +++ b/core/src/test/java/com/linecorp/armeria/server/ServerTlsCertificateMetricsTest.java @@ -27,7 +27,7 @@ import org.junit.jupiter.api.Test; import com.linecorp.armeria.common.HttpResponse; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import com.linecorp.armeria.internal.common.util.SelfSignedCertificate; import io.micrometer.core.instrument.Gauge; diff --git a/core/src/test/java/com/linecorp/armeria/server/ServerTlsHandshakeMetricsTest.java b/core/src/test/java/com/linecorp/armeria/server/ServerTlsHandshakeMetricsTest.java index 4838d5fba48..3bf17942903 100644 --- a/core/src/test/java/com/linecorp/armeria/server/ServerTlsHandshakeMetricsTest.java +++ b/core/src/test/java/com/linecorp/armeria/server/ServerTlsHandshakeMetricsTest.java @@ -35,7 +35,7 @@ import com.linecorp.armeria.common.HttpStatus; import com.linecorp.armeria.common.SessionProtocol; import com.linecorp.armeria.common.metric.MoreMeters; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import com.linecorp.armeria.testing.junit5.server.ServerExtension; import io.micrometer.core.instrument.Counter; diff --git a/core/src/test/java/com/linecorp/armeria/server/logging/ContentPreviewerCancellationTest.java b/core/src/test/java/com/linecorp/armeria/server/logging/ContentPreviewerCancellationTest.java index 3b6b2ea0818..f0d058e8ffd 100644 --- a/core/src/test/java/com/linecorp/armeria/server/logging/ContentPreviewerCancellationTest.java +++ b/core/src/test/java/com/linecorp/armeria/server/logging/ContentPreviewerCancellationTest.java @@ -39,7 +39,7 @@ import com.linecorp.armeria.common.annotation.Nullable; import com.linecorp.armeria.common.logging.ContentPreviewerFactory; import com.linecorp.armeria.common.metric.MeterIdPrefixFunction; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import com.linecorp.armeria.common.stream.CancelledSubscriptionException; import com.linecorp.armeria.common.util.Functions; import com.linecorp.armeria.internal.logging.ContentPreviewingUtil; @@ -51,10 +51,10 @@ import com.linecorp.armeria.server.annotation.Get; import com.linecorp.armeria.server.cors.CorsService; import com.linecorp.armeria.server.metric.MetricCollectingService; -import com.linecorp.armeria.server.metric.PrometheusExpositionService; +import com.linecorp.armeria.server.prometheus.PrometheusExpositionService; import com.linecorp.armeria.testing.junit5.server.ServerExtension; -import io.micrometer.prometheus.PrometheusMeterRegistry; +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; class ContentPreviewerCancellationTest { @@ -77,7 +77,8 @@ protected void configure(ServerBuilder sb) { .newDecorator() ) .annotatedService(new TestService()) - .service("/metrics", PrometheusExpositionService.of(registry.getPrometheusRegistry())); + .service("/metrics", + PrometheusExpositionService.of(registry.getPrometheusRegistry())); } }; diff --git a/core/src/test/java/com/linecorp/armeria/server/metric/MetricCollectingServiceTest.java b/core/src/test/java/com/linecorp/armeria/server/metric/MetricCollectingServiceTest.java index 8d1e4882d90..42016b2cdf6 100644 --- a/core/src/test/java/com/linecorp/armeria/server/metric/MetricCollectingServiceTest.java +++ b/core/src/test/java/com/linecorp/armeria/server/metric/MetricCollectingServiceTest.java @@ -27,11 +27,11 @@ import com.linecorp.armeria.common.HttpResponse; import com.linecorp.armeria.common.metric.MeterIdPrefixFunction; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.testing.junit5.server.ServerExtension; -import io.micrometer.prometheus.PrometheusMeterRegistry; +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; class MetricCollectingServiceTest { diff --git a/core/src/test/java/com/linecorp/armeria/server/metric/PrometheusExpositionServiceTest.java b/core/src/test/java/com/linecorp/armeria/server/metric/PrometheusExpositionServiceTest.java index a102ac377de..62c2a9a57e3 100644 --- a/core/src/test/java/com/linecorp/armeria/server/metric/PrometheusExpositionServiceTest.java +++ b/core/src/test/java/com/linecorp/armeria/server/metric/PrometheusExpositionServiceTest.java @@ -130,9 +130,9 @@ class FormatTest { void prometheusRequestsPrometheusFormat() throws InterruptedException { final WebClient client = WebClient.of(server.httpUri()); final HttpRequest request = HttpRequest.builder() - .get("/enabled") - .header(HttpHeaderNames.ACCEPT, TextFormat.CONTENT_TYPE_004) - .build(); + .get("/enabled") + .header(HttpHeaderNames.ACCEPT, TextFormat.CONTENT_TYPE_004) + .build(); final AggregatedHttpResponse response = client.execute(request).aggregate().join(); assertThat(response.headers().get(HttpHeaderNames.CONTENT_TYPE)) .isEqualTo(TextFormat.CONTENT_TYPE_004); @@ -142,9 +142,10 @@ void prometheusRequestsPrometheusFormat() throws InterruptedException { void prometheusRequestsOpenMetricsFormat() throws InterruptedException { final WebClient client = WebClient.of(server.httpUri()); final HttpRequest request = HttpRequest.builder() - .get("/enabled") - .header(HttpHeaderNames.ACCEPT, TextFormat.CONTENT_TYPE_OPENMETRICS_100) - .build(); + .get("/enabled") + .header(HttpHeaderNames.ACCEPT, + TextFormat.CONTENT_TYPE_OPENMETRICS_100) + .build(); final AggregatedHttpResponse response = client.execute(request).aggregate().join(); assertThat(response.headers().get(HttpHeaderNames.CONTENT_TYPE)) .isEqualTo(TextFormat.CONTENT_TYPE_OPENMETRICS_100); diff --git a/dependencies.toml b/dependencies.toml index 1c9c2eb275a..a4b19016f16 100644 --- a/dependencies.toml +++ b/dependencies.toml @@ -87,10 +87,9 @@ kubernetes-client = "6.12.1" logback12 = "1.2.13" logback13 = "1.3.14" logback14 = "1.4.14" -micrometer = "1.12.4" +micrometer = "1.13.0" micrometer-tracing = "1.2.4" micrometer-docs-generator = "1.0.2" -micrometer13 = "1.3.20" # Don't uprade mockito to 5.x.x that requires Java 11 mockito = "4.11.0" monix = "3.4.1" @@ -108,7 +107,8 @@ osdetector = "1.7.3" # Used for kubernetes-chaos-tests picocli = "4.7.6" proguard = "7.4.2" -prometheus = "0.16.0" +prometheus = "1.3.0" +prometheus-legacy = "0.16.0" # Ensure that we use the same Protobuf version as what gRPC depends on. # See: https://github.com/grpc/grpc-java/blob/master/gradle/libs.versions.toml # (Switch to the right tag and look for "protobuf".) @@ -868,7 +868,11 @@ version.ref = "micrometer" [libraries.micrometer-prometheus] module = "io.micrometer:micrometer-registry-prometheus" version.ref = "micrometer" -javadocs = "https://www.javadoc.io/doc/io.micrometer/micrometer-registry-prometheus/1.10.8/" +javadocs = "https://www.javadoc.io/doc/io.micrometer/micrometer-registry-prometheus/1.13.0/" +[libraries.micrometer-prometheus-legacy] +module = "io.micrometer:micrometer-registry-prometheus-simpleclient" +version.ref = "micrometer" +javadocs = "https://www.javadoc.io/doc/io.micrometer/micrometer-registry-prometheus-simpleclient/1.13.0/" [libraries.micrometer-spring-legacy] module = "io.micrometer:micrometer-spring-legacy" version.ref = "micrometer" @@ -885,17 +889,6 @@ module = "io.micrometer:micrometer-docs-generator" version.ref = "micrometer-docs-generator" javadocs = "https://www.javadoc.io/doc/io.micrometer/micrometer-docs-generator/1.0.2/" -[libraries.micrometer13-core] -module = "io.micrometer:micrometer-core" -version.ref = "micrometer13" -[libraries.micrometer13-prometheus] -module = "io.micrometer:micrometer-registry-prometheus" -version.ref = "micrometer13" -[libraries.micrometer13-spring-legacy] -module = "io.micrometer:micrometer-spring-legacy" -version.ref = "micrometer13" -exclusions = ["org.springframework:spring-web", "org.springframework:spring-webmvc"] - [libraries.mockito] module = "org.mockito:mockito-core" version.ref = "mockito" @@ -954,10 +947,13 @@ version.ref = "netty-incubator-transport-native-io_uring" module = "info.picocli:picocli" version.ref = "picocli" -[libraries.prometheus] -module = "io.prometheus:simpleclient_common" +[libraries.prometheus-metrics-exposition-formats] +module = "io.prometheus:prometheus-metrics-exposition-formats" version.ref = "prometheus" javadocs = "https://prometheus.github.io/client_java/" +[libraries.prometheus-legacy] +module = "io.prometheus:simpleclient_common" +version.ref = "prometheus-legacy" [libraries.protobuf-java] module = "com.google.protobuf:protobuf-java" diff --git a/dropwizard1/build.gradle b/dropwizard1/build.gradle index 5eaef271eb3..7c89efa5f2c 100644 --- a/dropwizard1/build.gradle +++ b/dropwizard1/build.gradle @@ -1,5 +1,3 @@ -final def DROPWIZARD_VERSION = '1.3.29' - dependencies { implementation project(':jetty9') // Dropwizard diff --git a/examples/resilience4j-spring/build.gradle b/examples/resilience4j-spring/build.gradle index 10b73bca035..fac2d70915b 100644 --- a/examples/resilience4j-spring/build.gradle +++ b/examples/resilience4j-spring/build.gradle @@ -1,11 +1,11 @@ dependencies { + implementation project(':prometheus1') implementation project(':resilience4j2') implementation project(':spring:boot2-starter') runtimeOnly project(':spring:boot2-actuator-starter') implementation libs.resilience4j.springboot2 implementation libs.spring.boot2.starter.web implementation libs.micrometer.prometheus - implementation libs.prometheus implementation libs.resilience4j.micrometer testImplementation libs.spring.boot2.starter.test diff --git a/examples/resilience4j-spring/src/main/java/example/armeria/resilience4j/spring/ServerConfiguration.java b/examples/resilience4j-spring/src/main/java/example/armeria/resilience4j/spring/ServerConfiguration.java index 8ae1c6ee083..2636bb829eb 100644 --- a/examples/resilience4j-spring/src/main/java/example/armeria/resilience4j/spring/ServerConfiguration.java +++ b/examples/resilience4j-spring/src/main/java/example/armeria/resilience4j/spring/ServerConfiguration.java @@ -4,13 +4,13 @@ import org.springframework.context.annotation.Configuration; import com.linecorp.armeria.common.HttpResponse; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import com.linecorp.armeria.spring.ArmeriaServerConfigurator; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.composite.CompositeMeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import io.micrometer.prometheus.PrometheusMeterRegistry; +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; @Configuration public class ServerConfiguration { diff --git a/grpc/build.gradle b/grpc/build.gradle index 96985516ba8..b898b81065e 100644 --- a/grpc/build.gradle +++ b/grpc/build.gradle @@ -20,6 +20,7 @@ dependencies { testImplementation(libs.gax.grpc) { exclude group: 'com.google.protobuf', module: 'protobuf-java' } + testImplementation project(':prometheus1') testImplementation libs.grpc.okhttp testImplementation libs.grpc.testing testImplementation libs.micrometer.prometheus diff --git a/grpc/src/test/java/com/linecorp/armeria/common/grpc/GrpcMeterIdPrefixFunctionTest.java b/grpc/src/test/java/com/linecorp/armeria/common/grpc/GrpcMeterIdPrefixFunctionTest.java index 14bc6fe69da..898090c1c99 100644 --- a/grpc/src/test/java/com/linecorp/armeria/common/grpc/GrpcMeterIdPrefixFunctionTest.java +++ b/grpc/src/test/java/com/linecorp/armeria/common/grpc/GrpcMeterIdPrefixFunctionTest.java @@ -48,7 +48,7 @@ import com.linecorp.armeria.common.logging.RequestLogAccess; import com.linecorp.armeria.common.logging.RequestLogProperty; import com.linecorp.armeria.common.metric.MeterIdPrefix; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.grpc.GrpcService; import com.linecorp.armeria.testing.junit5.server.ServerExtension; @@ -58,7 +58,7 @@ import io.grpc.StatusRuntimeException; import io.grpc.stub.StreamObserver; import io.micrometer.core.instrument.Statistic; -import io.micrometer.prometheus.PrometheusMeterRegistry; +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; import testing.grpc.EmptyProtos.Empty; import testing.grpc.Messages.SimpleRequest; import testing.grpc.Messages.SimpleResponse; diff --git a/grpc/src/test/java/com/linecorp/armeria/it/grpc/GrpcMetricsIntegrationTest.java b/grpc/src/test/java/com/linecorp/armeria/it/grpc/GrpcMetricsIntegrationTest.java index 1603df0987e..44bac4f4586 100644 --- a/grpc/src/test/java/com/linecorp/armeria/it/grpc/GrpcMetricsIntegrationTest.java +++ b/grpc/src/test/java/com/linecorp/armeria/it/grpc/GrpcMetricsIntegrationTest.java @@ -44,7 +44,7 @@ import com.linecorp.armeria.common.grpc.GrpcMeterIdPrefixFunction; import com.linecorp.armeria.common.metric.MeterIdPrefix; import com.linecorp.armeria.common.metric.MoreMeters; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import com.linecorp.armeria.internal.testing.GenerateNativeImageTrace; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.grpc.GrpcService; diff --git a/it/micrometer1.3/build.gradle b/it/micrometer1.3/build.gradle deleted file mode 100644 index c36ac1aeadf..00000000000 --- a/it/micrometer1.3/build.gradle +++ /dev/null @@ -1,40 +0,0 @@ -def thriftVersion = "" -if (project.ext.targetJavaVersion >= 11) { - thriftVersion = '0.18' -} else { - thriftVersion = '0.17' -} - -dependencies { - testImplementation project(":thrift$thriftVersion") - testImplementation project(':grpc') - testImplementation libs.dropwizard.metrics.core - testImplementation libs.prometheus - - testImplementation libs.micrometer13.core - testImplementation libs.micrometer13.prometheus -} - -tasks.compileTestJava.source "${rootProject.projectDir}/core/src/test/java/com/linecorp/armeria/internal/common/metric/RequestMetricSupportTest.java", - "${rootProject.projectDir}/thrift/src/test/java/com/linecorp/armeria/it/metric/PrometheusMetricsIntegrationTest.java", - "${rootProject.projectDir}/thrift/src/test/java/com/linecorp/armeria/it/metric/DropwizardMetricsIntegrationTest.java", - "${rootProject.projectDir}/grpc/src/test/java/com/linecorp/armeria/it/grpc/GrpcMetricsIntegrationTest.java" - -task copyThriftFiles(type: Copy) { - dependsOn project(":thrift$thriftVersion").tasks.compileTestThrift - dependsOn project(":thrift$thriftVersion").tasks.generateTestSources - - from "${rootProject.projectDir}/thrift/thrift$thriftVersion/gen-src/test/java" - into "${project.ext.genSrcDir}/test/java" - include '**/HelloService.java' -} - -task copyGrpcFiles(type: Copy) { - dependsOn project(':grpc').tasks.generateTestProto - - from "${rootProject.projectDir}/grpc/gen-src/test/java", "${rootProject.projectDir}/grpc/gen-src/test/grpc" - into "${project.ext.genSrcDir}/test/java" -} - -tasks.compileTestJava.dependsOn(copyThriftFiles) -tasks.compileTestJava.dependsOn(copyGrpcFiles) diff --git a/it/thrift0.9.1/build.gradle b/it/thrift0.9.1/build.gradle index 5ce82e82cc7..71f2c6636ad 100644 --- a/it/thrift0.9.1/build.gradle +++ b/it/thrift0.9.1/build.gradle @@ -15,7 +15,6 @@ dependencies { // Dropwizard and Prometheus, for testing metrics integration testImplementation libs.dropwizard.metrics.core testImplementation libs.micrometer.prometheus - testImplementation libs.prometheus } def suffix = rootProject.osdetector.classifier diff --git a/prometheus1/build.gradle b/prometheus1/build.gradle new file mode 100644 index 00000000000..e0bf811022d --- /dev/null +++ b/prometheus1/build.gradle @@ -0,0 +1,5 @@ +dependencies { + api libs.micrometer.prometheus + + implementation libs.prometheus.metrics.exposition.formats +} diff --git a/prometheus1/src/main/java/com/linecorp/armeria/common/prometheus/PrometheusMeterRegistries.java b/prometheus1/src/main/java/com/linecorp/armeria/common/prometheus/PrometheusMeterRegistries.java new file mode 100644 index 00000000000..fe83d916677 --- /dev/null +++ b/prometheus1/src/main/java/com/linecorp/armeria/common/prometheus/PrometheusMeterRegistries.java @@ -0,0 +1,69 @@ +/* + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.armeria.common.prometheus; + +import static java.util.Objects.requireNonNull; + +import com.linecorp.armeria.common.annotation.UnstableApi; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.prometheusmetrics.PrometheusConfig; +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; +import io.prometheus.metrics.model.registry.PrometheusRegistry; + +/** + * Provides the convenient factory methods for {@link PrometheusMeterRegistry}. + */ +@UnstableApi +public final class PrometheusMeterRegistries { + + private static final PrometheusMeterRegistry defaultRegistry = + newRegistry(PrometheusRegistry.defaultRegistry); + + /** + * Returns the default {@link PrometheusMeterRegistry} that uses {@link PrometheusRegistry#defaultRegistry}. + */ + public static PrometheusMeterRegistry defaultRegistry() { + return defaultRegistry; + } + + /** + * Returns a newly-created {@link PrometheusMeterRegistry} instance with a new {@link PrometheusRegistry}. + */ + public static PrometheusMeterRegistry newRegistry() { + return newRegistry(new PrometheusRegistry()); + } + + /** + * Returns a newly-created {@link PrometheusMeterRegistry} instance with the specified + * {@link PrometheusRegistry}. + */ + public static PrometheusMeterRegistry newRegistry(PrometheusRegistry registry) { + return newRegistry(registry, Clock.SYSTEM); + } + + /** + * Returns a newly-created {@link PrometheusMeterRegistry} instance with the specified + * {@link PrometheusRegistry} and {@link Clock}. + */ + public static PrometheusMeterRegistry newRegistry(PrometheusRegistry registry, Clock clock) { + return new PrometheusMeterRegistry( + PrometheusConfig.DEFAULT, requireNonNull(registry, "registry"), requireNonNull(clock, "clock")); + } + + private PrometheusMeterRegistries() {} +} diff --git a/prometheus1/src/main/java/com/linecorp/armeria/common/prometheus/package-info.java b/prometheus1/src/main/java/com/linecorp/armeria/common/prometheus/package-info.java new file mode 100644 index 00000000000..71c0716891b --- /dev/null +++ b/prometheus1/src/main/java/com/linecorp/armeria/common/prometheus/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +/** + * Prometheus version 1 metrics. + */ +@NonNullByDefault +package com.linecorp.armeria.common.prometheus; + +import com.linecorp.armeria.common.annotation.NonNullByDefault; diff --git a/prometheus1/src/main/java/com/linecorp/armeria/server/prometheus/PrometheusExpositionService.java b/prometheus1/src/main/java/com/linecorp/armeria/server/prometheus/PrometheusExpositionService.java new file mode 100644 index 00000000000..2406f548ee7 --- /dev/null +++ b/prometheus1/src/main/java/com/linecorp/armeria/server/prometheus/PrometheusExpositionService.java @@ -0,0 +1,121 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.linecorp.armeria.server.prometheus; + +import static java.util.Objects.requireNonNull; + +import java.util.Set; + +import com.google.common.collect.ImmutableSet; + +import com.linecorp.armeria.common.HttpData; +import com.linecorp.armeria.common.HttpHeaderNames; +import com.linecorp.armeria.common.HttpRequest; +import com.linecorp.armeria.common.HttpResponse; +import com.linecorp.armeria.common.HttpStatus; +import com.linecorp.armeria.common.MediaType; +import com.linecorp.armeria.common.annotation.UnstableApi; +import com.linecorp.armeria.server.AbstractHttpService; +import com.linecorp.armeria.server.ServiceRequestContext; +import com.linecorp.armeria.server.TransientHttpService; +import com.linecorp.armeria.server.TransientServiceOption; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufOutputStream; +import io.prometheus.metrics.expositionformats.ExpositionFormatWriter; +import io.prometheus.metrics.expositionformats.ExpositionFormats; +import io.prometheus.metrics.model.registry.PrometheusRegistry; + +/** + * Exposes Prometheus metrics in + * EXPOSITION FORMATS. + */ +@UnstableApi +public final class PrometheusExpositionService extends AbstractHttpService + implements TransientHttpService { + + /** + * Returns a new {@link PrometheusExpositionService} that exposes Prometheus metrics from + * {@link PrometheusRegistry#defaultRegistry}. + */ + public static PrometheusExpositionService of() { + return of(PrometheusRegistry.defaultRegistry); + } + + /** + * Returns a new {@link PrometheusExpositionService} that exposes Prometheus metrics from + * the specified {@link PrometheusRegistry}. + */ + public static PrometheusExpositionService of(PrometheusRegistry prometheusRegistry) { + return builder(prometheusRegistry).build(); + } + + /** + * Returns a new {@link PrometheusExpositionServiceBuilder} created with + * {@link PrometheusRegistry#defaultRegistry}. + */ + public static PrometheusExpositionServiceBuilder builder() { + return builder(PrometheusRegistry.defaultRegistry); + } + + /** + * Returns a new {@link PrometheusExpositionServiceBuilder} created with the specified + * {@link PrometheusRegistry}. + */ + public static PrometheusExpositionServiceBuilder builder(PrometheusRegistry prometheusRegistry) { + return new PrometheusExpositionServiceBuilder( + requireNonNull(prometheusRegistry, "prometheusRegistry")); + } + + private final PrometheusRegistry prometheusRegistry; + private final ExpositionFormats expositionFormats = ExpositionFormats.init(); + private final Set transientServiceOptions; + + PrometheusExpositionService(PrometheusRegistry prometheusRegistry, + Set transientServiceOptions) { + this.prometheusRegistry = prometheusRegistry; + this.transientServiceOptions = ImmutableSet.copyOf(transientServiceOptions); + } + + @Override + protected HttpResponse doGet(ServiceRequestContext ctx, HttpRequest req) throws Exception { + final String accept = req.headers().get(HttpHeaderNames.ACCEPT); + final ByteBuf buffer = ctx.alloc().buffer(); + final String format; + boolean success = false; + try (ByteBufOutputStream byteBufOutputStream = new ByteBufOutputStream(buffer)) { + final ExpositionFormatWriter writer = expositionFormats.findWriter(accept); + format = writer.getContentType(); + writer.write(byteBufOutputStream, prometheusRegistry.scrape()); + success = true; + } finally { + if (!success) { + buffer.release(); + } + } + return HttpResponse.of(HttpStatus.OK, MediaType.parse(format), HttpData.wrap(buffer)); + } + + @Override + protected HttpResponse doPost(ServiceRequestContext ctx, HttpRequest req) throws Exception { + return doGet(ctx, req); + } + + @Override + public Set transientServiceOptions() { + return transientServiceOptions; + } +} diff --git a/prometheus1/src/main/java/com/linecorp/armeria/server/prometheus/PrometheusExpositionServiceBuilder.java b/prometheus1/src/main/java/com/linecorp/armeria/server/prometheus/PrometheusExpositionServiceBuilder.java new file mode 100644 index 00000000000..0a4506c9f77 --- /dev/null +++ b/prometheus1/src/main/java/com/linecorp/armeria/server/prometheus/PrometheusExpositionServiceBuilder.java @@ -0,0 +1,64 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.linecorp.armeria.server.prometheus; + +import static java.util.Objects.requireNonNull; + +import com.linecorp.armeria.common.annotation.UnstableApi; +import com.linecorp.armeria.internal.server.TransientServiceOptionsBuilder; +import com.linecorp.armeria.server.TransientServiceBuilder; +import com.linecorp.armeria.server.TransientServiceOption; + +import io.prometheus.metrics.model.registry.PrometheusRegistry; + +/** + * Builds a {@link PrometheusExpositionService}. + */ +@UnstableApi +public final class PrometheusExpositionServiceBuilder implements TransientServiceBuilder { + + private final PrometheusRegistry prometheusRegistry; + + private final TransientServiceOptionsBuilder + transientServiceOptionsBuilder = new TransientServiceOptionsBuilder(); + + PrometheusExpositionServiceBuilder(PrometheusRegistry prometheusRegistry) { + this.prometheusRegistry = requireNonNull(prometheusRegistry, "prometheusRegistry"); + } + + @Override + public PrometheusExpositionServiceBuilder transientServiceOptions( + TransientServiceOption... transientServiceOptions) { + transientServiceOptionsBuilder.transientServiceOptions(transientServiceOptions); + return this; + } + + @Override + public PrometheusExpositionServiceBuilder transientServiceOptions( + Iterable transientServiceOptions) { + transientServiceOptionsBuilder.transientServiceOptions(transientServiceOptions); + return this; + } + + /** + * Returns a newly-created {@link PrometheusExpositionService} based on the properties + * of this builder. + */ + public PrometheusExpositionService build() { + return new PrometheusExpositionService(prometheusRegistry, + transientServiceOptionsBuilder.build()); + } +} diff --git a/prometheus1/src/main/java/com/linecorp/armeria/server/prometheus/package-info.java b/prometheus1/src/main/java/com/linecorp/armeria/server/prometheus/package-info.java new file mode 100644 index 00000000000..631bae0d214 --- /dev/null +++ b/prometheus1/src/main/java/com/linecorp/armeria/server/prometheus/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +/** + * Prometheus version 1 metrics. + */ +@NonNullByDefault +package com.linecorp.armeria.server.prometheus; + +import com.linecorp.armeria.common.annotation.NonNullByDefault; diff --git a/prometheus1/src/test/java/com/linecorp/armeria/server/prometheus/PrometheusExpositionServiceTest.java b/prometheus1/src/test/java/com/linecorp/armeria/server/prometheus/PrometheusExpositionServiceTest.java new file mode 100644 index 00000000000..f02fef62d03 --- /dev/null +++ b/prometheus1/src/test/java/com/linecorp/armeria/server/prometheus/PrometheusExpositionServiceTest.java @@ -0,0 +1,160 @@ +/* + * Copyright 2020 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.linecorp.armeria.server.prometheus; + +import static com.linecorp.armeria.common.metric.MoreMeters.measureAll; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.Logger; + +import com.linecorp.armeria.client.WebClient; +import com.linecorp.armeria.common.AggregatedHttpResponse; +import com.linecorp.armeria.common.HttpHeaderNames; +import com.linecorp.armeria.common.HttpRequest; +import com.linecorp.armeria.common.HttpResponse; +import com.linecorp.armeria.common.HttpStatus; +import com.linecorp.armeria.common.logging.LogWriter; +import com.linecorp.armeria.common.logging.RequestLog; +import com.linecorp.armeria.common.metric.MeterIdPrefixFunction; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; +import com.linecorp.armeria.server.ServerBuilder; +import com.linecorp.armeria.server.TransientServiceOption; +import com.linecorp.armeria.server.logging.LoggingService; +import com.linecorp.armeria.server.metric.MetricCollectingService; +import com.linecorp.armeria.testing.junit5.server.ServerExtension; + +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; +import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; +import io.prometheus.metrics.expositionformats.PrometheusTextFormatWriter; + +class PrometheusExpositionServiceTest { + + private static final PrometheusMeterRegistry registry = PrometheusMeterRegistries.defaultRegistry(); + + private static final Queue logs = new ConcurrentLinkedQueue<>(); + + private static final Logger logger = mock(Logger.class); + + @RegisterExtension + static final ServerExtension server = new ServerExtension() { + @Override + protected void configure(ServerBuilder sb) throws Exception { + sb.route().path("/api").defaultServiceName("Hello").build((ctx, req) -> HttpResponse.of(200)); + sb.meterRegistry(registry) + .decorator(MetricCollectingService.newDecorator(MeterIdPrefixFunction.ofDefault("foo"))) + .service("/disabled", PrometheusExpositionService.of(registry.getPrometheusRegistry())) + .service("/enabled", + PrometheusExpositionService.builder(registry.getPrometheusRegistry()) + .transientServiceOptions( + TransientServiceOption.allOf()) + .build()); + sb.accessLogWriter(logs::add, false); + sb.decorator(LoggingService.builder() + .logWriter(LogWriter.of(logger)) + .newDecorator()); + } + }; + + @Test + void prometheusRequests() throws InterruptedException { + when(logger.isDebugEnabled()).thenReturn(true); + final WebClient client = WebClient.of(server.httpUri()); + assertThat(client.get("/api").aggregate().join().status()).isSameAs(HttpStatus.OK); + await().until(() -> logs.size() == 1); + verify(logger, times(2)).isDebugEnabled(); + verify(logger, times(2)).debug(anyString()); + + final String exportedContent = client.get("/disabled").aggregate().join().contentUtf8(); + assertThat(exportedContent).contains("armeria_build_info{"); + // The last line must end with a line feed character. + // see https://prometheus.io/docs/instrumenting/exposition_formats/ + assertThat(exportedContent).endsWith("\n"); + + // prometheus requests are not collected. + await().untilAsserted(() -> { + final Map measurements = measureAll(registry); + assertThat(measurements) + .containsEntry("foo.active.requests#value{hostname.pattern=*,method=GET,service=Hello}", + 0.0) + .doesNotContainKey( + "foo.active.requests#value{hostname.pattern=*,method=GET,service=" + + "com.linecorp.armeria.server.prometheus.PrometheusExpositionService}"); + }); + // Access log is not written. + await().pollDelay(500, TimeUnit.MILLISECONDS).then().until(() -> logs.size() == 1); + verify(logger, times(2)).isDebugEnabled(); + verify(logger, times(2)).debug(anyString()); + + client.get("/enabled").aggregate().join(); + // prometheus requests are collected. + await().untilAsserted(() -> { + final Map measurements = measureAll(registry); + assertThat(measurements) + .containsEntry("foo.active.requests#value{hostname.pattern=*,method=GET,service=Hello}", + 0.0) + .containsEntry("foo.active.requests#value{hostname.pattern=*,method=GET,service=" + + "com.linecorp.armeria.server.prometheus.PrometheusExpositionService}", + 0.0); + }); + // Access log is written. + await().pollDelay(500, TimeUnit.MILLISECONDS).until(() -> logs.size() == 2); + verify(logger, times(4)).isDebugEnabled(); + verify(logger, times(4)).debug(anyString()); + } + + @Nested + class FormatTest { + @Test + void prometheusRequestsPrometheusFormat() throws InterruptedException { + final WebClient client = WebClient.of(server.httpUri()); + final HttpRequest request = HttpRequest.builder() + .get("/enabled") + .header(HttpHeaderNames.ACCEPT, + PrometheusTextFormatWriter.CONTENT_TYPE) + .build(); + final AggregatedHttpResponse response = client.execute(request).aggregate().join(); + assertThat(response.headers().get(HttpHeaderNames.CONTENT_TYPE)) + .isEqualTo(PrometheusTextFormatWriter.CONTENT_TYPE); + } + + @Test + void prometheusRequestsOpenMetricsFormat() throws InterruptedException { + final WebClient client = WebClient.of(server.httpUri()); + final HttpRequest request = HttpRequest.builder() + .get("/enabled") + .header(HttpHeaderNames.ACCEPT, + OpenMetricsTextFormatWriter.CONTENT_TYPE) + .build(); + final AggregatedHttpResponse response = client.execute(request).aggregate().join(); + assertThat(response.headers().get(HttpHeaderNames.CONTENT_TYPE)) + .isEqualTo(OpenMetricsTextFormatWriter.CONTENT_TYPE); + } + } +} diff --git a/settings.gradle b/settings.gradle index 41d6a2ecc73..b57bf7f875d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -112,6 +112,7 @@ includeWithFlags ':logback14', 'java11', 'publish', 'r project(':logback14').projectDir = file('logback/logback14') includeWithFlags ':native-image-config' includeWithFlags ':oauth2', 'java', 'publish', 'relocate', 'native' +includeWithFlags ':prometheus1', 'java', 'publish', 'relocate', 'native' includeWithFlags ':protobuf', 'java', 'publish', 'relocate', 'native' includeWithFlags ':reactor3', 'java', 'publish', 'relocate', 'native' includeWithFlags ':resilience4j2', 'java17', 'publish', 'relocate', 'native' @@ -200,7 +201,6 @@ includeWithFlags ':it:jackson-provider', 'java', 'relocate includeWithFlags ':it:kotlin', 'java', 'relocate', 'kotlin' includeWithFlags ':it:kubernetes-chaos-tests', 'java', 'relocate' includeWithFlags ':it:logback1.4', 'java11', 'relocate' -includeWithFlags ':it:micrometer1.3', 'java', 'relocate' includeWithFlags ':it:multipart', 'java17', 'relocate' includeWithFlags ':it:nio', 'java', 'relocate' includeWithFlags ':it:okhttp', 'java', 'relocate' diff --git a/spring/boot2-actuator-autoconfigure/build.gradle b/spring/boot2-actuator-autoconfigure/build.gradle index 6b90f1ebaab..e822159d162 100644 --- a/spring/boot2-actuator-autoconfigure/build.gradle +++ b/spring/boot2-actuator-autoconfigure/build.gradle @@ -3,8 +3,8 @@ dependencies { api libs.javax.inject compileOnly libs.javax.validation - // TODO(anuraaga): Consider removing these since this module does not have related functionality. - optionalApi libs.micrometer.prometheus + optionalApi project(':prometheus1') + optionalApi libs.micrometer.prometheus.legacy optionalApi libs.dropwizard.metrics.json implementation libs.spring.boot2.autoconfigure diff --git a/spring/boot2-autoconfigure/build.gradle b/spring/boot2-autoconfigure/build.gradle index a0153052be9..60167f63333 100644 --- a/spring/boot2-autoconfigure/build.gradle +++ b/spring/boot2-autoconfigure/build.gradle @@ -7,8 +7,8 @@ dependencies { } implementation project(':logback') - // TODO(anuraaga): Consider removing these since this module does not have related functionality. - optionalApi libs.micrometer.prometheus + optionalApi project(':prometheus1') + optionalApi libs.micrometer.prometheus.legacy optionalApi libs.dropwizard.metrics.json api libs.javax.inject diff --git a/spring/boot2-webflux-autoconfigure/build.gradle b/spring/boot2-webflux-autoconfigure/build.gradle index 6e51f6be215..a3f58820359 100644 --- a/spring/boot2-webflux-autoconfigure/build.gradle +++ b/spring/boot2-webflux-autoconfigure/build.gradle @@ -15,7 +15,8 @@ dependencies { } implementation project(':logback') - optionalApi libs.micrometer.prometheus + optionalApi project(':prometheus1') + optionalApi libs.micrometer.prometheus.legacy optionalApi libs.dropwizard.metrics.json api libs.javax.inject compileOnly libs.javax.validation @@ -57,6 +58,7 @@ task copyBoot3Sources(type: Copy) { include '**/LocalArmeriaPort.java' include '**/LocalArmeriaPorts.java' include '**/PrometheusSupport.java' + include '**/PrometheusLegacySupport.java' include '**/SpringDependencyInjector.java' include '**/Ssl.java' } diff --git a/spring/boot3-actuator-autoconfigure/build.gradle b/spring/boot3-actuator-autoconfigure/build.gradle index a4c084d6028..a0cd3ab60f7 100644 --- a/spring/boot3-actuator-autoconfigure/build.gradle +++ b/spring/boot3-actuator-autoconfigure/build.gradle @@ -3,8 +3,8 @@ dependencies { api libs.jakarta.inject compileOnly libs.jakarta.validation - // TODO(anuraaga): Consider removing these since this module does not have related functionality. - optionalApi libs.micrometer.prometheus + optionalApi project(':prometheus1') + optionalApi libs.micrometer.prometheus.legacy optionalApi libs.dropwizard.metrics.json implementation libs.spring.boot3.autoconfigure diff --git a/spring/boot3-actuator-autoconfigure/src/main/java/com/linecorp/armeria/spring/actuate/WebOperationService.java b/spring/boot3-actuator-autoconfigure/src/main/java/com/linecorp/armeria/spring/actuate/WebOperationService.java index 78cc1b52731..4fddc4b7fec 100644 --- a/spring/boot3-actuator-autoconfigure/src/main/java/com/linecorp/armeria/spring/actuate/WebOperationService.java +++ b/spring/boot3-actuator-autoconfigure/src/main/java/com/linecorp/armeria/spring/actuate/WebOperationService.java @@ -251,6 +251,10 @@ static HttpResponse handleResult0(HttpCodeStatusMapper statusMapper, return HttpResponse.of(status, contentType, (CharSequence) body); } + if (body instanceof byte[]) { + return HttpResponse.of(status, contentType, (byte[]) body); + } + if (body instanceof Resource) { final Resource resource = (Resource) body; final String filename = resource.getFilename(); diff --git a/spring/boot3-autoconfigure/build.gradle b/spring/boot3-autoconfigure/build.gradle index 1fe31ad461c..89bde8a6fd4 100644 --- a/spring/boot3-autoconfigure/build.gradle +++ b/spring/boot3-autoconfigure/build.gradle @@ -3,8 +3,8 @@ dependencies { compileOnly project(':thrift0.18') implementation project(':logback') - // TODO(anuraaga): Consider removing these since this module does not have related functionality. - optionalApi libs.micrometer.prometheus + optionalApi project(':prometheus1') + optionalApi libs.micrometer.prometheus.legacy optionalApi libs.dropwizard.metrics.json api libs.jakarta.inject diff --git a/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/AbstractArmeriaAutoConfiguration.java b/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/AbstractArmeriaAutoConfiguration.java index 5d31767cb1b..c576410fc5b 100644 --- a/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/AbstractArmeriaAutoConfiguration.java +++ b/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/AbstractArmeriaAutoConfiguration.java @@ -45,7 +45,7 @@ import com.linecorp.armeria.server.docs.DocService; import com.linecorp.armeria.server.healthcheck.HealthCheckService; import com.linecorp.armeria.server.healthcheck.HealthChecker; -import com.linecorp.armeria.server.metric.PrometheusExpositionService; +import com.linecorp.armeria.server.prometheus.PrometheusExpositionService; import com.linecorp.armeria.spring.ArmeriaSettings.Port; import io.micrometer.core.instrument.MeterRegistry; diff --git a/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/ArmeriaSettings.java b/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/ArmeriaSettings.java index d3226fab384..1fbb60f3bb0 100644 --- a/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/ArmeriaSettings.java +++ b/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/ArmeriaSettings.java @@ -37,10 +37,9 @@ import com.linecorp.armeria.server.docs.DocService; import com.linecorp.armeria.server.healthcheck.HealthCheckService; import com.linecorp.armeria.server.metric.MetricCollectingService; -import com.linecorp.armeria.server.metric.PrometheusExpositionService; +import com.linecorp.armeria.server.prometheus.PrometheusExpositionService; import io.micrometer.core.instrument.dropwizard.DropwizardMeterRegistry; -import io.micrometer.prometheus.PrometheusMeterRegistry; import io.netty.channel.EventLoopGroup; /** @@ -691,7 +690,7 @@ public void setDocsPath(@Nullable String docsPath) { /** * Returns the path of the metrics exposition service. {@link PrometheusExpositionService} will be used if - * {@link PrometheusMeterRegistry} is available. Otherwise, Dropwizard's {@link MetricsModule} will be used + * {@code armeria-prometheus1} module is added. Otherwise, Dropwizard's {@link MetricsModule} will be used * if {@link DropwizardMeterRegistry} is available. */ @Nullable @@ -701,7 +700,7 @@ public String getMetricsPath() { /** * Sets the path of the metrics exposition service. {@link PrometheusExpositionService} will be used if - * {@link PrometheusMeterRegistry} is available. Otherwise, Dropwizard's {@link MetricsModule} will be used + * {@code armeria-prometheus1} module is added. Otherwise, Dropwizard's {@link MetricsModule} will be used * if {@link DropwizardMeterRegistry} is available. */ public void setMetricsPath(@Nullable String metricsPath) { diff --git a/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/InternalServiceId.java b/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/InternalServiceId.java index dfc4a077a3a..dd48baece99 100644 --- a/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/InternalServiceId.java +++ b/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/InternalServiceId.java @@ -23,7 +23,7 @@ import com.linecorp.armeria.common.annotation.UnstableApi; import com.linecorp.armeria.server.HttpService; import com.linecorp.armeria.server.docs.DocService; -import com.linecorp.armeria.server.metric.PrometheusExpositionService; +import com.linecorp.armeria.server.prometheus.PrometheusExpositionService; /** * Defines the IDs of internal {@code HttpService}s that should not be exposed to the external network. diff --git a/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/InternalServices.java b/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/InternalServices.java index 23d4c337b15..ee5134be816 100644 --- a/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/InternalServices.java +++ b/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/InternalServices.java @@ -95,15 +95,27 @@ public static InternalServices of( HttpService expositionService = null; if (settings.isEnableMetrics() && !Strings.isNullOrEmpty(settings.getMetricsPath())) { - final String prometheusMeterRegistryClassName = "io.micrometer.prometheus.PrometheusMeterRegistry"; + final String prometheusMeterRegistryClassName = + "io.micrometer.prometheusmetrics.PrometheusMeterRegistry"; final boolean hasPrometheus = hasAllClasses( prometheusMeterRegistryClassName, - "io.prometheus.client.CollectorRegistry"); + "io.prometheus.metrics.model.registry.PrometheusRegistry", + "com.linecorp.armeria.server.prometheus.PrometheusExpositionService"); if (hasPrometheus) { expositionService = PrometheusSupport.newExpositionService(meterRegistry); } + final String legacyPrometheusMeterRegistryClassName = + "io.micrometer.prometheus.PrometheusMeterRegistry"; + final boolean hasLegacyPrometheus = hasAllClasses( + legacyPrometheusMeterRegistryClassName, + "io.prometheus.client.CollectorRegistry"); + + if (hasLegacyPrometheus) { + expositionService = PrometheusLegacySupport.newExpositionService(meterRegistry); + } + final String dropwizardMeterRegistryClassName = "io.micrometer.core.instrument.dropwizard.DropwizardMeterRegistry"; if (expositionService == null) { @@ -117,7 +129,7 @@ public static InternalServices of( } if (expositionService == null) { logger.debug("Failed to expose metrics to '{}' with {} (expected: either {} or {})", - settings.getMetricsPath(), meterRegistry, prometheusMeterRegistryClassName, + settings.getMetricsPath(), meterRegistry, legacyPrometheusMeterRegistryClassName, dropwizardMeterRegistryClassName); } } diff --git a/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/PrometheusLegacySupport.java b/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/PrometheusLegacySupport.java new file mode 100644 index 00000000000..0f7b7187dc4 --- /dev/null +++ b/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/PrometheusLegacySupport.java @@ -0,0 +1,60 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.linecorp.armeria.spring; + +import static com.linecorp.armeria.spring.PrometheusSupport.find; + +import java.util.Optional; +import java.util.Set; + +import com.linecorp.armeria.common.annotation.Nullable; +import com.linecorp.armeria.server.metric.PrometheusExpositionService; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.composite.CompositeMeterRegistry; +import io.micrometer.prometheus.PrometheusMeterRegistry; +import io.prometheus.client.CollectorRegistry; + +final class PrometheusLegacySupport { + + @Nullable + static PrometheusExpositionService newExpositionService(MeterRegistry meterRegistry) { + for (;;) { + if (meterRegistry instanceof PrometheusMeterRegistry) { + final CollectorRegistry prometheusRegistry = + ((PrometheusMeterRegistry) meterRegistry).getPrometheusRegistry(); + return PrometheusExpositionService.of(prometheusRegistry); + } + + if (meterRegistry instanceof CompositeMeterRegistry) { + final Set childRegistries = + ((CompositeMeterRegistry) meterRegistry).getRegistries(); + final Optional opt = + find(PrometheusMeterRegistry.class, childRegistries); + if (opt.isPresent()) { + meterRegistry = opt.get(); + continue; + } + + return null; + } + + return null; + } + } + + private PrometheusLegacySupport() {} +} diff --git a/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/PrometheusSupport.java b/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/PrometheusSupport.java index cc4c53de7e4..463627d5dcc 100644 --- a/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/PrometheusSupport.java +++ b/spring/boot3-autoconfigure/src/main/java/com/linecorp/armeria/spring/PrometheusSupport.java @@ -19,12 +19,12 @@ import java.util.Set; import com.linecorp.armeria.common.annotation.Nullable; -import com.linecorp.armeria.server.metric.PrometheusExpositionService; +import com.linecorp.armeria.server.prometheus.PrometheusExpositionService; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.composite.CompositeMeterRegistry; -import io.micrometer.prometheus.PrometheusMeterRegistry; -import io.prometheus.client.CollectorRegistry; +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; +import io.prometheus.metrics.model.registry.PrometheusRegistry; final class PrometheusSupport { @@ -32,7 +32,7 @@ final class PrometheusSupport { static PrometheusExpositionService newExpositionService(MeterRegistry meterRegistry) { for (;;) { if (meterRegistry instanceof PrometheusMeterRegistry) { - final CollectorRegistry prometheusRegistry = + final PrometheusRegistry prometheusRegistry = ((PrometheusMeterRegistry) meterRegistry).getPrometheusRegistry(); return PrometheusExpositionService.of(prometheusRegistry); } @@ -40,23 +40,27 @@ static PrometheusExpositionService newExpositionService(MeterRegistry meterRegis if (meterRegistry instanceof CompositeMeterRegistry) { final Set childRegistries = ((CompositeMeterRegistry) meterRegistry).getRegistries(); - final Optional opt = - childRegistries.stream() - .filter(PrometheusMeterRegistry.class::isInstance) - .map(PrometheusMeterRegistry.class::cast) - .findAny(); - if (!opt.isPresent()) { - return null; + final Optional opt = + find(PrometheusMeterRegistry.class, childRegistries); + if (opt.isPresent()) { + meterRegistry = opt.get(); + continue; } - meterRegistry = opt.get(); - continue; + return null; } return null; } } + static Optional find(Class type, Set childRegistries) { + return childRegistries.stream() + .filter(type::isInstance) + .map(type::cast) + .findAny(); + } + private PrometheusSupport() {} } diff --git a/spring/boot3-webflux-autoconfigure/build.gradle b/spring/boot3-webflux-autoconfigure/build.gradle index 8fccde7e96e..a9ba5976192 100644 --- a/spring/boot3-webflux-autoconfigure/build.gradle +++ b/spring/boot3-webflux-autoconfigure/build.gradle @@ -22,7 +22,8 @@ dependencies { implementation libs.jakarta.websocket implementation libs.jakarta.websocket.client - optionalApi libs.micrometer.prometheus + optionalApi project(':prometheus1') + optionalApi libs.micrometer.prometheus.legacy optionalApi libs.dropwizard.metrics.json annotationProcessor libs.spring.boot3.configuration.processor @@ -62,6 +63,7 @@ task generateSources(type: Copy) { include '**/LocalArmeriaPort.java' include '**/LocalArmeriaPorts.java' include '**/PrometheusSupport.java' + include '**/PrometheusLegacySupport.java' include '**/SpringDependencyInjector.java' include '**/Ssl.java' } diff --git a/spring/boot3-webflux-autoconfigure/src/main/java/com/linecorp/armeria/spring/web/reactive/ArmeriaReactiveWebServerFactoryAutoConfiguration.java b/spring/boot3-webflux-autoconfigure/src/main/java/com/linecorp/armeria/spring/web/reactive/ArmeriaReactiveWebServerFactoryAutoConfiguration.java index 87d046037c1..4577230f662 100644 --- a/spring/boot3-webflux-autoconfigure/src/main/java/com/linecorp/armeria/spring/web/reactive/ArmeriaReactiveWebServerFactoryAutoConfiguration.java +++ b/spring/boot3-webflux-autoconfigure/src/main/java/com/linecorp/armeria/spring/web/reactive/ArmeriaReactiveWebServerFactoryAutoConfiguration.java @@ -42,7 +42,7 @@ import com.linecorp.armeria.server.docs.DocService; import com.linecorp.armeria.server.healthcheck.HealthCheckService; import com.linecorp.armeria.server.healthcheck.HealthChecker; -import com.linecorp.armeria.server.metric.PrometheusExpositionService; +import com.linecorp.armeria.server.prometheus.PrometheusExpositionService; import com.linecorp.armeria.spring.ArmeriaSettings; import com.linecorp.armeria.spring.DocServiceConfigurator; import com.linecorp.armeria.spring.HealthCheckServiceConfigurator; diff --git a/spring/boot3-webflux-autoconfigure/src/test/java/com/linecorp/armeria/spring/web/reactive/ArmeriaReactiveWebServerFactoryTest.java b/spring/boot3-webflux-autoconfigure/src/test/java/com/linecorp/armeria/spring/web/reactive/ArmeriaReactiveWebServerFactoryTest.java index ad930cec7fa..58f33f8fb14 100644 --- a/spring/boot3-webflux-autoconfigure/src/test/java/com/linecorp/armeria/spring/web/reactive/ArmeriaReactiveWebServerFactoryTest.java +++ b/spring/boot3-webflux-autoconfigure/src/test/java/com/linecorp/armeria/spring/web/reactive/ArmeriaReactiveWebServerFactoryTest.java @@ -66,7 +66,7 @@ import com.linecorp.armeria.common.HttpResponse; import com.linecorp.armeria.common.MediaType; import com.linecorp.armeria.common.RequestHeaders; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import com.linecorp.armeria.internal.common.util.PortUtil; import com.linecorp.armeria.internal.testing.MockAddressResolverGroup; import com.linecorp.armeria.server.HttpStatusException; diff --git a/thrift/thrift0.12/build.gradle b/thrift/thrift0.12/build.gradle index e1c73dfe872..ca04e20422c 100644 --- a/thrift/thrift0.12/build.gradle +++ b/thrift/thrift0.12/build.gradle @@ -6,6 +6,8 @@ // See also: ../thrift0.9/build.gradle dependencies { + testImplementation project(':prometheus1') + // Use the classes compiled with the latest libthrift, // but use an older version for test classes. testImplementation(project(':thrift0.13')) { @@ -22,8 +24,7 @@ dependencies { // Dropwizard and Prometheus, for testing metrics integration testImplementation libs.dropwizard.metrics.core - testImplementation libs.micrometer.prometheus - testImplementation libs.prometheus + testImplementation libs.prometheus.metrics.exposition.formats // micrometer tracing testImplementation (libs.micrometer.tracing.integration.test) { diff --git a/thrift/thrift0.13/build.gradle b/thrift/thrift0.13/build.gradle index 6a1a26858b4..5eb0d477e92 100644 --- a/thrift/thrift0.13/build.gradle +++ b/thrift/thrift0.13/build.gradle @@ -4,6 +4,8 @@ dependencies { api libs.javax.annotation + testImplementation project(':prometheus1') + // thrift api depends on httpclient4 testImplementation libs.apache.httpclient4 @@ -13,8 +15,7 @@ dependencies { // Dropwizard and Prometheus, for testing metrics integration testImplementation libs.dropwizard.metrics.core - testImplementation libs.micrometer.prometheus - testImplementation libs.prometheus + testImplementation libs.prometheus.metrics.exposition.formats // micrometer tracing testImplementation (libs.micrometer.tracing.integration.test) { diff --git a/thrift/thrift0.13/src/test/java/com/linecorp/armeria/it/metric/PrometheusMetricsIntegrationTest.java b/thrift/thrift0.13/src/test/java/com/linecorp/armeria/it/metric/PrometheusMetricsIntegrationTest.java index 5e3cc5ba597..38ec2133af1 100644 --- a/thrift/thrift0.13/src/test/java/com/linecorp/armeria/it/metric/PrometheusMetricsIntegrationTest.java +++ b/thrift/thrift0.13/src/test/java/com/linecorp/armeria/it/metric/PrometheusMetricsIntegrationTest.java @@ -49,23 +49,21 @@ import com.linecorp.armeria.common.logging.RequestOnlyLog; import com.linecorp.armeria.common.metric.MeterIdPrefix; import com.linecorp.armeria.common.metric.MeterIdPrefixFunction; -import com.linecorp.armeria.common.metric.PrometheusMeterRegistries; +import com.linecorp.armeria.common.prometheus.PrometheusMeterRegistries; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.metric.MetricCollectingService; -import com.linecorp.armeria.server.metric.PrometheusExpositionService; +import com.linecorp.armeria.server.prometheus.PrometheusExpositionService; import com.linecorp.armeria.server.thrift.THttpService; import com.linecorp.armeria.testing.junit4.server.ServerRule; import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.prometheus.PrometheusMeterRegistry; -import io.prometheus.client.CollectorRegistry; +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; import testing.thrift.main.HelloService.Iface; public class PrometheusMetricsIntegrationTest { private static final Logger logger = LoggerFactory.getLogger(PrometheusMetricsIntegrationTest.class); private static final PrometheusMeterRegistry registry = PrometheusMeterRegistries.newRegistry(); - private static final CollectorRegistry prometheusRegistry = registry.getPrometheusRegistry(); @ClassRule public static final ServerRule server = new ServerRule() { @@ -86,7 +84,8 @@ protected void configure(ServerBuilder sb) throws Exception { sb.service("/bar", helloService.decorate( MetricCollectingService.newDecorator(new MeterIdPrefixFunctionImpl("server", "Bar")))); - sb.service("/internal/prometheus/metrics", PrometheusExpositionService.of(prometheusRegistry)); + sb.service("/internal/prometheus/metrics", + PrometheusExpositionService.of(registry.getPrometheusRegistry())); } }; @@ -151,60 +150,60 @@ private static void hello_first_endpoint() throws Exception { assertThat(content).containsPattern( multilinePattern("server_request_duration_seconds_count", "{handler=\"Foo\",hostname_pattern=\"*\",http_status=\"200\",", - "method=\"hello\",service=\"" + Iface.class.getName() + "\",} 7.0")); + "method=\"hello\",service=\"" + Iface.class.getName() + "\"} 7")); assertThat(content).containsPattern( multilinePattern("server_request_length_count", "{handler=\"Foo\",hostname_pattern=\"*\",http_status=\"200\",", - "method=\"hello\",service=\"" + Iface.class.getName() + "\",} 7.0")); + "method=\"hello\",service=\"" + Iface.class.getName() + "\"} 7")); assertThat(content).containsPattern( multilinePattern("server_response_length_count", "{handler=\"Foo\",hostname_pattern=\"*\",http_status=\"200\",", - "method=\"hello\",service=\"" + Iface.class.getName() + "\",} 7.0")); + "method=\"hello\",service=\"" + Iface.class.getName() + "\"} 7")); // Client entry count check assertThat(content).containsPattern( multilinePattern("client_request_duration_seconds_count", "{handler=\"Foo\",http_status=\"200\",method=\"hello\",service=\"" + - Iface.class.getName() + "\",} 7.0")); + Iface.class.getName() + "\"} 7")); assertThat(content).containsPattern( multilinePattern("client_request_length_count", "{handler=\"Foo\",http_status=\"200\",method=\"hello\",service=\"" + - Iface.class.getName() + "\",} 7.0")); + Iface.class.getName() + "\"} 7")); assertThat(content).containsPattern( multilinePattern("client_response_length_count", "{handler=\"Foo\",http_status=\"200\",method=\"hello\",service=\"" + - Iface.class.getName() + "\",} 7.0")); + Iface.class.getName() + "\"} 7")); // Failure count assertThat(content).containsPattern( multilinePattern("server_requests_total", "{handler=\"Foo\",hostname_pattern=\"*\",http_status=\"200\",", "method=\"hello\",result=\"failure\",", - "service=\"" + Iface.class.getName() + "\",} 3.0")); + "service=\"" + Iface.class.getName() + "\"} 3.0")); assertThat(content).containsPattern( multilinePattern("client_requests_total", "{handler=\"Foo\",http_status=\"200\",method=\"hello\"," + - "result=\"failure\",service=\"" + Iface.class.getName() + "\",} 3.0")); + "result=\"failure\",service=\"" + Iface.class.getName() + "\"} 3.0")); // Success count assertThat(content).containsPattern( multilinePattern("server_requests_total", "{handler=\"Foo\",hostname_pattern=\"*\",http_status=\"200\",", "method=\"hello\",result=\"success\",", - "service=\"" + Iface.class.getName() + "\",} 4.0")); + "service=\"" + Iface.class.getName() + "\"} 4.0")); assertThat(content).containsPattern( multilinePattern("client_requests_total", "{handler=\"Foo\",http_status=\"200\",method=\"hello\"," + - "result=\"success\",service=\"" + Iface.class.getName() + "\",} 4.0")); + "result=\"success\",service=\"" + Iface.class.getName() + "\"} 4.0")); // Active Requests 0 assertThat(content).containsPattern( multilinePattern("server_active_requests", "{handler=\"Foo\",hostname_pattern=\"*\",", - "method=\"hello\",service=\"" + Iface.class.getName() + "\",} 0.0")); + "method=\"hello\",service=\"" + Iface.class.getName() + "\"} 0.0")); assertThat(content).containsPattern( multilinePattern("client_active_requests", "{handler=\"Foo\",method=\"hello\",service=\"" + Iface.class.getName() + - "\",} 0.0")); + "\"} 0.0")); } private static void hello_second_endpoint() throws Exception { @@ -245,52 +244,52 @@ private static void hello_second_endpoint() throws Exception { multilinePattern("server_request_duration_seconds_count", "{handler=\"Bar\",hostname_pattern=\"*\",http_status=\"200\",", "method=\"hello\",service=\"" + Iface.class.getName() + - "\",} 1.0")); + "\"} 1")); assertThat(content).containsPattern( multilinePattern("server_request_length_count", "{handler=\"Bar\",hostname_pattern=\"*\",http_status=\"200\",", "method=\"hello\",service=\"" + Iface.class.getName() + - "\",} 1.0")); + "\"} 1")); assertThat(content).containsPattern( multilinePattern("server_response_length_count", "{handler=\"Bar\",hostname_pattern=\"*\",http_status=\"200\",", "method=\"hello\",service=\"" + Iface.class.getName() + - "\",} 1.0")); + "\"} 1")); // Client entry count check assertThat(content).containsPattern( multilinePattern("client_request_duration_seconds_count", "{handler=\"Bar\",http_status=\"200\",method=\"hello\",service=\"" + - Iface.class.getName() + "\",} 1.0")); + Iface.class.getName() + "\"} 1")); assertThat(content).containsPattern( multilinePattern("client_request_length_count", "{handler=\"Bar\",http_status=\"200\",method=\"hello\",service=\"" + - Iface.class.getName() + "\",} 1.0")); + Iface.class.getName() + "\"} 1")); assertThat(content).containsPattern( multilinePattern("client_response_length_count", "{handler=\"Bar\",http_status=\"200\",method=\"hello\",service=\"" + - Iface.class.getName() + "\",} 1.0")); + Iface.class.getName() + "\"} 1")); // Success count assertThat(content).containsPattern( multilinePattern("server_requests_total", "{handler=\"Bar\",hostname_pattern=\"*\",http_status=\"200\",", "method=\"hello\",result=\"success\",", - "service=\"" + Iface.class.getName() + "\",} 1.0")); + "service=\"" + Iface.class.getName() + "\"} 1.0")); assertThat(content).containsPattern( multilinePattern("client_requests_total", "{handler=\"Bar\",http_status=\"200\",method=\"hello\"," + - "result=\"success\",service=\"" + Iface.class.getName() + "\",} 1.0")); + "result=\"success\",service=\"" + Iface.class.getName() + "\"} 1.0")); // Active Requests 0 assertThat(content).containsPattern( multilinePattern("server_active_requests", "{handler=\"Bar\",hostname_pattern=\"*\",", "method=\"hello\",service=\"" + Iface.class.getName() + - "\",} 0.0")); + "\"} 0.0")); assertThat(content).containsPattern( multilinePattern("client_active_requests", "{handler=\"Bar\",method=\"hello\",service=\"" + Iface.class.getName() + - "\",} 0.0")); + "\"} 0.0")); } private static void makeRequest1(String name) throws TException { diff --git a/thrift/thrift0.14/build.gradle b/thrift/thrift0.14/build.gradle index 0ac1042a30d..a93c09493cd 100644 --- a/thrift/thrift0.14/build.gradle +++ b/thrift/thrift0.14/build.gradle @@ -5,6 +5,8 @@ dependencies { api libs.javax.annotation + testImplementation project(':prometheus1') + // thrift api depends on httpclient4 testImplementation libs.apache.httpclient4 @@ -14,8 +16,7 @@ dependencies { // Dropwizard and Prometheus, for testing metrics integration testImplementation libs.dropwizard.metrics.core - testImplementation libs.micrometer.prometheus - testImplementation libs.prometheus + testImplementation libs.prometheus.metrics.exposition.formats // micrometer tracing testImplementation (libs.micrometer.tracing.integration.test) { diff --git a/thrift/thrift0.15/build.gradle b/thrift/thrift0.15/build.gradle index 370fb7e4022..5b4dde0ede3 100644 --- a/thrift/thrift0.15/build.gradle +++ b/thrift/thrift0.15/build.gradle @@ -5,6 +5,8 @@ dependencies { api libs.javax.annotation + testImplementation project(':prometheus1') + // thrift api depends on httpclient4 testImplementation libs.apache.httpclient4 @@ -14,8 +16,7 @@ dependencies { // Dropwizard and Prometheus, for testing metrics integration testImplementation libs.dropwizard.metrics.core - testImplementation libs.micrometer.prometheus - testImplementation libs.prometheus + testImplementation libs.prometheus.metrics.exposition.formats // micrometer tracing testImplementation (libs.micrometer.tracing.integration.test) { diff --git a/thrift/thrift0.16/build.gradle b/thrift/thrift0.16/build.gradle index 0196ff228e8..9d73fcfa1f2 100644 --- a/thrift/thrift0.16/build.gradle +++ b/thrift/thrift0.16/build.gradle @@ -6,6 +6,8 @@ dependencies { api libs.javax.annotation + testImplementation project(':prometheus1') + // thrift api depends on httpclient4 testImplementation libs.apache.httpclient4 @@ -15,8 +17,7 @@ dependencies { // Dropwizard and Prometheus, for testing metrics integration testImplementation libs.dropwizard.metrics.core - testImplementation libs.micrometer.prometheus - testImplementation libs.prometheus + testImplementation libs.prometheus.metrics.exposition.formats // micrometer tracing testImplementation (libs.micrometer.tracing.integration.test) { diff --git a/thrift/thrift0.17/build.gradle b/thrift/thrift0.17/build.gradle index e9d866d30dd..9d0c8053322 100644 --- a/thrift/thrift0.17/build.gradle +++ b/thrift/thrift0.17/build.gradle @@ -6,6 +6,8 @@ dependencies { api libs.javax.annotation + testImplementation project(':prometheus1') + // thrift api depends on httpclient4 testImplementation libs.apache.httpclient4 @@ -15,8 +17,7 @@ dependencies { // Dropwizard and Prometheus, for testing metrics integration testImplementation libs.dropwizard.metrics.core - testImplementation libs.micrometer.prometheus - testImplementation libs.prometheus + testImplementation libs.prometheus.metrics.exposition.formats // micrometer tracing testImplementation (libs.micrometer.tracing.integration.test) { diff --git a/thrift/thrift0.18/build.gradle b/thrift/thrift0.18/build.gradle index 4ef57eba0c1..89bf208388e 100644 --- a/thrift/thrift0.18/build.gradle +++ b/thrift/thrift0.18/build.gradle @@ -6,6 +6,8 @@ dependencies { api libs.javax.annotation + testImplementation project(':prometheus1') + // thrift api depends on httpclient4 testImplementation libs.apache.httpclient4 @@ -15,8 +17,7 @@ dependencies { // Dropwizard and Prometheus, for testing metrics integration testImplementation libs.dropwizard.metrics.core - testImplementation libs.micrometer.prometheus - testImplementation libs.prometheus + testImplementation libs.prometheus.metrics.exposition.formats // micrometer tracing testImplementation (libs.micrometer.tracing.integration.test) { diff --git a/thrift/thrift0.9/build.gradle b/thrift/thrift0.9/build.gradle index fe239da1530..3af131edb65 100644 --- a/thrift/thrift0.9/build.gradle +++ b/thrift/thrift0.9/build.gradle @@ -12,6 +12,8 @@ dependencies { api libs.javax.annotation + testImplementation project(':prometheus1') + // thrift api depends on httpclient4 testImplementation libs.apache.httpclient4 @@ -21,8 +23,7 @@ dependencies { // Dropwizard and Prometheus, for testing metrics integration testImplementation libs.dropwizard.metrics.core - testImplementation libs.micrometer.prometheus - testImplementation libs.prometheus + testImplementation libs.prometheus.metrics.exposition.formats // micrometer tracing testImplementation (libs.micrometer.tracing.integration.test) {