Skip to content

Commit

Permalink
[WFLY-17679] Add Micrometer quickstart
Browse files Browse the repository at this point in the history
Add module
Develop demo app
Add README
  • Loading branch information
jasondlee committed Jul 10, 2023
1 parent 8f02e7c commit 7c951fb
Show file tree
Hide file tree
Showing 8 changed files with 601 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ NOTE: Some of these quickstarts use the H2 database included with {productNameFu
| link:logging/README{outfilesuffix}[logging]|Logging | The `logging` quickstart demonstrates how to configure different logging levels in {productName}. It also includes an asynchronous logging example. | Intermediate | _none_
| link:mail/README{outfilesuffix}[mail]|JavaMail, CDI, JSF | The `mail` quickstart demonstrates how to send email using CDI and JSF and the default Mail provider that ships with {productName}. | Beginner | _none_
| link:messaging-clustering-singleton/README{outfilesuffix}[messaging-clustering-singleton]|JMS, MDB, Clustering | The `messaging-clustering-singleton` quickstart uses a JMS topic and a queue to demonstrate clustering using {productName} messaging with MDB singleton configuration where only one node in the cluster will be active. | Advanced | _none_
| link:micrometer/README{outfilesuffix}[micrometer]|Micrometer | The `micrometer` quickstart demonstrates the use of the Micrometer library in {productName}. | Beginner | _none_
| link:microprofile-config/README{outfilesuffix}[microprofile-config]|MicroProfile Config | The `microprofile-config` quickstart demonstrates the use of the MicroProfile Config specification in {productName}. | Beginner | _none_
| link:microprofile-fault-tolerance/README{outfilesuffix}[microprofile-fault-tolerance]|MicroProfile, Fault Tolerance | The `microprofile-fault-tolerance` quickstart demonstrates how to use Eclipse MicroProfile Fault Tolerance in {productName}. | Intermediate | _none_
| link:microprofile-health/README{outfilesuffix}[microprofile-health]|MicroProfile Health | The `microprofile-health` quickstart demonstrates the use of the MicroProfile Health specification in {productName}. | Beginner | _none_
Expand Down
325 changes: 325 additions & 0 deletions micrometer/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
include::../shared-doc/attributes.adoc[]

= micrometer: Micrometer QuickStart
:author: Jason Lee
:level: Beginner
:technologies: Micrometer

[abstract]
The `micrometer` quickstart demonstrates the use of the Micrometer library in {productName}.

:standalone-server-type: default
:archiveType: jar
:archiveName: {artifactId}

== What is it?

https://micrometer.io[Micrometer] is a vendor-neutral facade that allows application developers to collect and report application and system metrics to the backend of their choice in an entirely portable manner. By simply replacing the `MeterRegistry` used, or combining them in Micrometer's `CompositeRegistry` data can be exported a variety of monitoring systems with no application code changes.

== Architecture

In this quickstart, we will build a small, simple application that shows the usage of a number of Micrometer's `Meter` implementations. We will also demonstrate the means by which {productName} exports the metrics data, which is via the https://opentelemetry.io/docs/reference/specification/protocol/otlp/[OpenTelemetry Protocol (OTLP)] to the https://opentelemetry.io/docs/collector/[OpenTelemetry Collector]. To provide simpler access to the published metrics, the Collector will be configured with a Prometheus endpoint, from which we can scrape data.

// System Requirements
include::../shared-doc/system-requirements.adoc[leveloffset=+1]

// Use of {jbossHomeName}
include::../shared-doc/use-of-jboss-home-name.adoc[leveloffset=+1]

// Start the {productName} Standalone Server
include::../shared-doc/start-the-standalone-server.adoc[leveloffset=+1]

== Solution

We recommend that you follow the instructions that
<<creating-new-project, create the application step by step>>. However, you can
also go right to the completed example which is available in this directory.

// Build and Deploy the Quickstart
include::../shared-doc/build-and-deploy-the-quickstart.adoc[leveloffset=+1]

// Undeploy the Quickstart
include::../shared-doc/undeploy-the-quickstart.adoc[leveloffset=+1]


// Run the Quickstart in Red Hat CodeReady Studio or Eclipse
include::../shared-doc/run-the-quickstart-in-jboss-developer-studio.adoc[leveloffset=+1]


[[creating-new-project]]
== Creating the Maven Project

We start by creating the basic `pom.xml`:
[source,options="nowrap"]
----
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.wildfly.quickstarts</groupId>
<artifactId>micrometer</artifactId>
<version>28.0.0.Beta1</version>
<packaging>war</packaging>
<name>Quickstart: micrometer</name>
</project>
----

Now we need to start adding our dependencies. Since this will be demo will use Jakarta REST, we need to import the dependencies. We'll start by setting up a `dependencyManagement` section:

[source,xml,subs="attributes+"]
----
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.wildfly.bom</groupId>
<artifactId>wildfly-ee</artifactId>
<version>${version.server.bom}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
----

Next, we need to add the dependencies for both Micrometer, Jakarta REST, and CDI:

[source,xml,subs="attributes+"]
----
<dependencies>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<version>1.9.3</version>
<scope>provided</scope>
</dependency>
<!-- Import the CDI API, we use provided scope as the API is included in the server -->
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- Import the Jakarta REST API, we use provided scope as the API is included in the server -->
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
----

The Micrometer version is set to match that in {productName}, but the versions for the `jakarta.*` artifacts are omitted, as they're handled by the BOM imported above. Also note that the `scope` for all three dependencies is `provided`, as {productName} bundles those for us.

As we are going to be deploying this application to the {productName} server, let's also add a maven plugin that will simplify the deployment operations:

[source,xml]
----
<build>
<!-- Set the name of the archive -->
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- Allows to use mvn wildfly:deploy -->
<plugin>
<groupId>org.wildfly.plugins</groupId>
<artifactId>wildfly-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
----

// Setup required repositories
include::../shared-doc/setup-repositories.adoc[leveloffset=+1]

As this is a Jakarta REST application we need to also create an application class. Create `org.wildfly.quickstarts.micrometer.JaxRsApplication` with the following content:

[source,java]
----
package org.wildfly.quickstarts.micrometer;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
@ApplicationPath("/")
public class JaxRsApplication extends Application {
}
----

With the project set up, we can now start to write our endpoint.

== Capturing the metrics

Micrometer uses a programmatic approach to metrics definition, as opposed the more declarative, annotation-based approach of other libraries. Because of that, we need to explicitly register our `Meter` s before they can be used:

[source,java]
----
@Path("/")
@ApplicationScoped
public class RootResource {
private long highestPrimeNumberSoFar = 2;
@Inject
private MeterRegistry registry;
private Counter performCheckCounter;
private Counter originalCounter;
private Counter duplicatedCounter;
@PostConstruct
private void createMeters() {
Gauge.builder("prime.highestSoFar", () -> highestPrimeNumberSoFar)
.description("Highest prime number so far.")
.register(registry);
performCheckCounter = Counter
.builder("prime.performedChecks")
.description("How many prime checks have been performed.")
.register(registry);
originalCounter = Counter
.builder("prime.duplicatedCounter")
.tags(List.of(Tag.of("type", "original")))
.register(registry);
duplicatedCounter = Counter
.builder("prime.duplicatedCounter")
.tags(List.of(Tag.of("type", "copy")))
.register(registry);
}
// ...
}
----

Notice that we start by `@Inject` ing the `MeterRegistry`. This is a {productName}-managed instance, so all applications need to do it inject it and start using. Once we have that, we can use to build and register our meters, which we do in `@PostConstuct private void createMeters()`

[NOTE]
====
This must be done _post_-construction, as the `MeterRegistry` must be injected before it can be used to register the meters.
====

In this example, we register several different types to demonstrate their use. With those registered, we can start writing application logic:

[source,java]
----
@GET
@Path("/prime/{number}")
public String checkIfPrime(@PathParam("number") long number) throws Exception {
performCheckCounter.increment();
Timer timer = registry.timer("prime.timer");
return timer.recordCallable(() -> {
if (number < 1) {
return "Only natural numbers can be prime numbers.";
}
if (number == 1) {
return "1 is not prime.";
}
if (number == 2) {
return "2 is prime.";
}
if (number % 2 == 0) {
return number + " is not prime, it is divisible by 2.";
}
for (int i = 3; i < Math.floor(Math.sqrt(number)) + 1; i = i + 2) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
//
}
if (number % i == 0) {
return number + " is not prime, is divisible by " + i + ".";
}
}
if (number > highestPrimeNumberSoFar) {
highestPrimeNumberSoFar = number;
}
return number + " is prime.";
});
}
----

This method represents a simple REST endpoint that is able to determine whether the number passed as a path parameter is a prime number.

Before we can deploy the application to {productName}, however, we need to configure the server. Given that not all environment may want metrics, the Micrometer extension is not enabled by default, so we need to do that now. We can do that by using the command line management too?:

[source,subs="attributes+"]
----
$ /path/to/{productName]/bin/jboss-cli -c
[standalone@localhost:9990 /] /extension=org.wildfly.extension.micrometer:add
[standalone@localhost:9990 /] /subsystem=micrometer:add(endpoint="http://localhost:4318/v1/metrics")
[standalone@localhost:9990 /] reload
----

By default, {productName} will publish metrics every 10 seconds, so you will soon start seeing errors about a refused connection. This is because we told {productName} to publish to a server that is not there, so we need to fix that. To make that as simple as possible, you can use Docker/Podman compose to start an instance of the OpenTelemetry Collector:

[source,yaml]
----
include::docker-compose.yml[]
----

We also need to provide a configuration file for the collector:

[source,yaml]
----
include::otel-collector-config.yml[]
----

We can now bring up the collector instance:

[source,bash]
----
$ docker-compose up
----

The service should be available almost immediately, which you can verify by looking at the Prometheus endpoint we've configured by pointing your browser at http://localhost:1234/metrics[]. You should see quite a few metrics listed, none of which are what our application has registered. What you're seeing are the system and JVM metrics automatically registered and published by {productName} to give systems/applications administrators a comprehensive view of system health and performance.

Now, we're ready to deploy our application:

[source,options="nowrap"]
----
$ mvn clean package wildfly:deploy
----

You can either access the application via your browser at http://localhost:8080/micrometer/prime/13[], or from the command line:

[source,bash]
----
$ curl http://localhost:8080/micrometer/prime/13
----

Once given enough time to allow {productName} to publish metrics updates, you now see your application's meters reported in the http://localhost:1234/metrics[Promethues export]. You can also view them via the command-line:

[source,bash]
----
$ curl -s http://localhost:1234/metrics | grep "prime_"
# HELP prime_duplicatedCounter
# TYPE prime_duplicatedCounter counter
prime_duplicatedCounter{job="wildfly",type="copy"} 0
prime_duplicatedCounter{job="wildfly",type="original"} 0
# HELP prime_highestSoFar Highest prime number so far.
# TYPE prime_highestSoFar gauge
prime_highestSoFar{job="wildfly"} 13
# HELP prime_performedChecks How many prime checks have been performed.
# TYPE prime_performedChecks counter
prime_performedChecks{job="wildfly"} 1
# HELP prime_timer
# TYPE prime_timer histogram
prime_timer_bucket{job="wildfly",le="+Inf"} 1
prime_timer_sum{job="wildfly"} 10.941035
prime_timer_count{job="wildfly"} 1
----

Notice that all four meters registered in the `@PostConstruct` method as well as the `Timer` in our endpoint method have all been published.

== Conclusion

Micrometer provides a de facto standard way of capturing and publishing metrics to the monitoring solution of your choice. {productName} provides a convenient, out-of-the-box integration of Micrometer to make it easier to capture those metrics and monitor your application's health and performance. For more information on Micrometer, please refer to the project's https://micrometer.io[website].
17 changes: 17 additions & 0 deletions micrometer/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: "3"

services:
otel-collector:
image: otel/opentelemetry-collector
command: [--config=/etc/otel-collector-config.yml]
volumes:
- ./otel-collector-config.yml:/etc/otel-collector-config.yml
ports:
- 1888:1888 # pprof extension
- 8888:8888 # Prometheus metrics exposed by the collector
- 8889:8889 # Prometheus exporter metrics
- 13133:13133 # health_check extension
- 4317:4317 # OTLP gRPC receiver
- 4318:4318 # OTLP http receiver
- 55679:55679 # zpages extension
- 1234:1234 # /metrics endpoint
29 changes: 29 additions & 0 deletions micrometer/otel-collector-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
extensions:
health_check:
pprof:
endpoint: 0.0.0.0:1777
zpages:
endpoint: 0.0.0.0:55679

receivers:
otlp:
protocols:
grpc:
http:

processors:
batch:

exporters:
prometheus:
endpoint: "0.0.0.0:1234"

service:
pipelines:
metrics:
receivers: [otlp]
processors: [batch]
exporters: [prometheus]

extensions: [health_check, pprof, zpages]

Loading

0 comments on commit 7c951fb

Please sign in to comment.