Metrics

Stormpot can be integrated with various metrics and telemetry systems in multiple ways. The two main ways for integrating Stormpot with metrics are the MetricsRecorder (see below) and the ManagedPool interface.

The ManagedPool interface exposes multiple counters that can be integrated with a metrics system as gauges. The ManagedPool interface can also be used as a JMX integration because it follows the MXBean conventions. See the JMX chapter for more information about how to integrate Stormpot with JMX.

Below is an example of a MeterBinder implementation, based on the Micrometer framework. It exposes gauges for allocation counts, leaks, and errors to a metrics registry:

package examples;

import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.MeterBinder;
import stormpot.ManagedPool;
import stormpot.Pool;

public class MicrometerBinderExample implements MeterBinder {
  private static final String SUCCESSFUL_ALLOCATIONS = "allocationCount";
  private static final String FAILED_ALLOCATIONS = "failedAllocationCount";
  private static final String LEAKED_OBJECTS = "leakedObjectCount";
  private static final String SEP = ".";
  private final ManagedPool pool;
  private final String poolName;

  public MicrometerBinderExample(String poolName, Pool<?> pool) {
    this.poolName = poolName;
    this.pool = pool.getManagedPool();
  }

  @Override
  public void bindTo(MeterRegistry registry) {
    Gauge.builder(poolName + SEP + SUCCESSFUL_ALLOCATIONS, pool::getAllocationCount)
        .description("Allocation count for pool")
        .baseUnit(SUCCESSFUL_ALLOCATIONS)
        .register(registry);

    Gauge.builder(poolName + SEP + FAILED_ALLOCATIONS, pool::getFailedAllocationCount)
        .description("Failed allocation count for pool")
        .baseUnit(FAILED_ALLOCATIONS)
        .register(registry);

    Gauge.builder(poolName + SEP + LEAKED_OBJECTS, pool::getLeakedObjectsCount)
        .description("Leaked object count for pool")
        .baseUnit(LEAKED_OBJECTS)
        .register(registry);
  }
}

The MetricsRecorder interface makes it possible to record the response times of various operations. A typical MetricsRecorder implementations would maintain internal histograms of the samples they record. The metrics integration can then query for percentiles in these histograms at regular intervals. But first, you have to implement a MetricsRecorder, and then configure the pool to use it.

Pools do not have a MetricsRecorder configured by default, and Stormpot does not come with any MetricsRecorder implementations built-in. You have to implement and integrate a MetricsRecorder yourself, then configure a pool to use it with the PoolBuilder.setMetricsRecorder method. You would typically do this by delegeting to an existing metrics library. Many metrics libraries already provide histogram implementations that can be used for this purpose.

Note
The MetricsRecorder instance you build and configure must be thread-safe. The various record* and get* methods can all be called concurrently.

In the example below we use the Dropwizard Metrics library to do implement a MetricsRecorder, but you can also use other libraries or frameworks. Obviously you need to first add this library to your projects list of dependencies, but once you’ve done that, integrating it is pretty easy:

package examples;

import com.codahale.metrics.Histogram;
import com.codahale.metrics.MetricRegistry;
import stormpot.MetricsRecorder;

public class DropwizardMetricsRecorder implements MetricsRecorder {
  private final Histogram allocationLatency;
  private final Histogram allocationFailureLatency;
  private final Histogram deallocationLatency;
  private final Histogram reallocationLatency;
  private final Histogram reallocationFailureLatency;
  private final Histogram objectLifetime;

  public DropwizardMetricsRecorder(String baseName, MetricRegistry registry) {
    allocationLatency = registry.histogram(baseName + ".allocationLatency");
    allocationFailureLatency = registry.histogram(baseName + ".allocationFailureLatency");
    deallocationLatency = registry.histogram(baseName + ".deallocationLatency");
    reallocationLatency = registry.histogram(baseName + ".reallocationLatency");
    reallocationFailureLatency = registry.histogram(baseName + ".reallocationFailureLatency");
    objectLifetime = registry.histogram(baseName + ".objectLifetime");
  }

  @Override
  public void recordAllocationLatencySampleMillis(long milliseconds) {
    allocationLatency.update(milliseconds);
  }

  @Override
  public void recordAllocationFailureLatencySampleMillis(long milliseconds) {
    allocationFailureLatency.update(milliseconds);
  }

  @Override
  public void recordDeallocationLatencySampleMillis(long milliseconds) {
    deallocationLatency.update(milliseconds);
  }

  @Override
  public void recordReallocationLatencySampleMillis(long milliseconds) {
    reallocationLatency.update(milliseconds);
  }

  @Override
  public void recordReallocationFailureLatencySampleMillis(long milliseconds) {
    reallocationFailureLatency.update(milliseconds);
  }

  @Override
  public void recordObjectLifetimeSampleMillis(long milliseconds) {
    objectLifetime.update(milliseconds);
  }

  @Override
  public double getAllocationLatencyPercentile(double percentile) {
    return allocationLatency.getSnapshot().getValue(percentile);
  }

  @Override
  public double getAllocationFailureLatencyPercentile(double percentile) {
    return allocationFailureLatency.getSnapshot().getValue(percentile);
  }

  @Override
  public double getDeallocationLatencyPercentile(double percentile) {
    return deallocationLatency.getSnapshot().getValue(percentile);
  }

  @Override
  public double getReallocationLatencyPercentile(double percentile) {
    return reallocationLatency.getSnapshot().getValue(percentile);
  }

  @Override
  public double getReallocationFailurePercentile(double percentile) {
    return reallocationFailureLatency.getSnapshot().getValue(percentile);
  }

  @Override
  public double getObjectLifetimePercentile(double percentile) {
    return objectLifetime.getSnapshot().getValue(percentile);
  }
}

Stormpot does not provide built-in metrics for claim counts or latencies, but these can easily be added by wrapping the pool object with something that performs these measurements.