Sunday, March 17, 2024

All about Dropwizard Metrics

Dropwizard stands out as a favored Java framework for developing rest-based applications. Ensuring the observability of an application stands as a critical aspect for maintaining service continuity and stability. This directly impacts the end-user experience and, in many instances, revenue generation. The Metrics library simplifies the process within a Dropwizard application, enabling the collection of various system metrics alongside custom metrics. Additionally, it facilitates the authoring of health checks for the application.

To integrate metrics into a Dropwizard application, adding the following dependency is essential:

<dependency>
    <groupId>io.dropwizard.metrics</groupId>
    <artifactId>metrics-core</artifactId>
    <version>3.1.2</version>
</dependency>

Metrics Overview:

Metrics necessitate a metric registry to function effectively, which can accommodate numerous metrics.

private final MetricRegistry metrics = new MetricRegistry();

In a Dropwizard application, access to the metric registry and health check registry is available in the initialize method:

public void initialize(final Bootstrap<AppConfiguration> bootstrap) {
    metrics = bootstrap.getMetricRegistry();
    healthCheckRegistry = bootstrap.getHealthCheckRegistry();
}

Metrics are broadly categorized into:

  1. Meters:
    Meters gauge rates of events and track mean rates. They also monitor 1, 5, and 15-minute moving averages.
    private final Meter requests = metrics.meter("requests");
    public void handleRequest(Request request, Response response) {
        requests.mark();
        // ...
    }
    
  2. Gauges:
    Gauges provide a snapshot of a particular value at a given time. For instance, the number of pending jobs.
    final Queue<String> queue = new LinkedList<String>();
    final Gauge<Integer> pendingJobs = new Gauge<Integer>() {
        public Integer getValue() {
            return queue.size();
        }
    };
    
  3. Counters:
    Counters are a type of gauge that can be incremented or decremented, offering more efficiency than gauges.
    private final Counter pendingJobs = metrics.counter(name(QueueManager.class, "pending-jobs"));
    public void addJob(Job job) {
        pendingJobs.inc();
        queue.offer(job);
    }
    public Job takeJob() {
        pendingJobs.dec();
        return queue.take();
    }
    
  4. Histograms:
    Histograms track various statistical aspects of a data stream, including minimum, maximum, mean, median, and various percentiles.
    private final Histogram stockPrice = metrics.histogram(name(StockPriceHandler.class, "stock-price"));
    public void updateStockPrice(Request request, Response response) {
        stockPrice.update(request.getStockPrice());
    }
    
  5. Timers:
    Timers measure both the rate at which a piece of code is called and the duration of its execution.
    private final Timer responses = metrics.timer(name(RequestHandler.class, "responses"));
    public String handleRequest(Request request, Response response) {
        try(final Timer.Context context = responses.time()) {
            return "OK";
        } 
    }
    
    Alternatively, the @Timed annotation can be used:
    @GET
    @Path("sayhello")
    @Timed(name = "my-timed-metric")
    public Response sayHello(@QueryParam("q") @DefaultValue("%") String queryString) {
        return Response.ok().entity("Hello " + queryString).build(); 
    }
    
  6. Healthchecks:
    Health checks determine whether a service is healthy or not.
    public class DatabaseHealthCheck extends HealthCheck {
        public final static String NAME = "database-health";
        private final HibernateBundle<ApiAppConfiguration> hibernate;
    
        public DatabaseHealthCheck(HibernateBundle<AppConfiguration> hibernate) {
            this.hibernate = hibernate;
        }
        @Override
        protected Result check() throws Exception {
            if (hibernate.getSessionFactory().isClosed()) {
                return Result.unhealthy("Database session factory is closed");
            }
            return Result.healthy();
        }
    }
    
    Registration of the health check in the health check registry is necessary:
    environment.healthChecks().register(DatabaseHealthCheck.NAME, new DatabaseHealthCheck(hibernate));
    

These metrics play a crucial role in monitoring and maintaining the health and performance of applications.

Reporters play a crucial role in outputting metrics gathered by Dropwizard. There are several out-of-the-box popular reporters available, including console, CSV, SLF4J, Graphite, and Prometheus.

Here is an example of a console reporter:

ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics).build();
reporter.start(10, TimeUnit.SECONDS);

Additionally, we can create our custom reporter to integrate with our monitoring system:

package example.monitoring;

import com.codahale.metrics.*;

import java.util.SortedMap;
import java.util.concurrent.TimeUnit;
import java.util.Map;

public class MyCustomReporter extends ScheduledReporter {

    public MyCustomReporter(MetricRegistry registry, TimeUnit rateUnit, TimeUnit durationUnit) {
        super(registry, "custom-metric-reporter", MetricFilter.ALL, rateUnit, durationUnit);
    }

    // This method will be called periodically by the ScheduledReporter
    @Override
    public void report(SortedMap<String, Gauge> gauges,
                       SortedMap<String, Counter> counters,
                       SortedMap<String, Histogram> histograms,
                       SortedMap<String, Meter> meters,
                       SortedMap<String, Timer> timers) {
        // Implement reporting logic here
        // Example: Print out all metric names and their values
        System.out.println("MyCustomReporter in action !!!");
        for (Map.Entry<String, Gauge> entry : gauges.entrySet()) {
            System.out.println("Gauge: " + entry.getKey() + ", Value: " + entry.getValue().getValue());
        }
        for (Map.Entry<String, Counter> entry : counters.entrySet()) {
            System.out.println("Counter: " + entry.getKey() + ", Count: " + entry.getValue().getCount());
        }
        // Similarly, report for histograms, meters, and timers
    }
    // Factory method to create an instance of CustomMetricReporter
    public static MyCustomReporter forRegistry(MetricRegistry registry) {
        return new MyCustomReporter(registry, TimeUnit.SECONDS, TimeUnit.SECONDS);
    }
}

MyCustomReporter myCustomReporter = MyCustomReporter.forRegistry(metrics);
myCustomReporter.start(10, TimeUnit.SECONDS); // Report every 10 seconds

I hope this information is useful. Please let me know if you have any thoughts or questions about it.

Reference: Dropwizard Metrics Documentation

No comments:

Post a Comment

Thanks.