Enterprise Java

Reusable MicroProfile Health probes

MicroProfile Health API is a very basic API to report the state of your service, based on one or more Health Probe. It’s very useful in scenarios where some Server or Cluster Controller need to decide if and when your instance should be restarted.

Using MicroProfile Health API in your application is as simple as implementing one (or more) org.eclipse.microprofile.health.HealthCheck and annotating the class with @Health.

The HealthCheck interface has one method you should implement, namely HealthCheckResponse call().

So, you decide, when this method is called, if your instance is healthy.

Your response (HealthCheckResponse) contains:

  • a name to identify this probe from other probes.
  • a UP or DOWN flag, to indicate the state.
  • any other metadata you want to include, in a key-value pair.

A Basic example.

Let’s say we have an application that uses a Database, and if the connection to the database is down (or very slow), we should report this application to be unhealthy:

@Health
    @ApplicationScoped
    public class MembershipHealthCheck implements HealthCheck {
    
        @Inject 
        private DataSource datasource;

        @Override
        public HealthCheckResponse call() {

            HealthCheckResponseBuilder responseBuilder = HealthCheckResponse.named("membership");
            try {
                Connection connection = datasource.getConnection();
                boolean isValid = connection.isValid(timeout);

                DatabaseMetaData metaData = connection.getMetaData();

                responseBuilder = responseBuilder
                        .withData("databaseProductName", metaData.getDatabaseProductName())
                        .withData("databaseProductVersion", metaData.getDatabaseProductVersion())
                        .withData("driverName", metaData.getDriverName())
                        .withData("driverVersion", metaData.getDriverVersion())
                        .withData("isValid", isValid);

                return responseBuilder.state(isValid).build();


            } catch(SQLException  e) {
                log.log(Level.SEVERE, null, e);
                responseBuilder = responseBuilder
                        .withData("exceptionMessage", e.getMessage());
                return responseBuilder.down().build();
            }
        }
    }

(see the full example here)

In the example above, the health probe name is “membership” and reports UP if a connection to the Database could be established within a certain time. It also includes a few metadata fields w.r.t the database.

/health.

If you browse to /health on your server, you will see an aggregated response from all probes and a total state (UP or DOWN) of the server.

{
       "outcome":"UP",
       "checks":[
          {
             "name":"membership",
             "state":"UP",
             "data":{
                "databaseProductVersion":"5.5.5-10.1.35-MariaDB",
                "databaseProductName":"MySQL",
                "driverVersion":"mysql-connector-java-8.0.11 (Revision: 6d4eaa273bc181b4cf1c8ad0821a2227f116fedf)",
                "isValid":"true",
                "driverName":"MySQL Connector/J"
             }
          }
       ]
    }

If the database goes down:

{
       "outcome":"DOWN",
       "checks":[
          {
             "name":"membership",
             "state":"DOWN",
             "data":{
                "exceptionMessage":"No operations allowed after connection closed."
             }
          }
       ]
    }

Creating reusable probes with MicroProfile config.

Certain health probes can be re-used by any of your applications, and you can externalize the settings by using the Microprofile Config API. As an example, if we want a Health Probe that checks the System load, we can externalize at what stage the system load should start reporting down.

@Health
    @ApplicationScoped
    public class SystemLoadHealthCheck implements HealthCheck {

        @Inject @ConfigProperty(name = "health.systemload.max", defaultValue = "0.7")
        private double max;

        @Override
        public HealthCheckResponse call() {
            OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();

            String arch = operatingSystemMXBean.getArch();
            String name = operatingSystemMXBean.getName();
            String version = operatingSystemMXBean.getVersion();
            int availableProcessors = operatingSystemMXBean.getAvailableProcessors();

            double systemLoadAverage = operatingSystemMXBean.getSystemLoadAverage();
            double systemLoadAveragePerProcessors = systemLoadAverage / availableProcessors;

            HealthCheckResponseBuilder responseBuilder = HealthCheckResponse.named("system-load")
                    .withData("name", name)
                    .withData("arch", arch)
                    .withData("version", version)
                    .withData("processors", availableProcessors)
                    .withData("loadAverage", String.valueOf(systemLoadAverage))
                    .withData("loadAverage per processor", String.valueOf(systemLoadAveragePerProcessors))
                    .withData("loadAverage max", String.valueOf(max));

            if(systemLoadAverage>0){
                boolean status = systemLoadAveragePerProcessors < max;
                return responseBuilder.state(status).build();
            }else{
                // Load average not available
                return responseBuilder.up().build();
            }

        }
    }

(see the full example here)

Above we can now override the default 0.7 system load to our own value by changing the health.systemload.max config value.

Other examples could include:

  • Heap Memory
  • Non Heap Memory
  • Thread count

Using it in your project

You can use all the above in your project, as it’s available in maven central and github:

In your pom.xml:

<dependency>
        <groupId>com.github.phillip-kruger.microprofile-extensions</groupId>
        <artifactId>health-ext</artifactId>
        <version>1.0.9</version>
    </dependency>

The aggregated result of /health can then look something like this:

{
       "outcome":"UP",
       "checks":[
          {
             "name":"system-load",
             "state":"UP",
             "data":{
                "name":"Linux",
                "arch":"amd64",
                "processors":"8",
                "loadAverage":"2.03",
                "version":"4.18.1-arch1-1-ARCH",
                "loadAverage max":"0.7",
                "loadAverage per processor":"0.25375"
             }
          },
          {
             "name":"membership",
             "state":"UP",
             "data":{
                "databaseProductVersion":"5.5.5-10.1.35-MariaDB",
                "databaseProductName":"MySQL",
                "driverVersion":"mysql-connector-java-8.0.11 (Revision: 6d4eaa273bc181b4cf1c8ad0821a2227f116fedf)",
                "isValid":"true",
                "driverName":"MySQL Connector/J"
             }
          },
          {
             "name":"non-heap-memory",
             "state":"UP",
             "data":{
                "max %":"0.9",
                "max":"-1",
                "used":"132792064"
             }
          },
          {
             "name":"threads",
             "state":"UP",
             "data":{
                "max thread count":"-1",
                "daemon thread count":"86",
                "monitor deadlocked thread count":"0",
                "thread count":"134",
                "deadlocked thread count":"0",
                "started thread count":"138",
                "peak thread count":"136"
             }
          },
          {
             "name":"heap-memory",
             "state":"UP",
             "data":{
                "max %":"0.9",
                "max":"14995161088",
                "used":"207556800"
             }
          }
       ]
    }
Published on Java Code Geeks with permission by Phillip Krüger, partner at our JCG program. See the original article here: Reusable MicroProfile Health probes

Opinions expressed by Java Code Geeks contributors are their own.

Phillip Kruger

Phillip is a software developer and a systems architect who knacks for solving problems. He has a passion for clean code and evolutionary architecture. He blogs about all technical things.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button