Skip to content

Latest commit

 

History

History
1020 lines (827 loc) · 32.3 KB

README.adoc

File metadata and controls

1020 lines (827 loc) · 32.3 KB

microprofile-metrics: MicroProfile Metrics QuickStart

The microprofile-metrics quickstart demonstrates the use of the MicroProfile Metrics specification use in {productName}.

What is it?

MicroProfile Metrics allows applications to expose different metrics from their execution which is necessary to provide monitoring of essential system parameters. The metrics are provided by default in the well-known Prometheus metric format which allows applications utilizing MicroProfile Metrics to be easily integrated in the common monitoring solutions.

Architecture

In this quickstart, we build a collection of REST resources that expose functionalities of the MicroProfile Metrics specification. The metrics are exposed through the management interface at /metrics path according to the specification. Metrics will be collected as a part of the REST invocations.

Solution

Creating the Maven Project

mvn archetype:generate \
    -DgroupId=org.wildfly.quickstarts \
    -DartifactId=microprofile-metrics \
    -DinteractiveMode=false \
    -DarchetypeGroupId=org.apache.maven.archetypes \
    -DarchetypeArtifactId=maven-archetype-webapp
cd microprofile-metrics

Open the project in your favourite IDE.

Open the generated pom.xml.

The first thing to do is to setup our dependencies. Add the following section to your pom.xml:

<dependencyManagement>
  <dependencies>
    <!-- importing the microprofile BOM adds MicroProfile specs -->
    <dependency>
        <groupId>org.wildfly.bom</groupId>
        <artifactId>wildfly-microprofile</artifactId>
        <version>{versionMicroprofileBom}</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

Now we need to add the following dependencies:

<!-- Import the MicroProfile Metrics API, we use provided scope as the API is included in the server -->
<dependency>
  <groupId>org.eclipse.microprofile.metrics</groupId>
  <artifactId>microprofile-metrics-api</artifactId>
  <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>
Note
Because the MicroProfile Metrics specifications uses CDI injection to expose some of its functionality in the user application we need to also include the CDI API dependency.

All dependencies can have provided scope.

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 (you can replace the generated build section):

<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>

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

Note
The new file should be created in src/main/java/org/quickstarts/microprofile/metrics/JaxRsApplication.java.
package org.wildfly.quickstarts.microprofile.metrics;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

@ApplicationPath("/")
public class JaxRsApplication extends Application {
}

Now we are ready to start working with MicroProfile Metrics.

Accessing the metrics

The MicroProfile Metrics exposes all collected metrics at a single REST endpoint /metrics. Even without any direct interaction, {productName} server already collects and exposes some metrics. You can check the metrics anytime by accessing the http://localhost:9990/metrics endpoint using your browser or curl http://localhost:9990/metrics.

Note
Don’t forget to have {productName} server started.

You will get back a text file similar to this:

# HELP base_cpu_processCpuLoad Displays the "recent cpu usage" for the Java Virtual Machine process.
# TYPE base_cpu_processCpuLoad gauge
base_cpu_processCpuLoad 0.005753138075313807
# HELP base_memory_committedNonHeap_bytes Displays the amount of memory that is committed for the Java virtual machine to use.
# TYPE base_memory_committedNonHeap_bytes gauge
base_memory_committedNonHeap_bytes 8.9915392E7

This is a Prometheus format which is directly consumable by the Prometheus monitoring system. This is a default format. If you prefer for your testing to use rather the JSON format you need to specify the HTTP header Accepted: application/json with for instance curl -H "Accept: application/json" http://localhost:9990/metrics.

Different scopes of metrics

The MicroProfile Metrics specification defines three different scopes of metrics:

  • Base scope (/metrics/base)- the metrics that all MicroProfile vendors have to provide

  • Vendor scope (/metrics/vendor) - vendor specific metrics

  • Application scope (/metrics/application) - application specific metrics

Base scope metrics cover the metrics that are generally useful in any environment like memory.usedHeap or thread.count. For the full list of required metrics please access the http://localhost:9990/metrics/base endpoint using your browser or curl http://localhost:9990/metrics/base.

Vendor metrics represent custom metrics that are provided by the implementation of the MicroProfile Metrics specification which are in our case the custom metrics provided by the {productName} server. To see the full list please access the http://localhost:9990/metrics/vendor endpoint using your browser or curl http://localhost:9990/metrics/vendor. You can note that there are custom metrics exposed by the individual {productName} subsystems which are prefixed by wildfly_.

The application scope covers the metrics defined by your application. We will demonstrate how to define application metrics in the following sections.

OPTIONS requests for metrics metadata

So far we’ve accessed the metrics only with the HTTP GET requests which will always return the individual values of the requested metrics. The specification also provides an OPTIONS method requests for users to request the metrics metadata:

Perform a request curl -X OPTIONS -H "Accept: application/json" http://localhost:9990/metrics and you can see that the returned JSON now contains metadata about individual metrics like unit, type, description, display name and tags. We will see later on how you can change these values for your custom applications scoped metrics.

Application specific metrics

So far we’ve covered how we can access the metrics which are already predefined in the {productName} server. Let’s now define some custom application metrics.

The MicroProfile Metrics specification defines several types of application metrics:

  • @Counted - counts the invocations of the annotated object

  • @Timed - tracks the duration of the annotated object

  • @Gauge - samples the value of the annotated object

  • @ConcurrentGauge - gauge that counts parallel invocations of the annotated object

  • @Metered - tracks the frequency of invocation of the annotated object

  • @Metric - injection of the metric object

Let’s see how we can use these annotations in our application. Create a new class org.wildfly.quickstarts.microprofile.metrics.PrimeNumberChecker:

package org.wildfly.quickstarts.microprofile.metrics;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/")
@ApplicationScoped
public class PrimeNumberChecker {

    @GET
    @Path("/prime/{number}")
    @Produces(MediaType.TEXT_PLAIN)
    public String checkIfPrime(@PathParam("number") long number) {
        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) {
            if (number % i == 0) {
                return number + " is not prime, is divisible by " + i + ".";
            }
        }

        return number + " is prime.";
    }
}

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

Build and redeploy the application

$ mvn clean package wildfly:deploy

Now you can access http://localhost:8080/microprofile-metrics/prime/350 endpoint using your browser or curl http://localhost:8080/microprofile-metrics/prime/350 to check whether the number 350 is a prime number.

Note
You can of course try this endpoint with different numbers.

Now we are ready to add our custom metrics to this REST resource. If you try to access the application metrics now (http://localhost:9990/metrics/application) it should be empty.

Counted

Update the PrimeNumberChecker:

package org.wildfly.quickstarts.microprofile.metrics;

import org.eclipse.microprofile.metrics.annotation.Counted;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/")
@ApplicationScoped
public class PrimeNumberChecker {

    @GET
    @Path("/prime/{number}")
    @Produces(MediaType.TEXT_PLAIN)
    @Counted
    public String checkIfPrime(@PathParam("number") long number) {
        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) {
            if (number % i == 0) {
                return number + " is not prime, is divisible by " + i + ".";
            }
        }

        return number + " is prime.";
    }
}

Just by adding this single annotation we are already exposing the counter metric that will be counting the invocations of the annotated method. If you now redeploy your application (mvn clean package wildfly:deploy) and try to access your application metrics again (http://localhost:9990/metrics/application) you will see something like this:

{
    "org.wildfly.quickstarts.microprofile.metrics.PrimeNumberChecker.checkIfPrime": 0
}

Try now to invoke the REST endpoint several times and you will see the counter works as expected.

If you perform an OPTIONS request for this metrics, it’s values should be empty.

$ curl -X OPTIONS -H "Accept: application/json" http://localhost:9990/metrics/application
{
    "org.wildfly.quickstarts.microprofile.metrics.PrimeNumberChecker.checkIfPrime": {
        "unit": "none",
        "type": "counter",
        "description": "",
        "displayName": "",
        "tags": [
            [
            ]
        ]
    }
}

To change these values you can simply edit the parameters in the @Counted annotation (similarly for other annotations).

@Counted(name = "performedChecks", displayName="Performed Checks", description = "How many prime checks have been performed.")

And rebuild, redeploy your application to the {productName} server and access the application metrics metadata again:

mvn clean package wildfly:deploy
curl -X OPTIONS -H "Accept: application/json" http://localhost:9990/metrics/application
{
    "org.wildfly.quickstarts.microprofile.metrics.PrimeNumberChecker.performedChecks": {
        "unit": "none",
        "type": "counter",
        "description": "How many prime checks have been performed.",
        "displayName": "Performed Checks",
        "tags": [
            [
            ]
        ]
    }
}

Timed

Timed annotation declares a timer which will time the duration of how long our method is executing. Add the following annotation to our checkIfPrime method:

@GET
@Path("/prime/{number}")
@Produces(MediaType.TEXT_PLAIN)
@Counted(name = "performedChecks", displayName="Performed Checks", description = "How many prime checks have been performed.")
@Timed(name = "checksTimer", absolute = true, description = "A measure of how long it takes to perform the primality test.", unit = MetricUnits.MILLISECONDS)
public String checkIfPrime(@PathParam("number") long number) {
Note
Note the parameter absolute = true this will make our metric name to be not prefixed by the fully qualified package of the class where our check is located.

And rebuild, redeploy your application to the {productName} server, invoke the method several times, and access the application metrics data again:

mvn clean package wildfly:deploy
curl http://localhost:8080/microprofile-metrics/prime/350
curl http://localhost:8080/microprofile-metrics/prime/7
curl http://localhost:8080/microprofile-metrics/prime/29
curl -H "Accept: application/json" http://localhost:9990/metrics/application
{
    "org.wildfly.quickstarts.microprofile.metrics.PrimeNumberChecker.performedChecks": 3,
    "checksTimer": {
        "p99": 0.136153,
        "min": 0.01075,
        "max": 0.136153,
        "mean": 0.07816790480018097,
        "p50": 0.088464,
        "p999": 0.136153,
        "stddev": 0.05164982729810127,
        "p95": 0.136153,
        "p98": 0.136153,
        "p75": 0.136153,
        "fiveMinRate": 0.0,
        "fifteenMinRate": 0.0,
        "meanRate": 5.254614407952331,
        "count": 3,
        "oneMinRate": 0.0
    }
}

Note the name of our timer check is only what we defined in our annotation because of the absolute = true. You can also access particular check directly:

curl -H "Accept: application/json" http://localhost:9990/metrics/application/checksTimer
{
    "checksTimer": {
        "p99": 0.136153,
        "min": 0.01075,
        "max": 0.136153,
        "mean": 0.07816790480018097,
        "p50": 0.088464,
        "p999": 0.136153,
        "stddev": 0.05164982729810127,
        "p95": 0.136153,
        "p98": 0.136153,
        "p75": 0.136153,
        "fiveMinRate": 0.4595570030187892,
        "fifteenMinRate": 0.5489683372380184,
        "meanRate": 0.03506919414327886,
        "count": 3,
        "oneMinRate": 0.1581582828694362
    }
}

Gauge

Gauge is the most variable metric which is fully up to the application. It is required to define the metric type for this kind of metric. Let’s update the PrimeNumberChecker:

package org.wildfly.quickstarts.microprofile.metrics;

import org.eclipse.microprofile.metrics.MetricUnits;
import org.eclipse.microprofile.metrics.annotation.Counted;
import org.eclipse.microprofile.metrics.annotation.Gauge;
import org.eclipse.microprofile.metrics.annotation.Timed;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/")
@ApplicationScoped
public class PrimeNumberChecker {

    private long highestPrimeNumberSoFar = 2;

    @GET
    @Path("/prime/{number}")
    @Produces(MediaType.TEXT_PLAIN)
    @Counted(name = "performedChecks", displayName="Performed Checks", description = "How many prime checks have been performed.")
    @Timed(name = "checksTimer", absolute = true, description = "A measure of how long it takes to perform the primality test.", unit = MetricUnits.MILLISECONDS)
    public String checkIfPrime(@PathParam("number") long number) {
        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) {
            if (number % i == 0) {
                return number + " is not prime, is divisible by " + i + ".";
            }
        }

        if (number > highestPrimeNumberSoFar) {
            highestPrimeNumberSoFar = number;
        }

        return number + " is prime.";
    }

    @Gauge(name = "highestPrimeNumberSoFar", unit = MetricUnits.NONE, description = "Highest prime number so far.")
    public Long highestPrimeNumberSoFar() {
        return highestPrimeNumberSoFar;
    }
}
Warning
The metric unit for gauges is required to be defined in the annotation.

And rebuild, redeploy your application to the {productName} server, invoke the method several times, and access the application metrics data again:

mvn clean package wildfly:deploy
curl http://localhost:8080/microprofile-metrics/prime/350
curl http://localhost:8080/microprofile-metrics/prime/7
curl http://localhost:8080/microprofile-metrics/prime/29
curl -H "Accept: application/json" http://localhost:9990/metrics/application
{
    "org.wildfly.quickstarts.microprofile.metrics.PrimeNumberChecker.highestPrimeNumberSoFar": 29,
    "org.wildfly.quickstarts.microprofile.metrics.PrimeNumberChecker.performedChecks": 3,
    "checksTimer": {
        "p99": 0.187263,
        "min": 0.02916,
        "max": 0.187263,
        "mean": 0.121405,
        "p50": 0.147792,
        "p999": 0.187263,
        "stddev": 0.06718801966124616,
        "p95": 0.187263,
        "p98": 0.187263,
        "p75": 0.187263,
        "fiveMinRate": 0.0,
        "fifteenMinRate": 0.0,
        "meanRate": 5.571895665830193,
        "count": 3,
        "oneMinRate": 0.0
    }
}

Of course, gauges can be any value your application need. This is a fully customizable metric type.

Concurrent gauge

As stated above, concurrent gauge is a gauge that count the number of parallel accesses to a particular object. Let’s add a new method to PrimeNumbersChecker:

private CountDownLatch countDownLatch = new CountDownLatch(1);

@GET
@Path("/parallel")
@ConcurrentGauge(name = "parallelAccess", description = "Number of parallel accesses")
public void parallelAccess() throws InterruptedException {
    countDownLatch.await();
    System.out.println("DONE");
}

@GET
@Path("/parallel-finish")
public void parallelFinish() {
    countDownLatch.countDown();
    System.out.println("Finished parallel execution");
}

Now let’s try to exercise it:

mvn clean package wildfly:deploy
for i in {1..3}; do curl http://localhost:8080/microprofile-metrics/parallel & done
sleep 1
curl -H "Accept: application/json" http://localhost:9990/metrics/application
{
    "org.wildfly.quickstarts.microprofile.metrics.PrimeNumberChecker.highestPrimeNumberSoFar": 2,
    "org.wildfly.quickstarts.microprofile.metrics.PrimeNumberChecker.performedChecks": 0,
    "org.wildfly.quickstarts.microprofile.metrics.PrimeNumberChecker.parallelAccess": {
        "current": 3,
        "min": 0,
        "max": 0
    },
    "checksTimer": {
        "p99": 0.0,
        "min": 0.0,
        "max": 0.0,
        "mean": 0.0,
        "p50": 0.0,
        "p999": 0.0,
        "stddev": 0.0,
        "p95": 0.0,
        "p98": 0.0,
        "p75": 0.0,
        "fiveMinRate": 0.0,
        "fifteenMinRate": 0.0,
        "meanRate": 0.0,
        "count": 0,
        "oneMinRate": 0.0
    }
}

As you can see the 3 started requests are currently executed in parallel. You can finish them by a call to http://localhost:8080/microprofile-metrics/parallel-finish.

Metered

A meter is a metric type that tracks the frequency of incovations. Let’s update our checkIfPrime method:

@GET
@Path("/prime/{number}")
@Produces(MediaType.TEXT_PLAIN)
@Counted(name = "performedChecks", displayName="Performed Checks", description = "How many prime checks have been performed.")
@Timed(name = "checksTimer", absolute = true, description = "A measure of how long it takes to perform the primality test.", unit = MetricUnits.MILLISECONDS)
@Metered(name = "checkIfPrimeFrequency", absolute = true)
public String checkIfPrime(@PathParam("number") long number) {

And rebuid, redeploy your application to the {productName} server, invoke the method several times, and access the application metrics data again:

mvn clean package wildfly:deploy
curl http://localhost:8080/microprofile-metrics/prime/350
curl http://localhost:8080/microprofile-metrics/prime/7
curl http://localhost:8080/microprofile-metrics/prime/29
curl -H "Accept: application/json" http://localhost:9990/metrics/application
{
    "org.wildfly.quickstarts.microprofile.metrics.PrimeNumberChecker.highestPrimeNumberSoFar": 7,
    "checkIfPrimeFrequency": {
        "fiveMinRate": 0.0,
        "fifteenMinRate": 0.0,
        "meanRate": 6.804175994067375,
        "count": 3,
        "oneMinRate": 0.0
    },
    "org.wildfly.quickstarts.microprofile.metrics.PrimeNumberChecker.performedChecks": 3,
    "org.wildfly.quickstarts.microprofile.metrics.PrimeNumberChecker.parallelAccess": {
        "current": 0,
        "min": 0,
        "max": 0
    },
    "checksTimer": {
        "p99": 0.063733,
        "min": 0.00611,
        "max": 0.063733,
        "mean": 0.04252133333333333,
        "p50": 0.057721,
        "p999": 0.063733,
        "stddev": 0.0258634224640815,
        "p95": 0.063733,
        "p98": 0.063733,
        "p75": 0.063733,
        "fiveMinRate": 0.0,
        "fifteenMinRate": 0.0,
        "meanRate": 6.794024967566174,
        "count": 3,
        "oneMinRate": 0.0
    }
}

Metric

The last annotation is the @Metric which is used to inject or produce a metric. Let’s add the following code into PrimeNumberChecker:

@Inject
@Metric(name = "injectedCounter", absolute = true)
private Counter injectedCounter;

@GET
@Path("/injected-metric")
public String injectedMetric() {
    injectedCounter.inc();
    return "counter invoked";
}

And rebuid, redeploy your application to the {productName} server, invoke the method several times, and access the application metrics data again:

mvn clean package wildfly:deploy
curl http://localhost:8080/microprofile-metrics/injected-metric
curl http://localhost:8080/microprofile-metrics/injected-metric
curl http://localhost:8080/microprofile-metrics/injected-metric
curl -H "Accept: application/json" http://localhost:9990/metrics/application/injectedCounter
{
    "injectedCounter": 3
}

The types that can be injected and produced are Counter, Timer, Meter, Histogram. Gauge can be only produced.

Accessing the metric registry

A metric registry a collection of metrics that corresponds to the different metric scopes the MicroProfile Metrics specification defines. To access the MetricRegistry you can inject and use it like this. Add the following code to PrimeNumberChecker:

@Inject
@RegistryType(type = MetricRegistry.Type.APPLICATION)
private MetricRegistry applicationRegistry;

@GET
@Path("/registry")
public void registry() {
    // register a new application scoped metric
    Counter programmaticCounter = applicationRegistry.counter(Metadata.builder()
        .withName("programmaticCounter")
        .withDescription("Programmatically created counter")
        .build());

    programmaticCounter.inc(42);
}
Note
You need to also set the Java language level to Java 8 to use the interface method invocations.

And rebuid, redeploy your application to the {productName} server, invoke the method , and access the application metrics data again:

mvn clean package wildfly:deploy
curl http://localhost:8080/microprofile-metrics/registry
curl -H "Accept: application/json" http://localhost:9990/metrics/application/programmaticCounter
{
    "programmaticCounter": 42
}

Tags

The tag is basically only a key-value pair that can be associated with a metric. This means that we can have a metrics which have the same name if the tags differ. This kind of labels are very common in modern microservices orchestration services like Kubernetes. Tags can be set as a part of metadata in the individual annotations or when you are registering a new metric with the metric registry. Add the following code to PrimeNumberChecker:

@GET
@Path("/duplicates")
@Counted(name = "duplicatedCounter", absolute = true, tags = {"type=original"})
public String duplicates() {
    return "duplicated metrics";
}

@GET
@Path("/duplicates2")
@Counted(name = "duplicatedCounter", absolute = true, tags = {"type=copy"})
public String duplicates2() {
    return "duplicated metrics";
}

And rebuid, redeploy your application to the {productName} server, invoke the method , and access the application metrics data again:

mvn clean package wildfly:deploy
curl http://localhost:8080/microprofile-metrics/duplicates
curl http://localhost:8080/microprofile-metrics/duplicates2
curl -H "Accept: application/json" http://localhost:9990/metrics/application/duplicatedCounter
{
    "duplicatedCounter;type=original": 1,
    "duplicatedCounter;type=copy": 1
}

Displaying metrics with Prometheus and Grafana

So far we exposed the metrics from our service but we didn’t consume them efficiently (in production we need to monitor services for long periods so manually invoking /metrics endpoint is not acceptable). As mentioned above, MicroProfile Metrics expose the metrics by default in the Prometheus format. So let’s route our exposed metrics from {productName} to an instance of Prometheus.

Note
In this section we will be running external microservices as Docker containers. If you don’t have Docker installed on your machine please follow the instructions at https://www.docker.com/get-started.

Prometheus is a monitoring system and time series database. You can find more information at https://prometheus.io/. To start the Prometheus instance on our local computer we need:

  • a prometheus.yml file with the following content - prometheus.yml

  • Docker installed and running

To start the prometheus docker container run the following command:

docker run --rm --name prometheus -p 9090:9090 --network host \
-v /path/to/prometheus.yml:/etc/prometheus/prometheus.yml:Z prom/prometheus:v2.14.0 \
--config.file=/etc/prometheus/prometheus.yml

Wait for the container to start and then you can access http://localhost:9090 in your browser to view the Prometheus console. Because we configured Prometheus (in prometheus.yml) to scrap our prime-checker service running on {productName} server we can directly access the collected metrics which Prometheus collects from http://localhost:9990/metrics. You can test it by typing for instance application_org_wildfly_quickstart_microprofile_metrics_PrimeNumberChecker_performedChecks_total as the query expression and executing this query.

Congratulations. Your service metrics are now queried by Prometheus every second so you can try to make a few request for prime numbers and see the values reflected in the console.

Now that we have the Prometheus collecting the metrics we can expose Grafana. Grafana is analytics & monitoring solution which can be viewed as a nice presentation of metrics which we are being collected in the Prometheus. You can find more information about Grafana at https://grafana.com/.

To run Grafana make sure that the Prometheus is started and collecting metrics. If you followed previous section, the prometheus should be running.

To start Grafana you can run:

docker run --rm -p 3000:3000 --network host grafana/grafana:6.4.4

This will expose Grafana console at http://localhost:3000. You can login with default admin user (admin:admin).

The first thing we need to do with the new Grafana instance is to add a data source. Click the Add data source button on the welcome page. Select Prometheus. In the HTTP section set URL to be http://localhost:9090 and change the Scrape interval to be 1s. Click Save & Test.

Now is the time to add a new dashboard with the metrics we are interested in. You can add a new custom dashboard and new panels manually (the UI is intuitive) or you can skip the manual configuration with the following simple provided dashboard:

  • click plus sign on the left side → Import

  • paste the following JSON - grafana.json

  • set the Prometheus (Prometheus = Prometheus) data source on the next screen and click Import. You will see four simple panels displaying some of the application metrics that we exposed in our {productName} server.

Note
In production you typically do not run these service yourself but they are provided for you by operations or platform directly. So you just need to pass the location of your services to allow these services to collect metrics for you.

You can again make several requests for prime numbers (e.g., http://localhost:8080/microprofile-metrics/prime/7) to see how the metrics change.

Conclusion

MicroProfile Metrics provide a way for your application to expose metrics about your application/service to the outside world. They expose the metrics in a well-known Prometheus format which is directly consumable in the cloud environment. The more information can be found in the MicroProfile Metrics specification.