Comprehensive metrics collection and monitoring library providing counters, gauges, histograms, meters, and timers for Java applications.
—
Beyond the basic Gauge<T> interface, Metrics Core provides several specialized gauge implementations that address common patterns in application monitoring. These advanced gauge types offer caching, value derivation, ratio calculations, and settable values while maintaining the same lightweight, thread-safe characteristics as core metrics.
CachedGauge is an abstract base class that caches gauge values for a specified duration, reducing the computational cost of expensive gauge calculations. This is particularly useful when the underlying value is expensive to compute but doesn't change frequently.
public abstract class CachedGauge<T> implements Gauge<T> {
// Constructors
public CachedGauge(long timeout, TimeUnit timeoutUnit);
public CachedGauge(Clock clock, long timeout, TimeUnit timeoutUnit);
// Implemented method
public T getValue();
// Abstract method to implement
protected abstract T loadValue();
}Database Connection Pool Size:
// Cache expensive database query result for 30 seconds
CachedGauge<Integer> connectionPoolSize = new CachedGauge<Integer>(30, TimeUnit.SECONDS) {
@Override
protected Integer loadValue() {
// Expensive operation - query database connection pool
return dataSource.getActiveConnections().size();
}
};
registry.gauge("db.pool.active.connections", connectionPoolSize);File System Space Usage:
// Cache disk space calculation for 5 minutes
CachedGauge<Long> diskSpaceUsed = new CachedGauge<Long>(5, TimeUnit.MINUTES) {
@Override
protected Long loadValue() {
// Expensive file system operation
Path dataDir = Paths.get("/var/data");
try {
return Files.walk(dataDir)
.filter(Files::isRegularFile)
.mapToLong(path -> {
try {
return Files.size(path);
} catch (IOException e) {
return 0L;
}
})
.sum();
} catch (IOException e) {
return -1L;
}
}
};
registry.gauge("filesystem.data.used.bytes", diskSpaceUsed);Custom Clock for Testing:
// Using custom clock for deterministic testing
Clock testClock = new Clock() {
private long time = 0;
public long getTick() { return time; }
public long getTime() { return time; }
public void advance(long millis) { time += millis * 1_000_000; }
};
CachedGauge<String> testGauge = new CachedGauge<String>(testClock, 1, TimeUnit.SECONDS) {
@Override
protected String loadValue() {
return "computed-" + System.currentTimeMillis();
}
};DerivativeGauge creates a new gauge by transforming the value of an existing gauge. This is useful for converting units, calculating percentages, or performing other transformations on existing metrics.
public abstract class DerivativeGauge<F, T> implements Gauge<T> {
// Constructor
public DerivativeGauge(Gauge<F> base);
// Implemented method
public T getValue();
// Abstract method to implement
protected abstract T transform(F value);
}Memory Usage Percentage:
// Base gauge for used memory in bytes
Gauge<Long> usedMemoryBytes = () -> {
Runtime runtime = Runtime.getRuntime();
return runtime.totalMemory() - runtime.freeMemory();
};
registry.gauge("memory.used.bytes", usedMemoryBytes);
// Derived gauge for memory usage percentage
DerivativeGauge<Long, Double> memoryUsagePercent = new DerivativeGauge<Long, Double>(usedMemoryBytes) {
@Override
protected Double transform(Long usedBytes) {
Runtime runtime = Runtime.getRuntime();
long totalBytes = runtime.totalMemory();
return totalBytes > 0 ? (usedBytes.doubleValue() / totalBytes) * 100.0 : 0.0;
}
};
registry.gauge("memory.used.percent", memoryUsagePercent);Unit Conversion:
// Base gauge in bytes
Gauge<Long> fileSizeBytes = () -> {
try {
return Files.size(Paths.get("/var/log/application.log"));
} catch (IOException e) {
return 0L;
}
};
// Derived gauge in megabytes
DerivativeGauge<Long, Double> fileSizeMB = new DerivativeGauge<Long, Double>(fileSizeBytes) {
@Override
protected Double transform(Long bytes) {
return bytes / (1024.0 * 1024.0);
}
};
registry.gauge("log.file.size.mb", fileSizeMB);Status Code Mapping:
// Base gauge returning numeric status
Gauge<Integer> serviceStatus = () -> healthChecker.getStatusCode();
// Derived gauge converting to human-readable status
DerivativeGauge<Integer, String> serviceStatusText = new DerivativeGauge<Integer, String>(serviceStatus) {
@Override
protected String transform(Integer statusCode) {
switch (statusCode) {
case 200: return "HEALTHY";
case 503: return "DEGRADED";
case 500: return "UNHEALTHY";
default: return "UNKNOWN";
}
}
};
registry.gauge("service.status.text", serviceStatusText);RatioGauge calculates ratios between two values with proper handling of edge cases like division by zero. It returns NaN when the denominator is zero or both numerator and denominator are zero.
public abstract class RatioGauge implements Gauge<Double> {
// Implemented method
public Double getValue();
// Abstract method to implement
protected abstract Ratio getRatio();
// Nested class for ratio representation
public static class Ratio {
public static Ratio of(double numerator, double denominator);
public double getNumerator();
public double getDenominator();
}
}Cache Hit Rate:
Counter cacheHits = registry.counter("cache.hits");
Counter cacheMisses = registry.counter("cache.misses");
RatioGauge cacheHitRate = new RatioGauge() {
@Override
protected Ratio getRatio() {
return Ratio.of(cacheHits.getCount(), cacheHits.getCount() + cacheMisses.getCount());
}
};
registry.gauge("cache.hit.rate", cacheHitRate);Error Rate:
Counter successfulRequests = registry.counter("requests.successful");
Counter failedRequests = registry.counter("requests.failed");
RatioGauge errorRate = new RatioGauge() {
@Override
protected Ratio getRatio() {
long successful = successfulRequests.getCount();
long failed = failedRequests.getCount();
return Ratio.of(failed, successful + failed);
}
};
registry.gauge("requests.error.rate", errorRate);Memory Usage Ratio:
RatioGauge heapUsageRatio = new RatioGauge() {
@Override
protected Ratio getRatio() {
Runtime runtime = Runtime.getRuntime();
long used = runtime.totalMemory() - runtime.freeMemory();
long total = runtime.maxMemory();
return Ratio.of(used, total);
}
};
registry.gauge("memory.heap.usage.ratio", heapUsageRatio);Resource Utilization:
// Thread pool utilization
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
RatioGauge threadPoolUtilization = new RatioGauge() {
@Override
protected Ratio getRatio() {
return Ratio.of(executor.getActiveCount(), executor.getMaximumPoolSize());
}
};
registry.gauge("threadpool.utilization", threadPoolUtilization);SettableGauge extends the basic Gauge interface to allow explicit setting of values. This is useful for metrics that are updated by external processes or for values that need to be set programmatically rather than calculated on demand.
public interface SettableGauge<T> extends Gauge<T> {
void setValue(T value);
}The default implementation of SettableGauge provides thread-safe value storage and retrieval.
public class DefaultSettableGauge<T> implements SettableGauge<T> {
// Constructors
public DefaultSettableGauge();
public DefaultSettableGauge(T initialValue);
// SettableGauge implementation
public void setValue(T value);
public T getValue();
}Configuration Values:
// Track current configuration values
DefaultSettableGauge<String> activeProfile = new DefaultSettableGauge<>("development");
registry.gauge("config.active.profile", activeProfile);
DefaultSettableGauge<Integer> maxConnections = new DefaultSettableGauge<>(100);
registry.gauge("config.max.connections", maxConnections);
// Update when configuration changes
configurationManager.addListener(new ConfigurationListener() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
activeProfile.setValue(newConfig.getProfile());
maxConnections.setValue(newConfig.getMaxConnections());
}
});External System Status:
// Track status reported by external monitoring system
DefaultSettableGauge<String> externalServiceStatus = new DefaultSettableGauge<>("UNKNOWN");
registry.gauge("external.service.status", externalServiceStatus);
// Update from monitoring callback
monitoringSystem.registerCallback(status -> {
externalServiceStatus.setValue(status.toString());
});Batch Processing Metrics:
// Track batch processing statistics
DefaultSettableGauge<Long> lastBatchSize = new DefaultSettableGauge<>(0L);
DefaultSettableGauge<Long> lastBatchDuration = new DefaultSettableGauge<>(0L);
registry.gauge("batch.last.size", lastBatchSize);
registry.gauge("batch.last.duration.ms", lastBatchDuration);
// Update after each batch completes
public void processBatch(List<Item> items) {
long startTime = System.currentTimeMillis();
// Process batch...
processItems(items);
long duration = System.currentTimeMillis() - startTime;
lastBatchSize.setValue((long) items.size());
lastBatchDuration.setValue(duration);
}Feature Flags:
// Track feature flag states
Map<String, DefaultSettableGauge<Boolean>> featureFlags = new HashMap<>();
public void registerFeatureFlag(String flagName, boolean initialState) {
DefaultSettableGauge<Boolean> flagGauge = new DefaultSettableGauge<>(initialState);
featureFlags.put(flagName, flagGauge);
registry.gauge("feature.flag." + flagName, flagGauge);
}
public void updateFeatureFlag(String flagName, boolean enabled) {
DefaultSettableGauge<Boolean> flagGauge = featureFlags.get(flagName);
if (flagGauge != null) {
flagGauge.setValue(enabled);
}
}You can combine different gauge types to create sophisticated monitoring solutions:
// Cached base gauge for expensive computation
CachedGauge<Double> rawProcessorLoad = new CachedGauge<Double>(5, TimeUnit.SECONDS) {
@Override
protected Double loadValue() {
return managementFactory.getOperatingSystemMXBean().getProcessCpuLoad();
}
};
// Derived gauge for percentage conversion
DerivativeGauge<Double, Integer> processorLoadPercent = new DerivativeGauge<Double, Integer>(rawProcessorLoad) {
@Override
protected Integer transform(Double load) {
return (int) Math.round(load * 100);
}
};
// Settable gauge for alert threshold
DefaultSettableGauge<Integer> alertThreshold = new DefaultSettableGauge<>(80);
// Derived gauge for alert status
DerivativeGauge<Integer, String> alertStatus = new DerivativeGauge<Integer, String>(processorLoadPercent) {
@Override
protected String transform(Integer currentLoad) {
return currentLoad > alertThreshold.getValue() ? "ALERT" : "OK";
}
};
registry.gauge("cpu.load.raw", rawProcessorLoad);
registry.gauge("cpu.load.percent", processorLoadPercent);
registry.gauge("cpu.alert.threshold", alertThreshold);
registry.gauge("cpu.alert.status", alertStatus);Create chains of derived gauges for complex transformations:
// Chain: raw bytes -> MB -> formatted string
Gauge<Long> diskUsageBytes = () -> getDiskUsageInBytes();
DerivativeGauge<Long, Double> diskUsageMB = new DerivativeGauge<Long, Double>(diskUsageBytes) {
@Override
protected Double transform(Long bytes) {
return bytes / (1024.0 * 1024.0);
}
};
DerivativeGauge<Double, String> diskUsageFormatted = new DerivativeGauge<Double, String>(diskUsageMB) {
@Override
protected String transform(Double mb) {
return String.format("%.1f MB", mb);
}
};CachedGauge for expensive computations that don't need real-time accuracyRatioGauge for division operations to handle edge cases automaticallyDefaultSettableGauge uses atomic operations for thread-safe value updatesClock implementations in CachedGauge for deterministic testingRatioGauge implementations (zero denominators, negative values)Install with Tessl CLI
npx tessl i tessl/maven-io-dropwizard-metrics--metrics-core