diff --git a/src/main/java/com/ryantenney/metrics/CompositeTimer.java b/src/main/java/com/ryantenney/metrics/CompositeTimer.java new file mode 100644 index 00000000..2bd478fd --- /dev/null +++ b/src/main/java/com/ryantenney/metrics/CompositeTimer.java @@ -0,0 +1,48 @@ +/** + * Copyright (C) 2012 Ryan W Tenney (ryan@10e.us) + * + * 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 com.ryantenney.metrics; + +import com.codahale.metrics.Timer; + +public class CompositeTimer +{ + private final Timer totalTimer; + private final Timer successTimer; + private final Timer failureTimer; + + public CompositeTimer(final Timer totalTimer, final Timer successTimer, final Timer failureTimer) + { + this.totalTimer = totalTimer; + this.successTimer = successTimer; + this.failureTimer = failureTimer; + } + + public Timer getTotalTimer() + { + return totalTimer; + } + + public Timer getSuccessTimer() + { + return successTimer; + } + + public Timer getFailureTimer() + { + return failureTimer; + } +} diff --git a/src/main/java/com/ryantenney/metrics/annotation/CompositeTimed.java b/src/main/java/com/ryantenney/metrics/annotation/CompositeTimed.java new file mode 100644 index 00000000..044e3f27 --- /dev/null +++ b/src/main/java/com/ryantenney/metrics/annotation/CompositeTimed.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2012 Ryan W Tenney (ryan@10e.us) + * + * 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 com.ryantenney.metrics.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation for marking a method of an annotated object as composite timed. + *

+ * Given a method like this: + *


+ *     {@literal @}CompositeTimed(name = "fancyName")
+ *     public String fancyName(String name) {
+ *         return "Sir Captain " + name;
+ *     }
+ * 
+ *

+ * Three timers for the defining class with the name {@code fancyName} will be created: + *

+ * and each time the + * {@code #fancyName(String)} method is invoked, the method's execution will be timed. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface CompositeTimed { + /** + * The name of the timer. + */ + String name() default ""; + + /** + * The suffix for the successful timer. + */ + String successSuffix() default ".success"; + + /** + * The suffix for the failing timer. + */ + String failedSuffix() default ".failed"; + + /** + * If {@code true}, use the given name as an absolute name. If {@code false}, use the given name + * relative to the annotated class. + */ + boolean absolute() default false; +} diff --git a/src/main/java/com/ryantenney/metrics/spring/CompositeTimedMethodInterceptor.java b/src/main/java/com/ryantenney/metrics/spring/CompositeTimedMethodInterceptor.java new file mode 100644 index 00000000..beed5666 --- /dev/null +++ b/src/main/java/com/ryantenney/metrics/spring/CompositeTimedMethodInterceptor.java @@ -0,0 +1,103 @@ +/** + * Copyright (C) 2012 Ryan W Tenney (ryan@10e.us) + * + * 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 com.ryantenney.metrics.spring; + +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import com.ryantenney.metrics.CompositeTimer; +import com.ryantenney.metrics.annotation.CompositeTimed; +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.Pointcut; +import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; +import org.springframework.core.Ordered; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Method; +import java.util.concurrent.TimeUnit; + +import static com.ryantenney.metrics.spring.AnnotationFilter.PROXYABLE_METHODS; + +class CompositeTimedMethodInterceptor extends AbstractMetricMethodInterceptor implements Ordered +{ + public static final Class ANNOTATION = CompositeTimed.class; + public static final Pointcut POINTCUT = new AnnotationMatchingPointcut(null, ANNOTATION); + public static final ReflectionUtils.MethodFilter METHOD_FILTER = new AnnotationFilter(ANNOTATION, PROXYABLE_METHODS); + + public CompositeTimedMethodInterceptor(final MetricRegistry metricRegistry, final Class targetClass) + { + super(metricRegistry, targetClass, ANNOTATION, METHOD_FILTER); + } + + @Override + protected Object invoke(final MethodInvocation invocation, final CompositeTimer metric, final CompositeTimed annotation) throws Throwable + { + final Timer.Context timerCtx = metric.getTotalTimer().time(); + boolean success = true; + final Object result; + try + { + result = invocation.proceed(); + } + catch (Throwable t) + { + success = false; + throw t; + } + finally + { + final long elapsed = timerCtx.stop(); + updateTimer(success ? metric.getSuccessTimer() : metric.getFailureTimer(), elapsed); + } + + return result; + } + + private void updateTimer(final Timer timer, final long elapsed) + { + timer.update(elapsed, TimeUnit.NANOSECONDS); + } + + @Override + protected CompositeTimer buildMetric(final MetricRegistry metricRegistry, final String metricName, final CompositeTimed annotation) + { + final Timer totalTimer = metricRegistry.timer(metricName); + final Timer successTimer = metricRegistry.timer(metricName + annotation.successSuffix()); + final Timer failureTimer = metricRegistry.timer(metricName + annotation.failedSuffix()); + return new CompositeTimer(totalTimer, successTimer, failureTimer); + } + + @Override + protected String buildMetricName(final Class targetClass, final Method method, final CompositeTimed annotation) + { + return Util.chooseName(annotation.name(), annotation.absolute(), targetClass, method); + } + + @Override + public int getOrder() + { + return HIGHEST_PRECEDENCE; + } + + static AdviceFactory adviceFactory(final MetricRegistry metricRegistry) { + return new AdviceFactory() { + @Override + public Advice getAdvice(Object bean, Class targetClass) { + return new CompositeTimedMethodInterceptor(metricRegistry, targetClass); + } + }; + } +} diff --git a/src/main/java/com/ryantenney/metrics/spring/MetricsBeanPostProcessorFactory.java b/src/main/java/com/ryantenney/metrics/spring/MetricsBeanPostProcessorFactory.java index 724a41ee..7293af8f 100644 --- a/src/main/java/com/ryantenney/metrics/spring/MetricsBeanPostProcessorFactory.java +++ b/src/main/java/com/ryantenney/metrics/spring/MetricsBeanPostProcessorFactory.java @@ -37,6 +37,12 @@ public static AdvisingBeanPostProcessor timed(final MetricRegistry metricRegistr return new AdvisingBeanPostProcessor(TimedMethodInterceptor.POINTCUT, TimedMethodInterceptor.adviceFactory(metricRegistry), proxyConfig); } + public static AdvisingBeanPostProcessor compositeTimed(final MetricRegistry metricRegistry, final ProxyConfig + proxyConfig) { + return new AdvisingBeanPostProcessor(CompositeTimedMethodInterceptor.POINTCUT, CompositeTimedMethodInterceptor.adviceFactory(metricRegistry), + proxyConfig); + } + public static AdvisingBeanPostProcessor counted(final MetricRegistry metricRegistry, final ProxyConfig proxyConfig) { return new AdvisingBeanPostProcessor(CountedMethodInterceptor.POINTCUT, CountedMethodInterceptor.adviceFactory(metricRegistry), proxyConfig); } diff --git a/src/main/java/com/ryantenney/metrics/spring/Util.java b/src/main/java/com/ryantenney/metrics/spring/Util.java index 2319d259..fd479170 100755 --- a/src/main/java/com/ryantenney/metrics/spring/Util.java +++ b/src/main/java/com/ryantenney/metrics/spring/Util.java @@ -24,6 +24,7 @@ import com.codahale.metrics.annotation.Metered; import com.codahale.metrics.annotation.Timed; import com.ryantenney.metrics.annotation.CachedGauge; +import com.ryantenney.metrics.annotation.CompositeTimed; import com.ryantenney.metrics.annotation.Counted; import com.ryantenney.metrics.annotation.Metric; @@ -35,6 +36,10 @@ static String forTimedMethod(Class klass, Member member, Timed annotation) { return chooseName(annotation.name(), annotation.absolute(), klass, member); } + static String forCompositeTimedMethod(Class klass, Member member, CompositeTimed annotation) { + return chooseName(annotation.name(), annotation.absolute(), klass, member); + } + static String forMeteredMethod(Class klass, Member member, Metered annotation) { return chooseName(annotation.name(), annotation.absolute(), klass, member); } diff --git a/src/main/java/com/ryantenney/metrics/spring/config/AnnotationDrivenBeanDefinitionParser.java b/src/main/java/com/ryantenney/metrics/spring/config/AnnotationDrivenBeanDefinitionParser.java index d54ed62d..9eff9cee 100755 --- a/src/main/java/com/ryantenney/metrics/spring/config/AnnotationDrivenBeanDefinitionParser.java +++ b/src/main/java/com/ryantenney/metrics/spring/config/AnnotationDrivenBeanDefinitionParser.java @@ -81,6 +81,12 @@ public BeanDefinition parse(Element element, ParserContext parserContext) { .addConstructorArgReference(metricsBeanName) .addConstructorArgValue(proxyConfig)); + registerComponent(parserContext, + build(MetricsBeanPostProcessorFactory.class, source, ROLE_INFRASTRUCTURE) + .setFactoryMethod("compositeTimed") + .addConstructorArgReference(metricsBeanName) + .addConstructorArgValue(proxyConfig)); + registerComponent(parserContext, build(MetricsBeanPostProcessorFactory.class, source, ROLE_INFRASTRUCTURE) .setFactoryMethod("counted") diff --git a/src/main/java/com/ryantenney/metrics/spring/config/annotation/MetricsConfigurationSupport.java b/src/main/java/com/ryantenney/metrics/spring/config/annotation/MetricsConfigurationSupport.java index 12711bbd..7277675e 100755 --- a/src/main/java/com/ryantenney/metrics/spring/config/annotation/MetricsConfigurationSupport.java +++ b/src/main/java/com/ryantenney/metrics/spring/config/annotation/MetricsConfigurationSupport.java @@ -15,6 +15,9 @@ */ package com.ryantenney.metrics.spring.config.annotation; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.health.HealthCheckRegistry; +import com.ryantenney.metrics.spring.MetricsBeanPostProcessorFactory; import org.springframework.aop.framework.ProxyConfig; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; @@ -25,10 +28,6 @@ import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.Assert; -import com.codahale.metrics.MetricRegistry; -import com.codahale.metrics.health.HealthCheckRegistry; -import com.ryantenney.metrics.spring.MetricsBeanPostProcessorFactory; - /** * This is the main class providing the configuration behind the Metrics Java config. * It is typically imported by adding {@link EnableMetrics @EnableMetrics} to an @@ -76,6 +75,12 @@ public BeanPostProcessor timedAnnotationBeanPostProcessor() { return MetricsBeanPostProcessorFactory.timed(getMetricRegistry(), proxyConfig); } + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public BeanPostProcessor compositeTimedAnnotationBeanPostProcessor() { + return MetricsBeanPostProcessorFactory.compositeTimed(getMetricRegistry(), proxyConfig); + } + @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public BeanPostProcessor countedAnnotationBeanPostProcessor() { diff --git a/src/test/java/com/ryantenney/metrics/spring/CovariantReturnTypeTest.java b/src/test/java/com/ryantenney/metrics/spring/CovariantReturnTypeTest.java index e49a9ba1..b2bef513 100644 --- a/src/test/java/com/ryantenney/metrics/spring/CovariantReturnTypeTest.java +++ b/src/test/java/com/ryantenney/metrics/spring/CovariantReturnTypeTest.java @@ -15,6 +15,7 @@ */ package com.ryantenney.metrics.spring; +import com.ryantenney.metrics.annotation.CompositeTimed; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -67,6 +68,12 @@ public void testTimedMethod() { Assert.assertTrue("No metrics should be registered", this.metricRegistry.getNames().isEmpty()); } + @Test + public void testCompositeTimedMethod() { + ctx.getBean(MeteredInterface.class).compositeTimedMethod(); + Assert.assertTrue("No metrics should be registered", this.metricRegistry.getNames().isEmpty()); + } + @Test public void testMeteredMethod() { ctx.getBean(MeteredInterface.class).meteredMethod(); @@ -95,6 +102,9 @@ public interface MeteredInterface { @Timed public Number timedMethod(); + @CompositeTimed + public Number compositeTimedMethod(); + @Metered public Number meteredMethod(); @@ -113,6 +123,11 @@ public Integer timedMethod() { return 0; } + @Override + public Integer compositeTimedMethod() { + return 0; + } + @Override public Long meteredMethod() { return 0L; diff --git a/src/test/java/com/ryantenney/metrics/spring/EnableMetricsTest.java b/src/test/java/com/ryantenney/metrics/spring/EnableMetricsTest.java index 588d69e3..df4e4ac4 100755 --- a/src/test/java/com/ryantenney/metrics/spring/EnableMetricsTest.java +++ b/src/test/java/com/ryantenney/metrics/spring/EnableMetricsTest.java @@ -15,18 +15,14 @@ */ package com.ryantenney.metrics.spring; -import static com.ryantenney.metrics.spring.TestUtil.forCachedGaugeMethod; -import static com.ryantenney.metrics.spring.TestUtil.forCountedMethod; -import static com.ryantenney.metrics.spring.TestUtil.forExceptionMeteredMethod; -import static com.ryantenney.metrics.spring.TestUtil.forGaugeField; -import static com.ryantenney.metrics.spring.TestUtil.forGaugeMethod; -import static com.ryantenney.metrics.spring.TestUtil.forMeteredMethod; -import static com.ryantenney.metrics.spring.TestUtil.forTimedMethod; +import static com.ryantenney.metrics.spring.TestUtil.*; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; +import com.ryantenney.metrics.CompositeTimer; +import com.ryantenney.metrics.annotation.CompositeTimed; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -136,6 +132,44 @@ public void timedMethod() throws Throwable { assertThat(timedMethodTimer.getCount(), is(1L)); } + @Test + public void compositeTimedMethod() throws Throwable { + // Verify that the Timer's counter is incremented on method invocation + CompositeTimer timedMethodTimer = forCompositeTimedMethod(metricRegistry, TestBean.class, "compositeTimedMethod"); + assertNotNull(timedMethodTimer); + assertNotNull(timedMethodTimer.getTotalTimer()); + assertNotNull(timedMethodTimer.getSuccessTimer()); + assertNotNull(timedMethodTimer.getFailureTimer()); + assertThat(timedMethodTimer.getTotalTimer().getCount(), is(0L)); + assertThat(timedMethodTimer.getSuccessTimer().getCount(), is(0L)); + assertThat(timedMethodTimer.getFailureTimer().getCount(), is(0L)); + testBean.compositeTimedMethod(); + assertThat(timedMethodTimer.getTotalTimer().getCount(), is(1L)); + assertThat(timedMethodTimer.getSuccessTimer().getCount(), is(1L)); + assertThat(timedMethodTimer.getFailureTimer().getCount(), is(0L)); + } + + @Test + public void exceptionCompositeTimedMethod() throws Throwable { + // Verify that the Timer's counter is incremented on method invocation + CompositeTimer timedMethodTimer = forCompositeTimedMethod(metricRegistry, TestBean.class, "exceptionCompositeTimedMethod"); + assertNotNull(timedMethodTimer); + assertNotNull(timedMethodTimer.getTotalTimer()); + assertNotNull(timedMethodTimer.getSuccessTimer()); + assertNotNull(timedMethodTimer.getFailureTimer()); + assertThat(timedMethodTimer.getTotalTimer().getCount(), is(0L)); + assertThat(timedMethodTimer.getSuccessTimer().getCount(), is(0L)); + assertThat(timedMethodTimer.getFailureTimer().getCount(), is(0L)); + try + { + testBean.exceptionCompositeTimedMethod(); + } + catch (Throwable t){} + assertThat(timedMethodTimer.getTotalTimer().getCount(), is(1L)); + assertThat(timedMethodTimer.getSuccessTimer().getCount(), is(0L)); + assertThat(timedMethodTimer.getFailureTimer().getCount(), is(1L)); + } + @Test public void meteredMethod() throws Throwable { // Verify that the Meter's counter is incremented on method invocation @@ -220,6 +254,14 @@ public int cachedGaugeMethod() { @Timed public void timedMethod() {} + @CompositeTimed + public void compositeTimedMethod() {} + + @CompositeTimed + public void exceptionCompositeTimedMethod() { + throw new RuntimeException(); + } + @Metered public void meteredMethod() {} @@ -232,7 +274,6 @@ public void countedMethod(Runnable runnable) { public void exceptionMeteredMethod() { throw new RuntimeException(); } - } } diff --git a/src/test/java/com/ryantenney/metrics/spring/MeteredClassImpementsInterfaceTest.java b/src/test/java/com/ryantenney/metrics/spring/MeteredClassImplementsInterfaceTest.java similarity index 72% rename from src/test/java/com/ryantenney/metrics/spring/MeteredClassImpementsInterfaceTest.java rename to src/test/java/com/ryantenney/metrics/spring/MeteredClassImplementsInterfaceTest.java index 6a5d227d..0fa021f4 100644 --- a/src/test/java/com/ryantenney/metrics/spring/MeteredClassImpementsInterfaceTest.java +++ b/src/test/java/com/ryantenney/metrics/spring/MeteredClassImplementsInterfaceTest.java @@ -15,11 +15,11 @@ */ package com.ryantenney.metrics.spring; -import static com.ryantenney.metrics.spring.TestUtil.forCountedMethod; -import static com.ryantenney.metrics.spring.TestUtil.forMeteredMethod; -import static com.ryantenney.metrics.spring.TestUtil.forTimedMethod; +import static com.ryantenney.metrics.spring.TestUtil.*; import static org.junit.Assert.assertEquals; +import com.ryantenney.metrics.CompositeTimer; +import com.ryantenney.metrics.annotation.CompositeTimed; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -42,7 +42,8 @@ * but annotated at the class level doesn't throw an NPE * Also verifies that it does register metrics. */ -public class MeteredClassImpementsInterfaceTest { +public class MeteredClassImplementsInterfaceTest +{ MeteredClassInterface meteredClass; @@ -84,6 +85,12 @@ public void testTimedMethod() { Assert.assertFalse("Metrics should be registered", this.metricRegistry.getTimers().isEmpty()); } + @Test + public void testCompositeTimedMethod() { + ctx.getBean(MeteredClassInterface.class).compositeTimedMethod(); + Assert.assertFalse("Metrics should be registered", this.metricRegistry.getTimers().isEmpty()); + } + @Test public void testMeteredMethod() { ctx.getBean(MeteredClassInterface.class).meteredMethod(); @@ -117,6 +124,43 @@ public void timedMethod() throws Throwable { assertEquals(1, timedMethod.getCount()); } + @Test + public void compositeTimedMethod() throws Throwable { + CompositeTimer timedMethod = forCompositeTimedMethod(metricRegistry, MeteredClassImpl.class, + "compositeTimedMethod"); + + assertEquals(0, timedMethod.getTotalTimer().getCount()); + assertEquals(0, timedMethod.getSuccessTimer().getCount()); + assertEquals(0, timedMethod.getFailureTimer().getCount()); + + meteredClass.compositeTimedMethod(); + + assertEquals(1, timedMethod.getTotalTimer().getCount()); + assertEquals(1, timedMethod.getSuccessTimer().getCount()); + assertEquals(0, timedMethod.getFailureTimer().getCount()); + } + + @Test + public void exceptionCompositeTimedMethod() throws Throwable { + CompositeTimer timedMethod = forCompositeTimedMethod(metricRegistry, MeteredClassImpl.class, + "exceptionCompositeTimedMethod"); + + assertEquals(0, timedMethod.getTotalTimer().getCount()); + assertEquals(0, timedMethod.getSuccessTimer().getCount()); + assertEquals(0, timedMethod.getFailureTimer().getCount()); + + try{ + meteredClass.exceptionCompositeTimedMethod(); + } + catch(Throwable e) + { + } + + assertEquals(1, timedMethod.getTotalTimer().getCount()); + assertEquals(0, timedMethod.getSuccessTimer().getCount()); + assertEquals(1, timedMethod.getFailureTimer().getCount()); + } + @Test public void meteredMethod() throws Throwable { Meter meteredMethod = forMeteredMethod(metricRegistry, MeteredClassImpl.class, "meteredMethod"); @@ -147,6 +191,10 @@ public interface MeteredClassInterface { public void timedMethod(); + public void compositeTimedMethod(); + + public void exceptionCompositeTimedMethod() throws Throwable; + public void meteredMethod(); public void countedMethod(Runnable runnable); @@ -161,6 +209,17 @@ public static class MeteredClassImpl implements MeteredClassInterface { @Timed public void timedMethod() {} + @Override + @CompositeTimed + public void compositeTimedMethod() {} + + @Override + @CompositeTimed + public void exceptionCompositeTimedMethod() throws Throwable + { + throw new BogusException(); + } + @Override @Metered public void meteredMethod() {} diff --git a/src/test/java/com/ryantenney/metrics/spring/MeteredClassTest.java b/src/test/java/com/ryantenney/metrics/spring/MeteredClassTest.java index 6c6f8b26..acf11b68 100755 --- a/src/test/java/com/ryantenney/metrics/spring/MeteredClassTest.java +++ b/src/test/java/com/ryantenney/metrics/spring/MeteredClassTest.java @@ -15,13 +15,7 @@ */ package com.ryantenney.metrics.spring; -import static com.ryantenney.metrics.spring.TestUtil.forCachedGaugeMethod; -import static com.ryantenney.metrics.spring.TestUtil.forCountedMethod; -import static com.ryantenney.metrics.spring.TestUtil.forExceptionMeteredMethod; -import static com.ryantenney.metrics.spring.TestUtil.forGaugeField; -import static com.ryantenney.metrics.spring.TestUtil.forGaugeMethod; -import static com.ryantenney.metrics.spring.TestUtil.forMeteredMethod; -import static com.ryantenney.metrics.spring.TestUtil.forTimedMethod; +import static com.ryantenney.metrics.spring.TestUtil.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -29,6 +23,8 @@ import java.util.concurrent.TimeUnit; +import com.ryantenney.metrics.CompositeTimer; +import com.ryantenney.metrics.annotation.CompositeTimed; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -102,6 +98,31 @@ public void timedMethod() throws Throwable { assertEquals(2, timedMethod.getCount()); } + @Test + public void compositeTimedMethod() throws Throwable { + CompositeTimer timedMethod = forCompositeTimedMethod(metricRegistry, MeteredClass.class, + "compositeTimedMethod"); + + assertEquals(0, timedMethod.getTotalTimer().getCount()); + + meteredClass.compositeTimedMethod(false); + assertEquals(1, timedMethod.getTotalTimer().getCount()); + assertEquals(1, timedMethod.getSuccessTimer().getCount()); + assertEquals(0, timedMethod.getFailureTimer().getCount()); + + // getCount increments even when the method throws an exception + try { + meteredClass.compositeTimedMethod(true); + fail(); + } + catch (Throwable e) { + assertTrue(e instanceof BogusException); + } + assertEquals(2, timedMethod.getTotalTimer().getCount()); + assertEquals(1, timedMethod.getSuccessTimer().getCount()); + assertEquals(1, timedMethod.getFailureTimer().getCount()); + } + @Test public void meteredMethod() throws Throwable { Meter meteredMethod = forMeteredMethod(metricRegistry, MeteredClass.class, "meteredMethod"); @@ -186,12 +207,15 @@ public void quadruplyMeteredMethod() throws Throwable { Timer quadruple_Timed = forTimedMethod(metricRegistry, MeteredClass.class, "quadruplyMeteredMethod"); Meter quadruple_Metered = forMeteredMethod(metricRegistry, MeteredClass.class, "quadruplyMeteredMethod"); Meter quadruple_ExceptionMetered = forExceptionMeteredMethod(metricRegistry, MeteredClass.class, "quadruplyMeteredMethod"); + CompositeTimer quadruple_CompositeTimed = forCompositeTimedMethod(metricRegistry, MeteredClass.class, + "quadruplyMeteredMethod"); final Counter quadruple_Counted = forCountedMethod(metricRegistry, MeteredClass.class, "quadruplyMeteredMethod"); assertEquals(0, quadruple_Metered.getCount()); assertEquals(0, quadruple_Timed.getCount()); assertEquals(0, quadruple_ExceptionMetered.getCount()); assertEquals(0, quadruple_Counted.getCount()); + assertEquals(0, quadruple_CompositeTimed.getTotalTimer().getCount()); // doesn't throw an exception meteredClass.quadruplyMeteredMethod(new Runnable() { @@ -203,6 +227,9 @@ public void run() { assertEquals(1, quadruple_Metered.getCount()); assertEquals(1, quadruple_Timed.getCount()); + assertEquals(1, quadruple_CompositeTimed.getTotalTimer().getCount()); + assertEquals(1, quadruple_CompositeTimed.getSuccessTimer().getCount()); + assertEquals(0, quadruple_CompositeTimed.getFailureTimer().getCount()); assertEquals(0, quadruple_ExceptionMetered.getCount()); assertEquals(0, quadruple_Counted.getCount()); @@ -222,6 +249,9 @@ public void run() { } assertEquals(2, quadruple_Metered.getCount()); assertEquals(2, quadruple_Timed.getCount()); + assertEquals(2, quadruple_CompositeTimed.getTotalTimer().getCount()); + assertEquals(1, quadruple_CompositeTimed.getSuccessTimer().getCount()); + assertEquals(1, quadruple_CompositeTimed.getFailureTimer().getCount()); assertEquals(1, quadruple_ExceptionMetered.getCount()); assertEquals(0, quadruple_Counted.getCount()); } @@ -263,6 +293,31 @@ public void overloadedTimedMethod() { assertEquals(2, overloaded_param.getCount()); } + @Test + public void overloadedCompositeTimedMethod() { + Timer compositeOverloaded = metricRegistry.getTimers().get(MetricRegistry.name(MeteredClass.class + .getCanonicalName(), "overloaded-compositeTimed")); + Timer compositeOverloaded_param = metricRegistry.getTimers().get(MetricRegistry.name(MeteredClass.class.getCanonicalName(), "overloaded-compositeTimed-param")); + + assertEquals(0, compositeOverloaded.getCount()); + assertEquals(0, compositeOverloaded_param.getCount()); + + meteredClass.overloadedCompositeTimedMethod(); + + assertEquals(1, compositeOverloaded.getCount()); + assertEquals(0, compositeOverloaded_param.getCount()); + + meteredClass.overloadedCompositeTimedMethod(1); + + assertEquals(1, compositeOverloaded.getCount()); + assertEquals(1, compositeOverloaded_param.getCount()); + + meteredClass.overloadedCompositeTimedMethod(1); + + assertEquals(1, compositeOverloaded.getCount()); + assertEquals(2, compositeOverloaded_param.getCount()); + } + @Test public void overloadedMeteredMethod() { Meter overloaded = metricRegistry.getMeters().get(MetricRegistry.name(MeteredClass.class.getCanonicalName(), "overloaded-metered")); @@ -438,6 +493,13 @@ public void timedMethod(boolean doThrow) throws Throwable { } } + @CompositeTimed + public void compositeTimedMethod(boolean doThrow) throws Throwable { + if (doThrow) { + throw new BogusException(); + } + } + @Metered public void meteredMethod() {} @@ -457,6 +519,7 @@ public void exceptionMeteredMethod(Class clazz) throws } @Timed(name = "quadruplyMeteredMethod-timed") + @CompositeTimed(name = "quadruplyMeteredMethod-compositeTimed") @Metered(name = "quadruplyMeteredMethod-metered") @Counted(name = "quadruplyMeteredMethod-counted") @ExceptionMetered(name = "quadruplyMeteredMethod-exceptionMetered", cause = BogusException.class) @@ -470,9 +533,15 @@ public void varargsMeteredMethod(int ... params) {} @Timed(name = "overloaded-timed") public void overloadedTimedMethod() {} + @CompositeTimed(name = "overloaded-compositeTimed") + public void overloadedCompositeTimedMethod() {} + @Timed(name = "overloaded-timed-param") public void overloadedTimedMethod(int param) {} + @CompositeTimed(name = "overloaded-compositeTimed-param") + public void overloadedCompositeTimedMethod(int param) {} + @Metered(name = "overloaded-metered") public void overloadedMeteredMethod() {} diff --git a/src/test/java/com/ryantenney/metrics/spring/MeteredInterfaceTest.java b/src/test/java/com/ryantenney/metrics/spring/MeteredInterfaceTest.java index ac58276e..53de83ed 100755 --- a/src/test/java/com/ryantenney/metrics/spring/MeteredInterfaceTest.java +++ b/src/test/java/com/ryantenney/metrics/spring/MeteredInterfaceTest.java @@ -15,6 +15,7 @@ */ package com.ryantenney.metrics.spring; +import com.ryantenney.metrics.annotation.CompositeTimed; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -73,6 +74,12 @@ public void testTimedMethod() { Assert.assertTrue("No metrics should be registered", this.metricRegistry.getNames().isEmpty()); } + @Test + public void testCompositeTimedMethod() { + ctx.getBean(MeteredInterface.class).compositeTimedMethod(); + Assert.assertTrue("No metrics should be registered", this.metricRegistry.getNames().isEmpty()); + } + @Test public void testMeteredMethod() { ctx.getBean(MeteredInterface.class).meteredMethod(); @@ -101,6 +108,9 @@ public interface MeteredInterface { @Timed public void timedMethod(); + @CompositeTimed + public void compositeTimedMethod(); + @Metered public void meteredMethod(); @@ -117,6 +127,9 @@ public static class MeteredInterfaceImpl implements MeteredInterface { @Override public void timedMethod() {} + @Override + public void compositeTimedMethod() {} + @Override public void meteredMethod() {} diff --git a/src/test/java/com/ryantenney/metrics/spring/TestSuite.java b/src/test/java/com/ryantenney/metrics/spring/TestSuite.java index 3266c6c4..c3ac0f6b 100644 --- a/src/test/java/com/ryantenney/metrics/spring/TestSuite.java +++ b/src/test/java/com/ryantenney/metrics/spring/TestSuite.java @@ -29,7 +29,7 @@ CovariantReturnTypeTest.class, EnableMetricsTest.class, HealthCheckTest.class, - MeteredClassImpementsInterfaceTest.class, + MeteredClassImplementsInterfaceTest.class, MeteredClassTest.class, MeteredInterfaceTest.class, MetricAnnotationTest.class, diff --git a/src/test/java/com/ryantenney/metrics/spring/TestUtil.java b/src/test/java/com/ryantenney/metrics/spring/TestUtil.java index d09c3148..6bcffab5 100755 --- a/src/test/java/com/ryantenney/metrics/spring/TestUtil.java +++ b/src/test/java/com/ryantenney/metrics/spring/TestUtil.java @@ -21,6 +21,8 @@ import java.util.ArrayList; import java.util.List; +import com.ryantenney.metrics.CompositeTimer; +import com.ryantenney.metrics.annotation.CompositeTimed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,6 +43,10 @@ class TestUtil { private static final Logger log = LoggerFactory.getLogger(TestUtil.class); + static String forCompositeTimedMethod(Class klass, Member member, CompositeTimed annotation) { + return Util.forCompositeTimedMethod(klass, member, annotation); + } + static String forTimedMethod(Class klass, Member member, Timed annotation) { return Util.forTimedMethod(klass, member, annotation); } @@ -97,6 +103,19 @@ static Timer forTimedMethod(MetricRegistry metricRegistry, Class clazz, Strin return metricRegistry.getTimers().get(metricName); } + static CompositeTimer forCompositeTimedMethod(MetricRegistry metricRegistry, Class clazz, String methodName) { + Method method = findMethod(clazz, methodName); + CompositeTimed annotation = method.getAnnotation(CompositeTimed.class); + String metricName = forCompositeTimedMethod(clazz, method, annotation); + String successMetricName = metricName + annotation.successSuffix(); + String failureMetricName = metricName + annotation.failedSuffix(); + Timer timer = metricRegistry.getTimers().get(metricName); + Timer successTimer = metricRegistry.getTimers().get(successMetricName); + Timer failedTimer = metricRegistry.getTimers().get(failureMetricName); + log.info("Looking up timed method named '{}'", metricName); + return new CompositeTimer(timer, successTimer, failedTimer); + } + static Meter forMeteredMethod(MetricRegistry metricRegistry, Class clazz, String methodName) { Method method = findMethod(clazz, methodName); String metricName = forMeteredMethod(clazz, method, method.getAnnotation(Metered.class)); diff --git a/src/test/resources/metered-interface-impl.xml b/src/test/resources/metered-interface-impl.xml index 5619e2f1..cafef7ca 100644 --- a/src/test/resources/metered-interface-impl.xml +++ b/src/test/resources/metered-interface-impl.xml @@ -28,6 +28,6 @@ - + \ No newline at end of file