CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-dropwizard-metrics--metrics-core

Comprehensive metrics collection and monitoring library providing counters, gauges, histograms, meters, and timers for Java applications.

Pending
Overview
Eval results
Files

advanced-gauges.mddocs/

Advanced Gauge Types

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

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();
}

Usage Examples

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

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);
}

Usage Examples

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

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();
    }
}

Usage Examples

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

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);
}

DefaultSettableGauge

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();
}

Usage Examples

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);
    }
}

Advanced Patterns

Combining Gauge Types

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);

Gauge Chaining

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);
    }
};

Best Practices

Performance Considerations

  • Use CachedGauge for expensive computations that don't need real-time accuracy
  • Cache duration should balance accuracy requirements with computational cost
  • Consider the frequency of gauge reads when setting cache timeouts

Error Handling

  • Always handle exceptions in gauge implementations to prevent metric collection failures
  • Return meaningful default values (like -1 or 0) when computations fail
  • Use RatioGauge for division operations to handle edge cases automatically

Thread Safety

  • All provided gauge implementations are thread-safe
  • When implementing custom gauges, ensure thread safety if accessed concurrently
  • DefaultSettableGauge uses atomic operations for thread-safe value updates

Testing

  • Use custom Clock implementations in CachedGauge for deterministic testing
  • Test edge cases in RatioGauge implementations (zero denominators, negative values)
  • Verify that derived gauges handle null or exceptional base gauge values appropriately

Install with Tessl CLI

npx tessl i tessl/maven-io-dropwizard-metrics--metrics-core

docs

advanced-gauges.md

core-metrics.md

index.md

reporting.md

reservoirs-sampling.md

utilities.md

tile.json