package com.yammer.metrics.web;

import com.yammer.metrics.Metrics;
import com.yammer.metrics.core.Counter;
import com.yammer.metrics.core.Meter;
import com.yammer.metrics.core.Timer;

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

/**
 * {@link Filter} implementation which captures request information and a breakdown of the response
 * codes being returned.
 */
public abstract class WebappMetricsFilter implements Filter {
    private final ConcurrentMap<Integer, Meter> metersByStatusCode;
    private final Meter otherMeter;
    private final Counter activeRequests;
    private final Timer requestTimer;

    /**
     * Creates a new instance of the filter.
     *
     * @param meterNamesByStatusCode A map, keyed by status code, of meter names that we are
     *                               interested in.
     * @param otherMetricName        The name used for the catch-all meter.
     */
    public WebappMetricsFilter(Map<Integer, String> meterNamesByStatusCode,
                               String otherMetricName) {
        this.metersByStatusCode = new ConcurrentHashMap<Integer, Meter>(meterNamesByStatusCode
                                                                                      .size());
        for (Entry<Integer, String> entry : meterNamesByStatusCode.entrySet()) {
            metersByStatusCode.put(entry.getKey(),
                                   Metrics.newMeter(WebappMetricsFilter.class,
                                                    entry.getValue(),
                                                    "responses",
                                                    TimeUnit.SECONDS));
        }
        this.otherMeter = Metrics.newMeter(WebappMetricsFilter.class,
                                                 otherMetricName,
                                                 "responses",
                                                 TimeUnit.SECONDS);
        this.activeRequests = Metrics.newCounter(WebappMetricsFilter.class, "activeRequests");
        this.requestTimer = Metrics.newTimer(WebappMetricsFilter.class,
                                             "requests",
                                             TimeUnit.MILLISECONDS,
                                             TimeUnit.SECONDS);

    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request,
                         ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        final long start = System.currentTimeMillis();
        final StatusExposingServletResponse wrappedResponse =
                new StatusExposingServletResponse((HttpServletResponse) response);
        activeRequests.inc();
        try {
            chain.doFilter(request, wrappedResponse);
        } finally {
            activeRequests.dec();
            markMeterForStatusCode(wrappedResponse.getStatus());
            requestTimer.update(System.currentTimeMillis() - start, TimeUnit.MILLISECONDS);
        }
    }

    private void markMeterForStatusCode(int status) {
        final Meter metric = metersByStatusCode.get(status);
        if (metric != null) {
            metric.mark();
        } else {
            otherMeter.mark();
        }
    }

    private static class StatusExposingServletResponse extends HttpServletResponseWrapper {
        private int httpStatus;

        public StatusExposingServletResponse(HttpServletResponse response) {
            super(response);
        }

        @Override
        public void sendError(int sc) throws IOException {
            httpStatus = sc;
            super.sendError(sc);
        }

        @Override
        public void sendError(int sc, String msg) throws IOException {
            httpStatus = sc;
            super.sendError(sc, msg);
        }

        @Override
        public void setStatus(int sc) {
            httpStatus = sc;
            super.setStatus(sc);
        }

        public int getStatus() {
            return httpStatus;
        }
    }
}

@@ -176,16 +176,61 @@
 e.Timer;
+%0Aimport com.yammer.metrics.core.TimerContext;
 %0A%0Aimport
@@ -2951,63 +2951,8 @@
 n %7B%0A
-        final long start = System.currentTimeMillis();%0A
     
@@ -3009,16 +3009,16 @@
 ponse =%0A
+
         
@@ -3118,24 +3118,82 @@
 ests.inc();%0A
+        final TimerContext context = requestTimer.time();%0A
         try 
@@ -3264,24 +3264,52 @@
 %7D finally %7B%0A
+            context.stop();%0A
             
@@ -3326,24 +3326,24 @@
 ests.dec();%0A
+
             
@@ -3399,100 +3399,8 @@
 ));%0A
-            requestTimer.update(System.currentTimeMillis() - start, TimeUnit.MILLISECONDS);%0A
     

Improve timing resolution of WebappMetricsFilter.