/* * JBoss, Home of Professional Open Source * Copyright 2009 Red Hat Inc. and/or its affiliates and other * contributors as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.infinispan.interceptors; import org.infinispan.commands.read.GetKeyValueCommand; import org.infinispan.commands.write.EvictCommand; import org.infinispan.commands.write.PutKeyValueCommand; import org.infinispan.commands.write.PutMapCommand; import org.infinispan.commands.write.RemoveCommand; import org.infinispan.container.DataContainer; import org.infinispan.context.InvocationContext; import org.infinispan.factories.annotations.Inject; import org.infinispan.interceptors.base.JmxStatsCommandInterceptor; import org.infinispan.jmx.annotations.MBean; import org.infinispan.jmx.annotations.ManagedAttribute; import org.infinispan.jmx.annotations.ManagedOperation; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.rhq.helpers.pluginAnnotations.agent.DisplayType; import org.rhq.helpers.pluginAnnotations.agent.MeasurementType; import org.rhq.helpers.pluginAnnotations.agent.Metric; import org.rhq.helpers.pluginAnnotations.agent.Operation; import org.rhq.helpers.pluginAnnotations.agent.Units; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; /** * Captures cache management statistics * * @author Jerry Gauthier * @since 4.0 */ @MBean(objectName = "Statistics", description = "General statistics such as timings, hit/miss ratio, etc.") public class CacheMgmtInterceptor extends JmxStatsCommandInterceptor { private final AtomicLong hitTimes = new AtomicLong(0); private final AtomicLong missTimes = new AtomicLong(0); private final AtomicLong storeTimes = new AtomicLong(0); private final AtomicLong hits = new AtomicLong(0); private final AtomicLong misses = new AtomicLong(0); private final AtomicLong stores = new AtomicLong(0); private final AtomicLong evictions = new AtomicLong(0); private final AtomicLong startNanoseconds = new AtomicLong(System.nanoTime()); private final AtomicLong resetNanoseconds = new AtomicLong(startNanoseconds.get()); private final AtomicLong removeHits = new AtomicLong(0); private final AtomicLong removeMisses = new AtomicLong(0); private DataContainer dataContainer; private static final Log log = LogFactory.getLog(CacheMgmtInterceptor.class); @Override protected Log getLog() { return log; } @Inject public void setDependencies(DataContainer dataContainer) { this.dataContainer = dataContainer; } @Override public Object visitEvictCommand(InvocationContext ctx, EvictCommand command) throws Throwable { Object returnValue = invokeNextInterceptor(ctx, command); evictions.incrementAndGet(); return returnValue; } @Override public Object visitGetKeyValueCommand(InvocationContext ctx, GetKeyValueCommand command) throws Throwable { long t1 = System.nanoTime(); Object retval = invokeNextInterceptor(ctx, command); long t2 = System.nanoTime(); long intervalMilliseconds = nanosecondsIntervalToMilliseconds(t1, t2); if (retval == null) { missTimes.getAndAdd(intervalMilliseconds); misses.incrementAndGet(); } else { hitTimes.getAndAdd(intervalMilliseconds); hits.incrementAndGet(); } return retval; } @Override public Object visitPutMapCommand(InvocationContext ctx, PutMapCommand command) throws Throwable { Map<Object, Object> data = command.getMap(); long t1 = System.nanoTime(); Object retval = invokeNextInterceptor(ctx, command); long t2 = System.nanoTime(); long intervalMilliseconds = nanosecondsIntervalToMilliseconds(t1, t2); if (data != null && !data.isEmpty()) { storeTimes.getAndAdd(intervalMilliseconds); stores.getAndAdd(data.size()); } return retval; } @Override //Map.put(key,value) :: oldValue public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable { long t1 = System.nanoTime(); Object retval = invokeNextInterceptor(ctx, command); long t2 = System.nanoTime(); long intervalMilliseconds = nanosecondsIntervalToMilliseconds(t1, t2); storeTimes.getAndAdd(intervalMilliseconds); stores.incrementAndGet(); return retval; } @Override public Object visitRemoveCommand(InvocationContext ctx, RemoveCommand command) throws Throwable { Object retval = invokeNextInterceptor(ctx, command); if (retval == null) { removeMisses.incrementAndGet(); } else { removeHits.incrementAndGet(); } return retval; } @ManagedAttribute(description = "Number of cache attribute hits") @Metric(displayName = "Number of cache hits", measurementType = MeasurementType.TRENDSUP, displayType = DisplayType.SUMMARY) public long getHits() { return hits.get(); } @ManagedAttribute(description = "Number of cache attribute misses") @Metric(displayName = "Number of cache misses", measurementType = MeasurementType.TRENDSUP, displayType = DisplayType.SUMMARY) public long getMisses() { return misses.get(); } @ManagedAttribute(description = "Number of cache removal hits") @Metric(displayName = "Number of cache removal hits", measurementType = MeasurementType.TRENDSUP, displayType = DisplayType.SUMMARY) public long getRemoveHits() { return removeHits.get(); } @ManagedAttribute(description = "Number of cache removals where keys were not found") @Metric(displayName = "Number of cache removal misses", measurementType = MeasurementType.TRENDSUP, displayType = DisplayType.SUMMARY) public long getRemoveMisses() { return removeMisses.get(); } @ManagedAttribute(description = "number of cache attribute put operations") @Metric(displayName = "Number of cache puts" , measurementType = MeasurementType.TRENDSUP, displayType = DisplayType.SUMMARY) public long getStores() { return stores.get(); } @ManagedAttribute(description = "Number of cache eviction operations") @Metric(displayName = "Number of cache evictions", measurementType = MeasurementType.TRENDSUP, displayType = DisplayType.SUMMARY) public long getEvictions() { return evictions.get(); } @ManagedAttribute(description = "Percentage hit/(hit+miss) ratio for the cache") @Metric(displayName = "Hit ratio", units = Units.PERCENTAGE, displayType = DisplayType.SUMMARY) public double getHitRatio() { long hitsL = hits.get(); double total = hitsL + misses.get(); // The reason for <= is that equality checks // should be avoided for floating point numbers. if (total <= 0) return 0; return (hitsL / total); } @ManagedAttribute(description = "read/writes ratio for the cache") @Metric(displayName = "Read/write ratio", units = Units.PERCENTAGE, displayType = DisplayType.SUMMARY) public double getReadWriteRatio() { if (stores.get() == 0) return 0; return (((double) (hits.get() + misses.get()) / (double) stores.get())); } @ManagedAttribute(description = "Average number of milliseconds for a read operation on the cache") @Metric(displayName = "Average read time", units = Units.MILLISECONDS, displayType = DisplayType.SUMMARY) public long getAverageReadTime() { long total = hits.get() + misses.get(); if (total == 0) return 0; return (hitTimes.get() + missTimes.get()) / total; } @ManagedAttribute(description = "Average number of milliseconds for a write operation in the cache") @Metric(displayName = "Average write time", units = Units.MILLISECONDS, displayType = DisplayType.SUMMARY) public long getAverageWriteTime() { if (stores.get() == 0) return 0; return (storeTimes.get()) / stores.get(); } @ManagedAttribute(description = "Number of entries currently in the cache") @Metric(displayName = "Number of current cache entries", displayType = DisplayType.SUMMARY) public int getNumberOfEntries() { return dataContainer.size(null); } @ManagedAttribute(description = "Number of seconds since cache started") @Metric(displayName = "Seconds since cache started", units = Units.SECONDS, measurementType = MeasurementType.TRENDSUP, displayType = DisplayType.SUMMARY) public long getElapsedTime() { return TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startNanoseconds.get()); } @ManagedAttribute(description = "Number of seconds since the cache statistics were last reset") @Metric(displayName = "Seconds since cache statistics were reset", units = Units.SECONDS, displayType = DisplayType.SUMMARY) public long getTimeSinceReset() { return TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - resetNanoseconds.get()); } @Override @ManagedOperation(description = "Resets statistics gathered by this component") @Operation(displayName = "Reset Statistics (Statistics)") public void resetStatistics() { hits.set(0); misses.set(0); stores.set(0); evictions.set(0); hitTimes.set(0); missTimes.set(0); storeTimes.set(0); removeHits.set(0); removeMisses.set(0); resetNanoseconds.set(System.nanoTime()); } /** * @param nanoStart * @param nanoEnd * @return the interval rounded in milliseconds */ private long nanosecondsIntervalToMilliseconds(final long nanoStart, final long nanoEnd) { return TimeUnit.MILLISECONDS.convert(nanoEnd - nanoStart, TimeUnit.NANOSECONDS); } }