- * The metric name itself is required, and configured with a {@code metric-name} init parameter. + *
The metric name itself is required, and configured with a {@code metric-name} init parameter. * - *
- * The help parameter, configured with the {@code help} init parameter, is not required but strongly - * recommended. + *
The help parameter, configured with the {@code help} init parameter, is not required but strongly recommended. * - *
- * By default, this filter will provide metrics that distinguish only 1 level deep for the request - * path (including servlet context path), but can be configured with the {@code path-components} - * init parameter. Any number provided that is less than 1 will provide the full path granularity - * (warning, this may affect performance). + *
By default, this filter will provide metrics that distinguish only 1 level deep for the request path + * (including servlet context path), but can be configured with the {@code path-components} init parameter. Any number + * provided that is less than 1 will provide the full path granularity (warning, this may affect performance). * - *
- * The Histogram buckets can be configured with a {@code buckets} init parameter whose value is a - * comma-separated list of valid {@code double} values. + *
The Histogram buckets can be configured with a {@code buckets} init parameter whose value is a comma-separated list + * of valid {@code double} values. * - *
- * HTTP statuses will be aggregated via Counter. The name for this counter will be derived from the - * {@code metric-name} init parameter. + *
HTTP statuses will be aggregated via Counter. The name for this counter will be derived from the {@code metric-name} init parameter. * - *
- * {@code + ** * @author Andrew Stuart <andrew.stuart2@gmail.com> */ public class MetricsFilter implements Filter { - static final String PATH_COMPONENT_PARAM = "path-components"; - static final String HELP_PARAM = "help"; - static final String METRIC_NAME_PARAM = "metric-name"; - static final String BUCKET_CONFIG_PARAM = "buckets"; - static final String UNKNOWN_HTTP_STATUS_CODE = ""; - - private Histogram histogram = null; - private Counter statusCounter = null; - - // Package-level for testing purposes. - int pathComponents = 1; - private String metricName = null; - private String help = "The time taken fulfilling servlet requests"; - private double[] buckets = null; - - public MetricsFilter() { - } - - public MetricsFilter(String metricName, String help, Integer pathComponents, double[] buckets) { - this.metricName = metricName; - this.buckets = buckets; - if (help != null) { - this.help = help; - } - if (pathComponents != null) { - this.pathComponents = pathComponents; - } - } - - private boolean isEmpty(String s) { - return s == null || s.length() == 0; - } - - private String getComponents(String str) { - if (str == null || pathComponents < 1) { - return str; - } - int count = 0; - int i = -1; - do { - i = str.indexOf("/", i + 1); - if (i < 0) { - // Path is longer than specified pathComponents. - return str; - } - count++; - } while (count <= pathComponents); - - return str.substring(0, i); - } - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - Histogram.Builder builder = Histogram.build().labelNames("path", "method"); - - if (filterConfig == null && isEmpty(metricName)) { - throw new ServletException( - "No configuration object provided, and no metricName passed via constructor"); - } - - if (filterConfig != null) { - if (isEmpty(metricName)) { - metricName = filterConfig.getInitParameter(METRIC_NAME_PARAM); - if (isEmpty(metricName)) { - throw new ServletException("Init parameter \"" + METRIC_NAME_PARAM - + "\" is required; please supply a value"); - } - } - - if (!isEmpty(filterConfig.getInitParameter(HELP_PARAM))) { - help = filterConfig.getInitParameter(HELP_PARAM); - } - - // Allow overriding of the path "depth" to track - if (!isEmpty(filterConfig.getInitParameter(PATH_COMPONENT_PARAM))) { - pathComponents = Integer - .valueOf(filterConfig.getInitParameter(PATH_COMPONENT_PARAM)); - } - - // Allow users to override the default bucket configuration - if (!isEmpty(filterConfig.getInitParameter(BUCKET_CONFIG_PARAM))) { - String[] bucketParams = filterConfig.getInitParameter(BUCKET_CONFIG_PARAM) - .split(","); - buckets = new double[bucketParams.length]; - - for (int i = 0; i < bucketParams.length; i++) { - buckets[i] = Double.parseDouble(bucketParams[i]); - } - } - } - - if (buckets != null) { - builder = builder.buckets(buckets); - } - - histogram = builder.help(help).name(metricName).register(); - - statusCounter = Counter.build(metricName + "_status_total", "HTTP status codes of " + help) - .labelNames("path", "method", "status") - .register(); - } - - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, - FilterChain filterChain) throws IOException, ServletException { - if (!(servletRequest instanceof HttpServletRequest)) { - filterChain.doFilter(servletRequest, servletResponse); - return; - } - - HttpServletRequest request = (HttpServletRequest) servletRequest; - - String path = request.getRequestURI(); - - String components = getComponents(path); - String method = request.getMethod(); - Histogram.Timer timer = histogram.labels(components, method).startTimer(); - - try { - filterChain.doFilter(servletRequest, servletResponse); - } finally { - timer.observeDuration(); - statusCounter.labels(components, method, getStatusCode(servletResponse)).inc(); - } - } - - private String getStatusCode(ServletResponse servletResponse) { - if (!(servletResponse instanceof HttpServletResponse)) { - return UNKNOWN_HTTP_STATUS_CODE; - } - - return Integer.toString(((HttpServletResponse) servletResponse).getStatus()); - } - - @Override - public void destroy() { - } + static final String PATH_COMPONENT_PARAM = "path-components"; + static final String HELP_PARAM = "help"; + static final String METRIC_NAME_PARAM = "metric-name"; + static final String BUCKET_CONFIG_PARAM = "buckets"; + static final String UNKNOWN_HTTP_STATUS_CODE = ""; + + private Histogram histogram = null; + private Counter statusCounter = null; + + // Package-level for testing purposes. + int pathComponents = 1; + private String metricName = null; + private String help = "The time taken fulfilling servlet requests"; + private double[] buckets = null; + + public MetricsFilter() {} + + public MetricsFilter( + String metricName, + String help, + Integer pathComponents, + double[] buckets) { + this.metricName = metricName; + this.buckets = buckets; + if (help != null) { + this.help = help; + } + if (pathComponents != null) { + this.pathComponents = pathComponents; + } + } + + private boolean isEmpty(String s) { + return s == null || s.length() == 0; + } + + private String getComponents(String str) { + if (str == null || pathComponents < 1) { + return str; + } + int count = 0; + int i = -1; + do { + i = str.indexOf("/", i + 1); + if (i < 0) { + // Path is longer than specified pathComponents. + return str; + } + count++; + } while (count <= pathComponents); + + return str.substring(0, i); + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + Histogram.Builder builder = Histogram.build() + .labelNames("path", "method"); + + if (filterConfig == null && isEmpty(metricName)) { + throw new ServletException("No configuration object provided, and no metricName passed via constructor"); + } + + if (filterConfig != null) { + if (isEmpty(metricName)) { + metricName = filterConfig.getInitParameter(METRIC_NAME_PARAM); + if (isEmpty(metricName)) { + throw new ServletException("Init parameter \"" + METRIC_NAME_PARAM + "\" is required; please supply a value"); + } + } + + if (!isEmpty(filterConfig.getInitParameter(HELP_PARAM))) { + help = filterConfig.getInitParameter(HELP_PARAM); + } + + // Allow overriding of the path "depth" to track + if (!isEmpty(filterConfig.getInitParameter(PATH_COMPONENT_PARAM))) { + pathComponents = Integer.valueOf(filterConfig.getInitParameter(PATH_COMPONENT_PARAM)); + } + + // Allow users to override the default bucket configuration + if (!isEmpty(filterConfig.getInitParameter(BUCKET_CONFIG_PARAM))) { + String[] bucketParams = filterConfig.getInitParameter(BUCKET_CONFIG_PARAM).split(","); + buckets = new double[bucketParams.length]; + + for (int i = 0; i < bucketParams.length; i++) { + buckets[i] = Double.parseDouble(bucketParams[i]); + } + } + } + + if (buckets != null) { + builder = builder.buckets(buckets); + } + + histogram = builder + .help(help) + .name(metricName) + .register(); + + statusCounter = Counter.build(metricName + "_status_total", "HTTP status codes of " + help) + .labelNames("path", "method", "status") + .register(); + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + if (!(servletRequest instanceof HttpServletRequest)) { + filterChain.doFilter(servletRequest, servletResponse); + return; + } + + HttpServletRequest request = (HttpServletRequest) servletRequest; + + String path = request.getRequestURI(); + + String components = getComponents(path); + String method = request.getMethod(); + Histogram.Timer timer = histogram + .labels(components, method) + .startTimer(); + + try { + filterChain.doFilter(servletRequest, servletResponse); + } finally { + timer.observeDuration(); + statusCounter.labels(components, method, getStatusCode(servletResponse)).inc(); + } + } + + private String getStatusCode(ServletResponse servletResponse) { + if (!(servletResponse instanceof HttpServletResponse)) { + return UNKNOWN_HTTP_STATUS_CODE; + } + + return Integer.toString(((HttpServletResponse) servletResponse).getStatus()); + } + + @Override + public void destroy() { + } } diff --git a/simpleclient_servlet_jakarta/src/test/java/io/prometheus/client/exporter/ExampleBenchmark.java b/simpleclient_servlet_jakarta/src/test/java/io/prometheus/client/exporter/ExampleBenchmark.java index 6f710177e..bfbe9cd48 100644 --- a/simpleclient_servlet_jakarta/src/test/java/io/prometheus/client/exporter/ExampleBenchmark.java +++ b/simpleclient_servlet_jakarta/src/test/java/io/prometheus/client/exporter/ExampleBenchmark.java @@ -1,8 +1,9 @@ package io.prometheus.client.exporter; import io.prometheus.client.Gauge; -import org.eclipse.jetty.server.Server; + import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; @@ -28,6 +29,7 @@ public static void main(String[] args) throws Exception { server.setHandler(context); server.start(); Thread.sleep(1000); + ServerConnector connector = (ServerConnector) server.getConnectors()[0]; byte[] bytes = new byte[8192]; URL url = new URL("http", "localhost", connector.getLocalPort(), "/metrics"); diff --git a/simpleclient_servlet_jakarta/src/test/java/io/prometheus/client/filter/MetricsFilterTest.java b/simpleclient_servlet_jakarta/src/test/java/io/prometheus/client/filter/MetricsFilterTest.java index 93581ce51..493222cbc 100644 --- a/simpleclient_servlet_jakarta/src/test/java/io/prometheus/client/filter/MetricsFilterTest.java +++ b/simpleclient_servlet_jakarta/src/test/java/io/prometheus/client/filter/MetricsFilterTest.java @@ -2,8 +2,6 @@ import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; - -import org.eclipse.jetty.http.HttpMethod; import org.junit.After; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; @@ -26,220 +24,214 @@ import static org.mockito.Mockito.when; public class MetricsFilterTest { - MetricsFilter f = new MetricsFilter(); + + public static final String GET = "GET"; + public static final String POST = "POST"; + + MetricsFilter f = new MetricsFilter(); + + @After + public void clear() { + CollectorRegistry.defaultRegistry.clear(); + } + + @Test + public void init() throws Exception { + FilterConfig cfg = mock(FilterConfig.class); + when(cfg.getInitParameter(anyString())).thenReturn(null); + + String metricName = "foo"; + + when(cfg.getInitParameter(MetricsFilter.METRIC_NAME_PARAM)).thenReturn(metricName); + when(cfg.getInitParameter(MetricsFilter.PATH_COMPONENT_PARAM)).thenReturn("4"); + + f.init(cfg); + + assertEquals(f.pathComponents, 4); + + HttpServletRequest req = mock(HttpServletRequest.class); + + when(req.getRequestURI()).thenReturn("/foo/bar/baz/bang/zilch/zip/nada"); + when(req.getMethod()).thenReturn(GET); + + HttpServletResponse res = mock(HttpServletResponse.class); + FilterChain c = mock(FilterChain.class); + + f.doFilter(req, res, c); + + verify(c).doFilter(req, res); + + final Double sampleValue = CollectorRegistry.defaultRegistry.getSampleValue(metricName + "_count", new String[]{"path", "method"}, new String[]{"/foo/bar/baz/bang", GET}); + assertNotNull(sampleValue); + assertEquals(1, sampleValue, 0.0001); + } - @After - public void clear() { - CollectorRegistry.defaultRegistry.clear(); - } + @Test + public void doFilter() throws Exception { + HttpServletRequest req = mock(HttpServletRequest.class); + final String path = "/foo/bar/baz/bang/zilch/zip/nada"; + + when(req.getRequestURI()).thenReturn(path); + when(req.getMethod()).thenReturn(GET); - @Test - public void init() throws Exception { - FilterConfig cfg = mock(FilterConfig.class); - when(cfg.getInitParameter(anyString())).thenReturn(null); - - String metricName = "foo"; - - when(cfg.getInitParameter(MetricsFilter.METRIC_NAME_PARAM)).thenReturn(metricName); - when(cfg.getInitParameter(MetricsFilter.PATH_COMPONENT_PARAM)).thenReturn("4"); - - f.init(cfg); - - assertEquals(f.pathComponents, 4); - - HttpServletRequest req = mock(HttpServletRequest.class); - - when(req.getRequestURI()).thenReturn("/foo/bar/baz/bang/zilch/zip/nada"); - when(req.getMethod()).thenReturn(HttpMethod.GET.asString()); - - HttpServletResponse res = mock(HttpServletResponse.class); - FilterChain c = mock(FilterChain.class); - - f.doFilter(req, res, c); - - verify(c).doFilter(req, res); - - final Double sampleValue = CollectorRegistry.defaultRegistry.getSampleValue( - metricName + "_count", - new String[] { "path", "method" }, - new String[] { "/foo/bar/baz/bang", HttpMethod.GET.asString() }); - assertNotNull(sampleValue); - assertEquals(1, sampleValue, 0.0001); - } - - @Test - public void doFilter() throws Exception { - HttpServletRequest req = mock(HttpServletRequest.class); - final String path = "/foo/bar/baz/bang/zilch/zip/nada"; - - when(req.getRequestURI()).thenReturn(path); - when(req.getMethod()).thenReturn(HttpMethod.GET.asString()); - - HttpServletResponse res = mock(HttpServletResponse.class); - FilterChain c = mock(FilterChain.class); - - String name = "foo"; - FilterConfig cfg = mock(FilterConfig.class); - when(cfg.getInitParameter(MetricsFilter.METRIC_NAME_PARAM)).thenReturn(name); - when(cfg.getInitParameter(MetricsFilter.PATH_COMPONENT_PARAM)).thenReturn("0"); - - f.init(cfg); - f.doFilter(req, res, c); - - verify(c).doFilter(req, res); - - final Double sampleValue = CollectorRegistry.defaultRegistry.getSampleValue(name + "_count", - new String[] { "path", "method" }, - new String[] { path, HttpMethod.GET.asString() }); - assertNotNull(sampleValue); - assertEquals(1, sampleValue, 0.0001); - } - - @Test - public void testConstructor() throws Exception { - HttpServletRequest req = mock(HttpServletRequest.class); - final String path = "/foo/bar/baz/bang"; - when(req.getRequestURI()).thenReturn(path); - when(req.getMethod()).thenReturn(HttpMethod.POST.asString()); - - FilterChain c = mock(FilterChain.class); - doAnswer(new Answer{@code *+ * }* - * } - *prometheusFilter *io.prometheus.client.filter.MetricsFilter @@ -60,147 +51,150 @@ *0 * *