Comprehensive metrics collection and monitoring library providing counters, gauges, histograms, meters, and timers for Java applications.
—
The core metrics framework provides the fundamental building blocks for application monitoring: the central registry system and the five primary metric types. These components form the foundation for all metrics collection and are designed for high-performance, thread-safe operation in production environments.
The MetricRegistry is the central repository for all metrics in an application. It provides factory methods for creating metrics, manages metric lifecycles, and serves as the primary integration point for reporting systems.
public class MetricRegistry implements MetricSet {
// Constructor
public MetricRegistry();
// Metric factory methods - primary API for creating metrics
public Counter counter(String name);
public Counter counter(String name, MetricSupplier<Counter> supplier);
public Histogram histogram(String name);
public Histogram histogram(String name, MetricSupplier<Histogram> supplier);
public Meter meter(String name);
public Meter meter(String name, MetricSupplier<Meter> supplier);
public Timer timer(String name);
public Timer timer(String name, MetricSupplier<Timer> supplier);
public <T extends Gauge> T gauge(String name);
public <T extends Gauge> T gauge(String name, MetricSupplier<T> supplier);
// Generic registration and management
public <T extends Metric> T register(String name, T metric) throws IllegalArgumentException;
public <T> Gauge<T> registerGauge(String name, Gauge<T> metric) throws IllegalArgumentException;
public void registerAll(MetricSet metrics) throws IllegalArgumentException;
public void registerAll(String prefix, MetricSet metrics) throws IllegalArgumentException;
public boolean remove(String name);
public void removeMatching(MetricFilter filter);
// Registry inspection
public SortedSet<String> getNames();
// Metric retrieval by type
public SortedMap<String, Counter> getCounters();
public SortedMap<String, Counter> getCounters(MetricFilter filter);
public SortedMap<String, Gauge> getGauges();
public SortedMap<String, Gauge> getGauges(MetricFilter filter);
public SortedMap<String, Histogram> getHistograms();
public SortedMap<String, Histogram> getHistograms(MetricFilter filter);
public SortedMap<String, Meter> getMeters();
public SortedMap<String, Meter> getMeters(MetricFilter filter);
public SortedMap<String, Timer> getTimers();
public SortedMap<String, Timer> getTimers(MetricFilter filter);
public Map<String, Metric> getMetrics();
// Listener management
public void addListener(MetricRegistryListener listener);
public void removeListener(MetricRegistryListener listener);
// Utility methods for metric naming
public static String name(String name, String... names);
public static String name(Class<?> klass, String... names);
// MetricSet implementation
public Map<String, Metric> getMetrics();
}Basic Registry Setup:
MetricRegistry registry = new MetricRegistry();
// Create metrics using factory methods
Counter requestCount = registry.counter("requests");
Timer responseTime = registry.timer("response.time");
Histogram responseSizes = registry.histogram("response.sizes");Hierarchical Metric Naming:
// Using static name() method for dot-separated hierarchical names
String timerName = MetricRegistry.name("http", "requests", "GET", "/api/users");
Timer timer = registry.timer(timerName); // Creates "http.requests.GET./api/users"
// Using class-based naming
Timer classTimer = registry.timer(MetricRegistry.name(UserService.class, "createUser"));
// Creates "com.example.UserService.createUser"Registry Filtering:
// Get only counters with names starting with "http"
SortedMap<String, Counter> httpCounters = registry.getCounters(
MetricFilter.startsWith("http"));
// Remove all metrics matching a pattern
registry.removeMatching(MetricFilter.contains("temp"));Counters are the simplest metric type, providing thread-safe increment and decrement operations. They maintain a running total that can only be changed by specified amounts.
public class Counter implements Metric, Counting {
// Constructor
public Counter();
// Increment operations
public void inc();
public void inc(long n);
// Decrement operations
public void dec();
public void dec(long n);
// Value access
public long getCount();
}Counter requestCounter = registry.counter("requests.total");
Counter errorCounter = registry.counter("errors.total");
// Basic incrementing
requestCounter.inc(); // Increment by 1
requestCounter.inc(5); // Increment by 5
// Error tracking
if (errorOccurred) {
errorCounter.inc();
}
// Batch processing
int batchSize = processedItems.size();
requestCounter.inc(batchSize);
// Current value
long totalRequests = requestCounter.getCount();Gauges provide instantaneous readings of arbitrary values. They are functional interfaces that you implement to return current values, making them ideal for measuring things like queue sizes, memory usage, or any other fluctuating values.
@FunctionalInterface
public interface Gauge<T> extends Metric {
T getValue();
}import java.util.concurrent.ConcurrentLinkedQueue;
Queue<String> processingQueue = new ConcurrentLinkedQueue<>();
// Lambda gauge for queue size
Gauge<Integer> queueSize = processingQueue::size;
registry.gauge("queue.size", queueSize);
// Anonymous class gauge
registry.gauge("memory.usage", new Gauge<Long>() {
@Override
public Long getValue() {
Runtime runtime = Runtime.getRuntime();
return runtime.totalMemory() - runtime.freeMemory();
}
});
// Method reference gauge
registry.gauge("active.threads", Thread::activeCount);
// Custom calculation gauge
registry.gauge("cpu.utilization", () -> {
return managementFactory.getOperatingSystemMXBean().getProcessCpuLoad();
});Meters measure the rate of events over time, providing mean throughput and exponentially-weighted moving averages over 1, 5, and 15-minute windows. They're similar to Unix load averages but for arbitrary events.
public class Meter implements Metered {
// Constructors
public Meter();
public Meter(MovingAverages movingAverages);
public Meter(Clock clock);
public Meter(MovingAverages movingAverages, Clock clock);
// Event marking
public void mark();
public void mark(long n);
// Rate access (from Metered interface)
public long getCount();
public double getMeanRate();
public double getOneMinuteRate();
public double getFiveMinuteRate();
public double getFifteenMinuteRate();
}Meter requestMeter = registry.meter("requests.rate");
Meter errorMeter = registry.meter("errors.rate");
// Mark single events
requestMeter.mark();
// Mark multiple events
int batchSize = 10;
requestMeter.mark(batchSize);
// Error rate tracking
try {
processRequest();
requestMeter.mark();
} catch (Exception e) {
errorMeter.mark();
throw e;
}
// Accessing rates
double avgRequestsPerSecond = requestMeter.getMeanRate();
double recentRequestsPerSecond = requestMeter.getOneMinuteRate();
System.out.printf("Requests: %.2f/sec (1min: %.2f/sec)%n",
avgRequestsPerSecond, recentRequestsPerSecond);Histograms measure the statistical distribution of values in a stream of data. They provide statistical summaries including minimum, maximum, mean, standard deviation, and various percentiles, while using configurable sampling strategies to manage memory usage.
public class Histogram implements Metric, Sampling, Counting {
// Constructor
public Histogram(Reservoir reservoir);
// Value recording
public void update(int value);
public void update(long value);
// Statistical access
public long getCount();
public Snapshot getSnapshot();
}// Using default exponentially decaying reservoir
Histogram responseSizes = registry.histogram("response.sizes");
// Using custom reservoir
Histogram customHistogram = new Histogram(new UniformReservoir(1000));
registry.register("custom.distribution", customHistogram);
// Recording values
responseSizes.update(1024); // Response size in bytes
responseSizes.update(2048);
responseSizes.update(512);
// Getting statistics
Snapshot snapshot = responseSizes.getSnapshot();
System.out.printf("Response sizes - Count: %d, Mean: %.2f, Median: %.2f, 95th: %.2f%n",
responseSizes.getCount(),
snapshot.getMean(),
snapshot.getMedian(),
snapshot.get95thPercentile());
// Batch processing statistics
List<Integer> responseTimes = getResponseTimes();
for (int time : responseTimes) {
responseSizes.update(time);
}Timers are essentially histograms of duration measurements that also track the rate of events. They provide comprehensive timing statistics including duration distributions and throughput metrics, making them ideal for measuring method execution times, request processing times, and other time-based operations.
public class Timer implements Metered, Sampling {
// Constructors
public Timer();
public Timer(Reservoir reservoir);
public Timer(Reservoir reservoir, Clock clock);
// Duration recording
public void update(long duration, TimeUnit unit);
public void update(Duration duration);
// Timing convenience methods
public <T> T time(Callable<T> event) throws Exception;
public void time(Runnable event);
public <T> T timeSupplier(Supplier<T> event);
public Context time();
// Statistical access (from Sampling)
public Snapshot getSnapshot();
// Rate access (from Metered)
public long getCount();
public double getMeanRate();
public double getOneMinuteRate();
public double getFiveMinuteRate();
public double getFifteenMinuteRate();
// Nested Context class for manual timing
public static class Context implements AutoCloseable {
public long stop();
public void close();
}
}Manual Timing with Context:
Timer requestTimer = registry.timer("request.duration");
// Manual timing with try-with-resources
try (Timer.Context context = requestTimer.time()) {
processRequest();
// Timing stops automatically when context closes
}
// Manual timing with explicit stop
Timer.Context context = requestTimer.time();
try {
processRequest();
} finally {
long elapsed = context.stop(); // Returns elapsed time in nanoseconds
System.out.println("Request took " + elapsed + " nanoseconds");
}Timing Callables and Runnables:
Timer dbTimer = registry.timer("database.query.time");
// Time a Callable (with return value)
String result = dbTimer.time(() -> {
return database.executeQuery("SELECT * FROM users");
});
// Time a Runnable (no return value)
dbTimer.time(() -> {
database.executeUpdate("UPDATE users SET last_login = NOW()");
});
// Time a Supplier
List<User> users = dbTimer.timeSupplier(() -> userService.getAllUsers());Direct Duration Recording:
Timer responseTimer = registry.timer("response.time");
long startTime = System.nanoTime();
processRequest();
long endTime = System.nanoTime();
// Record duration directly
responseTimer.update(endTime - startTime, TimeUnit.NANOSECONDS);
// Record using Duration (Java 8+)
Instant start = Instant.now();
processRequest();
Duration elapsed = Duration.between(start, Instant.now());
responseTimer.update(elapsed);Accessing Timer Statistics:
Timer timer = registry.timer("api.request.time");
// Get duration statistics
Snapshot snapshot = timer.getSnapshot();
System.out.printf("Request timing - Count: %d, Mean: %.2fms, 99th: %.2fms%n",
timer.getCount(),
snapshot.getMean() / 1_000_000.0, // Convert nanoseconds to milliseconds
snapshot.get99thPercentile() / 1_000_000.0);
// Get rate statistics
System.out.printf("Request rate - Mean: %.2f/sec, 1min: %.2f/sec%n",
timer.getMeanRate(),
timer.getOneMinuteRate());The core metrics implement several key interfaces that provide common functionality:
public interface Counting {
long getCount();
}Implemented by: Counter, Histogram, Meter, Timer
public interface Metered extends Metric, Counting {
long getCount();
double getFifteenMinuteRate();
double getFiveMinuteRate();
double getMeanRate();
double getOneMinuteRate();
}Implemented by: Meter, Timer
public interface Sampling {
Snapshot getSnapshot();
}Implemented by: Histogram, Timer
public interface Metric {
// Tag interface - no methods
}Base interface implemented by all metric types.
"http.requests.GET.api.users"MetricRegistry.name() utility methods for consistent namingMetricRegistry per application (use SharedMetricRegistries for global access)IllegalArgumentException for duplicate namesInstall with Tessl CLI
npx tessl i tessl/maven-io-dropwizard-metrics--metrics-core