0
# Utilities and Extensions
1
2
Metrics Core provides a comprehensive set of utility classes and extensions that enhance the core functionality with global registry management, filtering capabilities, automatic instrumentation, and event listening. These utilities simplify integration and provide advanced functionality for complex monitoring scenarios.
3
4
## SharedMetricRegistries
5
6
`SharedMetricRegistries` provides global registry management, allowing different parts of an application to access the same metrics without explicit registry passing. This is particularly useful in modular applications, frameworks, and scenarios where dependency injection isn't available.
7
8
```java { .api }
9
public class SharedMetricRegistries {
10
// Global registry management
11
public static MetricRegistry getOrCreate(String name);
12
public static void setDefault(String name);
13
public static MetricRegistry getDefault();
14
15
// Named registry management
16
public static void add(String name, MetricRegistry registry);
17
public static void remove(String name);
18
public static Set<String> names();
19
public static void clear();
20
}
21
```
22
23
### Usage Examples
24
25
**Basic Global Registry:**
26
```java
27
// Set up a default registry early in application lifecycle
28
MetricRegistry mainRegistry = new MetricRegistry();
29
SharedMetricRegistries.add("main", mainRegistry);
30
SharedMetricRegistries.setDefault("main");
31
32
// Access from anywhere in the application
33
public class UserService {
34
private final Counter userCreations = SharedMetricRegistries.getDefault()
35
.counter("users.created");
36
37
public void createUser(User user) {
38
// ... create user logic ...
39
userCreations.inc();
40
}
41
}
42
43
public class OrderService {
44
private final Timer orderProcessing = SharedMetricRegistries.getDefault()
45
.timer("orders.processing.time");
46
47
public void processOrder(Order order) {
48
try (Timer.Context context = orderProcessing.time()) {
49
// ... process order logic ...
50
}
51
}
52
}
53
```
54
55
**Multiple Named Registries:**
56
```java
57
// Set up different registries for different subsystems
58
MetricRegistry webRegistry = new MetricRegistry();
59
MetricRegistry databaseRegistry = new MetricRegistry();
60
MetricRegistry cacheRegistry = new MetricRegistry();
61
62
SharedMetricRegistries.add("web", webRegistry);
63
SharedMetricRegistries.add("database", databaseRegistry);
64
SharedMetricRegistries.add("cache", cacheRegistry);
65
66
// Use specific registries in different components
67
public class WebController {
68
private final Counter requests = SharedMetricRegistries
69
.getOrCreate("web")
70
.counter("http.requests");
71
}
72
73
public class DatabaseService {
74
private final Histogram queryTimes = SharedMetricRegistries
75
.getOrCreate("database")
76
.histogram("query.execution.time");
77
}
78
79
public class CacheService {
80
private final Meter hitRate = SharedMetricRegistries
81
.getOrCreate("cache")
82
.meter("cache.hits");
83
}
84
```
85
86
**Registry Lifecycle Management:**
87
```java
88
// Application startup
89
public void initializeMetrics() {
90
MetricRegistry appRegistry = new MetricRegistry();
91
SharedMetricRegistries.add("application", appRegistry);
92
SharedMetricRegistries.setDefault("application");
93
94
// Set up reporters
95
ConsoleReporter.forRegistry(appRegistry)
96
.build()
97
.start(30, TimeUnit.SECONDS);
98
}
99
100
// Application shutdown
101
public void shutdownMetrics() {
102
// Clean up shared registries
103
SharedMetricRegistries.clear();
104
}
105
106
// Module-specific registries
107
public class ModuleInitializer {
108
public void initializeModule(String moduleName) {
109
MetricRegistry moduleRegistry = new MetricRegistry();
110
SharedMetricRegistries.add(moduleName, moduleRegistry);
111
112
// Configure module-specific reporting
113
Slf4jReporter.forRegistry(moduleRegistry)
114
.outputTo("metrics." + moduleName)
115
.build()
116
.start(1, TimeUnit.MINUTES);
117
}
118
}
119
```
120
121
## MetricFilter
122
123
`MetricFilter` provides flexible filtering mechanisms for controlling which metrics are processed by reporters, registry operations, and other metric consumers.
124
125
```java { .api }
126
@FunctionalInterface
127
public interface MetricFilter {
128
boolean matches(String name, Metric metric);
129
130
// Predefined filters
131
MetricFilter ALL = (name, metric) -> true;
132
133
// Static factory methods
134
static MetricFilter startsWith(String prefix);
135
static MetricFilter endsWith(String suffix);
136
static MetricFilter contains(String substring);
137
}
138
```
139
140
### Usage Examples
141
142
**Basic String-Based Filters:**
143
```java
144
// Predefined string filters
145
MetricFilter httpFilter = MetricFilter.startsWith("http");
146
MetricFilter errorFilter = MetricFilter.contains("error");
147
MetricFilter apiFilter = MetricFilter.endsWith("api");
148
149
// Use with reporters
150
ConsoleReporter httpReporter = ConsoleReporter.forRegistry(registry)
151
.filter(httpFilter)
152
.build();
153
154
CsvReporter errorReporter = CsvReporter.forRegistry(registry)
155
.filter(errorFilter)
156
.build(new File("/var/logs/errors"));
157
```
158
159
**Custom Filter Implementations:**
160
```java
161
// Filter by metric type
162
MetricFilter timersOnly = new MetricFilter() {
163
@Override
164
public boolean matches(String name, Metric metric) {
165
return metric instanceof Timer;
166
}
167
};
168
169
// Filter by activity level
170
MetricFilter activeMetrics = new MetricFilter() {
171
@Override
172
public boolean matches(String name, Metric metric) {
173
if (metric instanceof Counting) {
174
return ((Counting) metric).getCount() > 0;
175
}
176
return true; // Include non-counting metrics
177
}
178
};
179
180
// Filter by pattern matching
181
MetricFilter patternFilter = new MetricFilter() {
182
private final Pattern pattern = Pattern.compile(".*\\.(time|duration)$");
183
184
@Override
185
public boolean matches(String name, Metric metric) {
186
return pattern.matcher(name).matches();
187
}
188
};
189
```
190
191
**Composite Filters:**
192
```java
193
// Combine multiple filters with AND logic
194
public class AndFilter implements MetricFilter {
195
private final MetricFilter[] filters;
196
197
public AndFilter(MetricFilter... filters) {
198
this.filters = filters;
199
}
200
201
@Override
202
public boolean matches(String name, Metric metric) {
203
for (MetricFilter filter : filters) {
204
if (!filter.matches(name, metric)) {
205
return false;
206
}
207
}
208
return true;
209
}
210
}
211
212
// Combine multiple filters with OR logic
213
public class OrFilter implements MetricFilter {
214
private final MetricFilter[] filters;
215
216
public OrFilter(MetricFilter... filters) {
217
this.filters = filters;
218
}
219
220
@Override
221
public boolean matches(String name, Metric metric) {
222
for (MetricFilter filter : filters) {
223
if (filter.matches(name, metric)) {
224
return true;
225
}
226
}
227
return false;
228
}
229
}
230
231
// Usage
232
MetricFilter criticalMetrics = new OrFilter(
233
MetricFilter.contains("error"),
234
MetricFilter.contains("critical"),
235
MetricFilter.startsWith("system.health")
236
);
237
238
MetricFilter performanceTimers = new AndFilter(
239
timersOnly,
240
MetricFilter.contains("performance")
241
);
242
```
243
244
**Registry Filtering Operations:**
245
```java
246
// Remove metrics matching filter
247
registry.removeMatching(MetricFilter.startsWith("temp"));
248
249
// Get filtered metric maps
250
SortedMap<String, Counter> httpCounters = registry.getCounters(
251
MetricFilter.startsWith("http"));
252
253
SortedMap<String, Timer> activeTimers = registry.getTimers(activeMetrics);
254
```
255
256
## MetricAttribute
257
258
`MetricAttribute` represents the various attributes that can be reported for different metric types, allowing fine-grained control over which statistical values are included in reports.
259
260
```java { .api }
261
public enum MetricAttribute {
262
// Histogram and Timer attributes
263
COUNT("count"),
264
MAX("max"),
265
MEAN("mean"),
266
MIN("min"),
267
STDDEV("stddev"),
268
P50("p50"),
269
P75("p75"),
270
P95("p95"),
271
P98("p98"),
272
P99("p99"),
273
P999("p999"),
274
275
// Meter and Timer rate attributes
276
M1_RATE("m1_rate"),
277
M5_RATE("m5_rate"),
278
M15_RATE("m15_rate"),
279
MEAN_RATE("mean_rate");
280
281
// Methods
282
public String getCode();
283
}
284
```
285
286
### Usage Examples
287
288
```java
289
// Exclude high percentiles from console output for readability
290
Set<MetricAttribute> excludeHighPercentiles = EnumSet.of(
291
MetricAttribute.P98,
292
MetricAttribute.P99,
293
MetricAttribute.P999
294
);
295
296
ConsoleReporter simpleReporter = ConsoleReporter.forRegistry(registry)
297
.disabledMetricAttributes(excludeHighPercentiles)
298
.build();
299
300
// Include only basic statistics for CSV export
301
Set<MetricAttribute> basicStats = EnumSet.of(
302
MetricAttribute.COUNT,
303
MetricAttribute.MIN,
304
MetricAttribute.MAX,
305
MetricAttribute.MEAN,
306
MetricAttribute.P50,
307
MetricAttribute.P95
308
);
309
310
Set<MetricAttribute> excludeAdvanced = EnumSet.complementOf(basicStats);
311
312
CsvReporter basicCsvReporter = CsvReporter.forRegistry(registry)
313
.disabledMetricAttributes(excludeAdvanced)
314
.build(new File("/var/metrics"));
315
```
316
317
## InstrumentedExecutorService
318
319
`InstrumentedExecutorService` wraps any `ExecutorService` implementation with comprehensive metrics, providing visibility into thread pool behavior, task execution times, and queue statistics.
320
321
```java { .api }
322
public class InstrumentedExecutorService implements ExecutorService {
323
// Constructors
324
public InstrumentedExecutorService(ExecutorService delegate, MetricRegistry registry);
325
public InstrumentedExecutorService(ExecutorService delegate, MetricRegistry registry, String name);
326
327
// ExecutorService interface implementation
328
public void shutdown();
329
public List<Runnable> shutdownNow();
330
public boolean isShutdown();
331
public boolean isTerminated();
332
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
333
public <T> Future<T> submit(Callable<T> task);
334
public <T> Future<T> submit(Runnable task, T result);
335
public Future<?> submit(Runnable task);
336
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
337
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;
338
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;
339
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
340
public void execute(Runnable command);
341
}
342
```
343
344
### Metrics Created
345
346
When you wrap an `ExecutorService`, the following metrics are automatically created:
347
348
- `{name}.submitted` - Counter: Total tasks submitted
349
- `{name}.running` - Counter: Currently executing tasks
350
- `{name}.completed` - Counter: Successfully completed tasks
351
- `{name}.duration` - Timer: Task execution duration
352
- `{name}.rejected` - Meter: Rejected task rate (if applicable)
353
354
Additional metrics for `ThreadPoolExecutor`:
355
- `{name}.pool.size` - Gauge: Current pool size
356
- `{name}.pool.core` - Gauge: Core pool size
357
- `{name}.pool.max` - Gauge: Maximum pool size
358
- `{name}.pool.active` - Gauge: Active thread count
359
- `{name}.queue.size` - Gauge: Queue size
360
361
### Usage Examples
362
363
**Basic Thread Pool Instrumentation:**
364
```java
365
// Create and instrument a fixed thread pool
366
ExecutorService originalExecutor = Executors.newFixedThreadPool(10);
367
ExecutorService instrumentedExecutor = new InstrumentedExecutorService(
368
originalExecutor, registry, "worker-pool");
369
370
// Use normally - metrics are collected automatically
371
instrumentedExecutor.submit(() -> performWork());
372
instrumentedExecutor.submit(() -> processTask());
373
374
// Metrics are available in registry:
375
// worker-pool.submitted, worker-pool.running, worker-pool.completed, etc.
376
```
377
378
**Multiple Instrumented Executors:**
379
```java
380
// Web request processing pool
381
ExecutorService webPool = new InstrumentedExecutorService(
382
Executors.newCachedThreadPool(), registry, "web-requests");
383
384
// Background task processing pool
385
ExecutorService backgroundPool = new InstrumentedExecutorService(
386
Executors.newFixedThreadPool(5), registry, "background-tasks");
387
388
// Database operation pool
389
ExecutorService dbPool = new InstrumentedExecutorService(
390
Executors.newFixedThreadPool(20), registry, "database-ops");
391
392
// Each pool gets its own set of metrics
393
webPool.submit(() -> handleHttpRequest());
394
backgroundPool.submit(() -> processBackgroundJob());
395
dbPool.submit(() -> executeQuery());
396
```
397
398
**Custom ThreadPoolExecutor Instrumentation:**
399
```java
400
// Create custom thread pool with detailed configuration
401
ThreadPoolExecutor customPool = new ThreadPoolExecutor(
402
5, // corePoolSize
403
20, // maximumPoolSize
404
60, TimeUnit.SECONDS, // keepAliveTime
405
new LinkedBlockingQueue<>(100), // workQueue
406
new ThreadFactory() {
407
private final AtomicInteger threadNumber = new AtomicInteger(1);
408
@Override
409
public Thread newThread(Runnable r) {
410
Thread t = new Thread(r, "custom-worker-" + threadNumber.getAndIncrement());
411
t.setDaemon(true);
412
return t;
413
}
414
}
415
);
416
417
// Instrument the custom pool
418
ExecutorService instrumentedCustomPool = new InstrumentedExecutorService(
419
customPool, registry, "custom-processing");
420
421
// Monitor queue saturation and thread utilization
422
instrumentedCustomPool.submit(() -> heavyProcessingTask());
423
```
424
425
**Monitoring Executor Health:**
426
```java
427
public class ExecutorHealthChecker {
428
private final MetricRegistry registry;
429
430
public ExecutorHealthChecker(MetricRegistry registry) {
431
this.registry = registry;
432
}
433
434
public boolean isExecutorHealthy(String executorName) {
435
// Check queue size (assuming ThreadPoolExecutor)
436
Gauge<Integer> queueSize = registry.getGauges().get(executorName + ".queue.size");
437
if (queueSize != null && queueSize.getValue() > 50) {
438
return false; // Queue too full
439
}
440
441
// Check rejection rate
442
Meter rejectedRate = registry.getMeters().get(executorName + ".rejected");
443
if (rejectedRate != null && rejectedRate.getOneMinuteRate() > 10) {
444
return false; // Too many rejections
445
}
446
447
// Check task completion rate vs submission rate
448
Counter submitted = registry.getCounters().get(executorName + ".submitted");
449
Counter completed = registry.getCounters().get(executorName + ".completed");
450
if (submitted != null && completed != null) {
451
long backlog = submitted.getCount() - completed.getCount();
452
if (backlog > 1000) {
453
return false; // Too much backlog
454
}
455
}
456
457
return true;
458
}
459
}
460
```
461
462
## MetricRegistryListener
463
464
`MetricRegistryListener` provides an event-driven mechanism for responding to metric registration and removal events, enabling advanced monitoring scenarios, metric validation, and automatic configuration.
465
466
```java { .api }
467
public interface MetricRegistryListener extends EventListener {
468
// Metric addition events
469
void onGaugeAdded(String name, Gauge<?> gauge);
470
void onCounterAdded(String name, Counter counter);
471
void onHistogramAdded(String name, Histogram histogram);
472
void onMeterAdded(String name, Meter meter);
473
void onTimerAdded(String name, Timer timer);
474
475
// Metric removal events
476
void onGaugeRemoved(String name);
477
void onCounterRemoved(String name);
478
void onHistogramRemoved(String name);
479
void onMeterRemoved(String name);
480
void onTimerRemoved(String name);
481
482
// Convenience base class with no-op implementations
483
abstract class Base implements MetricRegistryListener {
484
@Override public void onGaugeAdded(String name, Gauge<?> gauge) {}
485
@Override public void onCounterAdded(String name, Counter counter) {}
486
@Override public void onHistogramAdded(String name, Histogram histogram) {}
487
@Override public void onMeterAdded(String name, Meter meter) {}
488
@Override public void onTimerAdded(String name, Timer timer) {}
489
@Override public void onGaugeRemoved(String name) {}
490
@Override public void onCounterRemoved(String name) {}
491
@Override public void onHistogramRemoved(String name) {}
492
@Override public void onMeterRemoved(String name) {}
493
@Override public void onTimerRemoved(String name) {}
494
}
495
}
496
```
497
498
### Usage Examples
499
500
**Automatic Reporter Configuration:**
501
```java
502
public class AutoReporterListener extends MetricRegistryListener.Base {
503
private final CsvReporter csvReporter;
504
private final Slf4jReporter slf4jReporter;
505
506
public AutoReporterListener(MetricRegistry registry) {
507
this.csvReporter = CsvReporter.forRegistry(registry)
508
.build(new File("/var/metrics"));
509
this.slf4jReporter = Slf4jReporter.forRegistry(registry)
510
.outputTo("metrics")
511
.build();
512
}
513
514
@Override
515
public void onTimerAdded(String name, Timer timer) {
516
// Start CSV reporting when first timer is added
517
if (!csvReporter.isStarted()) {
518
csvReporter.start(5, TimeUnit.MINUTES);
519
}
520
521
// Log important timer additions
522
if (name.contains("critical")) {
523
System.out.println("Critical timer added: " + name);
524
}
525
}
526
527
@Override
528
public void onMeterAdded(String name, Meter meter) {
529
// Start SLF4J reporting when first meter is added
530
if (!slf4jReporter.isStarted()) {
531
slf4jReporter.start(1, TimeUnit.MINUTES);
532
}
533
}
534
}
535
536
// Register the listener
537
registry.addListener(new AutoReporterListener(registry));
538
```
539
540
**Metric Validation and Alerting:**
541
```java
542
public class MetricValidationListener extends MetricRegistryListener.Base {
543
private final Set<String> allowedPrefixes = Set.of("http", "db", "cache", "business");
544
private final Logger logger = LoggerFactory.getLogger(MetricValidationListener.class);
545
546
@Override
547
public void onCounterAdded(String name, Counter counter) {
548
validateMetricName(name);
549
setupAlertingForCounter(name, counter);
550
}
551
552
@Override
553
public void onHistogramAdded(String name, Histogram histogram) {
554
validateMetricName(name);
555
validateHistogramConfiguration(name, histogram);
556
}
557
558
private void validateMetricName(String name) {
559
boolean validPrefix = allowedPrefixes.stream()
560
.anyMatch(name::startsWith);
561
562
if (!validPrefix) {
563
logger.warn("Metric '{}' uses non-standard prefix", name);
564
}
565
566
if (name.contains(" ") || name.contains("/")) {
567
logger.error("Metric '{}' contains invalid characters", name);
568
}
569
}
570
571
private void validateHistogramConfiguration(String name, Histogram histogram) {
572
Snapshot snapshot = histogram.getSnapshot();
573
if (snapshot.size() == 0) {
574
// New histogram, check reservoir type by examining behavior
575
histogram.update(1);
576
if (histogram.getSnapshot().size() > 0) {
577
logger.info("Histogram '{}' configured and ready", name);
578
}
579
}
580
}
581
582
private void setupAlertingForCounter(String name, Counter counter) {
583
if (name.contains("error") || name.contains("failure")) {
584
// Schedule periodic error count checking
585
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
586
scheduler.scheduleAtFixedRate(() -> {
587
long count = counter.getCount();
588
if (count > 100) {
589
logger.error("High error count detected: {} = {}", name, count);
590
}
591
}, 1, 1, TimeUnit.MINUTES);
592
}
593
}
594
}
595
```
596
597
**Dynamic Metric Grouping:**
598
```java
599
public class MetricGroupingListener extends MetricRegistryListener.Base {
600
private final Map<String, List<String>> metricGroups = new ConcurrentHashMap<>();
601
602
@Override
603
public void onCounterAdded(String name, Counter counter) {
604
addToGroup(name, "counters");
605
}
606
607
@Override
608
public void onTimerAdded(String name, Timer timer) {
609
addToGroup(name, "timers");
610
611
// Group by subsystem
612
String subsystem = extractSubsystem(name);
613
if (subsystem != null) {
614
addToGroup(name, "subsystem." + subsystem);
615
}
616
}
617
618
private void addToGroup(String metricName, String groupName) {
619
metricGroups.computeIfAbsent(groupName, k -> new ArrayList<>())
620
.add(metricName);
621
}
622
623
private String extractSubsystem(String name) {
624
String[] parts = name.split("\\.");
625
return parts.length > 0 ? parts[0] : null;
626
}
627
628
public List<String> getMetricsInGroup(String groupName) {
629
return metricGroups.getOrDefault(groupName, Collections.emptyList());
630
}
631
632
public Set<String> getAllGroups() {
633
return metricGroups.keySet();
634
}
635
}
636
```
637
638
## Additional Utility Classes
639
640
### NoopMetricRegistry
641
642
`NoopMetricRegistry` implements the null object pattern for scenarios where metrics should be disabled:
643
644
```java { .api }
645
public class NoopMetricRegistry extends MetricRegistry {
646
// All operations are no-ops
647
// Useful for testing or when metrics are disabled
648
}
649
```
650
651
**Usage:**
652
```java
653
// Conditional metric registry
654
MetricRegistry registry;
655
if (metricsEnabled) {
656
registry = new MetricRegistry();
657
} else {
658
registry = new NoopMetricRegistry();
659
}
660
661
// Code works the same way regardless
662
Counter requests = registry.counter("requests");
663
requests.inc(); // No-op if metrics disabled
664
```
665
666
### Clock Abstraction
667
668
`Clock` provides time abstraction for testing and alternative time sources:
669
670
```java { .api }
671
public abstract class Clock {
672
public abstract long getTick(); // High-resolution time for durations
673
public abstract long getTime(); // Wall-clock time in milliseconds
674
675
public static Clock defaultClock(); // System clock implementation
676
677
// Built-in implementation
678
public static class UserTimeClock extends Clock {
679
@Override public long getTick() { return System.nanoTime(); }
680
@Override public long getTime() { return System.currentTimeMillis(); }
681
}
682
}
683
```
684
685
**Testing with Custom Clock:**
686
```java
687
public class TestClock extends Clock {
688
private long currentTime = 0;
689
690
@Override
691
public long getTick() {
692
return currentTime * 1_000_000; // Convert millis to nanos
693
}
694
695
@Override
696
public long getTime() {
697
return currentTime;
698
}
699
700
public void advance(long millis) {
701
currentTime += millis;
702
}
703
}
704
705
// Use in tests
706
TestClock testClock = new TestClock();
707
Timer timer = new Timer(new ExponentiallyDecayingReservoir(1000, 0.015, testClock), testClock);
708
709
Timer.Context context = timer.time();
710
testClock.advance(100); // Simulate 100ms elapsed
711
context.stop();
712
713
assertEquals(1, timer.getCount());
714
assertTrue(timer.getSnapshot().getMean() > 90_000_000); // ~100ms in nanos
715
```
716
717
## Best Practices
718
719
### Global Registry Management
720
- Use `SharedMetricRegistries` judiciously - prefer dependency injection when possible
721
- Set up default registries early in application lifecycle
722
- Clean up shared registries during application shutdown
723
- Use named registries to separate concerns in modular applications
724
725
### Filtering Strategies
726
- Create reusable filter instances rather than recreating them
727
- Use filtering to reduce reporter overhead with large numbers of metrics
728
- Combine filters logically to create complex selection criteria
729
- Test filter logic with known metric sets to ensure correct behavior
730
731
### Instrumentation Best Practices
732
- Instrument executors close to their creation to capture all activity
733
- Choose meaningful names for instrumented components
734
- Monitor instrumented executor metrics for performance issues and capacity planning
735
- Use custom thread factories with descriptive thread names for better debugging
736
737
### Listener Implementation
738
- Extend `MetricRegistryListener.Base` to avoid implementing unused methods
739
- Keep listener implementations lightweight to avoid impacting metric registration performance
740
- Use listeners for cross-cutting concerns like validation, alerting, and automatic configuration
741
- Be careful with listener exception handling - exceptions can disrupt metric registration
742
743
### Performance Considerations
744
- Utility operations should be performed during application startup when possible
745
- Monitor the performance impact of registry listeners, especially in high-metric-registration scenarios
746
- Use filtering effectively to reduce processing overhead in reporters and other metric consumers
747
- Consider the memory implications of keeping references to metrics in listener implementations