Add metrics at runtime

Record method timings on a runtime proxy through Dropwizard Metrics and read them back.

Problem

You want timing data on every method call through a proxy — how often it runs, how long it takes, distribution percentiles — without touching the implementation and without standing up a separate instrumentation layer.

addMetrics() on the builder wires Dropwizard Metrics into the proxy. From then on, every invocation is timed, and the metrics show up in RapidPMMetricsRegistry.

Files involved

End-to-end metrics test

testusage/src/test/java/.../DynamicProxyBuilderTest.java#testAddMetrics View on GitHub ↗
@Test
public void testAddMetrics() throws Exception {

  final InnerDemoClass original = new InnerDemoClass();
  final InnerDemoInterface demoLogic = DynamicProxyBuilder
      .createBuilder(InnerDemoInterface.class, original)
      .addSecurityRule(() -> true)
      .addSecurityRule(() -> true)
      .addMetrics()
      .build();
  Assertions.assertNotNull(demoLogic);
  Assertions.assertEquals(demoLogic.doWork(), original.doWork());

  final MetricRegistry metrics = RapidPMMetricsRegistry.getInstance().getMetrics();
  final ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics)
      .convertRatesTo(TimeUnit.NANOSECONDS)
      .convertDurationsTo(TimeUnit.MILLISECONDS)
      .build();
  reporter.start(1, TimeUnit.SECONDS);

  IntStream.range(0, 10_000_000).forEach(i -> {
    final String s = demoLogic.doWork();
    workingHole(s.toUpperCase());
  });
  logger().info("s1 = {}", s1);

  final SortedMap<String, Histogram> histograms = metrics.getHistograms();
  Assertions.assertNotNull(histograms);
  Assertions.assertFalse(histograms.isEmpty());
  Assertions.assertTrue(histograms.containsKey(InnerDemoInterface.class.getName() + ".doWork"));
}

From proxybuilder tag 00.10.00

There are three things happening in this test, intentionally combined to show the full flow.

  1. Wrap the implementation. DynamicProxyBuilder.createBuilder(...) takes the interface and the instance. .addSecurityRule(() -> true) is present twice to exercise rule composition; both return true, so the call is never blocked. .addMetrics() is the line that turns on timing collection. .build() produces the proxy.
  2. Set up a console reporter. RapidPMMetricsRegistry.getInstance() exposes the shared MetricRegistry. A ConsoleReporter is attached to it, printing collected metrics once per second on the test’s stdout.
  3. Generate load. Ten million calls to demoLogic.doWork() flow through the proxy. Each call is timed. After the loop, the test reads the resulting histogram out of the registry and asserts that it contains an entry keyed by InnerDemoInterface.class.getName() + ".doWork".

Run it

./mvnw -pl testusage test -Dtest=DynamicProxyBuilderTest#testAddMetrics

The console reporter prints histograms during the run. Expect output similar to:

-- Histograms ----------------------------------------------------------------
junit.com.svenruppert.proxybuilder.proxy.dynamic.InnerDemoInterface.doWork
             count = 9876543
               min = 0
               max = 47
              mean = 0.13
           median = 0.00
             75% <= 0.00
             95% <= 0.00
             98% <= 1.00
             99% <= 2.00
           99.9% <= 6.00

Exact numbers depend on the host, but the histogram entry will be there.

Reading metrics back programmatically

The test grabs the histogram and asserts on it:

final SortedMap<String, Histogram> histograms = metrics.getHistograms();
Assertions.assertTrue(histograms.containsKey(
    InnerDemoInterface.class.getName() + ".doWork"));

Use the same pattern in your application to drive alerts, dashboards or custom reporters. The keys are derived from the fully qualified interface name plus the method name, so overloads are distinguishable.

When you would prefer compile-time metrics

addMetrics() is convenient when you build the proxy once and hand it off. If you want timings without runtime reflection — for GraalVM native image, for startup-time reasons, or because the metrics class should show up by name in stack traces — see @StaticMetricsProxy instead.

Edit this page on GitHub ↗