package io.prometheus.client.filter; import io.prometheus.client.Histogram; 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 java.io.IOException; /** * The MetricsFilter class exists to provide a high-level filter that enables tunable collection of metrics for Servlet * performance. * * The Histogram 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. * * 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. * * {@code * <filter> * <filter-name>prometheusFilter</filter-name> * <filter-class>net.cccnext.ssp.portal.spring.filter.PrometheusMetricsFilter</filter-class> * <init-param> * <param-name>metric-name</param-name> * <param-value>webapp_metrics_filter</param-value> * </init-param> * <init-param> * <param-name>help</param-name> * <param-value>The time taken fulfilling servlet requests</param-value> * </init-param> * <init-param> * <param-name>buckets</param-name> * <param-value>0.005,0.01,0.025,0.05,0.075,0.1,0.25,0.5,0.75,1,2.5,5,7.5,10</param-value> * </init-param> * <init-param> * <param-name>path-components</param-name> * <param-value>0</param-value> * </init-param> * </filter> * } * * @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"; private Histogram histogram = 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 ) throws ServletException { this.metricName = metricName; this.buckets = buckets; if (help != null) { this.help = help; } if (pathComponents != null) { this.pathComponents = pathComponents; } this.init(null); } 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(); } @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(); Histogram.Timer timer = histogram .labels(getComponents(path), request.getMethod()) .startTimer(); try { filterChain.doFilter(servletRequest, servletResponse); } finally { timer.observeDuration(); } } @Override public void destroy() { } }