All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tqdev.metrics.jetty.InstrumentedHandler Maven / Gradle / Ivy

/* Copyright (C) 2017 Maurits van der Schee
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.tqdev.metrics.jetty;

import java.io.IOException;

import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.server.AsyncContextState;
import org.eclipse.jetty.server.HttpChannelState;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.util.thread.QueuedThreadPool;

import com.tqdev.metrics.core.Gauge;
import com.tqdev.metrics.core.MetricRegistry;

/**
 * An instrumented Jetty handler wrapper to keep track of total duration and
 * invocation count of requests grouped by status code, method and path.
 */
public class InstrumentedHandler extends HandlerWrapper {

	/** The registry to store metrics in. */
	private final MetricRegistry registry;

	/**
	 * Instantiates a new instrumented handler.
	 *
	 * @param registry
	 *            the registry
	 */
	public InstrumentedHandler(MetricRegistry registry) {
		this.registry = registry;
	}

	/** The listener to handle async requests consistently. */
	private AsyncListener listener = new AsyncListener() {
		private long startTime;

		@Override
		public void onTimeout(AsyncEvent event) throws IOException {
		}

		@Override
		public void onStartAsync(AsyncEvent event) throws IOException {
			startTime = System.currentTimeMillis() * 1000000;
			event.getAsyncContext().addListener(this);
		}

		@Override
		public void onError(AsyncEvent event) throws IOException {
		}

		@Override
		public void onComplete(AsyncEvent event) throws IOException {
			final AsyncContextState state = (AsyncContextState) event.getAsyncContext();
			final HttpServletRequest request = (HttpServletRequest) state.getRequest();
			final HttpServletResponse response = (HttpServletResponse) state.getResponse();
			updateResponses(request, response, startTime);
		}
	};

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jetty.server.handler.AbstractHandler#doStart()
	 */
	@Override
	protected void doStart() throws Exception {
		super.doStart();

		for (int responseStatus = 1; responseStatus <= 5; responseStatus++) {
			registry.set("jetty.Response.Invocations", responseStatus + "xx-responses", 0);
			registry.set("jetty.Response.Durations", responseStatus + "xx-responses", 0);
		}
		registry.set("jetty.Response.Invocations", "other-responses", 0);
		registry.set("jetty.Response.Durations", "other-responses", 0);
		for (HttpMethod method : HttpMethod.values()) {
			registry.set("jetty.Request.Invocations", method.asString().toLowerCase() + "-requests", 0);
			registry.set("jetty.Request.Durations", method.asString().toLowerCase() + "-requests", 0);
		}
		registry.set("jetty.Request.Invocations", "other-requests", 0);
		registry.set("jetty.Request.Durations", "other-requests", 0);
		registry.set("jetty.Aggregated.Invocations", "requests", 0);
		registry.set("jetty.Aggregated.Durations", "requests", 0);
		registry.set("jetty.Aggregated.Invocations", "dispatches", 0);
		registry.set("jetty.Aggregated.Durations", "dispatches", 0);

		registry.set("jetty.Thread.Gauges", "threads", (Gauge) () -> getServer().getThreadPool().getThreads());
		registry.set("jetty.Thread.Gauges", "idle-threads", (Gauge) () -> getServer().getThreadPool().getIdleThreads());
		if (getServer().getThreadPool() instanceof QueuedThreadPool) {
			registry.set("jetty.Thread.Gauges", "busy-threads",
					(Gauge) () -> ((QueuedThreadPool) getServer().getThreadPool()).getBusyThreads());
			registry.set("jetty.Thread.Gauges", "min-threads",
					(Gauge) () -> ((QueuedThreadPool) getServer().getThreadPool()).getMinThreads());
			registry.set("jetty.Thread.Gauges", "max-threads",
					(Gauge) () -> ((QueuedThreadPool) getServer().getThreadPool()).getMaxThreads());
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String,
	 * org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest,
	 * javax.servlet.http.HttpServletResponse)
	 */
	@Override
	public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
			throws IOException, ServletException {

		final long start;
		final HttpChannelState state = request.getHttpChannelState();
		if (state.isInitial()) {
			// new request
			start = request.getTimeStamp() * 1000000;
			state.addListener(listener);
		} else {
			// resumed request
			start = System.currentTimeMillis() * 1000000;
		}

		try {
			super.handle(path, request, httpRequest, httpResponse);
		} finally {
			final long duration = System.currentTimeMillis() * 1000000 - start;

			registry.increment("jetty.Aggregated.Invocations", "dispatches");
			registry.add("jetty.Aggregated.Durations", "dispatches", duration);

			if (!state.isSuspended() && state.isInitial()) {
				updateResponses(httpRequest, httpResponse, start);
			}
			// else onCompletion will handle it.
		}
	}

	/**
	 * Get a grouping identifier for metrics based on request method.
	 *
	 * @param method
	 *            the method
	 * @return the string
	 */
	private String getMethodGroup(String method) {
		final HttpMethod m = HttpMethod.fromString(method);
		if (m == null) {
			return "other";
		}
		return m.asString().toLowerCase();
	}

	/**
	 * Get a grouping identifier for metrics based on response status.
	 *
	 * @param status
	 *            the status
	 * @return the string
	 */
	private String getStatusGroup(int status) {
		final int responseStatus = status / 100;
		if ((responseStatus < 1) || (responseStatus > 5)) {
			return "other";
		}
		return responseStatus + "xx";
	}

	/**
	 * Update response based metrics such as duration.
	 *
	 * @param request
	 *            the request
	 * @param response
	 *            the response
	 * @param start
	 *            the start
	 */
	private void updateResponses(HttpServletRequest request, HttpServletResponse response, long start) {
		final long duration = System.currentTimeMillis() * 1000000 - start;
		registry.increment("jetty.Aggregated.Invocations", "requests");
		registry.add("jetty.Aggregated.Durations", "requests", duration);
		final String methodGroup = getMethodGroup(request.getMethod());
		registry.increment("jetty.Request.Invocations", methodGroup + "-requests");
		registry.add("jetty.Request.Durations", methodGroup + "-requests", duration);
		final String statusGroup = getStatusGroup(response.getStatus());
		registry.increment("jetty.Response.Invocations", statusGroup + "-responses");
		registry.add("jetty.Response.Durations", statusGroup + "-responses", duration);
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy