Providing metrics from a microservice

duration 15 minutes

Prerequisites:

You’ll explore how to provide system and application metrics from a microservice with MicroProfile Metrics.

What you’ll learn

You will learn how to use MicroProfile Metrics to provide metrics from a microservice. You can monitor metrics to determine the performance and health of a service. You can also use them to pinpoint issues, collect data for capacity planning, or to decide when to scale a service to run with more or fewer resources.

The application that you will work with is an inventory service that stores information about various systems. The inventory service communicates with the system service on a particular host to retrieve its system properties when necessary.

You will use annotations provided by MicroProfile Metrics to instrument the inventory service to provide application-level metrics data. You will add counter, gauge, and timer metrics to the service.

You will also check well-known REST endpoints that are defined by MicroProfile Metrics to review the metrics data collected. Monitoring agents can access these endpoints to collect metrics.

Getting started

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-microprofile-metrics.git
cd guide-microprofile-metrics

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

Try what you’ll build

The finish directory contains the finished implementation for the application. You can try it out before you build your own.

To try out the application, first go to the finish directory and run the following Maven goal to build the application and deploy it to Open Liberty:

mvn liberty:run

Point your browser to the http://localhost:9080/inventory/systems URL to access the inventory service. Because you just started the application, the inventory is currently empty. Access the http://localhost:9080/inventory/systems/localhost URL to add the localhost into the inventory.

Access the inventory service at the http://localhost:9080/inventory/systems URL at least once for application metrics to be collected. Otherwise, the metrics will not appear.

Next, point your browser to the http://localhost:9443/metrics MicroProfile Metrics endpoint. Log in as the admin user with adminpwd as the password. You can see both the system and application metrics in a text format.

To see only the application metrics, point your browser to https://localhost:9443/metrics/application.

See the following sample outputs for the @Timed, @Gauge, and @Counted metrics:

# TYPE application_inventoryProcessingTime_rate_per_second gauge
application_inventoryProcessingTime_rate_per_second{method="get"} 0.0019189661542898407
...
# TYPE application_inventoryProcessingTime_seconds summary
# HELP application_inventoryProcessingTime_seconds Time needed to process the inventory
application_inventoryProcessingTime_seconds_count{method="get"} 1
application_inventoryProcessingTime_seconds{method="get",quantile="0.5"} 0.127965469
...
# TYPE application_inventoryProcessingTime_rate_per_second gauge
application_inventoryProcessingTime_rate_per_second{method="list"} 0.0038379320982686884
...
# TYPE application_inventoryProcessingTime_seconds summary
# HELP application_inventoryProcessingTime_seconds Time needed to process the inventory
application_inventoryProcessingTime_seconds_count{method="list"} 2
application_inventoryProcessingTime_seconds{method="list",quantile="0.5"} 2.2185000000000002E-5
...
# TYPE application_inventorySizeGauge gauge
# HELP application_inventorySizeGauge Number of systems in the inventory
application_inventorySizeGauge 1
# TYPE application_inventoryAccessCount_total counter
# HELP application_inventoryAccessCount_total Number of times the list of systems method is requested
application_inventoryAccessCount_total 1

To see only the system metrics, point your browser to https://localhost:9443/metrics/base.

See the following sample output:

# TYPE base_jvm_uptime_seconds gauge
# HELP base_jvm_uptime_seconds Displays the start time of the Java virtual machine in milliseconds. This attribute displays the approximate time when the Java virtual machine started.
base_jvm_uptime_seconds 30.342000000000002
# TYPE base_classloader_loadedClasses_count gauge
# HELP base_classloader_loadedClasses_count Displays the number of classes that are currently loaded in the Java virtual machine.
base_classloader_loadedClasses_count 11231

To see only the vendor metrics, point your browser to https://localhost:9443/metrics/vendor.

See the following sample output:

# TYPE vendor_threadpool_size gauge
# HELP vendor_threadpool_size The size of the thread pool.
vendor_threadpool_size{pool="Default_Executor"} 32
# TYPE vendor_servlet_request_total counter
# HELP vendor_servlet_request_total The number of visits to this servlet since the start of the server.
vendor_servlet_request_total{servlet="microprofile_metrics_io_openliberty_guides_inventory_InventoryApplication"} 1

After you are finished checking out the application, stop the Open Liberty server by pressing CTRL+C in the shell session where you ran the server. Alternatively, you can run the liberty:stop goal from the finish directory in another shell session:

mvn liberty:stop

Adding MicroProfile Metrics to the inventory service

pom.xml

  1<?xml version='1.0' encoding='utf-8'?>
  2<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">
  3
  4  <modelVersion>4.0.0</modelVersion>
  5
  6  <groupId>io.openliberty.guides</groupId>
  7  <artifactId>guide-microprofile-metrics</artifactId>
  8  <version>1.0-SNAPSHOT</version>
  9  <packaging>war</packaging>
 10
 11  <properties>
 12    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 13    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 14    <maven.compiler.source>1.8</maven.compiler.source>
 15    <maven.compiler.target>1.8</maven.compiler.target>
 16    <failOnMissingWebXml>false</failOnMissingWebXml>
 17    <!-- Plugin versions -->
 18    <version.liberty-maven-plugin>3.2</version.liberty-maven-plugin>
 19    <version.maven-failsafe-plugin>2.22.2</version.maven-failsafe-plugin>
 20    <version.maven-war-plugin>3.2.2</version.maven-war-plugin>
 21    <version.maven-surefire-plugin>2.22.2</version.maven-surefire-plugin>
 22    <!-- Liberty configuration -->
 23    <liberty.var.default.http.port>9080</liberty.var.default.http.port>
 24    <liberty.var.default.https.port>9443</liberty.var.default.https.port>
 25  </properties>
 26
 27  <dependencies>
 28    <!-- Provided dependencies -->
 29    <dependency>
 30      <groupId>org.eclipse.microprofile</groupId>
 31      <!-- tag::microprofile[] -->
 32      <artifactId>microprofile</artifactId>
 33      <!-- end::microprofile[] -->
 34      <version>3.2</version>
 35      <type>pom</type>
 36      <scope>provided</scope>
 37    </dependency>
 38    <!-- For tests -->
 39    <dependency>
 40      <groupId>org.junit.jupiter</groupId>
 41      <artifactId>junit-jupiter-engine</artifactId>
 42      <version>5.6.0</version>
 43      <scope>test</scope>
 44    </dependency>
 45    <dependency>
 46      <groupId>org.apache.cxf</groupId>
 47      <artifactId>cxf-rt-rs-client</artifactId>
 48      <version>3.3.5</version>
 49      <scope>test</scope>
 50    </dependency>
 51    <dependency>
 52      <groupId>org.apache.cxf</groupId>
 53      <artifactId>cxf-rt-rs-extension-providers</artifactId>
 54      <version>3.3.5</version>
 55      <scope>test</scope>
 56    </dependency>
 57    <dependency>
 58      <groupId>org.glassfish</groupId>
 59      <artifactId>javax.json</artifactId>
 60      <version>1.1.4</version>
 61      <scope>test</scope>
 62    </dependency>
 63    <!-- Java utility classes -->
 64    <dependency>
 65      <groupId>org.apache.commons</groupId>
 66      <artifactId>commons-lang3</artifactId>
 67      <version>3.9</version>
 68    </dependency>
 69    <!-- Support for JDK 9 and above -->
 70    <dependency>
 71        <groupId>javax.xml.bind</groupId>
 72        <artifactId>jaxb-api</artifactId>
 73        <version>2.3.1</version>
 74        <scope>test</scope>
 75    </dependency>
 76  </dependencies>
 77
 78  <build>
 79    <finalName>${project.artifactId}</finalName>
 80    <plugins>
 81      <plugin>
 82        <groupId>org.apache.maven.plugins</groupId>
 83        <artifactId>maven-war-plugin</artifactId>
 84        <version>${version.maven-war-plugin}</version>
 85      </plugin>
 86      <!-- Plugin to run unit tests -->
 87      <plugin>
 88        <groupId>org.apache.maven.plugins</groupId>
 89        <artifactId>maven-surefire-plugin</artifactId>
 90        <version>${version.maven-surefire-plugin}</version>
 91      </plugin>
 92      <!-- Enable liberty-maven plugin -->
 93      <plugin>
 94        <groupId>io.openliberty.tools</groupId>
 95        <artifactId>liberty-maven-plugin</artifactId>
 96        <version>${version.liberty-maven-plugin}</version>
 97      </plugin>
 98      <!-- Plugin to run functional tests -->
 99      <plugin>
100        <groupId>org.apache.maven.plugins</groupId>
101        <artifactId>maven-failsafe-plugin</artifactId>
102        <version>${version.maven-failsafe-plugin}</version>
103        <configuration>
104          <systemPropertyVariables>
105            <http.port>${liberty.var.default.http.port}</http.port>
106            <https.port>${liberty.var.default.https.port}</https.port>
107            <javax.net.ssl.trustStore>${project.build.directory}/liberty/wlp/usr/servers/defaultServer/resources/security/key.jks</javax.net.ssl.trustStore>
108          </systemPropertyVariables>
109        </configuration>
110      </plugin>
111    </plugins>
112  </build>
113</project>

server.xml

 1<server description="Sample Liberty server">
 2
 3  <featureManager>
 4    <feature>jaxrs-2.1</feature>
 5    <feature>jsonp-1.1</feature>
 6    <feature>cdi-2.0</feature>
 7    <!-- tag::mpMetrics[] -->
 8    <feature>mpMetrics-2.2</feature>
 9    <!-- end::mpMetrics[] -->
10    <!-- tag::monitor[] -->
11    <feature>monitor-1.0</feature>
12    <!-- end::monitor[] -->
13    <feature>mpRestClient-1.3</feature>
14  </featureManager>
15
16  <variable name="default.http.port" defaultValue="9080"/>
17  <variable name="default.https.port" defaultValue="9443"/>
18
19  <applicationManager autoExpand="true" />
20  <!-- tag::quickStartSecurity[] -->
21  <quickStartSecurity userName="admin" userPassword="adminpwd"/>
22  <!-- end::quickStartSecurity[] -->
23  <!-- tag::keyStore[] -->
24  <keyStore id="defaultKeyStore" location="key.jks" type="jks" password="mpKeystore"/>
25  <!-- end::keyStore[] -->
26  <httpEndpoint host="*" httpPort="${default.http.port}"
27      httpsPort="${default.https.port}" id="defaultHttpEndpoint"/>
28  <webApplication location="guide-microprofile-metrics.war" contextRoot="/"/>
29</server>

Start Open Liberty in development mode, which starts the Open Liberty server and listens for file changes:

mvn liberty:dev

After you see the following message, your application server in development mode is ready.

Press the Enter key to run tests on demand.

The development mode holds your command prompt to listen for file changes. You need to open another command prompt to continue, or simply open the project in your editor.

The MicroProfile Metrics API is included in the MicroProfile dependency specified by your pom.xml file. Look for the dependency with the microprofile artifact ID. This dependency provides a library that allows you to use the MicroProfile Metrics API in your code to provide metrics from your microservices.

Replace the server configuration file.
src/main/liberty/config/server.xml

The mpMetrics feature enables MicroProfile Metrics support in Open Liberty. Note that this feature requires SSL and the configuration has been provided for you.

The quickStartSecurity and keyStore configuration elements provide basic security to secure the server. When you visit the /metrics endpoint, use the credentials defined in the server configuration to log in and view the data.

Adding the annotations

Replace the InventoryManager class.
src/main/java/io/openliberty/guides/inventory/InventoryManager.java

InventoryManager.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2017, 2019 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License v1.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-v10.html
  8 *
  9 * Contributors:
 10 *     IBM Corporation - Initial implementation
 11 *******************************************************************************/
 12// end::copyright[]
 13// tag::InventoryManager[]
 14package io.openliberty.guides.inventory;
 15
 16import java.util.ArrayList;
 17import java.util.Collections;
 18import java.util.List;
 19import java.util.Properties;
 20
 21import javax.enterprise.context.ApplicationScoped;
 22
 23import org.eclipse.microprofile.metrics.MetricUnits;
 24import org.eclipse.microprofile.metrics.annotation.Counted;
 25import org.eclipse.microprofile.metrics.annotation.Gauge;
 26import org.eclipse.microprofile.metrics.annotation.Timed;
 27
 28import io.openliberty.guides.inventory.model.InventoryList;
 29import io.openliberty.guides.inventory.model.SystemData;
 30
 31// tag::ApplicationScoped[]
 32@ApplicationScoped
 33// end::ApplicationScoped[]
 34public class InventoryManager {
 35
 36  private List<SystemData> systems = Collections.synchronizedList(new ArrayList<>());
 37  private InventoryUtils invUtils = new InventoryUtils();
 38
 39  // tag::timedForGet[]
 40  // tag::nameForGet[]
 41  @Timed(name = "inventoryProcessingTime",
 42  // end::nameForGet[]
 43         // tag::tagForGet[]
 44         tags = {"method=get"},
 45         // end::tagForGet[]
 46         // tag::absoluteForGet[]
 47         absolute = true,
 48         // end::absoluteForGet[]
 49         // tag::desForGet[]
 50         description = "Time needed to process the inventory")
 51         // end::desForGet[]
 52  // end::timedForGet[]
 53  // tag::get[]
 54  public Properties get(String hostname) {
 55    return invUtils.getProperties(hostname);
 56  }
 57  // end::get[]
 58
 59  public void add(String hostname, Properties systemProps) {
 60    Properties props = new Properties();
 61    props.setProperty("os.name", systemProps.getProperty("os.name"));
 62    props.setProperty("user.name", systemProps.getProperty("user.name"));
 63
 64    SystemData host = new SystemData(hostname, props);
 65    if (!systems.contains(host))
 66      systems.add(host);
 67  }
 68
 69  // tag::timedForList[]
 70  // tag::nameForList[]
 71  @Timed(name = "inventoryProcessingTime",
 72  // end::nameForList[]
 73         // tag::tagForList[]
 74         tags = {"method=list"},
 75         // end::tagForList[]
 76         // tag::absoluteForList[]
 77         absolute = true,
 78         // end::absoluteForList[]
 79         // tag::desForList[]
 80         description = "Time needed to process the inventory")
 81         // end::desForList[]
 82  // end::timedForList[]
 83  // tag::countedForList[]
 84  @Counted(name = "inventoryAccessCount",
 85           absolute = true,
 86           description = "Number of times the list of systems method is requested")
 87  // end::countedForList[]
 88  // tag::list[]
 89  public InventoryList list() {
 90    return new InventoryList(systems);
 91  }
 92  // end::list[]
 93
 94  // tag::gaugeForGetTotal[]
 95  // tag::unitForGetTotal[]
 96  @Gauge(unit = MetricUnits.NONE,
 97  // end::unitForGetTotal[]
 98         name = "inventorySizeGauge",
 99         absolute = true,
100         description = "Number of systems in the inventory")
101  // end::gaugeForGetTotal[]
102  // tag::getTotal[]
103  public int getTotal() {
104    return systems.size();
105  }
106  // end::getTotal[]
107}
108// end::InventoryManager[]

Apply the @Timed annotation to the get() method, and apply the @Timed annotation to the list() method.

This annotation has these metadata fields:

name

Optional. Use this field to name the metric.

tags

Optional. Use this field to add tags to the metric with the same name.

absolute

Optional. Use this field to determine whether the metric name is the exact name that is specified in the name field or that is specified with the package prefix.

description

Optional. Use this field to describe the purpose of the metric.

The @Timed annotation tracks how frequently the method is invoked and how long it takes for each invocation of the method to complete. Both the get() and list() methods are annotated with the @Timed metric and have the same inventoryProcessingTime name. The method=get and method=list tags add a dimension that uniquely identifies the collected metric data from the inventory processing time in getting the system properties.

  • The method=get tag identifies the inventoryProcessingTime metric that measures the elapse time in getting the system properties from calling the system service.

  • The method=list tag identifies the inventoryProcessingTime metric that measures the elapse time for the inventory service to list all of the system properties in the inventory.

The tags allow you to query the metrics together or separately based on the functionality of the monitoring tool of your choice. The inventoryProcessingTime metrics for example could be queried to display an aggregate time of both tagged metrics or individual times.

Apply the @Counted annotation to the list() method to count how many times the http://localhost:9080/inventory/systems URL is accessed monotonically, which is counting up sequentially.

Apply the @Gauge annotation to the getTotal() method to track the number of systems that are stored in the inventory. When the value of the gauge is retrieved, the underlying getTotal() method is called to return the size of the inventory. Note the additional metadata field:

unit

Set the unit of the metric. If it is MetricUnits.NONE, the metric name is used without appending the unit name, no scaling is applied.

Additional information about these annotations, relevant metadata fields, and more are available at the MicroProfile Metrics Annotation Javadoc.

Enabling vendor metrics for the micorservices

server.xml

 1<server description="Sample Liberty server">
 2
 3  <featureManager>
 4    <feature>jaxrs-2.1</feature>
 5    <feature>jsonp-1.1</feature>
 6    <feature>cdi-2.0</feature>
 7    <!-- tag::mpMetrics[] -->
 8    <feature>mpMetrics-2.2</feature>
 9    <!-- end::mpMetrics[] -->
10    <!-- tag::monitor[] -->
11    <feature>monitor-1.0</feature>
12    <!-- end::monitor[] -->
13    <feature>mpRestClient-1.3</feature>
14  </featureManager>
15
16  <variable name="default.http.port" defaultValue="9080"/>
17  <variable name="default.https.port" defaultValue="9443"/>
18
19  <applicationManager autoExpand="true" />
20  <!-- tag::quickStartSecurity[] -->
21  <quickStartSecurity userName="admin" userPassword="adminpwd"/>
22  <!-- end::quickStartSecurity[] -->
23  <!-- tag::keyStore[] -->
24  <keyStore id="defaultKeyStore" location="key.jks" type="jks" password="mpKeystore"/>
25  <!-- end::keyStore[] -->
26  <httpEndpoint host="*" httpPort="${default.http.port}"
27      httpsPort="${default.https.port}" id="defaultHttpEndpoint"/>
28  <webApplication location="guide-microprofile-metrics.war" contextRoot="/"/>
29</server>

MicroProfile Metrics API implementers can provide vendor metrics in the same forms as the base and application metrics do. Liberty as a vendor supplies server component metrics when both the mpMetrics and monitor features are enabled in the server.xml configuration file.

You can see the vendor-only metrics in the metrics/vendor endpoint. You see metrics from the runtime components, such as Web Application, ThreadPool and Session Management. Note that these metrics are specific to the Liberty application server. Different vendors may provide other metrics. Visit the Metrics reference list for more information.

Building and running the application

The Open Liberty server was started in development mode at the beginning of the guide and all the changes were automatically picked up.

Point your browser to the https://localhost:9443/metrics URL to review the all available metrics that have been enabled through MicroProfile Metrics. Log in with admin as your username and adminpwd as your password. You see only the system and vendor metrics because the server just started, and the inventory service has not been accessed.

Next, point your browser to the http://localhost:9080/inventory/systems URL. Reload the https://localhost:9443/metrics URL, or access only the application metrics at the https://localhost:9443/metrics/application URL.

You can see the system metrics in the https://localhost:9443/metrics/base URL as well as see the vendor metrics in the https://localhost:9443/metrics/vendor URL.

Testing the metrics

MetricsIT.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2017, 2020 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License v1.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-v10.html
  8 *
  9 * Contributors:
 10 *     IBM Corporation - Initial implementation
 11 *******************************************************************************/
 12// end::copyright[]
 13// tag::MetricsTest[]
 14package it.io.openliberty.guides.metrics;
 15
 16import static org.junit.jupiter.api.Assertions.*;
 17import java.io.*;
 18import java.util.*;
 19import javax.ws.rs.client.Client;
 20import javax.ws.rs.client.ClientBuilder;
 21import javax.ws.rs.core.MediaType;
 22import javax.ws.rs.core.Response;
 23import org.apache.cxf.jaxrs.provider.jsrjsonp.JsrJsonpProvider;
 24import org.junit.jupiter.api.AfterEach;
 25import org.junit.jupiter.api.BeforeEach;
 26import org.junit.jupiter.api.BeforeAll;
 27import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
 28import org.junit.jupiter.api.Order;
 29import org.junit.jupiter.api.Test;
 30import org.junit.jupiter.api.TestMethodOrder;
 31
 32// tag::TestMethodOrder[]
 33@TestMethodOrder(OrderAnnotation.class)
 34// end::TestMethodOrder[]
 35public class MetricsIT {
 36  private static String httpPort;
 37  private static String httpsPort;
 38  private static String baseHttpUrl;
 39  private static String baseHttpsUrl;
 40
 41  private List<String> metrics;
 42  private Client client;
 43
 44  private final String INVENTORY_HOSTS = "inventory/systems";
 45  private final String INVENTORY_HOSTNAME = "inventory/systems/localhost";
 46  private final String METRICS_APPLICATION = "metrics/application";
 47
 48  // tag::BeforeAll[]
 49  @BeforeAll
 50  // end::BeforeAll[]
 51  // tag::oneTimeSetup[]
 52  public static void oneTimeSetup() {
 53    httpPort = System.getProperty("http.port");
 54    httpsPort = System.getProperty("https.port");
 55    baseHttpUrl = "http://localhost:" + httpPort + "/";
 56    baseHttpsUrl = "https://localhost:" + httpsPort + "/";
 57  }
 58  // end::oneTimeSetup[]
 59
 60  // tag::BeforeEach[]
 61  @BeforeEach
 62  // end::BeforeEach[]
 63  // tag::setup[]
 64  public void setup() {
 65    client = ClientBuilder.newClient();
 66    // tag::JsrJsonpProvider[]
 67    client.register(JsrJsonpProvider.class);
 68    // end::JsrJsonpProvider[]
 69  }
 70  // end::setup[]
 71
 72  // tag::AfterEach[]
 73  @AfterEach
 74  // end::AfterEach[]
 75  // tag::teardown[]
 76  public void teardown() {
 77    client.close();
 78  }
 79  // end::teardown[]
 80
 81  // tag::Test1[]
 82  @Test
 83  // end::Test1[]
 84  // tag::Order1[]
 85  @Order(1)
 86  // end::Order1[]
 87  // tag::testPropertiesRequestTimeMetric[]
 88  public void testPropertiesRequestTimeMetric() {
 89    connectToEndpoint(baseHttpUrl + INVENTORY_HOSTNAME);
 90    metrics = getMetrics();
 91    for (String metric : metrics) {
 92      if (metric.startsWith(
 93          "application_inventoryProcessingTime_rate_per_second")) {
 94        float seconds = Float.parseFloat(metric.split(" ")[1]);
 95        assertTrue(4 > seconds);
 96      }
 97    }
 98  }
 99  // end::testPropertiesRequestTimeMetric[]
100
101  // tag::Test2[]
102  @Test
103  // end::Test2[]
104  // tag::Order2[]
105  @Order(2)
106  // end::Order2[]
107  // tag::testInventoryAccessCountMetric[]
108  public void testInventoryAccessCountMetric() {
109    connectToEndpoint(baseHttpUrl + INVENTORY_HOSTS);
110    metrics = getMetrics();
111    for (String metric : metrics) {
112      if (metric.startsWith("application_inventoryAccessCount_total")) {
113        assertTrue(
114            1 <= Integer.parseInt(metric.split(" ")[metric.split(" ").length - 1]));
115      }
116    }
117  }
118  // end::testInventoryAccessCountMetric[]
119
120  // tag::Test3[]
121  @Test
122  // end::Test3[]
123  // tag::Order3[]
124  @Order(3)
125  // end::Order3[]
126  // tag::testInventorySizeGaugeMetric[]
127  public void testInventorySizeGaugeMetric() {
128    metrics = getMetrics();
129    for (String metric : metrics) {
130      if (metric.startsWith("application_inventorySizeGauge")) {
131        assertTrue(
132            1 <= Character.getNumericValue(metric.charAt(metric.length() - 1)));
133      }
134    }
135  }
136  // end::testInventorySizeGaugeMetric[]
137
138  public void connectToEndpoint(String url) {
139    Response response = this.getResponse(url);
140    this.assertResponse(url, response);
141    response.close();
142  }
143
144  private List<String> getMetrics() {
145    String usernameAndPassword = "admin" + ":" + "adminpwd";
146    String authorizationHeaderValue = "Basic "
147        + java.util.Base64.getEncoder()
148                          .encodeToString(usernameAndPassword.getBytes());
149    Response metricsResponse = client.target(baseHttpsUrl + METRICS_APPLICATION)
150                                     .request(MediaType.TEXT_PLAIN)
151                                     .header("Authorization",
152                                         authorizationHeaderValue)
153                                     .get();
154
155    BufferedReader br = new BufferedReader(new InputStreamReader((InputStream)
156    metricsResponse.getEntity()));
157    List<String> result = new ArrayList<String>();
158    try {
159      String input;
160      while ((input = br.readLine()) != null) {
161        result.add(input);
162      }
163      br.close();
164    } catch (IOException e) {
165      e.printStackTrace();
166      fail();
167    }
168
169    metricsResponse.close();
170    return result;
171  }
172
173  private Response getResponse(String url) {
174    return client.target(url).request().get();
175  }
176
177  private void assertResponse(String url, Response response) {
178    assertEquals(200, response.getStatus(), "Incorrect response code from " + url);
179  }
180}
181// end::MetricsTest[]

InventoryManager.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2017, 2019 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License v1.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-v10.html
  8 *
  9 * Contributors:
 10 *     IBM Corporation - Initial implementation
 11 *******************************************************************************/
 12// end::copyright[]
 13// tag::InventoryManager[]
 14package io.openliberty.guides.inventory;
 15
 16import java.util.ArrayList;
 17import java.util.Collections;
 18import java.util.List;
 19import java.util.Properties;
 20
 21import javax.enterprise.context.ApplicationScoped;
 22
 23import org.eclipse.microprofile.metrics.MetricUnits;
 24import org.eclipse.microprofile.metrics.annotation.Counted;
 25import org.eclipse.microprofile.metrics.annotation.Gauge;
 26import org.eclipse.microprofile.metrics.annotation.Timed;
 27
 28import io.openliberty.guides.inventory.model.InventoryList;
 29import io.openliberty.guides.inventory.model.SystemData;
 30
 31// tag::ApplicationScoped[]
 32@ApplicationScoped
 33// end::ApplicationScoped[]
 34public class InventoryManager {
 35
 36  private List<SystemData> systems = Collections.synchronizedList(new ArrayList<>());
 37  private InventoryUtils invUtils = new InventoryUtils();
 38
 39  // tag::timedForGet[]
 40  // tag::nameForGet[]
 41  @Timed(name = "inventoryProcessingTime",
 42  // end::nameForGet[]
 43         // tag::tagForGet[]
 44         tags = {"method=get"},
 45         // end::tagForGet[]
 46         // tag::absoluteForGet[]
 47         absolute = true,
 48         // end::absoluteForGet[]
 49         // tag::desForGet[]
 50         description = "Time needed to process the inventory")
 51         // end::desForGet[]
 52  // end::timedForGet[]
 53  // tag::get[]
 54  public Properties get(String hostname) {
 55    return invUtils.getProperties(hostname);
 56  }
 57  // end::get[]
 58
 59  public void add(String hostname, Properties systemProps) {
 60    Properties props = new Properties();
 61    props.setProperty("os.name", systemProps.getProperty("os.name"));
 62    props.setProperty("user.name", systemProps.getProperty("user.name"));
 63
 64    SystemData host = new SystemData(hostname, props);
 65    if (!systems.contains(host))
 66      systems.add(host);
 67  }
 68
 69  // tag::timedForList[]
 70  // tag::nameForList[]
 71  @Timed(name = "inventoryProcessingTime",
 72  // end::nameForList[]
 73         // tag::tagForList[]
 74         tags = {"method=list"},
 75         // end::tagForList[]
 76         // tag::absoluteForList[]
 77         absolute = true,
 78         // end::absoluteForList[]
 79         // tag::desForList[]
 80         description = "Time needed to process the inventory")
 81         // end::desForList[]
 82  // end::timedForList[]
 83  // tag::countedForList[]
 84  @Counted(name = "inventoryAccessCount",
 85           absolute = true,
 86           description = "Number of times the list of systems method is requested")
 87  // end::countedForList[]
 88  // tag::list[]
 89  public InventoryList list() {
 90    return new InventoryList(systems);
 91  }
 92  // end::list[]
 93
 94  // tag::gaugeForGetTotal[]
 95  // tag::unitForGetTotal[]
 96  @Gauge(unit = MetricUnits.NONE,
 97  // end::unitForGetTotal[]
 98         name = "inventorySizeGauge",
 99         absolute = true,
100         description = "Number of systems in the inventory")
101  // end::gaugeForGetTotal[]
102  // tag::getTotal[]
103  public int getTotal() {
104    return systems.size();
105  }
106  // end::getTotal[]
107}
108// end::InventoryManager[]

You can test your application manually, but automated tests ensure code quality because they trigger a failure whenever a code change introduces a defect. JUnit and the JAX-RS Client API provide a simple environment for you to write tests.

Create the MetricsIT class.
src/test/java/it/io/openliberty/guides/metrics/MetricsIT.java
  • The testPropertiesRequestTimeMetric() test case validates the @Timed metric. The test case sends a request to the http://localhost:9080/inventory/systems/localhost URL to access the inventory service, which adds the localhost host to the inventory. Next, the test case makes a connection to the https://localhost:9443/metrics/application URL to retrieve application metrics as plain text. Then, it asserts whether the time that is needed to retrieve the system properties for localhost is less than 4 seconds.

  • The testInventoryAccessCountMetric() test case validates the @Counted metric. The test case sends a request to the http://localhost:9080/inventory/systems URL to retrieve the whole inventory, and then it asserts that this endpoint is accessed at least once.

  • The testInventorySizeGaugeMetric() test case validates the @Gauge metric. The test case first ensures that the localhost is in the inventory, then looks for the @Gauge metric and asserts that the inventory size is greater or equal to 1.

The oneTimeSetup() method retrieves the port number for the server and builds a base URL string to set up the tests. Apply the @BeforeAll annotation to this method to run it before any of the test cases.

The setup() method creates a JAX-RS client that makes HTTP requests to the inventory service. Register this client with a JsrJsonpProvider JSON-P provider to process JSON resources. The teardown() method destroys this client instance. Apply the @BeforeEach annotation so that a method runs before a test case and apply the @AfterEach annotation so that a method runs after a test case. Apply these annotations to methods that are generally used to perform any setup and teardown tasks before and after a test.

To force these test cases to run in a particular order, annotate your MetricsIT test class with the @TestMethodOrder(OrderAnnotation.class) annotation. OrderAnnotation.class runs test methods in numerical order, according to the values specified in the @Order annotation. You can also create a custom MethodOrderer class or use built-in MethodOrderer implementations, such as OrderAnnotation.class, Alphanumeric.class, or Random.class. Label your test cases with the @Test annotation so that they automatically run when your test class runs.

In addition, the endpoint tests src/test/java/it/io/openliberty/guides/inventory/InventoryEndpointIT.java and src/test/java/it/io/openliberty/guides/system/SystemEndpointIT.java are provided for you to test the basic functionality of the inventory and system services. If a test failure occurs, then you might have introduced a bug into the code.

Running the tests

Because you started Open Liberty in development mode at the start of the guide, press the enter/return key to run the tests and see the following output:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.system.SystemEndpointIT
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.4 sec - in it.io.openliberty.guides.system.SystemEndpointIT
Running it.io.openliberty.guides.metrics.MetricsIT
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.697 sec - in it.io.openliberty.guides.metrics.MetricsIT
Running it.io.openliberty.guides.inventory.InventoryEndpointIT
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.264 sec - in it.io.openliberty.guides.inventory.InventoryEndpointIT

Results :

Tests run: 7, Failures: 0, Errors: 0, Skipped: 0

To determine whether the tests detect a failure, go to the MetricsIT.java file and change any of the assertions in the test methods. Then re-run the tests to see a test failure occur.

When you are done checking out the service, exit development mode by pressing CTRL+C in the shell session where you ran the server, or by typing q and then pressing the enter/return key.

Great work! You’re done!

You learned how to enable system, application and vendor metrics for microservices by using MicroProfile Metrics and wrote tests to validate them in Open Liberty.

Guide Attribution

Providing metrics from a microservice by Open Liberty is licensed under CC BY-ND 4.0

Copied to clipboard
Copy code block
Copy file contents

Prerequisites:

Nice work! Where to next?

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

Like Open Liberty? Star our repo on GitHub.

Star