diff --git a/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java b/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java index 2f10f0730..d3dd2d768 100644 --- a/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java +++ b/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java @@ -11,7 +11,7 @@ * This class handles common initialization and label logic for the standard metrics. * You should never subclass this class. *

- *

Initilization

+ *

Initialization

* After calling build() on a subclass, {@link Builder#name(String) name}, * {@link SimpleCollector.Builder#help(String) help}, * {@link SimpleCollector.Builder#labelNames(String...) labelNames}, diff --git a/simpleclient_servlet/pom.xml b/simpleclient_servlet/pom.xml index f0a0ae40a..a13b6ca5c 100644 --- a/simpleclient_servlet/pom.xml +++ b/simpleclient_servlet/pom.xml @@ -67,5 +67,10 @@ 8.1.7.v20120910 test + + org.apache.commons + commons-lang3 + 3.4 + diff --git a/simpleclient_servlet/src/main/java/io/prometheus/client/filter/MetricsFilter.java b/simpleclient_servlet/src/main/java/io/prometheus/client/filter/MetricsFilter.java new file mode 100644 index 000000000..6b8443d55 --- /dev/null +++ b/simpleclient_servlet/src/main/java/io/prometheus/client/filter/MetricsFilter.java @@ -0,0 +1,77 @@ +package io.prometheus.client.filter; + +import io.prometheus.client.Counter; +import io.prometheus.client.Histogram; +import org.apache.commons.lang3.StringUtils; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Created by andrewstuart on 11/19/16. + * + * The MetricsFilter class exists to provide a high-level filter that enables tunable collection of metrics for Servlet + * performance. + * + * By default, this filter will provide metrics that distinguish 3 levels deep for the request path + * (including servlet context path). + */ +public class MetricsFilter implements Filter { + public static final String PATH_COMPONENT_PARAM = "path-components"; + public static final int DEFAULT_PATH_COMPONENTS = 3; + + private static Histogram servletLatency = null; + + private static int pathComponents = DEFAULT_PATH_COMPONENTS; + + private static boolean isEmpty(String string) { + return string != null && !"".equals(string); + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + servletLatency = Histogram.build() + .name("servlet_request_latency") + .help("The time taken fulfilling uportal requests") + .labelNames("path", "verb") + .register(); + + if (!isEmpty(filterConfig.getInitParameter(PATH_COMPONENT_PARAM))) { + pathComponents = Integer.valueOf(filterConfig.getInitParameter(PATH_COMPONENT_PARAM)); + } + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + if (servletLatency == null || !(servletRequest instanceof HttpServletRequest)) { + filterChain.doFilter(servletRequest, servletResponse); + return; + } + + HttpServletRequest request = (HttpServletRequest) servletRequest; + + String path = request.getRequestURI(); + int lastSlash = StringUtils.ordinalIndexOf(path, "/", pathComponents+1); + + Histogram.Timer timer = servletLatency + .labels(lastSlash == -1 ? path : path.substring(0, lastSlash), request.getMethod()) + .startTimer(); + + try { + filterChain.doFilter(servletRequest, servletResponse); + } finally { + timer.observeDuration(); + } + } + + @Override + public void destroy() { + } +} diff --git a/simpleclient_spring_boot/pom.xml b/simpleclient_spring_boot/pom.xml index be308780b..9b4b5c58f 100644 --- a/simpleclient_spring_boot/pom.xml +++ b/simpleclient_spring_boot/pom.xml @@ -69,6 +69,16 @@ spring-boot-actuator 1.3.3.RELEASE + + org.springframework.boot + spring-boot-starter-aop + 1.3.3.RELEASE + + + org.apache.commons + commons-lang3 + 3.4 + diff --git a/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/EnablePrometheusEndpoint.java b/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/EnablePrometheusEndpoint.java index e799756e9..b11bd747f 100644 --- a/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/EnablePrometheusEndpoint.java +++ b/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/EnablePrometheusEndpoint.java @@ -38,7 +38,7 @@ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented -@Import(PrometheusEndpointConfiguration.class) +@Import({PrometheusEndpointConfiguration.class, MethodTimer.class}) public @interface EnablePrometheusEndpoint { } diff --git a/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/EnablePrometheusTiming.java b/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/EnablePrometheusTiming.java new file mode 100644 index 000000000..116039d1c --- /dev/null +++ b/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/EnablePrometheusTiming.java @@ -0,0 +1,17 @@ +package io.prometheus.client.spring.boot; + +import org.springframework.context.annotation.Import; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Created by andrewstuart on 11/26/16. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Import(MethodTimer.class) +public @interface EnablePrometheusTiming { +} \ No newline at end of file diff --git a/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/EnableSpringBootMetricsCollector.java b/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/EnableSpringBootMetricsCollector.java index c9a52850c..ddd204a5b 100644 --- a/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/EnableSpringBootMetricsCollector.java +++ b/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/EnableSpringBootMetricsCollector.java @@ -12,6 +12,4 @@ @Retention(RetentionPolicy.RUNTIME) @Documented @Import(PrometheusMetricsConfiguration.class) -public @interface EnableSpringBootMetricsCollector { - -} +public @interface EnableSpringBootMetricsCollector {} diff --git a/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/MethodTimer.java b/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/MethodTimer.java new file mode 100644 index 000000000..1584150dc --- /dev/null +++ b/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/MethodTimer.java @@ -0,0 +1,40 @@ +package io.prometheus.client.spring.boot; + +import io.prometheus.client.Histogram; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; + +import org.springframework.context.annotation.Import; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.ControllerAdvice; + +/** + * Created by andrew on 11/24/16. + */ +@Aspect +@ControllerAdvice +@Component +public class MethodTimer { + public static final String METRIC_NAME = "prometheus_method_timing"; + + public static final Histogram hist = Histogram.build() + .name(METRIC_NAME) + .help("Automatic method timing") + .labelNames("signature") + .register(); + + @Around("within(@PrometheusMethodTiming *) || execution(@PrometheusMethodTiming * *.*(..))") + public Object timeMethod(ProceedingJoinPoint pjp) throws Throwable { + Histogram.Timer t = hist.labels(pjp.getSignature().toShortString()).startTimer(); + try { + Object result = pjp.proceed(); + t.observeDuration(); + return result; + } catch (Throwable exception) { + t.observeDuration(); + throw exception; + } + } +} diff --git a/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/PrometheusMethodTiming.java b/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/PrometheusMethodTiming.java new file mode 100644 index 000000000..e97be4e28 --- /dev/null +++ b/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/PrometheusMethodTiming.java @@ -0,0 +1,16 @@ +package io.prometheus.client.spring.boot; + +import org.springframework.context.annotation.Import; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Created by andrew on 11/24/16. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.TYPE}) +public @interface PrometheusMethodTiming { +} diff --git a/simpleclient_spring_boot/src/test/java/io/prometheus/client/annotation/AutomaticMethodTimerTest.java b/simpleclient_spring_boot/src/test/java/io/prometheus/client/annotation/AutomaticMethodTimerTest.java new file mode 100644 index 000000000..0887268b4 --- /dev/null +++ b/simpleclient_spring_boot/src/test/java/io/prometheus/client/annotation/AutomaticMethodTimerTest.java @@ -0,0 +1,89 @@ +package io.prometheus.client.annotation; + +import io.prometheus.client.Collector; +import io.prometheus.client.spring.boot.MethodTimer; +import io.prometheus.client.spring.boot.PrometheusMethodTiming; +import org.junit.Test; +import org.springframework.aop.aspectj.annotation.AspectJProxyFactory; + +import java.util.List; + +import static org.junit.Assert.*; + +/** + * Created by andrew on 11/25/16. + */ +public class AutomaticMethodTimerTest { + Timeable proxy; + + private static interface Timeable { + public void timeMe() throws Exception; + } + + private static class TestClass implements Timeable { + @PrometheusMethodTiming + public void timeMe() throws Exception { + Thread.sleep(20); + } + } + + private static interface Time2 { + public void timeMe() throws Exception; + } + + @PrometheusMethodTiming + private static class TestClass2 implements Time2 { + public void timeMe() throws Exception { + Thread.sleep(30); + } + } + + private TestClass c; + + @Test + public void timeMethod() throws Exception { + Timeable cprime = new TestClass(); + AspectJProxyFactory factory = new AspectJProxyFactory(cprime); + MethodTimer timer = new MethodTimer(); + factory.addAspect(timer); + Timeable proxy = factory.getProxy(); + + proxy.timeMe(); + + final List samples = MethodTimer.hist.collect(); + + assertNotNull(samples); + assertEquals(samples.size(), 1); + double tot = 0.0; + for(Collector.MetricFamilySamples.Sample s : samples.get(0).samples) { + if (s.name.equals(MethodTimer.METRIC_NAME + "_sum") && s.labelValues.get(0).equals("Timeable.timeMe()")) { + tot = s.value; + } + } + System.out.print(tot); + assert(0.019 < tot && tot < 0.025); + } + + @Test + public void timeClassAnnotation() throws Exception { + Time2 cprime = new TestClass2(); + AspectJProxyFactory factory = new AspectJProxyFactory(cprime); + MethodTimer timer = new MethodTimer(); + factory.addAspect(timer); + Time2 proxy = factory.getProxy(); + + proxy.timeMe(); + + final List samples = MethodTimer.hist.collect(); + + assertNotNull(samples); + assertEquals(samples.size(), 1); + double tot = 0.0; + for(Collector.MetricFamilySamples.Sample s : samples.get(0).samples) { + if (s.name.equals(MethodTimer.METRIC_NAME + "_sum") && s.labelValues.get(0).equals("Time2.timeMe()")) { + tot = s.value; + } + } + assert(0.029 < tot && tot < 0.035); + } +} \ No newline at end of file