001package nl.nlighten.prometheus.wildfly; 002 003import io.prometheus.client.*; 004 005import javax.servlet.Filter; 006import javax.servlet.FilterChain; 007import javax.servlet.FilterConfig; 008import javax.servlet.ServletException; 009import javax.servlet.ServletRequest; 010import javax.servlet.ServletResponse; 011import javax.servlet.http.HttpServletRequest; 012import javax.servlet.http.HttpServletResponse; 013import java.io.IOException; 014 015/** 016 * A servlet filter that will be added to deployed servlets by the MetricFilterExtension that provides the following metrics: 017 * <p> 018 * - A Histogram with response time distribution per context 019 * - A Gauge with the number of concurrent request per context 020 * - A Gauge with a the number of responses per context and status code 021 * </p> 022 * Example metrics being exported: 023 * <pre> 024 * servlet_request_seconds_bucket{"/foo", "GET", "0.1",} 1.0 025 * .... 026 * servlet_request_seconds_bucket{"/foo", "GET", "+Inf",} 1.0 027 * servlet_request_concurrent_total{"/foo",} 1.0 028 * servlet_response_status_total{"/foo", "200",} 1.0 029 * </pre> 030 */ 031public class ServletMetricsFilter implements Filter { 032 public static final String BUCKET_CONFIG_PARAM = "buckets"; 033 private static Histogram servletLatency; 034 private static Gauge servletConcurrentRequest; 035 private static Gauge servletStatusCodes; 036 037 private static int UNDEFINED_HTTP_STATUS = 999; 038 039 @Override 040 public void init(FilterConfig filterConfig) throws ServletException { 041 synchronized (this){ 042 if (servletLatency == null) { 043 Histogram.Builder servletLatencyBuilder = Histogram.build() 044 .name("servlet_request_seconds") 045 .help("The time taken fulfilling servlet requests") 046 .labelNames("context", "method"); 047 048 if ((filterConfig.getInitParameter(BUCKET_CONFIG_PARAM) != null) && (!filterConfig.getInitParameter(BUCKET_CONFIG_PARAM).isEmpty())) { 049 String[] bucketParams = filterConfig.getInitParameter(BUCKET_CONFIG_PARAM).split(","); 050 double[] buckets = new double[bucketParams.length]; 051 for (int i = 0; i < bucketParams.length; i++) { 052 buckets[i] = Double.parseDouble(bucketParams[i].trim()); 053 } 054 servletLatencyBuilder.buckets(buckets); 055 } else { 056 servletLatencyBuilder.buckets(.01, .05, .1, .25, .5, 1, 2.5, 5, 10, 30); 057 } 058 059 servletLatency = servletLatencyBuilder.register(); 060 061 Gauge.Builder servletConcurrentRequestBuilder = Gauge.build() 062 .name("servlet_request_concurrent_total") 063 .help("Number of concurrent requests for given context.") 064 .labelNames("context"); 065 066 servletConcurrentRequest = servletConcurrentRequestBuilder.register(); 067 068 Gauge.Builder servletStatusCodesBuilder = Gauge.build() 069 .name("servlet_response_status_total") 070 .help("Number of requests for given context and status code.") 071 .labelNames("context", "status"); 072 073 servletStatusCodes = servletStatusCodesBuilder.register(); 074 075 } 076 } 077 } 078 079 @Override 080 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 081 if (!(servletRequest instanceof HttpServletRequest)) { 082 filterChain.doFilter(servletRequest, servletResponse); 083 return; 084 } 085 086 HttpServletRequest request = (HttpServletRequest) servletRequest; 087 088 if (!request.isAsyncStarted()) { 089 String context = getContext(request); 090 091 servletConcurrentRequest.labels(context).inc(); 092 093 Histogram.Timer timer = servletLatency 094 .labels(context, request.getMethod()) 095 .startTimer(); 096 097 try { 098 filterChain.doFilter(servletRequest, servletResponse); 099 } finally { 100 timer.observeDuration(); 101 servletConcurrentRequest.labels(context).dec(); 102 servletStatusCodes.labels(context, Integer.toString(getStatus((HttpServletResponse) servletResponse))).inc(); 103 } 104 } else { 105 filterChain.doFilter(servletRequest, servletResponse); 106 } 107 } 108 109 private int getStatus(HttpServletResponse response) { 110 try { 111 return response.getStatus(); 112 } catch (Exception ex) { 113 return UNDEFINED_HTTP_STATUS; 114 } 115 } 116 117 private String getContext(HttpServletRequest request) { 118 if (request.getContextPath() != null && !request.getContextPath().isEmpty()) { 119 return request.getContextPath(); 120 } else { 121 return "/"; 122 } 123 } 124 125 @Override 126 public void destroy() { 127 // NOOP 128 } 129}