or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-gauges.mdcore-metrics.mdindex.mdreporting.mdreservoirs-sampling.mdutilities.md

utilities.mddocs/

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