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