0
votes

Problem statement: Expose Latency, Error-Rate, Throughput, Retry-Count (For persisting into SQL DB failures and Kafka publishing failures), and Unsuccessful-Retries of the Spring Boot application as ManagedBeans on JConsole. There are no REST endpoints in this application. It is purely asynchronously event-driven via Kafka (Use of Spring StreamListener).

I cannot use Prometheus due to the licensing issue on the Production level.

My Approach: Created a MetricsBean class (implementing an interface) essentially containing the POJO for the above metrics:

import org.springframework.context.annotation.Lazy;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;

@Lazy(false)
@ManagedResource
public class ConfigMBean implements IConfigMBean {

    private Double latency;
    private Double errorRate;
    private Double throughput;
    private Long retryCount;
    private Long unsuccessfulRetries;

    public GatewayConfigMBean(Double latency, Double errorRate, Double throughput, Long retryCount,
            Long unsuccessfulRetries) {
        super();
        this.latency = latency;
        this.errorRate = errorRate;
        this.throughput = throughput;
        this.retryCount = retryCount;
        this.unsuccessfulRetries = unsuccessfulRetries;
    }

    public GatewayConfigMBean() {
    }

    @ManagedAttribute
    @Override
    public Double getLatency() {
        return latency;
    }

    @ManagedAttribute
    @Override
    public Double getErrorRate() {
        return errorRate;
    }

    @ManagedAttribute
    @Override
    public Double getThroughput() {
        return throughput;
    }

    @ManagedAttribute
    @Override
    public Long getRetryCount() {
        return retryCount;
    }

    @ManagedAttribute
    @Override
    public Long getUnsuccessfulRetries() {
        return unsuccessfulRetries;
    }

    @ManagedOperation
    @Override
    public String showMetrics() {
        StringBuilder builder = new StringBuilder();
        builder.append("GatewayConfigMBean [errorRate=").append(errorRate).append(", latency=").append(latency)
                .append(", retryCount=").append(retryCount).append(", throughput=").append(throughput)
                .append(", unsuccessfulRetries=").append(unsuccessfulRetries).append("]");
        return builder.toString();
    }

}

Then I exposed this Bean in the Config class:

@Bean("My_Microservice_Name")
    public ConfigMBean configMBean () {
        return new ConfigMBean ();
}

I was able to see My_Microservice_Name on the JConsole MBeans tab, but as expected all values were null.

Question: How to use Spring Micrometer to have correct values assigned to these metrics each? Also if I need to use some kind of AOP to get these metrics, how I need to incorporate that in my code?

I was searching on the internet and found a few hits:

    import io.micrometer.core.instrument.Counter;
    import io.micrometer.core.instrument.Metrics;

    private Counter counter = Metrics.counter("ErrorCount");
    counter.increment();

    private Timer timer = Metrics.timer("Latency");
    // Assume startTime was declared earlier
    timer.record(System.nanoTime() - startTime, TimeUnit.NANOSECONDS);

But not sure how to get the other Metrics and set these values to ConfigMBean class? Assume all dependencies are available for use in Spring. Need help with the above queries, please mention if any other information is also required.

1

1 Answers

0
votes

The best way to tackle this was to add the following dependency:

<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-registry-jmx</artifactId>
</dependency>

Using this dependency, we can have

A. private static final Counter counterErrorCount = Metrics.counter("ErrorRate");
B. private static final Counter counterThroughput = Metrics.counter("Throughput");
C. private static final Timer timerLatency = Metrics.timer("Latency");
D. private static final Counter counterRetryCount = Metrics.counter("RetryCount");
E. private static final Counter counterUnsuccessfulRetryCount = Metrics.counter("UnsuccessfulRetryCount");

Import from the following package wherever necessary: io.micrometer.core.instrument.*

For the latency part, have some logic like this:

long startTime = System.nanoTime();
try {
    // Some code already here
} catch (){
    // Some code already here
} finally {
    timerLatency.record(System.nanoTime() - startTime, TimeUnit.NANOSECONDS);
}

And for the Counters, we need to identify where to put the incremental code, i.e. counterErrorCount.increment(); and others.

Finally, we can equate these Metrics with the above POJO class, and Metrics shall be exposed to JConsole.

Open JConsole (for the respective PID) -> MBeans tab -> metrics check-box -> Desired metrics -> Attributes.