0
# Advanced Gauge Types
1
2
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.
3
4
## CachedGauge
5
6
`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.
7
8
```java { .api }
9
public abstract class CachedGauge<T> implements Gauge<T> {
10
// Constructors
11
public CachedGauge(long timeout, TimeUnit timeoutUnit);
12
public CachedGauge(Clock clock, long timeout, TimeUnit timeoutUnit);
13
14
// Implemented method
15
public T getValue();
16
17
// Abstract method to implement
18
protected abstract T loadValue();
19
}
20
```
21
22
### Usage Examples
23
24
**Database Connection Pool Size:**
25
```java
26
// Cache expensive database query result for 30 seconds
27
CachedGauge<Integer> connectionPoolSize = new CachedGauge<Integer>(30, TimeUnit.SECONDS) {
28
@Override
29
protected Integer loadValue() {
30
// Expensive operation - query database connection pool
31
return dataSource.getActiveConnections().size();
32
}
33
};
34
registry.gauge("db.pool.active.connections", connectionPoolSize);
35
```
36
37
**File System Space Usage:**
38
```java
39
// Cache disk space calculation for 5 minutes
40
CachedGauge<Long> diskSpaceUsed = new CachedGauge<Long>(5, TimeUnit.MINUTES) {
41
@Override
42
protected Long loadValue() {
43
// Expensive file system operation
44
Path dataDir = Paths.get("/var/data");
45
try {
46
return Files.walk(dataDir)
47
.filter(Files::isRegularFile)
48
.mapToLong(path -> {
49
try {
50
return Files.size(path);
51
} catch (IOException e) {
52
return 0L;
53
}
54
})
55
.sum();
56
} catch (IOException e) {
57
return -1L;
58
}
59
}
60
};
61
registry.gauge("filesystem.data.used.bytes", diskSpaceUsed);
62
```
63
64
**Custom Clock for Testing:**
65
```java
66
// Using custom clock for deterministic testing
67
Clock testClock = new Clock() {
68
private long time = 0;
69
public long getTick() { return time; }
70
public long getTime() { return time; }
71
public void advance(long millis) { time += millis * 1_000_000; }
72
};
73
74
CachedGauge<String> testGauge = new CachedGauge<String>(testClock, 1, TimeUnit.SECONDS) {
75
@Override
76
protected String loadValue() {
77
return "computed-" + System.currentTimeMillis();
78
}
79
};
80
```
81
82
## DerivativeGauge
83
84
`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.
85
86
```java { .api }
87
public abstract class DerivativeGauge<F, T> implements Gauge<T> {
88
// Constructor
89
public DerivativeGauge(Gauge<F> base);
90
91
// Implemented method
92
public T getValue();
93
94
// Abstract method to implement
95
protected abstract T transform(F value);
96
}
97
```
98
99
### Usage Examples
100
101
**Memory Usage Percentage:**
102
```java
103
// Base gauge for used memory in bytes
104
Gauge<Long> usedMemoryBytes = () -> {
105
Runtime runtime = Runtime.getRuntime();
106
return runtime.totalMemory() - runtime.freeMemory();
107
};
108
registry.gauge("memory.used.bytes", usedMemoryBytes);
109
110
// Derived gauge for memory usage percentage
111
DerivativeGauge<Long, Double> memoryUsagePercent = new DerivativeGauge<Long, Double>(usedMemoryBytes) {
112
@Override
113
protected Double transform(Long usedBytes) {
114
Runtime runtime = Runtime.getRuntime();
115
long totalBytes = runtime.totalMemory();
116
return totalBytes > 0 ? (usedBytes.doubleValue() / totalBytes) * 100.0 : 0.0;
117
}
118
};
119
registry.gauge("memory.used.percent", memoryUsagePercent);
120
```
121
122
**Unit Conversion:**
123
```java
124
// Base gauge in bytes
125
Gauge<Long> fileSizeBytes = () -> {
126
try {
127
return Files.size(Paths.get("/var/log/application.log"));
128
} catch (IOException e) {
129
return 0L;
130
}
131
};
132
133
// Derived gauge in megabytes
134
DerivativeGauge<Long, Double> fileSizeMB = new DerivativeGauge<Long, Double>(fileSizeBytes) {
135
@Override
136
protected Double transform(Long bytes) {
137
return bytes / (1024.0 * 1024.0);
138
}
139
};
140
registry.gauge("log.file.size.mb", fileSizeMB);
141
```
142
143
**Status Code Mapping:**
144
```java
145
// Base gauge returning numeric status
146
Gauge<Integer> serviceStatus = () -> healthChecker.getStatusCode();
147
148
// Derived gauge converting to human-readable status
149
DerivativeGauge<Integer, String> serviceStatusText = new DerivativeGauge<Integer, String>(serviceStatus) {
150
@Override
151
protected String transform(Integer statusCode) {
152
switch (statusCode) {
153
case 200: return "HEALTHY";
154
case 503: return "DEGRADED";
155
case 500: return "UNHEALTHY";
156
default: return "UNKNOWN";
157
}
158
}
159
};
160
registry.gauge("service.status.text", serviceStatusText);
161
```
162
163
## RatioGauge
164
165
`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.
166
167
```java { .api }
168
public abstract class RatioGauge implements Gauge<Double> {
169
// Implemented method
170
public Double getValue();
171
172
// Abstract method to implement
173
protected abstract Ratio getRatio();
174
175
// Nested class for ratio representation
176
public static class Ratio {
177
public static Ratio of(double numerator, double denominator);
178
public double getNumerator();
179
public double getDenominator();
180
}
181
}
182
```
183
184
### Usage Examples
185
186
**Cache Hit Rate:**
187
```java
188
Counter cacheHits = registry.counter("cache.hits");
189
Counter cacheMisses = registry.counter("cache.misses");
190
191
RatioGauge cacheHitRate = new RatioGauge() {
192
@Override
193
protected Ratio getRatio() {
194
return Ratio.of(cacheHits.getCount(), cacheHits.getCount() + cacheMisses.getCount());
195
}
196
};
197
registry.gauge("cache.hit.rate", cacheHitRate);
198
```
199
200
**Error Rate:**
201
```java
202
Counter successfulRequests = registry.counter("requests.successful");
203
Counter failedRequests = registry.counter("requests.failed");
204
205
RatioGauge errorRate = new RatioGauge() {
206
@Override
207
protected Ratio getRatio() {
208
long successful = successfulRequests.getCount();
209
long failed = failedRequests.getCount();
210
return Ratio.of(failed, successful + failed);
211
}
212
};
213
registry.gauge("requests.error.rate", errorRate);
214
```
215
216
**Memory Usage Ratio:**
217
```java
218
RatioGauge heapUsageRatio = new RatioGauge() {
219
@Override
220
protected Ratio getRatio() {
221
Runtime runtime = Runtime.getRuntime();
222
long used = runtime.totalMemory() - runtime.freeMemory();
223
long total = runtime.maxMemory();
224
return Ratio.of(used, total);
225
}
226
};
227
registry.gauge("memory.heap.usage.ratio", heapUsageRatio);
228
```
229
230
**Resource Utilization:**
231
```java
232
// Thread pool utilization
233
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
234
235
RatioGauge threadPoolUtilization = new RatioGauge() {
236
@Override
237
protected Ratio getRatio() {
238
return Ratio.of(executor.getActiveCount(), executor.getMaximumPoolSize());
239
}
240
};
241
registry.gauge("threadpool.utilization", threadPoolUtilization);
242
```
243
244
## SettableGauge
245
246
`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.
247
248
```java { .api }
249
public interface SettableGauge<T> extends Gauge<T> {
250
void setValue(T value);
251
}
252
```
253
254
### DefaultSettableGauge
255
256
The default implementation of `SettableGauge` provides thread-safe value storage and retrieval.
257
258
```java { .api }
259
public class DefaultSettableGauge<T> implements SettableGauge<T> {
260
// Constructors
261
public DefaultSettableGauge();
262
public DefaultSettableGauge(T initialValue);
263
264
// SettableGauge implementation
265
public void setValue(T value);
266
public T getValue();
267
}
268
```
269
270
### Usage Examples
271
272
**Configuration Values:**
273
```java
274
// Track current configuration values
275
DefaultSettableGauge<String> activeProfile = new DefaultSettableGauge<>("development");
276
registry.gauge("config.active.profile", activeProfile);
277
278
DefaultSettableGauge<Integer> maxConnections = new DefaultSettableGauge<>(100);
279
registry.gauge("config.max.connections", maxConnections);
280
281
// Update when configuration changes
282
configurationManager.addListener(new ConfigurationListener() {
283
@Override
284
public void onConfigurationChanged(Configuration newConfig) {
285
activeProfile.setValue(newConfig.getProfile());
286
maxConnections.setValue(newConfig.getMaxConnections());
287
}
288
});
289
```
290
291
**External System Status:**
292
```java
293
// Track status reported by external monitoring system
294
DefaultSettableGauge<String> externalServiceStatus = new DefaultSettableGauge<>("UNKNOWN");
295
registry.gauge("external.service.status", externalServiceStatus);
296
297
// Update from monitoring callback
298
monitoringSystem.registerCallback(status -> {
299
externalServiceStatus.setValue(status.toString());
300
});
301
```
302
303
**Batch Processing Metrics:**
304
```java
305
// Track batch processing statistics
306
DefaultSettableGauge<Long> lastBatchSize = new DefaultSettableGauge<>(0L);
307
DefaultSettableGauge<Long> lastBatchDuration = new DefaultSettableGauge<>(0L);
308
registry.gauge("batch.last.size", lastBatchSize);
309
registry.gauge("batch.last.duration.ms", lastBatchDuration);
310
311
// Update after each batch completes
312
public void processBatch(List<Item> items) {
313
long startTime = System.currentTimeMillis();
314
315
// Process batch...
316
processItems(items);
317
318
long duration = System.currentTimeMillis() - startTime;
319
lastBatchSize.setValue((long) items.size());
320
lastBatchDuration.setValue(duration);
321
}
322
```
323
324
**Feature Flags:**
325
```java
326
// Track feature flag states
327
Map<String, DefaultSettableGauge<Boolean>> featureFlags = new HashMap<>();
328
329
public void registerFeatureFlag(String flagName, boolean initialState) {
330
DefaultSettableGauge<Boolean> flagGauge = new DefaultSettableGauge<>(initialState);
331
featureFlags.put(flagName, flagGauge);
332
registry.gauge("feature.flag." + flagName, flagGauge);
333
}
334
335
public void updateFeatureFlag(String flagName, boolean enabled) {
336
DefaultSettableGauge<Boolean> flagGauge = featureFlags.get(flagName);
337
if (flagGauge != null) {
338
flagGauge.setValue(enabled);
339
}
340
}
341
```
342
343
## Advanced Patterns
344
345
### Combining Gauge Types
346
347
You can combine different gauge types to create sophisticated monitoring solutions:
348
349
```java
350
// Cached base gauge for expensive computation
351
CachedGauge<Double> rawProcessorLoad = new CachedGauge<Double>(5, TimeUnit.SECONDS) {
352
@Override
353
protected Double loadValue() {
354
return managementFactory.getOperatingSystemMXBean().getProcessCpuLoad();
355
}
356
};
357
358
// Derived gauge for percentage conversion
359
DerivativeGauge<Double, Integer> processorLoadPercent = new DerivativeGauge<Double, Integer>(rawProcessorLoad) {
360
@Override
361
protected Integer transform(Double load) {
362
return (int) Math.round(load * 100);
363
}
364
};
365
366
// Settable gauge for alert threshold
367
DefaultSettableGauge<Integer> alertThreshold = new DefaultSettableGauge<>(80);
368
369
// Derived gauge for alert status
370
DerivativeGauge<Integer, String> alertStatus = new DerivativeGauge<Integer, String>(processorLoadPercent) {
371
@Override
372
protected String transform(Integer currentLoad) {
373
return currentLoad > alertThreshold.getValue() ? "ALERT" : "OK";
374
}
375
};
376
377
registry.gauge("cpu.load.raw", rawProcessorLoad);
378
registry.gauge("cpu.load.percent", processorLoadPercent);
379
registry.gauge("cpu.alert.threshold", alertThreshold);
380
registry.gauge("cpu.alert.status", alertStatus);
381
```
382
383
### Gauge Chaining
384
385
Create chains of derived gauges for complex transformations:
386
387
```java
388
// Chain: raw bytes -> MB -> formatted string
389
Gauge<Long> diskUsageBytes = () -> getDiskUsageInBytes();
390
391
DerivativeGauge<Long, Double> diskUsageMB = new DerivativeGauge<Long, Double>(diskUsageBytes) {
392
@Override
393
protected Double transform(Long bytes) {
394
return bytes / (1024.0 * 1024.0);
395
}
396
};
397
398
DerivativeGauge<Double, String> diskUsageFormatted = new DerivativeGauge<Double, String>(diskUsageMB) {
399
@Override
400
protected String transform(Double mb) {
401
return String.format("%.1f MB", mb);
402
}
403
};
404
```
405
406
## Best Practices
407
408
### Performance Considerations
409
- Use `CachedGauge` for expensive computations that don't need real-time accuracy
410
- Cache duration should balance accuracy requirements with computational cost
411
- Consider the frequency of gauge reads when setting cache timeouts
412
413
### Error Handling
414
- Always handle exceptions in gauge implementations to prevent metric collection failures
415
- Return meaningful default values (like -1 or 0) when computations fail
416
- Use `RatioGauge` for division operations to handle edge cases automatically
417
418
### Thread Safety
419
- All provided gauge implementations are thread-safe
420
- When implementing custom gauges, ensure thread safety if accessed concurrently
421
- `DefaultSettableGauge` uses atomic operations for thread-safe value updates
422
423
### Testing
424
- Use custom `Clock` implementations in `CachedGauge` for deterministic testing
425
- Test edge cases in `RatioGauge` implementations (zero denominators, negative values)
426
- Verify that derived gauges handle null or exceptional base gauge values appropriately