/* * Copyright 2008-2010 Xebia and the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package fr.xebia.management.statistics; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import org.springframework.core.style.ToStringCreator; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedMetric; import org.springframework.jmx.export.annotation.ManagedResource; import org.springframework.jmx.export.naming.SelfNaming; import org.springframework.jmx.support.MetricType; @ManagedResource public class ServiceStatistics implements SelfNaming { /** * Returns <code>true</code> if the given <code>throwable</code> or one of * its cause is an instance of one of the given <code>throwableTypes</code>. */ public static boolean containsThrowableOfType(Throwable throwable, Class<?>... throwableTypes) { List<Throwable> alreadyProcessedThrowables = new ArrayList<Throwable>(); while (true) { if (throwable == null) { // end of the list of causes return false; } else if (alreadyProcessedThrowables.contains(throwable)) { // infinite loop in causes return false; } else { for (Class<?> throwableType : throwableTypes) { if (throwableType.isAssignableFrom(throwable.getClass())) { return true; } } alreadyProcessedThrowables.add(throwable); throwable = throwable.getCause(); } } } private final AtomicInteger businessExceptionCounter = new AtomicInteger(); private Class<?>[] businessExceptionsTypes; private final AtomicInteger communicationExceptionCounter = new AtomicInteger(); private Class<?>[] communicationExceptionsTypes; private final AtomicInteger currentActiveCounter = new AtomicInteger(); private final AtomicInteger invocationCounter = new AtomicInteger(); private Semaphore maxActiveSemaphore; private long maxActiveSemaphoreAcquisitionMaxTimeInNanos; private final ObjectName objectName; private final AtomicInteger otherExceptionCounter = new AtomicInteger(); private final AtomicInteger serviceUnavailableExceptionCounter = new AtomicInteger(); private final AtomicInteger slowInvocationCounter = new AtomicInteger(); private long slowInvocationThresholdInNanos; private final AtomicLong totalDurationInNanosCounter = new AtomicLong(); private final AtomicInteger verySlowInvocationCounter = new AtomicInteger(); private long verySlowInvocationThresholdInNanos; /** * * @param objectName * identifier of the service * @param businessExceptionsTypes * types of exceptions that are categorized as business * exceptions * @param communicationExceptionsTypes * types of exceptions that are categorized as communication * exceptions */ public ServiceStatistics(ObjectName objectName, Class<?>[] businessExceptionsTypes, Class<?>[] communicationExceptionsTypes) { super(); this.objectName = objectName; this.businessExceptionsTypes = businessExceptionsTypes.clone(); this.communicationExceptionsTypes = communicationExceptionsTypes.clone(); } /** * * @param name * identifier of the service * @param businessExceptionsTypes * types of exceptions that are categorized as business * exceptions * @param communicationExceptionsTypes * types of exceptions that are categorized as communication * exceptions * @throws MalformedObjectNameException */ public ServiceStatistics(String name, Class<?>[] businessExceptionsTypes, Class<?>[] communicationExceptionsTypes) throws MalformedObjectNameException { this(new ObjectName("fr.xebia:type=ServiceStatistics,name=" + name), businessExceptionsTypes, communicationExceptionsTypes); } /** * Decrements the {@link #currentActiveCounter}. */ public void decrementCurrentActiveCount() { currentActiveCounter.decrementAndGet(); } /** * <p> * Number of throwned communication exceptions. * </p> * <p> * Exceptions are categorized in: * <ul> * <li>{@link #getCommunicationExceptionCount()}</li> * <li>{@link #getBusinessExceptionCount()}</li> * <li>{@link #getOtherExceptionCount()}</li> * </ul> * </p> * * @see #incrementBusinessExceptionCount() */ @ManagedMetric(description = "Number of business exceptions", metricType = MetricType.COUNTER, category = "throughput") public int getBusinessExceptionCount() { return businessExceptionCounter.get(); } /** * <p> * Number of throwned communication exceptions. * </p> * <p> * Exceptions are categorized in: * <ul> * <li>{@link #getCommunicationExceptionCount()}</li> * <li>{@link #getBusinessExceptionCount()}</li> * <li>{@link #getOtherExceptionCount()}</li> * </ul> * </p> * * @see #incrementCommunicationExceptionCount() */ @ManagedMetric(description = "Number of communication exceptions (timeout, connection refused, etc)", metricType = MetricType.COUNTER, category = "throughput") public int getCommunicationExceptionCount() { return communicationExceptionCounter.get(); } /** * Number of active invocations * * see {@link #incrementCurrentActiveCount()} * * @see #decrementCurrentActiveCount() */ @ManagedMetric(description = "Number of currently active invocations", metricType = MetricType.GAUGE, category = "utilization") public int getCurrentActive() { return currentActiveCounter.get(); } /** * Number of invocations * * @see #incrementInvocationCount() */ @ManagedMetric(description = "Number of invocations", metricType = MetricType.COUNTER, category = "throughput") public int getInvocationCount() { return invocationCounter.get(); } @ManagedAttribute(description = "Max active connections or -1 if max active invocation is disabled. Can be slightly inacurrate") public int getMaxActive() { if (this.maxActiveSemaphore == null) { return -1; } else { return this.maxActiveSemaphore.availablePermits() + getCurrentActive(); } } @ManagedMetric(description = "Number of available additional active request", category = "dynamic") public int getMaxActiveAvailablePermits() { if (this.maxActiveSemaphore == null) { return -1; } else { return this.maxActiveSemaphore.availablePermits(); } } public Semaphore getMaxActiveSemaphore() { return maxActiveSemaphore; } public long getMaxActiveSemaphoreAcquisitionMaxTimeInNanos() { return maxActiveSemaphoreAcquisitionMaxTimeInNanos; } /** * ObjectName of the service statics. */ public ObjectName getObjectName() throws MalformedObjectNameException { return objectName; } @ManagedMetric(description = "Number of non business exceptions excluding communication exceptions", metricType = MetricType.COUNTER, category = "throughput") public int getOtherExceptionCount() { return otherExceptionCounter.get(); } @ManagedMetric(description = "Total Number of non business exceptions", metricType = MetricType.COUNTER, category = "throughput") public int getTotalExceptionCount() { return getBusinessExceptionCount() + // getCommunicationExceptionCount() + // getOtherExceptionCount() + // getServiceUnavailableExceptionCount(); } public AtomicInteger getOtherExceptionCounter() { return otherExceptionCounter; } @ManagedAttribute(description = "Max acquisition duration fur the max active semaphore") public long getSemaphoreAcquisitionMaxTimeInMillis() { return TimeUnit.MILLISECONDS.convert(maxActiveSemaphoreAcquisitionMaxTimeInNanos, TimeUnit.NANOSECONDS); } @ManagedMetric(description = "Number of slow 'service unavailable' exceptions", metricType = MetricType.COUNTER, category = "throughput") public int getServiceUnavailableExceptionCount() { return serviceUnavailableExceptionCounter.get(); } @ManagedMetric(description = "Number of slow invocations", metricType = MetricType.COUNTER, category = "throughput") public int getSlowInvocationCount() { return slowInvocationCounter.get(); } @ManagedAttribute public long getSlowInvocationThresholdInMillis() { return TimeUnit.MILLISECONDS.convert(slowInvocationThresholdInNanos, TimeUnit.NANOSECONDS); } public long getSlowInvocationThresholdInNanos() { return slowInvocationThresholdInNanos; } @ManagedAttribute(description = "Total durations in millis of the invocations") public long getTotalDurationInMillis() { return TimeUnit.MILLISECONDS.convert(getTotalDurationInNanos(), TimeUnit.NANOSECONDS); } @ManagedMetric(description = "Total durations in nanos of the invocations", metricType = MetricType.COUNTER, unit = "ns", category = "throughput") public long getTotalDurationInNanos() { return totalDurationInNanosCounter.get(); } public AtomicLong getTotalDurationInNanosCounter() { return totalDurationInNanosCounter; } @ManagedMetric(description = "Number of very slow invocations", metricType = MetricType.COUNTER, category = "throughput") public int getVerySlowInvocationCount() { return verySlowInvocationCounter.get(); } @ManagedAttribute public long getVerySlowInvocationThresholdInMillis() { return TimeUnit.MILLISECONDS.convert(this.verySlowInvocationThresholdInNanos, TimeUnit.NANOSECONDS); } public long getVerySlowInvocationThresholdInNanos() { return verySlowInvocationThresholdInNanos; } /** * Increment {@link #communicationExceptionCounter}. */ public void incrementBusinessExceptionCount() { communicationExceptionCounter.incrementAndGet(); } /** * Increment {@link #businessExceptionCounter}. */ public void incrementCommunicationExceptionCount() { businessExceptionCounter.incrementAndGet(); } /** * <p> * Increment {@link #currentActiveCounter}. Pattern : * </p> * <code><pre> * statistics.incrementCurrentActiveCount(); * try { * ... * } finally { * decrementCurrentActiveCount(); * } * <pre></code> * * @see #decrementCurrentActiveCount() */ public void incrementCurrentActiveCount() { currentActiveCounter.incrementAndGet(); } /** * Increment the {@link #communicationExceptionCounter} if the given * throwable or one of its cause is an instance of on of * {@link #communicationExceptionsTypes} ; otherwise, increment * {@link #otherExceptionCounter}. */ public void incrementExceptionCount(Throwable throwable) { if (throwable instanceof ServiceUnavailableException) { serviceUnavailableExceptionCounter.incrementAndGet(); } else if (containsThrowableOfType(throwable, communicationExceptionsTypes)) { communicationExceptionCounter.incrementAndGet(); } else if (containsThrowableOfType(throwable, businessExceptionsTypes)) { businessExceptionCounter.incrementAndGet(); } else { otherExceptionCounter.incrementAndGet(); } } /** * Increment {@link #invocationCounter}. */ public void incrementInvocationCount() { invocationCounter.incrementAndGet(); } /** * Increment {@link #totalDurationInNanosCounter}, * {@link #invocationCounter} and, if eligible, * {@link #verySlowInvocationCounter} or {@link #slowInvocationCounter}. * * @param deltaInNanos * delta in nanos */ public void incrementInvocationCounterAndTotalDurationWithNanos(long deltaInNanos) { totalDurationInNanosCounter.addAndGet(deltaInNanos); invocationCounter.incrementAndGet(); if (deltaInNanos >= this.verySlowInvocationThresholdInNanos) { this.verySlowInvocationCounter.incrementAndGet(); } else if (deltaInNanos >= this.slowInvocationThresholdInNanos) { this.slowInvocationCounter.incrementAndGet(); } } /** * Increment {@link #otherExceptionCounter}. */ public void incrementOtherExceptionCount() { otherExceptionCounter.incrementAndGet(); } /** * Increment {@link #serviceUnavailableExceptionCounter} */ public void incrementServiceUnavailableExceptionCount() { serviceUnavailableExceptionCounter.incrementAndGet(); } /** * Increment {@link #totalDurationInNanosCounter}. * * @param deltaInMillis * delta in millis */ public void incrementTotalDurationWithMillis(long deltaInMillis) { incrementTotalDurationWithNanos(TimeUnit.NANOSECONDS.convert(deltaInMillis, TimeUnit.MILLISECONDS)); } /** * Increment {@link #totalDurationInNanosCounter}. * * @param deltaInNanos * delta in nanos */ public void incrementTotalDurationWithNanos(long deltaInNanos) { totalDurationInNanosCounter.addAndGet(deltaInNanos); } public void setBusinessExceptionsTypes(Class<?>[] businessExceptionsTypes) { this.businessExceptionsTypes = businessExceptionsTypes.clone(); } public void setCommunicationExceptionsTypes(Class<?>[] communicationExceptionsTypes) { this.communicationExceptionsTypes = communicationExceptionsTypes.clone(); } @ManagedAttribute public void setMaxActive(int maxActive) { if (maxActive > 0) { this.maxActiveSemaphore = new Semaphore(maxActive); } else { this.maxActiveSemaphore = null; } } public void setMaxActiveSemaphoreAcquisitionMaxTimeInNanos(long semaphoreAcquisitionMaxTimeInNanos) { this.maxActiveSemaphoreAcquisitionMaxTimeInNanos = semaphoreAcquisitionMaxTimeInNanos; } @ManagedAttribute(description = "Max acquisition duration fur the max active semaphore") public void setSemaphoreAcquisitionMaxTimeInMillis(long semaphoreAcquisitionMaxTimeInMillis) { this.maxActiveSemaphoreAcquisitionMaxTimeInNanos = TimeUnit.NANOSECONDS.convert(semaphoreAcquisitionMaxTimeInMillis, TimeUnit.MILLISECONDS); } @ManagedAttribute public void setSlowInvocationThresholdInMillis(long slowInvocationThresholdInMillis) { this.slowInvocationThresholdInNanos = TimeUnit.NANOSECONDS.convert(slowInvocationThresholdInMillis, TimeUnit.MILLISECONDS); } public void setSlowInvocationThresholdInNanos(long slowInvocationThresholdInNanos) { this.slowInvocationThresholdInNanos = slowInvocationThresholdInNanos; } @ManagedAttribute public void setVerySlowInvocationThresholdInMillis(long verySlowInvocationThresholdInMillis) { this.verySlowInvocationThresholdInNanos = TimeUnit.NANOSECONDS.convert(verySlowInvocationThresholdInMillis, TimeUnit.MILLISECONDS); } public void setVerySlowInvocationThresholdInNanos(long verySlowInvocationThresholdInNanos) { this.verySlowInvocationThresholdInNanos = verySlowInvocationThresholdInNanos; } @Override public String toString() { return new ToStringCreator(this) // .append("objectName", this.objectName) // .append("slowInvocationThresholdInMillis", this.getSlowInvocationThresholdInMillis()) // .append("verySlowInvocationThresholdInMillis", this.getVerySlowInvocationThresholdInMillis()) // .append("communicationExceptionsTypes", this.communicationExceptionsTypes) // .append("businessExceptionsTypes", this.businessExceptionsTypes) // .append("invocationCount", this.invocationCounter) // .append("maxActiveAvailablePermits", getMaxActiveAvailablePermits()) // .append("totalDurationInMillis", this.getTotalDurationInMillis()) // .toString(); } }