Skip to content

Commit

Permalink
Stork observability integration
Browse files Browse the repository at this point in the history
  • Loading branch information
aureamunoz committed Oct 23, 2023
1 parent 0213d81 commit ebbcf7c
Show file tree
Hide file tree
Showing 26 changed files with 924 additions and 7 deletions.
2 changes: 1 addition & 1 deletion bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
<smallrye-reactive-types-converter.version>3.0.1</smallrye-reactive-types-converter.version>
<smallrye-mutiny-vertx-binding.version>3.7.2</smallrye-mutiny-vertx-binding.version>
<smallrye-reactive-messaging.version>4.10.1</smallrye-reactive-messaging.version>
<smallrye-stork.version>2.3.1</smallrye-stork.version>
<smallrye-stork.version>2.4.0</smallrye-stork.version>
<jakarta.activation.version>2.1.2</jakarta.activation.version>
<jakarta.annotation-api.version>2.1.1</jakarta.annotation-api.version>
<jakarta.authentication-api>3.0.0</jakarta.authentication-api>
Expand Down
67 changes: 67 additions & 0 deletions docs/src/main/asciidoc/stork-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,70 @@ To learn about custom service discovery and service selection, check:

- https://smallrye.io/smallrye-stork/latest/service-discovery/custom-service-discovery/[Implement a custom service discover provider]
- https://smallrye.io/smallrye-stork/latest/load-balancer/custom-load-balancer/[Implement a custom service selection provider]

== Configure Stork observability

Check warning on line 76 in docs/src/main/asciidoc/stork-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Headings] Use sentence-style capitalization in 'Configure Stork observability'. Raw Output: {"message": "[Quarkus.Headings] Use sentence-style capitalization in 'Configure Stork observability'.", "location": {"path": "docs/src/main/asciidoc/stork-reference.adoc", "range": {"start": {"line": 76, "column": 4}}}, "severity": "INFO"}

=== Enable metrics

Stork metrics are automatically enabled when the application also uses the xref:telemetry-micrometer.adoc[`quarkus-micrometer`] extension.

Micrometer collects the metrics of rest/grpc clients using Stork and the client using Stork programmatically.

Check warning on line 82 in docs/src/main/asciidoc/stork-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/stork-reference.adoc", "range": {"start": {"line": 82, "column": 53}}}, "severity": "INFO"}

Check warning on line 82 in docs/src/main/asciidoc/stork-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/stork-reference.adoc", "range": {"start": {"line": 82, "column": 80}}}, "severity": "INFO"}

As an example, if you export the metrics to Prometheus, you will get:

[source,text]
----
# HELP stork_service_selection_failures_total The number of failures during service selection.
# TYPE stork_service_selection_failures_total counter
stork_service_selection_failures_total{service_name="hello-service",} 0.0
# HELP stork_service_selection_duration_seconds The duration of the selection operation
# TYPE stork_service_selection_duration_seconds summary
stork_service_selection_duration_seconds_count{service_name="hello-service",} 13.0
stork_service_selection_duration_seconds_sum{service_name="hello-service",} 0.001049291
# HELP stork_service_selection_duration_seconds_max The duration of the selection operation
# TYPE stork_service_selection_duration_seconds_max gauge
stork_service_selection_duration_seconds_max{service_name="hello-service",} 0.0
# HELP stork_overall_duration_seconds_max The total duration of the Stork service discovery and selection operations
# TYPE stork_overall_duration_seconds_max gauge
stork_overall_duration_seconds_max{service_name="hello-service",} 0.0
# HELP stork_overall_duration_seconds The total duration of the Stork service discovery and selection operations
# TYPE stork_overall_duration_seconds summary
stork_overall_duration_seconds_count{service_name="hello-service",} 13.0
stork_overall_duration_seconds_sum{service_name="hello-service",} 0.001049291
# HELP stork_service_discovery_failures_total The number of failures during service discovery
# TYPE stork_service_discovery_failures_total counter
stork_service_discovery_failures_total{service_name="hello-service",} 0.0
# HELP stork_service_discovery_duration_seconds_max The duration of the discovery operation
# TYPE stork_service_discovery_duration_seconds_max gauge
stork_service_discovery_duration_seconds_max{service_name="hello-service",} 0.0
# HELP stork_service_discovery_duration_seconds The duration of the discovery operation
# TYPE stork_service_discovery_duration_seconds summary
stork_service_discovery_duration_seconds_count{service_name="hello-service",} 13.0
stork_service_discovery_duration_seconds_sum{service_name="hello-service",} 6.585046209
# HELP stork_service_discovery_instances_count_total The number of service instances discovered
# TYPE stork_service_discovery_instances_count_total counter
stork_service_discovery_instances_count_total{service_name="hello-service",} 26.0
----

The Stork service name can be found in the _tags_.

The metrics contain both the service discovery (`stork_service_discovery_*`) and the metrics about the service selection (`stork_service_selection_*`) such as the number of service instances, failures, and durations.

=== Disable metrics

To disable the Stork metrics when `quarkus-micrometer` is used, add the following property to the application configuration:

[source,properties]
----
quarkus.micrometer.binder.stork.enabled=false
----


[[stork-configuration-reference]]
== Configuration reference

include::{generated-dir}/config/quarkus-stork.adoc[opts=optional, leveloffset=+1]




1 change: 1 addition & 0 deletions docs/src/main/asciidoc/telemetry-micrometer.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,7 @@ Refer to the xref:./management-interface-reference.adoc[management interface ref
** JMS
** MQTT
** Camel Messaging
* https://quarkus.io/guides/stork-reference[`quarkus-smallrye-stork`]
* https://quarkus.io/guides/vertx[`quarkus-vertx`] (http requests)

Check warning on line 712 in docs/src/main/asciidoc/telemetry-micrometer.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Headings] Use sentence-style capitalization in 'Configuration Reference'. Raw Output: {"message": "[Quarkus.Headings] Use sentence-style capitalization in 'Configuration Reference'.", "location": {"path": "docs/src/main/asciidoc/telemetry-micrometer.adoc", "range": {"start": {"line": 712, "column": 28}}}, "severity": "INFO"}

== Configuration Reference
Expand Down
22 changes: 18 additions & 4 deletions extensions/micrometer/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,22 +81,26 @@

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-deployment</artifactId>
<artifactId>quarkus-resteasy-reactive-deployment</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-deployment</artifactId>
<artifactId>quarkus-rest-client-reactive-deployment</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson-deployment</artifactId>
<artifactId>quarkus-resteasy-reactive-jackson-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.smallrye.stork</groupId>
<artifactId>stork-service-discovery-static-list</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-undertow-deployment</artifactId>
Expand Down Expand Up @@ -132,6 +136,16 @@
<artifactId>resteasy-reactive-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.quarkus.micrometer.deployment.binder;

import java.util.function.BooleanSupplier;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.micrometer.runtime.MicrometerRecorder;
import io.quarkus.micrometer.runtime.config.MicrometerConfig;

public class StorkBinderProcessor {

static final String OBSERVABLE_CLIENT = "io.smallrye.stork.api.Service";
static final String METRICS_BEAN_CLASS = "io.quarkus.micrometer.runtime.binder.stork.StorkObservationCollectorBean";

static final Class<?> OBSERVABLE_CLIENT_CLASS = MicrometerRecorder.getClassForName(OBSERVABLE_CLIENT);

static class StorkMetricsSupportEnabled implements BooleanSupplier {
MicrometerConfig mConfig;

public boolean getAsBoolean() {
return OBSERVABLE_CLIENT_CLASS != null && mConfig.checkBinderEnabledWithDefault(mConfig.binder.stork);
}
}

@BuildStep(onlyIf = StorkMetricsSupportEnabled.class)
AdditionalBeanBuildItem addStorkObservationCollector() {
return AdditionalBeanBuildItem.unremovableOf(METRICS_BEAN_CLASS);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.hamcrest.Matchers.not;

import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

Expand All @@ -26,6 +27,7 @@ public class HttpDevModeConfigTest {
"quarkus.redis.devservices.enabled=false\n" +
"orange=banana"), "application.properties"));

@Disabled
@Test
public void test() throws Exception {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.quarkus.micrometer.deployment.binder;

import static org.junit.jupiter.api.Assertions.assertTrue;

import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.smallrye.stork.api.observability.ObservationCollector;

public class StorkMetricsDisabledTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withConfigurationResource("test-logging.properties")
.overrideConfigKey("quarkus.micrometer.binder.stork.enabled", "false")
.overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false")
.overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false")
.withEmptyApplication();

@Inject
Instance<ObservationCollector> bean;

@Test
void testNoInstancePresentIfNoRedisClientsClass() {
assertTrue(bean.isUnsatisfied(),
"No Stork metrics bean");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@

package io.quarkus.micrometer.deployment.binder;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

import java.util.concurrent.TimeUnit;

import jakarta.inject.Inject;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.Mockito;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Timer;
import io.quarkus.micrometer.runtime.binder.stork.StorkObservationCollectorBean;
import io.quarkus.micrometer.test.GreetingResource;
import io.quarkus.micrometer.test.MockServiceSelectorConfiguration;
import io.quarkus.micrometer.test.MockServiceSelectorProvider;
import io.quarkus.micrometer.test.MockServiceSelectorProviderLoader;
import io.quarkus.micrometer.test.PingPongResource;
import io.quarkus.micrometer.test.Util;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import io.smallrye.stork.api.observability.StorkObservation;

@DisabledOnOs(OS.WINDOWS)
public class StorkMetricsLoadBalancerFailTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withConfigurationResource("test-logging.properties")
.overrideConfigKey("pingpong/mp-rest/url", "stork://pingpong-service")
.overrideConfigKey("quarkus.stork.pingpong-service.service-discovery.type", "static")
.overrideConfigKey("quarkus.stork.pingpong-service.service-discovery.address-list", "${test.url}")
.overrideConfigKey("quarkus.stork.pingpong-service.load-balancer.type", "mock")
.overrideConfigKey("greeting/mp-rest/url", "stork://greeting-service/greeting")
.overrideConfigKey("quarkus.stork.greeting-service.service-discovery.type", "static")
.overrideConfigKey("quarkus.stork.greeting-service.service-discovery.address-list", "${test.url}")
.overrideConfigKey("quarkus.stork.greeting-service.load-balancer.type", "mock")
.withApplicationRoot((jar) -> jar
.addClasses(PingPongResource.class, PingPongResource.PingPongRestClient.class,
MockServiceSelectorProvider.class, MockServiceSelectorConfiguration.class,
MockServiceSelectorProviderLoader.class, GreetingResource.class,
GreetingResource.GreetingRestClient.class, Util.class));

@Inject
MeterRegistry registry;

@Inject
MockServiceSelectorProvider provider;

@Test
public void shouldGetStorkMetricsWhenServiceSelectorFails() {

Mockito.when(provider.getLoadBalancer().selectServiceInstance(Mockito.anyCollection()))
.thenThrow(new RuntimeException("Load Balancer induced failure"));
RestAssured.when().get("/ping/one").then().statusCode(500);
RestAssured.when().get("/greeting/hola").then().statusCode(500);

//Stork metrics
assertStorkMetrics("pingpong-service");
assertStorkMetrics("greeting-service");

// Stork metrics exposed to Micrometer
assertStorkMetricsInMicrometerRegistry("pingpong-service");
assertStorkMetricsInMicrometerRegistry("greeting-service");

}

private static void assertStorkMetrics(String serviceName) {
StorkObservation metrics = StorkObservationCollectorBean.STORK_METRICS
.get(serviceName + StorkObservationCollectorBean.METRICS_SUFIX);
Assertions.assertThat(metrics.getDiscoveredInstancesCount()).isEqualTo(1);
Assertions.assertThat(metrics.getServiceName()).isEqualTo(serviceName);
Assertions.assertThat(metrics.isDone()).isTrue();
Assertions.assertThat(metrics.isServiceDiscoverySuccessful()).isTrue();
Assertions.assertThat(metrics.failure().getMessage())
.isEqualTo("Load Balancer induced failure");
Assertions.assertThat(metrics.getOverallDuration()).isNotNull();
Assertions.assertThat(metrics.getServiceDiscoveryType()).isEqualTo("static");
Assertions.assertThat(metrics.getServiceSelectionType()).isEqualTo("mock");
Assertions.assertThat(metrics.getServiceDiscoveryDuration()).isNotNull();
Assertions.assertThat(metrics.getServiceSelectionDuration()).isNotNull();
}

private void assertStorkMetricsInMicrometerRegistry(String serviceName) {
Counter instanceCounter = registry.counter("stork.service-discovery.instances.count", "service-name", serviceName);
Timer serviceDiscoveryDuration = registry.timer("stork.service-discovery.duration", "service-name", serviceName);
Timer serviceSelectionDuration = registry.timer("stork.service-selection.duration", "service-name", serviceName);
Counter serviceDiscoveryFailures = registry.get("stork.service-discovery.failures")
.tags("service-name", serviceName).counter();
Counter loadBalancerFailures = registry.get("stork.service-selection.failures").tags("service-name", serviceName)
.counter();

Util.assertTags(Tag.of("service-name", serviceName), instanceCounter, serviceDiscoveryDuration,
serviceSelectionDuration);

Assertions.assertThat(instanceCounter.count()).isEqualTo(1);
Assertions.assertThat(loadBalancerFailures.count()).isEqualTo(1);
Assertions.assertThat(serviceDiscoveryFailures.count()).isEqualTo(0);
Assertions.assertThat(serviceDiscoveryDuration.totalTime(TimeUnit.NANOSECONDS)).isGreaterThan(0);
Assertions.assertThat(serviceSelectionDuration.totalTime(TimeUnit.NANOSECONDS)).isGreaterThan(0);
}

}
Loading

0 comments on commit ebbcf7c

Please sign in to comment.