Google

Oct 1, 2014

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.

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: ,

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home