| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.apache.openejb.monitoring; |
| |
| import org.apache.openejb.api.Monitor; |
| import org.apache.openejb.core.interceptor.InterceptorData; |
| import org.apache.openejb.loader.SystemInstance; |
| import org.apache.openejb.math.stat.descriptive.SynchronizedDescriptiveStatistics; |
| import org.apache.openejb.util.LogCategory; |
| import org.apache.openejb.util.Logger; |
| import org.apache.xbean.finder.ClassFinder; |
| |
| import javax.annotation.PostConstruct; |
| import javax.annotation.PreDestroy; |
| import javax.ejb.AfterBegin; |
| import javax.ejb.AfterCompletion; |
| import javax.ejb.BeforeCompletion; |
| import javax.ejb.PostActivate; |
| import javax.ejb.PrePassivate; |
| import javax.interceptor.AroundInvoke; |
| import javax.interceptor.AroundTimeout; |
| import javax.interceptor.InvocationContext; |
| import java.lang.reflect.Method; |
| import java.util.Collection; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicLong; |
| |
| /** |
| * @version $Rev$ $Date$ |
| */ |
| public class StatsInterceptor { |
| static { |
| InterceptorData.cacheScan(StatsInterceptor.class); |
| } |
| |
| private static final String DISABLE_STAT_INTERCEPTOR_PROPERTY = "openejb.stats.interceptor.disable"; |
| |
| public static final InterceptorData metadata = InterceptorData.scan(StatsInterceptor.class); |
| |
| private final Map<Method, Stats> map = new ConcurrentHashMap<>(); |
| private final AtomicLong invocations = new AtomicLong(); |
| private final AtomicLong invocationTime = new AtomicLong(); |
| |
| private final Monitor monitor; |
| private final boolean enabled; |
| |
| public StatsInterceptor(final Class<?> componentClass) { |
| |
| monitor = componentClass.getAnnotation(Monitor.class); |
| final ClassFinder finder = new ClassFinder(componentClass); |
| for (final Method method : finder.findAnnotatedMethods(Monitor.class)) { |
| map.put(method, new Stats(method, monitor)); |
| } |
| enabled = monitor != null || map.size() > 0; |
| } |
| |
| public boolean isMonitoringEnabled() { |
| return enabled; |
| } |
| |
| @Managed |
| public long getInvocationCount() { |
| return invocations.get(); |
| } |
| |
| @Managed |
| public long getInvocationTime() { |
| return invocationTime.get(); |
| } |
| |
| @Managed |
| public long getMonitoredMethods() { |
| return map.size(); |
| } |
| |
| @ManagedCollection(type = Stats.class, key = "method") |
| public Collection<Stats> stats() { |
| return map.values(); |
| } |
| |
| // private Method $n() throws NoSuchMethodException { return this.getClass().getMethod(\"$n\"); } @$n public void $n(InvocationContext invocationContext) throws Exception { record(invocationContext, $n()); } |
| |
| @AroundInvoke |
| public Object invoke(final InvocationContext invocationContext) throws Exception { |
| return record(invocationContext, null); |
| } |
| |
| public Method PostConstruct() throws NoSuchMethodException { |
| return this.getClass().getMethod("PostConstruct"); |
| } |
| |
| @PostConstruct |
| public void PostConstruct(final InvocationContext invocationContext) throws Exception { |
| final long start = System.nanoTime(); |
| record(invocationContext, PostConstruct()); |
| final long end = System.nanoTime(); |
| Logger.getInstance(LogCategory.MONITORING, "org.apache.openejb.monitoring") |
| .debug("instance.created", invocationContext.getTarget().getClass().getName(), end - start); |
| } |
| |
| public Method PreDestroy() throws NoSuchMethodException { |
| return this.getClass().getMethod("PreDestroy"); |
| } |
| |
| @PreDestroy |
| public void PreDestroy(final InvocationContext invocationContext) throws Exception { |
| final long start = System.nanoTime(); |
| record(invocationContext, PreDestroy()); |
| final long end = System.nanoTime(); |
| Logger.getInstance(LogCategory.MONITORING, "org.apache.openejb.monitoring") |
| .debug("instance.discarded", invocationContext.getTarget().getClass().getName(), end - start); |
| } |
| |
| public Method PostActivate() throws NoSuchMethodException { |
| return this.getClass().getMethod("PostActivate"); |
| } |
| |
| @PostActivate |
| public void PostActivate(final InvocationContext invocationContext) throws Exception { |
| record(invocationContext, PostActivate()); |
| } |
| |
| public Method PrePassivate() throws NoSuchMethodException { |
| return this.getClass().getMethod("PrePassivate"); |
| } |
| |
| @PrePassivate |
| public void PrePassivate(final InvocationContext invocationContext) throws Exception { |
| record(invocationContext, PrePassivate()); |
| } |
| |
| public Method AroundTimeout() throws NoSuchMethodException { |
| return this.getClass().getMethod("AroundTimeout"); |
| } |
| |
| @AroundTimeout |
| public void AroundTimeout(final InvocationContext invocationContext) throws Exception { |
| record(invocationContext, AroundTimeout()); |
| } |
| |
| public Method AfterBegin() throws NoSuchMethodException { |
| return this.getClass().getMethod("AfterBegin"); |
| } |
| |
| @AfterBegin |
| public void AfterBegin(final InvocationContext invocationContext) throws Exception { |
| record(invocationContext, AfterBegin()); |
| } |
| |
| public Method BeforeCompletion() throws NoSuchMethodException { |
| return this.getClass().getMethod("BeforeCompletion"); |
| } |
| |
| @BeforeCompletion |
| public void BeforeCompletion(final InvocationContext invocationContext) throws Exception { |
| record(invocationContext, BeforeCompletion()); |
| } |
| |
| public Method AfterCompletion() throws NoSuchMethodException { |
| return this.getClass().getMethod("AfterCompletion"); |
| } |
| |
| @AfterCompletion |
| public void AfterCompletion(final InvocationContext invocationContext) throws Exception { |
| record(invocationContext, AfterCompletion()); |
| } |
| |
| private Object record(final InvocationContext invocationContext, final Method callback) throws Exception { |
| invocations.incrementAndGet(); |
| |
| final Stats stats = enabled ? stats(invocationContext, callback) : null; |
| final long start = System.nanoTime(); |
| try { |
| return invocationContext.proceed(); |
| } finally { |
| long time = System.nanoTime() - start; |
| time = millis(time); // do it in 2 steps since otherwise the measure is false (more false) |
| if (stats != null) { |
| stats.record(time); |
| } |
| invocationTime.addAndGet(time); |
| } |
| } |
| |
| private long millis(final long nanos) { |
| return TimeUnit.MILLISECONDS.convert(nanos, TimeUnit.NANOSECONDS); |
| } |
| |
| private Stats stats(final InvocationContext invocationContext, final Method callback) { |
| final Method method = callback == null ? invocationContext.getMethod() : callback; |
| |
| Stats stats = map.get(method); |
| if (stats == null) { |
| synchronized (map) { |
| stats = map.get(method); |
| if (stats == null) { |
| stats = new Stats(method, monitor); |
| map.put(method, stats); |
| } |
| } |
| } |
| return stats; |
| } |
| |
| public class Stats { |
| private final AtomicLong count = new AtomicLong(); |
| private final SynchronizedDescriptiveStatistics samples; |
| |
| // Used as the prefix for the MBeanAttributeInfo |
| private final String method; |
| |
| public Stats(final Method method, final Monitor classAnnotation) { |
| final Monitor methodAnnotation = method.getAnnotation(Monitor.class); |
| |
| final int window = methodAnnotation != null ? methodAnnotation.sample() : classAnnotation != null ? classAnnotation.sample() : 2000; |
| |
| this.samples = new SynchronizedDescriptiveStatistics(window); |
| final String s = ","; |
| |
| final StringBuilder sb = new StringBuilder(method.getName()); |
| sb.append("("); |
| final Class<?>[] params = method.getParameterTypes(); |
| for (final Class<?> clazz : params) { |
| sb.append(clazz.getSimpleName()); |
| sb.append(s); |
| } |
| if (params.length > 0) { |
| sb.delete(sb.length() - s.length(), sb.length()); |
| } |
| sb.append(")"); |
| |
| this.method = sb.toString(); |
| } |
| |
| @Managed |
| public void setSampleSize(final int i) { |
| samples.setWindowSize(i); |
| } |
| |
| @Managed |
| public int getSampleSize() { |
| return samples.getWindowSize(); |
| } |
| |
| @Managed |
| public long getCount() { |
| return count.get(); |
| } |
| |
| @Managed |
| public double getPercentile99() { |
| return samples.getPercentile(99.0); |
| } |
| |
| @Managed |
| public double getPercentile90() { |
| return samples.getPercentile(90.0); |
| } |
| |
| @Managed |
| public double getPercentile75() { |
| return samples.getPercentile(75.0); |
| } |
| |
| @Managed |
| public double getPercentile50() { |
| return samples.getPercentile(50.0); |
| } |
| |
| @Managed |
| public double getPercentile25() { |
| return samples.getPercentile(25.0); |
| } |
| |
| @Managed |
| public double getPercentile10() { |
| return samples.getPercentile(10.0); |
| } |
| |
| @Managed |
| public double getPercentile01() { |
| return samples.getPercentile(1.0); |
| } |
| |
| @Managed |
| public double getStandardDeviation() { |
| return samples.getStandardDeviation(); |
| } |
| |
| @Managed |
| public double getMean() { |
| return samples.getMean(); |
| } |
| |
| @Managed |
| public double getVariance() { |
| return samples.getVariance(); |
| } |
| |
| @Managed |
| public double getGeometricMean() { |
| return samples.getGeometricMean(); |
| } |
| |
| @Managed |
| public double getSkewness() { |
| return samples.getSkewness(); |
| } |
| |
| @Managed |
| public double getKurtosis() { |
| return samples.getKurtosis(); |
| } |
| |
| @Managed |
| public double getMax() { |
| return samples.getMax(); |
| } |
| |
| @Managed |
| public double getMin() { |
| return samples.getMin(); |
| } |
| |
| @Managed |
| public double getSum() { |
| return samples.getSum(); |
| } |
| |
| @Managed |
| public double getSumsq() { |
| return samples.getSumsq(); |
| } |
| |
| @Managed |
| public double[] sortedValues() { |
| return samples.getSortedValues(); |
| } |
| |
| @Managed |
| public double[] values() { |
| return samples.getValues(); |
| } |
| |
| public void record(final long time) { |
| count.incrementAndGet(); |
| samples.addValue(time); |
| } |
| |
| } |
| |
| public static boolean isStatsActivated() { |
| return SystemInstance.get().getOptions().get(DISABLE_STAT_INTERCEPTOR_PROPERTY, true); |
| } |
| } |