diff --git a/common/concurrency/limits/src/main/java/io/helidon/common/concurrency/limits/AimdLimitImpl.java b/common/concurrency/limits/src/main/java/io/helidon/common/concurrency/limits/AimdLimitImpl.java index 6fedb0684d0..fcd1ac3a28d 100644 --- a/common/concurrency/limits/src/main/java/io/helidon/common/concurrency/limits/AimdLimitImpl.java +++ b/common/concurrency/limits/src/main/java/io/helidon/common/concurrency/limits/AimdLimitImpl.java @@ -17,6 +17,7 @@ package io.helidon.common.concurrency.limits; import java.io.Serial; +import java.util.List; import java.util.Optional; import java.util.concurrent.Callable; import java.util.concurrent.Semaphore; @@ -31,6 +32,7 @@ import io.helidon.metrics.api.MeterRegistry; import io.helidon.metrics.api.Metrics; import io.helidon.metrics.api.MetricsFactory; +import io.helidon.metrics.api.Tag; import io.helidon.metrics.api.Timer; import static io.helidon.metrics.api.Meter.Scope.VENDOR; @@ -192,39 +194,61 @@ void initMetrics(String socketName, AimdLimitConfig config) { if (config.enableMetrics()) { MetricsFactory metricsFactory = MetricsFactory.getInstance(); MeterRegistry meterRegistry = Metrics.globalRegistry(); - String namePrefix = (socketName.startsWith("@") ? socketName.substring(1) : socketName) - + "_" + config.name(); + + // define tag if socket is not the default + Tag socketNameTag = null; + if (!socketName.equals("@default")) { + socketNameTag = Tag.create("socketName", socketName); + } // actual value of limit at this time Gauge.Builder limitBuilder = metricsFactory.gaugeBuilder( - namePrefix + "_limit", limit::get).scope(VENDOR); + config.name() + "_limit", limit::get).scope(VENDOR); + if (socketNameTag != null) { + limitBuilder.tags(List.of(socketNameTag)); + } meterRegistry.getOrCreate(limitBuilder); // count of current requests running Gauge.Builder concurrentRequestsBuilder = metricsFactory.gaugeBuilder( - namePrefix + "_concurrent_requests", concurrentRequests::get).scope(VENDOR); + config.name() + "_concurrent_requests", concurrentRequests::get).scope(VENDOR); + if (socketNameTag != null) { + concurrentRequestsBuilder.tags(List.of(socketNameTag)); + } meterRegistry.getOrCreate(concurrentRequestsBuilder); // count of number of requests rejected Gauge.Builder rejectedRequestsBuilder = metricsFactory.gaugeBuilder( - namePrefix + "_rejected_requests", rejectedRequests::get).scope(VENDOR); + config.name() + "_rejected_requests", rejectedRequests::get).scope(VENDOR); + if (socketNameTag != null) { + rejectedRequestsBuilder.tags(List.of(socketNameTag)); + } meterRegistry.getOrCreate(rejectedRequestsBuilder); // actual number of requests queued Gauge.Builder queueLengthBuilder = metricsFactory.gaugeBuilder( - namePrefix + "_queue_length", semaphore::getQueueLength).scope(VENDOR); + config.name() + "_queue_length", semaphore::getQueueLength).scope(VENDOR); + if (socketNameTag != null) { + queueLengthBuilder.tags(List.of(socketNameTag)); + } meterRegistry.getOrCreate(queueLengthBuilder); // histogram of round-trip times, excluding any time queued - Timer.Builder rttTimerBuilder = metricsFactory.timerBuilder(namePrefix + "_rtt") + Timer.Builder rttTimerBuilder = metricsFactory.timerBuilder(config.name() + "_rtt") .scope(VENDOR) .baseUnit(Timer.BaseUnits.MILLISECONDS); + if (socketNameTag != null) { + rttTimerBuilder.tags(List.of(socketNameTag)); + } rttTimer = meterRegistry.getOrCreate(rttTimerBuilder); // histogram of wait times for a permit in queue - Timer.Builder waitTimerBuilder = metricsFactory.timerBuilder(namePrefix + "_queue_wait_time") + Timer.Builder waitTimerBuilder = metricsFactory.timerBuilder(config.name() + "_queue_wait_time") .scope(VENDOR) .baseUnit(Timer.BaseUnits.MILLISECONDS); + if (socketNameTag != null) { + waitTimerBuilder.tags(List.of(socketNameTag)); + } queueWaitTimer = meterRegistry.getOrCreate(waitTimerBuilder); } } diff --git a/common/concurrency/limits/src/main/java/io/helidon/common/concurrency/limits/FixedLimit.java b/common/concurrency/limits/src/main/java/io/helidon/common/concurrency/limits/FixedLimit.java index 7987ec7c7a9..dfff0518b24 100644 --- a/common/concurrency/limits/src/main/java/io/helidon/common/concurrency/limits/FixedLimit.java +++ b/common/concurrency/limits/src/main/java/io/helidon/common/concurrency/limits/FixedLimit.java @@ -16,6 +16,7 @@ package io.helidon.common.concurrency.limits; +import java.util.List; import java.util.Optional; import java.util.concurrent.Callable; import java.util.concurrent.Semaphore; @@ -30,6 +31,7 @@ import io.helidon.metrics.api.MeterRegistry; import io.helidon.metrics.api.Metrics; import io.helidon.metrics.api.MetricsFactory; +import io.helidon.metrics.api.Tag; import io.helidon.metrics.api.Timer; import static io.helidon.metrics.api.Meter.Scope.VENDOR; @@ -260,36 +262,55 @@ public void init(String socketName) { if (config.enableMetrics()) { MetricsFactory metricsFactory = MetricsFactory.getInstance(); MeterRegistry meterRegistry = Metrics.globalRegistry(); - String namePrefix = (socketName.startsWith("@") ? socketName.substring(1) : socketName) - + "_" + config.name(); + + // define tag if socket is not the default + Tag socketNameTag = null; + if (!socketName.equals("@default")) { + socketNameTag = Tag.create("socketName", socketName); + } if (semaphore != null) { // actual number of requests queued Gauge.Builder queueLengthBuilder = metricsFactory.gaugeBuilder( - namePrefix + "_queue_length", semaphore::getQueueLength).scope(VENDOR); + config.name() + "_queue_length", semaphore::getQueueLength).scope(VENDOR); + if (socketNameTag != null) { + queueLengthBuilder.tags(List.of(socketNameTag)); + } meterRegistry.getOrCreate(queueLengthBuilder); } // count of current requests running Gauge.Builder concurrentRequestsBuilder = metricsFactory.gaugeBuilder( - namePrefix + "_concurrent_requests", concurrentRequests::get).scope(VENDOR); + config.name() + "_concurrent_requests", concurrentRequests::get).scope(VENDOR); + if (socketNameTag != null) { + concurrentRequestsBuilder.tags(List.of(socketNameTag)); + } meterRegistry.getOrCreate(concurrentRequestsBuilder); // actual number of requests queued Gauge.Builder rejectedRequestsBuilder = metricsFactory.gaugeBuilder( - namePrefix + "_rejected_requests", rejectedRequests::get).scope(VENDOR); + config.name() + "_rejected_requests", rejectedRequests::get).scope(VENDOR); + if (socketNameTag != null) { + rejectedRequestsBuilder.tags(List.of(socketNameTag)); + } meterRegistry.getOrCreate(rejectedRequestsBuilder); // histogram of round-trip times, excluding any time queued - Timer.Builder rttTimerBuilder = metricsFactory.timerBuilder(namePrefix + "_rtt") + Timer.Builder rttTimerBuilder = metricsFactory.timerBuilder(config.name() + "_rtt") .scope(VENDOR) .baseUnit(Timer.BaseUnits.MILLISECONDS); + if (socketNameTag != null) { + rttTimerBuilder.tags(List.of(socketNameTag)); + } rttTimer = meterRegistry.getOrCreate(rttTimerBuilder); // histogram of wait times for a permit in queue - Timer.Builder waitTimerBuilder = metricsFactory.timerBuilder(namePrefix + "_queue_wait_time") + Timer.Builder waitTimerBuilder = metricsFactory.timerBuilder(config.name() + "_queue_wait_time") .scope(VENDOR) .baseUnit(Timer.BaseUnits.MILLISECONDS); + if (socketNameTag != null) { + waitTimerBuilder.tags(List.of(socketNameTag)); + } queueWaitTimer = meterRegistry.getOrCreate(waitTimerBuilder); } } diff --git a/webserver/tests/resource-limits/src/test/java/io/helidon/webserver/tests/resourcelimit/AimdLimitMetricsTest.java b/webserver/tests/resource-limits/src/test/java/io/helidon/webserver/tests/resourcelimit/AimdLimitMetricsTest.java index ecd28013314..e50a7d863da 100644 --- a/webserver/tests/resource-limits/src/test/java/io/helidon/webserver/tests/resourcelimit/AimdLimitMetricsTest.java +++ b/webserver/tests/resource-limits/src/test/java/io/helidon/webserver/tests/resourcelimit/AimdLimitMetricsTest.java @@ -16,7 +16,13 @@ package io.helidon.webserver.tests.resourcelimit; +import java.util.Collections; +import java.util.Optional; + import io.helidon.common.concurrency.limits.AimdLimit; +import io.helidon.metrics.api.MeterRegistry; +import io.helidon.metrics.api.MetricsFactory; +import io.helidon.metrics.api.Timer; import io.helidon.webclient.api.HttpClientResponse; import io.helidon.webclient.api.WebClient; import io.helidon.webserver.WebServerConfig; @@ -29,22 +35,23 @@ import org.junit.jupiter.api.Test; -import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; @ServerTest class AimdLimitMetricsTest { private static final String[] METRIC_NAMES = { - "default_aimd_queue_length", - "default_aimd_rejected_requests", - "default_aimd_rtt_seconds", - "default_aimd_rtt_seconds_max", - "default_aimd_queue_wait_time_seconds", - "default_aimd_queue_wait_time_seconds_max", - "default_aimd_concurrent_requests", - "default_aimd_limit" + "aimd_queue_length", + "aimd_rejected_requests", + "aimd_rtt_seconds", + "aimd_rtt_seconds_max", + "aimd_queue_wait_time_seconds", + "aimd_queue_wait_time_seconds_max", + "aimd_concurrent_requests", + "aimd_limit" }; private final WebClient webClient; @@ -80,6 +87,7 @@ void testMetrics() { try (HttpClientResponse res = webClient.get("/greet").request()) { assertThat(res.status().code(), is(200)); } + try (HttpClientResponse res = webClient.get("/observe/metrics").request()) { String s = res.as(String.class); for (String metricName : METRIC_NAMES) { @@ -87,5 +95,10 @@ void testMetrics() { } assertThat(res.status().code(), is(200)); } + + MeterRegistry meterRegistry = MetricsFactory.getInstance().globalRegistry(); + Optional rtt = meterRegistry.timer("aimd_rtt", Collections.emptyList()); + assertThat(rtt.isPresent(), is(true)); + assertThat(rtt.get().count(), is(greaterThan(0L))); } } diff --git a/webserver/tests/resource-limits/src/test/java/io/helidon/webserver/tests/resourcelimit/FixedLimitMetricsTest.java b/webserver/tests/resource-limits/src/test/java/io/helidon/webserver/tests/resourcelimit/FixedLimitMetricsTest.java index c6ad544d3fd..f5d90674bca 100644 --- a/webserver/tests/resource-limits/src/test/java/io/helidon/webserver/tests/resourcelimit/FixedLimitMetricsTest.java +++ b/webserver/tests/resource-limits/src/test/java/io/helidon/webserver/tests/resourcelimit/FixedLimitMetricsTest.java @@ -16,10 +16,15 @@ package io.helidon.webserver.tests.resourcelimit; +import java.util.Collections; +import java.util.Optional; + import io.helidon.common.concurrency.limits.FixedLimit; +import io.helidon.metrics.api.MeterRegistry; +import io.helidon.metrics.api.MetricsFactory; +import io.helidon.metrics.api.Timer; import io.helidon.webclient.api.HttpClientResponse; import io.helidon.webclient.api.WebClient; - import io.helidon.webserver.WebServerConfig; import io.helidon.webserver.http.HttpRules; import io.helidon.webserver.observe.ObserveFeature; @@ -30,21 +35,22 @@ import org.junit.jupiter.api.Test; -import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; @ServerTest class FixedLimitMetricsTest { private static final String[] METRIC_NAMES = { - "default_fixed_queue_length", - "default_fixed_rejected_requests", - "default_fixed_rtt_seconds", - "default_fixed_rtt_seconds_max", - "default_fixed_queue_wait_time_seconds", - "default_fixed_queue_wait_time_seconds_max", - "default_fixed_concurrent_requests" + "fixed_queue_length", + "fixed_rejected_requests", + "fixed_rtt_seconds", + "fixed_rtt_seconds_max", + "fixed_queue_wait_time_seconds", + "fixed_queue_wait_time_seconds_max", + "fixed_concurrent_requests" }; private final WebClient webClient; @@ -77,6 +83,7 @@ void testMetrics() { try (HttpClientResponse res = webClient.get("/greet").request()) { assertThat(res.status().code(), is(200)); } + try (HttpClientResponse res = webClient.get("/observe/metrics").request()) { String s = res.as(String.class); for (String metricName : METRIC_NAMES) { @@ -84,5 +91,10 @@ void testMetrics() { } assertThat(res.status().code(), is(200)); } + + MeterRegistry meterRegistry = MetricsFactory.getInstance().globalRegistry(); + Optional rtt = meterRegistry.timer("fixed_rtt", Collections.emptyList()); + assertThat(rtt.isPresent(), is(true)); + assertThat(rtt.get().count(), is(greaterThan(0L))); } }