Yammer metrics to monitor RESTful web services with a Servlet filter
This extends Yammer metrics to monitor RESTful web services and report them via its admin web to demonstrate how a Servlet filter can be added to gather metrics for
- response codes for 200, 201, 404, etc to get a count.
- active requests count
- requests timing.
Step 1: Decorate the "HttpServletResponseWrapper" class. HttpServletResponseWrapper is one particular implementation of HttpServletResponse which gives you a convenient way to wrap an existing response with some logic of your own without having to write a whole new implementation of the interface.
package com.mytutorial; import java.io.IOException; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; public class StatusServletResponse extends HttpServletResponseWrapper { private int httpStatus; public StatusServletResponse(HttpServletResponse response) { super(response); } @Override public void sendError(int sc) throws IOException { this.httpStatus = sc; super.sendError(sc); } @Override public void sendError(int sc, String msg) throws IOException { this.httpStatus = sc; super.sendError(sc, msg); } @Override public void setStatus(int sc) { this.httpStatus = sc; super.setStatus(sc); } public int getStatus() { return this.httpStatus; } }
Step 2: Add a Servlet filter to capture the above metrics. This where Yammer metrics is used.
Step 3: Register this Servlet filter via web.xml
Step 4: Deploy the application to a web server.
Step 5: Invoke the RESTFul web service via : http://localhost:8080/tutorial/rest/myapp/name/sam
Step 6: Invoke the Yammer metrics admin servlet via: http://localhost:8080/tutorial/admin and then click on "metrics".
Step 7: The metrics will be displayed as JSON data.Only a subset of JSON data is shown
package com.mytutorial; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; 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.HttpServletResponse; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import com.yammer.metrics.core.Counter; import com.yammer.metrics.core.Meter; import com.yammer.metrics.core.MetricsRegistry; import com.yammer.metrics.core.Timer; import com.yammer.metrics.core.TimerContext; public class MetricsFilter implements Filter { private static final String NAME_PREFIX = "responseCodes."; private static final int OK = 200; private static final int CREATED = 201; private static final int NO_CONTENT = 204; private static final int BAD_REQUEST = 400; private static final int NOT_FOUND = 404; private static final int SERVER_ERROR = 500; private ConcurrentMap<Integer, Meter> metersByStatusCode; private Counter activeRequests; private Timer requestTimer; public MetricsFilter() { } public void init(FilterConfig filterConfig) throws ServletException { MetricsRegistry metricsRegistry = getMetricsRegistry(filterConfig); Map<Integer, String> meterNamesByStatusCode = getMeterNamesByStatesCode(); this.metersByStatusCode = new ConcurrentHashMap<Integer, Meter>(meterNamesByStatusCode.size()); for (Entry<Integer, String> entry : meterNamesByStatusCode.entrySet()) { metersByStatusCode.put(entry.getKey(), metricsRegistry.newMeter(this.getClass(), entry.getValue(), "responses", TimeUnit.SECONDS)); } this.activeRequests = metricsRegistry.newCounter(this.getClass(), "activeRequests"); this.requestTimer = metricsRegistry.newTimer(this.getClass(), "requests", TimeUnit.MILLISECONDS, TimeUnit.SECONDS); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { final StatusServletResponse wrappedResponse = new StatusServletResponse( (HttpServletResponse) response); this.activeRequests.inc(); final TimerContext context = this.requestTimer.time(); try { chain.doFilter(request, wrappedResponse); } finally { context.stop(); this.activeRequests.dec(); markMeterForStatusCode(wrappedResponse.getStatus()); } } private void markMeterForStatusCode(int status) { final Meter metric = metersByStatusCode.get(status); if (metric != null) { metric.mark(); } } public void destroy() { } protected MetricsRegistry getMetricsRegistry(FilterConfig config) { WebApplicationContext springContext = WebApplicationContextUtils.getWebApplicationContext(config .getServletContext()); return springContext.getBean(MetricsRegistry.class); } protected Map<Integer, String> getMeterNamesByStatesCode() { final Map<Integer, String> meterNamesByStatusCode = new HashMap<Integer, String>(6); meterNamesByStatusCode.put(OK, NAME_PREFIX + "ok"); meterNamesByStatusCode.put(CREATED, NAME_PREFIX + "created"); meterNamesByStatusCode.put(NO_CONTENT, NAME_PREFIX + "noContent"); meterNamesByStatusCode.put(BAD_REQUEST, NAME_PREFIX + "badRequest"); meterNamesByStatusCode.put(NOT_FOUND, NAME_PREFIX + "notFound"); meterNamesByStatusCode.put(SERVER_ERROR, NAME_PREFIX + "serverError"); return meterNamesByStatusCode; } }
Step 3: Register this Servlet filter via web.xml
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:/com/mytutorial/applicationContext.xml</param-value> </context-param> <context-param> <param-name>resteasy.use.deployment.sensitive.factory</param-name> <param-value>false</param-value> </context-param> <context-param> <param-name>resteasy.servlet.mapping.prefix</param-name> <param-value>/rest</param-value> </context-param> <listener> <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class> </listener> <listener> <listener-class>org.jboss.resteasy.plugins.spring.SpringContextLoaderListener</listener-class> </listener> <listener> <listener-class>com.mytutorial.MetricsContextLoaderListener</listener-class> </listener> <filter> <description> Collects request metrics...</description> <display-name>MetricsFilter</display-name> <filter-name>MetricsFilter</filter-name> <filter-class>com.mytutorial.MetricsFilter</filter-class> </filter> <filter-mapping> <filter-name>MetricsFilter</filter-name> <servlet-name>resteasy-simple</servlet-name> </filter-mapping> <servlet> <servlet-name>resteasy-simple</servlet-name> <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class> </servlet> <servlet> <servlet-name>admin-servlet</servlet-name> <servlet-class>com.yammer.metrics.reporting.AdminServlet</servlet-class> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>resteasy-simple</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>admin-servlet</servlet-name> <url-pattern>/admin/*</url-pattern> </servlet-mapping> </web-app>
Step 4: Deploy the application to a web server.
Step 5: Invoke the RESTFul web service via : http://localhost:8080/tutorial/rest/myapp/name/sam
Step 6: Invoke the Yammer metrics admin servlet via: http://localhost:8080/tutorial/admin and then click on "metrics".
Step 7: The metrics will be displayed as JSON data.Only a subset of JSON data is shown
"com.mytutorial.MetricsFilter" : { "activeRequests" : { "type" : "counter", "count" : 1 }, "requests" : { "type" : "timer", "duration" : { "unit" : "milliseconds", "min" : 3075.95668, "max" : 3075.95668, "mean" : 3075.95668, "std_dev" : 0.0, "median" : 3075.95668, "p75" : 3075.95668, "p95" : 3075.95668, "p98" : 3075.95668, "p99" : 3075.95668, "p999" : 3075.95668 }, "rate" : { "unit" : "seconds", "count" : 1, "mean" : 0.01570764346171342, "m1" : 0.015991117074135343, "m5" : 0.0033057092356765017, "m15" : 0.0011080303990206543 } }, "responseCodes.badRequest" : { "type" : "meter", "event_type" : "responses", "unit" : "seconds", "count" : 0, "mean" : 0.0, "m1" : 0.0, "m5" : 0.0, "m15" : 0.0 }, "responseCodes.created" : { "type" : "meter", "event_type" : "responses", "unit" : "seconds", "count" : 0, "mean" : 0.0, "m1" : 0.0, "m5" : 0.0, "m15" : 0.0 }, "responseCodes.noContent" : { "type" : "meter", "event_type" : "responses", "unit" : "seconds", "count" : 0, "mean" : 0.0, "m1" : 0.0, "m5" : 0.0, "m15" : 0.0 }, "responseCodes.notFound" : { "type" : "meter", "event_type" : "responses", "unit" : "seconds", "count" : 0, "mean" : 0.0, "m1" : 0.0, "m5" : 0.0, "m15" : 0.0 }, "responseCodes.ok" : { "type" : "meter", "event_type" : "responses", "unit" : "seconds", "count" : 1, "mean" : 0.015706988535186733, "m1" : 0.015991117074135343, "m5" : 0.0033057092356765017, "m15" : 0.0011080303990206543 }, "responseCodes.serverError" : { "type" : "meter", "event_type" : "responses", "unit" : "seconds", "count" : 0, "mean" : 0.0, "m1" : 0.0, "m5" : 0.0, "m15" : 0.0 } }
Labels: Metrics, Monitoring
0 Comments:
Post a Comment
Subscribe to Post Comments [Atom]
<< Home