package io.dropwizard.metrics.ehcache; import static io.dropwizard.metrics.MetricRegistry.name; import io.dropwizard.metrics.Gauge; import io.dropwizard.metrics.MetricName; import io.dropwizard.metrics.MetricRegistry; import io.dropwizard.metrics.Timer; import net.sf.ehcache.CacheException; import net.sf.ehcache.Ehcache; import net.sf.ehcache.Element; import net.sf.ehcache.constructs.EhcacheDecoratorAdapter; import net.sf.ehcache.statistics.StatisticsGateway; import java.io.Serializable; /** * An instrumented {@link Ehcache} instance. */ public class InstrumentedEhcache extends EhcacheDecoratorAdapter { /** * Instruments the given {@link Ehcache} instance with get and put timers * and a set of gauges for Ehcache's built-in statistics: * <p/> * <table> * <caption>Ehcache timered metrics</caption> * <tr> * <td>{@code hits}</td> * <td>The number of times a requested item was found in the * cache.</td> * </tr> * <tr> * <td>{@code in-memory-hits}</td> * <td>Number of times a requested item was found in the memory * store.</td> * </tr> * <tr> * <td>{@code off-heap-hits}</td> * <td>Number of times a requested item was found in the off-heap * store.</td> * </tr> * <tr> * <td>{@code on-disk-hits}</td> * <td>Number of times a requested item was found in the disk * store.</td> * </tr> * <tr> * <td>{@code misses}</td> * <td>Number of times a requested item was not found in the * cache.</td> * </tr> * <tr> * <td>{@code in-memory-misses}</td> * <td>Number of times a requested item was not found in the memory * store.</td> * </tr> * <tr> * <td>{@code off-heap-misses}</td> * <td>Number of times a requested item was not found in the * off-heap store.</td> * </tr> * <tr> * <td>{@code on-disk-misses}</td> * <td>Number of times a requested item was not found in the disk * store.</td> * </tr> * <tr> * <td>{@code objects}</td> * <td>Number of elements stored in the cache.</td> * </tr> * <tr> * <td>{@code in-memory-objects}</td> * <td>Number of objects in the memory store.</td> * </tr> * <tr> * <td>{@code off-heap-objects}</td> * <td>Number of objects in the off-heap store.</td> * </tr> * <tr> * <td>{@code on-disk-objects}</td> * <td>Number of objects in the disk store.</td> * </tr> * <tr> * <td>{@code mean-get-time}</td> * <td>The average get time. Because ehcache support JDK1.4.2, each * get time uses {@link System#currentTimeMillis()}, rather than * nanoseconds. The accuracy is thus limited.</td> * </tr> * <tr> * <td>{@code mean-search-time}</td> * <td>The average execution time (in milliseconds) within the last * sample period.</td> * </tr> * <tr> * <td>{@code eviction-count}</td> * <td>The number of cache evictions, since the cache was created, * or statistics were cleared.</td> * </tr> * <tr> * <td>{@code searches-per-second}</td> * <td>The number of search executions that have completed in the * last second.</td> * </tr> * <tr> * <td>{@code accuracy}</td> * <td>A human readable description of the accuracy setting. One of * "None", "Best Effort" or "Guaranteed".</td> * </tr> * </table> * * <b>N.B.: This enables Ehcache's sampling statistics with an accuracy * level of "none."</b> * * @param cache an {@link Ehcache} instance * @param registry a {@link MetricRegistry} * @return an instrumented decorator for {@code cache} * @see StatisticsGateway */ public static Ehcache instrument(MetricRegistry registry, final Ehcache cache) { final MetricName prefix = name(cache.getClass(), cache.getName()); registry.register(prefix.resolve("hits"), new Gauge<Long>() { @Override public Long getValue() { return cache.getStatistics().cacheHitCount(); } }); registry.register(prefix.resolve("in-memory-hits"), new Gauge<Long>() { @Override public Long getValue() { return cache.getStatistics().localHeapHitCount(); } }); registry.register(prefix.resolve("off-heap-hits"), new Gauge<Long>() { @Override public Long getValue() { return cache.getStatistics().localOffHeapHitCount(); } }); registry.register(prefix.resolve("on-disk-hits"), new Gauge<Long>() { @Override public Long getValue() { return cache.getStatistics().localDiskHitCount(); } }); registry.register(prefix.resolve("misses"), new Gauge<Long>() { @Override public Long getValue() { return cache.getStatistics().cacheMissCount(); } }); registry.register(prefix.resolve("in-memory-misses"), new Gauge<Long>() { @Override public Long getValue() { return cache.getStatistics().localHeapMissCount(); } }); registry.register(prefix.resolve("off-heap-misses"), new Gauge<Long>() { @Override public Long getValue() { return cache.getStatistics().localOffHeapMissCount(); } }); registry.register(prefix.resolve("on-disk-misses"), new Gauge<Long>() { @Override public Long getValue() { return cache.getStatistics().localDiskMissCount(); } }); registry.register(prefix.resolve("objects"), new Gauge<Long>() { @Override public Long getValue() { return cache.getStatistics().getSize(); } }); registry.register(prefix.resolve("in-memory-objects"), new Gauge<Long>() { @Override public Long getValue() { return cache.getStatistics().getLocalHeapSize(); } }); registry.register(prefix.resolve("off-heap-objects"), new Gauge<Long>() { @Override public Long getValue() { return cache.getStatistics().getLocalOffHeapSize(); } }); registry.register(prefix.resolve("on-disk-objects"), new Gauge<Long>() { @Override public Long getValue() { return cache.getStatistics().getLocalDiskSize(); } }); registry.register(prefix.resolve("mean-get-time"), new Gauge<Double>() { @Override public Double getValue() { return cache.getStatistics().cacheGetOperation().latency().average().value(); } }); registry.register(prefix.resolve("mean-search-time"), new Gauge<Double>() { @Override public Double getValue() { return cache.getStatistics().cacheSearchOperation().latency().average().value(); } }); registry.register(prefix.resolve("eviction-count"), new Gauge<Long>() { @Override public Long getValue() { return cache.getStatistics().cacheEvictionOperation().count().value(); } }); registry.register(prefix.resolve("searches-per-second"), new Gauge<Double>() { @Override public Double getValue() { return cache.getStatistics().cacheSearchOperation().rate().value(); } }); registry.register(prefix.resolve("writer-queue-size"), new Gauge<Long>() { @Override public Long getValue() { return cache.getStatistics().getWriterQueueLength(); } }); return new InstrumentedEhcache(registry, cache); } private final Timer getTimer, putTimer; private InstrumentedEhcache(MetricRegistry registry, Ehcache cache) { super(cache); this.getTimer = registry.timer(name(cache.getClass(), cache.getName(), "gets")); this.putTimer = registry.timer(name(cache.getClass(), cache.getName(), "puts")); } @Override public Element get(Object key) throws IllegalStateException, CacheException { final Timer.Context ctx = getTimer.time(); try { return underlyingCache.get(key); } finally { ctx.stop(); } } @Override public Element get(Serializable key) throws IllegalStateException, CacheException { final Timer.Context ctx = getTimer.time(); try { return underlyingCache.get(key); } finally { ctx.stop(); } } @Override public void put(Element element) throws IllegalArgumentException, IllegalStateException, CacheException { final Timer.Context ctx = putTimer.time(); try { underlyingCache.put(element); } finally { ctx.stop(); } } @Override public void put(Element element, boolean doNotNotifyCacheReplicators) throws IllegalArgumentException, IllegalStateException, CacheException { final Timer.Context ctx = putTimer.time(); try { underlyingCache.put(element, doNotNotifyCacheReplicators); } finally { ctx.stop(); } } @Override public Element putIfAbsent(Element element) throws NullPointerException { final Timer.Context ctx = putTimer.time(); try { return underlyingCache.putIfAbsent(element); } finally { ctx.stop(); } } }