Package org.stagemonitor.web.monitor.widget

Source Code of org.stagemonitor.web.monitor.widget.RequestTraceServlet$OldRequestTraceRemover

package org.stagemonitor.web.monitor.widget;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.stagemonitor.core.Stagemonitor;
import org.stagemonitor.requestmonitor.RequestMonitor;
import org.stagemonitor.requestmonitor.RequestTrace;
import org.stagemonitor.requestmonitor.RequestTraceReporter;
import org.stagemonitor.web.WebPlugin;
import org.stagemonitor.web.monitor.HttpRequestTrace;

@WebServlet(urlPatterns = "/stagemonitor/request-traces", asyncSupported = true)
public class RequestTraceServlet extends HttpServlet implements RequestTraceReporter {

  public static final String CONNECTION_ID = "x-stagemonitor-connection-id";
  private static final long REQUEST_TIMEOUT = TimeUnit.SECONDS.toMillis(25);
  private static final long MAX_REQUEST_TRACE_BUFFERING_TIME = 60 * 1000;

  private final Logger logger = LoggerFactory.getLogger(getClass());
  private final WebPlugin webPlugin;
  private ConcurrentMap<String, ConcurrentLinkedQueue<HttpRequestTrace>> connectionIdToRequestTracesMap =
      new ConcurrentHashMap<String, ConcurrentLinkedQueue<HttpRequestTrace>>();
  private ConcurrentMap<String, AsyncContext> connectionIdToAsyncContextMap =
      new ConcurrentHashMap<String, AsyncContext>();
  private ConcurrentMap<String, Object> connectionIdToLockMap =
      new ConcurrentHashMap<String, Object>();

  /**
   * see {@link OldRequestTraceRemover}
   */
  private ScheduledExecutorService oldRequestTracesRemoverPool = Executors.newScheduledThreadPool(1, new ThreadFactory() {
    @Override
    public Thread newThread(Runnable r) {
      Thread thread = new Thread(r);
      thread.setDaemon(true);
      thread.setName("request-trace-remover");
      return thread;
    }
  });


  public RequestTraceServlet() {
    this(Stagemonitor.getConfiguration(WebPlugin.class));
  }

  public RequestTraceServlet(WebPlugin webPlugin) {
    RequestMonitor.addRequestTraceReporter(this);
    this.webPlugin = webPlugin;
    oldRequestTracesRemoverPool.schedule(new OldRequestTraceRemover(), MAX_REQUEST_TRACE_BUFFERING_TIME, TimeUnit.MILLISECONDS);
  }

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    if (!webPlugin.isWidgetEnabled()) {
      resp.sendError(HttpServletResponse.SC_NOT_FOUND);
      return;
    }
    final String connectionId = req.getParameter("connectionId");
    if (connectionId != null && !connectionId.trim().isEmpty()) {
      if (connectionIdToRequestTracesMap.containsKey(connectionId)) {
        logger.debug("picking up buffered requests");
        writeRequestTracesToResponse(resp, connectionIdToRequestTracesMap.remove(connectionId));
      } else {
        if (!startAsync(connectionId, req, resp)) {
          blockingWaitForRequestTrace(connectionId, resp);
        }
      }
    } else {
      resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
    }
  }

  private void blockingWaitForRequestTrace(String connectionId, HttpServletResponse resp) throws IOException {
    logger.info("Request does not support async processing. Falling back to blocking processor thread. " +
        "Mark your filters with <async-supported>true</async-supported> to enable non-blocking AsyncContext mode. " +
        "See https://blogs.oracle.com/enterprisetechtips/entry/asynchronous_support_in_servlet_3 for more information on why this is necessary.");
    Object lock = new Object();
    synchronized (lock) {
      connectionIdToLockMap.put(connectionId, lock);
      try {
        lock.wait(REQUEST_TIMEOUT);
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      } finally {
        connectionIdToLockMap.remove(connectionId, lock);
      }
      if (connectionIdToRequestTracesMap.containsKey(connectionId)) {
        writeRequestTracesToResponse(resp, connectionIdToRequestTracesMap.remove(connectionId));
      } else {
        writeEmptyResponse(resp);
      }
    }
  }

  private boolean startAsync(final String connectionId, HttpServletRequest req, HttpServletResponse response) {
    if (req.isAsyncSupported()) {
      final AsyncContext asyncContext = req.startAsync(req, response);
      asyncContext.addListener(new AsyncListener() {
        @Override
        public void onComplete(AsyncEvent event) throws IOException {
        }

        @Override
        public void onTimeout(AsyncEvent event) throws IOException {
          connectionIdToAsyncContextMap.remove(connectionId, event.getAsyncContext());
          writeEmptyResponse((HttpServletResponse)event.getSuppliedResponse());
        }

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

        @Override
        public void onStartAsync(AsyncEvent event) throws IOException {
        }
      });
      asyncContext.setTimeout(REQUEST_TIMEOUT);
      connectionIdToAsyncContextMap.put(connectionId, asyncContext);
    }
    return req.isAsyncSupported();
  }

  @Override
  public <T extends RequestTrace> void reportRequestTrace(T requestTrace) throws IOException {
    if (isActive() && requestTrace instanceof HttpRequestTrace) {
      HttpRequestTrace httpRequestTrace = (HttpRequestTrace) requestTrace;

      final String connectionId = httpRequestTrace.getConnectionId();
      if (connectionId != null && !connectionId.trim().isEmpty()) {
        logger.debug("reportRequestTrace {} ({})", requestTrace.getName(), requestTrace.getTimestamp());
        final AsyncContext asyncContext = connectionIdToAsyncContextMap.remove(connectionId);
        if (isActive(asyncContext)) {
          logger.debug("asyncContext {}", httpRequestTrace.getConnectionId());
          writeRequestTracesToResponse((HttpServletResponse) asyncContext.getResponse(), getAllRequestTraces(httpRequestTrace, connectionId));
          asyncContext.complete();
        } else {
          bufferRequestTrace(connectionId, httpRequestTrace);
        }

        final Object lock = connectionIdToLockMap.remove(connectionId);
        if (lock != null) {
          synchronized (lock) {
            lock.notifyAll();
          }
        }
      }
    }
  }

  private boolean isActive(AsyncContext asyncContext) {
    try {
      return asyncContext != null && !asyncContext.getResponse().isCommitted();
    } catch (RuntimeException e) {
      return false;
    }
  }

  private Collection<HttpRequestTrace> getAllRequestTraces(HttpRequestTrace httpRequestTrace, String connectionId) {
    Collection<HttpRequestTrace> allRequestTraces = new ConcurrentLinkedQueue<HttpRequestTrace>();
    allRequestTraces.add(httpRequestTrace);

    final ConcurrentLinkedQueue<HttpRequestTrace> bufferedRequestTraces = connectionIdToRequestTracesMap.remove(connectionId);
    if (bufferedRequestTraces != null) {
      allRequestTraces.addAll(bufferedRequestTraces);
    }
    return allRequestTraces;
  }

  private void bufferRequestTrace(String connectionId, HttpRequestTrace requestTrace) {
    logger.debug("bufferRequestTrace {} ({})", requestTrace.getName(), requestTrace.getTimestamp());
    ConcurrentLinkedQueue<HttpRequestTrace> httpRequestTraces = new ConcurrentLinkedQueue<HttpRequestTrace>();
    httpRequestTraces.add(requestTrace);

    final ConcurrentLinkedQueue<HttpRequestTrace> alreadyAssociatedValue = connectionIdToRequestTracesMap
        .putIfAbsent(connectionId, httpRequestTraces);
    if (alreadyAssociatedValue != null) {
      alreadyAssociatedValue.add(requestTrace);
    }
  }

  private void writeRequestTracesToResponse(HttpServletResponse response, Collection<HttpRequestTrace> requestTraces)
      throws IOException {
    response.setContentType("application/json");
    response.setHeader("Pragma", "no-cache");
    response.setCharacterEncoding("UTF-8");

    final ArrayList<String> jsonResponse = new ArrayList<String>(requestTraces.size());
    for (HttpRequestTrace requestTrace : requestTraces) {
      logger.debug("writeRequestTracesToResponse {} ({})", requestTrace.getName(), requestTrace.getTimestamp());
      jsonResponse.add(requestTrace.toJson());
    }
    response.getOutputStream().print(jsonResponse.toString());
    response.getOutputStream().close();
  }

  private void writeEmptyResponse(HttpServletResponse resp) throws IOException {
    writeRequestTracesToResponse(resp, Collections.<HttpRequestTrace>emptyList());
  }

  @Override
  public boolean isActive() {
    return webPlugin.isWidgetEnabled();
  }

  /**
   * Clears old request traces that are buffered in {@link #connectionIdToRequestTracesMap} but are never picked up
   * to prevent a memory leak
   */
  private class OldRequestTraceRemover implements Runnable {
    @Override
    public void run() {
      for (Map.Entry<String, ConcurrentLinkedQueue<HttpRequestTrace>> entry : connectionIdToRequestTracesMap.entrySet()) {
        final ConcurrentLinkedQueue<HttpRequestTrace> httpRequestTraces = entry.getValue();
        removeOldRequestTraces(httpRequestTraces);
        if (httpRequestTraces.isEmpty()) {
          removeOrphanEntry(entry);
        }
      }
    }

    private void removeOldRequestTraces(ConcurrentLinkedQueue<HttpRequestTrace> httpRequestTraces) {
      for (Iterator<HttpRequestTrace> iterator = httpRequestTraces.iterator(); iterator.hasNext(); ) {
        HttpRequestTrace httpRequestTrace = iterator.next();
        final long timeInBuffer = System.currentTimeMillis() - httpRequestTrace.getTimestampEnd();
        if (timeInBuffer > MAX_REQUEST_TRACE_BUFFERING_TIME) {
          iterator.remove();
        }
      }
    }

    private void removeOrphanEntry(Map.Entry<String, ConcurrentLinkedQueue<HttpRequestTrace>> entry) {
      // to eliminate race conditions remove only if queue is still empty
      connectionIdToRequestTracesMap.remove(entry.getKey(), new ConcurrentLinkedQueue<HttpRequestTrace>());
    }
  }
}
TOP

Related Classes of org.stagemonitor.web.monitor.widget.RequestTraceServlet$OldRequestTraceRemover

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.