mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Jean-Noel Rouvignac
13.35.2013 dc50d3e793098123cf7417713f2790e862e13bbb
OPENDJ-858 (CR-1651) Add stats tracking to HTTP client connections 

Added support for monitoring statistics of the HTTP connection handler:
It monitors: total number of requests, and number of delete, get, patch, post and put requests.
It also monitors the internal LDAP operations to display how the server is performing.


HTTPConnectionHandler.java, HTTPClientConnection.java, CollectClientConnectionsFilter.java, SdkConnectionAdapter.java:
Added support for statistics.

config.ldif, HTTPConnectionHandlerConfiguration.xml, HTTPConnectionHandlerCfgDefn.properties:
Added "keep-stats" property.

02-config.ldif:
Added attributes "ds-mon-http-*" for monitoring HTTP statistics.

LDAPStatistics.java:
Changed getMonitorData() return type to List.
Made createAttribute protected.

HTTPStatsProbe.java, HTTPStatistics.java: ADDED
2 files added
9 files modified
663 ■■■■■ changed files
opends/resource/config/config.ldif 2 ●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 46 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/HTTPConnectionHandlerConfiguration.xml 26 ●●●●● patch | view | raw | blame | history
opends/src/admin/messages/HTTPConnectionHandlerCfgDefn.properties 2 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java 5 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/http/HTTPClientConnection.java 151 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/http/HTTPConnectionHandler.java 107 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/http/HTTPStatistics.java 144 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/http/HTTPStatsProbe.java 76 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/http/SdkConnectionAdapter.java 94 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/ldap/LDAPStatistics.java 10 ●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -487,7 +487,7 @@
ds-cfg-listen-address: 0.0.0.0
ds-cfg-listen-port: 8080
ds-cfg-accept-backlog: 128
#ds-cfg-keep-stats: true
ds-cfg-keep-stats: true
ds-cfg-use-tcp-keep-alive: true
ds-cfg-use-tcp-no-delay: true
ds-cfg-allow-tcp-reuse-address: true
opends/resource/schema/02-config.ldif
@@ -3677,6 +3677,42 @@
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
  SINGLE-VALUE
  X-ORIGIN 'OpenDJ Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.120
  NAME 'ds-mon-http-requests-total-count'
  EQUALITY integerMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
  SINGLE-VALUE
  X-ORIGIN 'OpenDJ Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.121
  NAME 'ds-mon-http-delete-requests-total-count'
  EQUALITY integerMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
  SINGLE-VALUE
  X-ORIGIN 'OpenDJ Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.122
  NAME 'ds-mon-http-get-requests-total-count'
  EQUALITY integerMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
  SINGLE-VALUE
  X-ORIGIN 'OpenDJ Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.123
  NAME 'ds-mon-http-patch-requests-total-count'
  EQUALITY integerMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
  SINGLE-VALUE
  X-ORIGIN 'OpenDJ Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.124
  NAME 'ds-mon-http-post-requests-total-count'
  EQUALITY integerMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
  SINGLE-VALUE
  X-ORIGIN 'OpenDJ Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.125
  NAME 'ds-mon-http-put-requests-total-count'
  EQUALITY integerMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
  SINGLE-VALUE
  X-ORIGIN 'OpenDJ Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
  NAME 'ds-cfg-access-control-handler'
  SUP top
@@ -3838,7 +3874,7 @@
  STRUCTURAL
  MUST ds-cfg-listen-port
  MAY ( ds-cfg-listen-address $
#        ds-cfg-keep-stats $
        ds-cfg-keep-stats $
        ds-cfg-use-tcp-keep-alive $
        ds-cfg-use-tcp-no-delay $
        ds-cfg-allow-tcp-reuse-address $
@@ -5591,7 +5627,13 @@
  ds-mon-abandon-operations-total-count $
  ds-mon-resident-time-abandon-operations-total-time $
  ds-mon-extended-operations-total-count $
  ds-mon-resident-time-extended-operations-total-time )
  ds-mon-resident-time-extended-operations-total-time $
  ds-mon-http-requests-total-count $
  ds-mon-http-delete-requests-total-count $
  ds-mon-http-get-requests-total-count $
  ds-mon-http-patch-requests-total-count $
  ds-mon-http-post-requests-total-count $
  ds-mon-http-put-requests-total-count )
  X-ORIGIN 'OpenDJ Directory Server' )
objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.14
  NAME 'ds-cfg-pbkdf2-password-storage-scheme'
opends/src/admin/defn/org/opends/server/admin/std/HTTPConnectionHandlerConfiguration.xml
@@ -214,6 +214,32 @@
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="keep-stats">
    <adm:synopsis>
      Indicates whether the
      <adm:user-friendly-name />
      should keep statistics.
    </adm:synopsis>
    <adm:description>
      If enabled, the
      <adm:user-friendly-name />
      maintains statistics about the number and types of operations
      requested over HTTP and the amount of data sent and received.
    </adm:description>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>true</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:boolean />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-keep-stats</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="max-request-size" advanced="true">
    <adm:synopsis>
      Specifies the size in bytes of the largest HTTP request message that will
opends/src/admin/messages/HTTPConnectionHandlerCfgDefn.properties
@@ -24,6 +24,8 @@
property.denied-client.requires-admin-action.synopsis=Changes to this property take effect immediately and do not interfere with connections that may have already been established.
property.enabled.synopsis=Indicates whether the HTTP Connection Handler is enabled.
property.java-class.synopsis=Specifies the fully-qualified name of the Java class that provides the HTTP Connection Handler implementation.
property.keep-stats.synopsis=Indicates whether the HTTP Connection Handler should keep statistics.
property.keep-stats.description=If enabled, the HTTP Connection Handler maintains statistics about the number and types of operations requested over HTTP and the amount of data sent and received.
property.key-manager-provider.synopsis=Specifies the name of the key manager that should be used with this HTTP Connection Handler .
property.key-manager-provider.requires-admin-action.synopsis=Changes to this property take effect immediately, but only for subsequent attempts to access the key manager provider for associated client connections.
property.key-manager-provider.syntax.aggregation.constraint-synopsis=The referenced key manager provider must be enabled when the HTTP Connection Handler is enabled and configured to use SSL.
opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java
@@ -279,6 +279,11 @@
    ctx.requestInfo =
        new HTTPRequestInfo(ctx.request, clientConnection.getConnectionID());
    if (this.connectionHandler.keepStats()) {
      this.connectionHandler.getStatTracker().addRequest(
          ctx.request.getMethod());
    }
    try
    {
      if (!canProcessRequest(request, clientConnection))
opends/src/server/org/opends/server/protocols/http/HTTPClientConnection.java
@@ -48,9 +48,28 @@
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import org.opends.server.api.ClientConnection;
import org.opends.server.core.AddOperation;
import org.opends.server.core.BindOperation;
import org.opends.server.core.CompareOperation;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ExtendedOperation;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.SearchOperation;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.ldap.AddResponseProtocolOp;
import org.opends.server.protocols.ldap.BindResponseProtocolOp;
import org.opends.server.protocols.ldap.CompareResponseProtocolOp;
import org.opends.server.protocols.ldap.DeleteResponseProtocolOp;
import org.opends.server.protocols.ldap.ExtendedResponseProtocolOp;
import org.opends.server.protocols.ldap.LDAPMessage;
import org.opends.server.protocols.ldap.ModifyDNResponseProtocolOp;
import org.opends.server.protocols.ldap.ModifyResponseProtocolOp;
import org.opends.server.protocols.ldap.ProtocolOp;
import org.opends.server.protocols.ldap.SearchResultDoneProtocolOp;
import org.opends.server.protocols.ldap.SearchResultEntryProtocolOp;
import org.opends.server.protocols.ldap.SearchResultReferenceProtocolOp;
import org.opends.server.types.CancelRequest;
import org.opends.server.types.CancelResult;
import org.opends.server.types.DN;
@@ -59,6 +78,7 @@
import org.opends.server.types.DisconnectReason;
import org.opends.server.types.IntermediateResponse;
import org.opends.server.types.Operation;
import org.opends.server.types.OperationType;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchResultReference;
@@ -76,7 +96,6 @@
  // TODO JNR Confirm with Matt that persistent searches are inapplicable to
  // Rest2LDAP.
  // TODO JNR Should I override getIdleTime()?
  // TODO JNR Implement stats
  /**
   * Class grouping together an {@link Operation} and its associated
@@ -128,6 +147,12 @@
  private boolean disconnectRequested;
  /**
   * Indicates whether the connection should keep statistics regarding the
   * operations that it is performing.
   */
  private final boolean keepStats;
  /**
   * The Map (messageID => {@link OperationWithFutureResult}) of all operations
   * currently in progress on this connection.
   */
@@ -153,6 +178,10 @@
  /** The reference to the connection handler that accepted this connection. */
  private final HTTPConnectionHandler connectionHandler;
  /** The statistics tracker associated with this client connection. */
  private final HTTPStatistics statTracker;
  private boolean useNanoTime = false;
  /** The protocol in use for this client connection. */
  private String protocol;
@@ -206,6 +235,15 @@
    this.securityStrengthFactor =
        calcSSF(request.getAttribute(SERVLET_SSF_CONSTANT));
    this.statTracker = this.connectionHandler.getStatTracker();
    this.keepStats = connectionHandler.keepStats();
    if (this.keepStats)
    {
      this.statTracker.updateConnect();
      this.useNanoTime = DirectoryServer.getUseNanoTime();
    }
    this.connectionID = DirectoryServer.newConnectionAccepted(this);
  }
@@ -283,6 +321,21 @@
  @Override
  public void sendResponse(Operation operation)
  {
    if (keepStats)
    {
      long time;
      if (useNanoTime)
      {
        time = operation.getProcessingNanoTime();
      }
      else
      {
        time = operation.getProcessingTime();
      }
      this.statTracker.updateOperationMonitoringData(operation
          .getOperationType(), time);
    }
    OperationWithFutureResult op =
        this.operationsInProgress.get(operation.getMessageID());
    if (op != null)
@@ -290,6 +343,12 @@
      try
      {
        op.futureResult.handleResult(getResponseResult(operation));
        if (keepStats)
        {
          this.statTracker.updateMessageWritten(new LDAPMessage(operation
              .getMessageID(), toResponseProtocolOp(operation)));
        }
      }
      catch (ErrorResultException e)
      {
@@ -298,6 +357,44 @@
    }
  }
  private ProtocolOp toResponseProtocolOp(Operation operation)
  {
    final int resultCode = operation.getResultCode().getIntValue();
    if (operation instanceof AddOperation)
    {
      return new AddResponseProtocolOp(resultCode);
    }
    else if (operation instanceof BindOperation)
    {
      return new BindResponseProtocolOp(resultCode);
    }
    else if (operation instanceof CompareOperation)
    {
      return new CompareResponseProtocolOp(resultCode);
    }
    else if (operation instanceof DeleteOperation)
    {
      return new DeleteResponseProtocolOp(resultCode);
    }
    else if (operation instanceof ExtendedOperation)
    {
      return new ExtendedResponseProtocolOp(resultCode);
    }
    else if (operation instanceof ModifyDNOperation)
    {
      return new ModifyDNResponseProtocolOp(resultCode);
    }
    else if (operation instanceof ModifyOperation)
    {
      return new ModifyResponseProtocolOp(resultCode);
    }
    else if (operation instanceof SearchOperation)
    {
      return new SearchResultDoneProtocolOp(resultCode);
    }
    throw new RuntimeException("Not implemented for operation " + operation);
  }
  /** {@inheritDoc} */
  @Override
  public void sendSearchEntry(SearchOperation operation,
@@ -309,6 +406,12 @@
    {
      ((SearchResultHandler) op.futureResult.getResultHandler())
          .handleEntry(from(searchEntry));
      if (keepStats)
      {
        this.statTracker.updateMessageWritten(new LDAPMessage(operation
            .getMessageID(), new SearchResultEntryProtocolOp(searchEntry)));
      }
    }
  }
@@ -323,6 +426,13 @@
    {
      ((SearchResultHandler) op.futureResult.getResultHandler())
          .handleReference(from(searchReference));
      if (keepStats)
      {
        this.statTracker.updateMessageWritten(new LDAPMessage(operation
            .getMessageID(), new SearchResultReferenceProtocolOp(
            searchReference)));
      }
    }
    return connectionValid;
  }
@@ -332,6 +442,12 @@
  protected boolean sendIntermediateResponseMessage(
      IntermediateResponse intermediateResponse)
  {
    // if (keepStats)
    // {
    // this.statTracker.updateMessageWritten(new LDAPMessage(
    // intermediateResponse.getOperation().getMessageID(),
    // new IntermediateResponseProtocolOp(intermediateResponse.getOID())));
    // }
    throw new RuntimeException("Not implemented");
  }
@@ -360,11 +476,10 @@
      disconnectRequested = true;
    }
    // TODO JNR
    // if (keepStats)
    // {
    // statTracker.updateDisconnect();
    // }
    if (keepStats)
    {
      statTracker.updateDisconnect();
    }
    if (connectionID >= 0)
    {
@@ -459,6 +574,15 @@
    if (previousValue != null)
    {
      operationsPerformed.incrementAndGet();
      final Operation operation = previousValue.operation;
      if (operation.getOperationType() == OperationType.ABANDON)
      {
        if (keepStats && operation.getResultCode() == ResultCode.CANCELED)
        {
          statTracker.updateAbandonedOperation();
        }
      }
    }
    return previousValue != null;
  }
@@ -517,6 +641,11 @@
            op.futureResult.handleErrorResult(ErrorResultException
               .newErrorResult(org.forgerock.opendj.ldap.ResultCode.CANCELLED));
            op.operation.abort(cancelRequest);
            if (keepStats)
            {
              statTracker.updateAbandonedOperation();
            }
          }
          catch (Exception e)
          { // make sure all operations are cancelled, no matter what
@@ -613,6 +742,16 @@
    buffer.append(getServerAddress()).append(":").append(getServerPort());
  }
  /**
   * Returns the statTracker for this connection handler.
   *
   * @return the statTracker for this connection handler
   */
  public HTTPStatistics getStatTracker()
  {
    return statTracker;
  }
  /** {@inheritDoc} */
  @Override
  public int getSSF()
opends/src/server/org/opends/server/protocols/http/HTTPConnectionHandler.java
@@ -70,9 +70,12 @@
import org.forgerock.opendj.rest2ldap.AuthorizationPolicy;
import org.forgerock.opendj.rest2ldap.Rest2LDAP;
import org.forgerock.opendj.rest2ldap.servlet.Rest2LDAPContextFactory;
import org.glassfish.grizzly.http.HttpProbe;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.HttpServerMonitoringConfig;
import org.glassfish.grizzly.http.server.NetworkListener;
import org.glassfish.grizzly.http.server.ServerConfiguration;
import org.glassfish.grizzly.monitoring.MonitoringConfig;
import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
import org.glassfish.grizzly.servlet.ServletRegistration;
import org.glassfish.grizzly.servlet.WebappContext;
@@ -94,6 +97,7 @@
import org.opends.server.extensions.NullTrustManagerProvider;
import org.opends.server.loggers.HTTPAccessLogger;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.monitors.ClientConnectionMonitorProvider;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
@@ -144,6 +148,9 @@
  /** The HTTP server embedded in OpenDJ. */
  private HttpServer httpServer;
  /** The HTTP probe that collects stats. */
  private HTTPStatsProbe httpProbe;
  /**
   * Holds the current client connections. Using {@link ConcurrentHashMap} to
   * ensure no concurrent reads/writes can happen and adds/removes are fast. We
@@ -152,6 +159,15 @@
  private Map<ClientConnection, ClientConnection> clientConnections =
      new ConcurrentHashMap<ClientConnection, ClientConnection>();
  /** The set of statistics collected for this connection handler. */
  private HTTPStatistics statTracker;
  /**
   * The client connection monitor provider associated with this connection
   * handler.
   */
  private ClientConnectionMonitorProvider connMonitor;
  /** The unique name assigned to this connection handler. */
  private String handlerName;
@@ -245,6 +261,22 @@
          messages);
    }
    if (config.isEnabled() && this.currentConfig.isEnabled() && isListening())
    { // server was running and will still be running
      // if the "enabled" was flipped, leave it to the stop / start server to
      // handle it
      if (!this.currentConfig.isKeepStats() && config.isKeepStats())
      { // it must now keep stats while it was not previously
        setHttpStatsProbe();
      }
      else if (this.currentConfig.isKeepStats() && !config.isKeepStats()
          && this.httpProbe != null)
      { // it must NOT keep stats anymore
        getHttpConfig().removeProbes(this.httpProbe);
        this.httpProbe = null;
      }
    }
    this.initConfig = config;
    this.currentConfig = config;
@@ -324,19 +356,17 @@
    // Unregister this as a change listener.
    currentConfig.removeHTTPChangeListener(this);
    // TODO JNR
    // if (connMonitor != null)
    // {
    // String lowerName = toLowerCase(connMonitor.getMonitorInstanceName());
    // DirectoryServer.deregisterMonitorProvider(lowerName);
    // }
    //
    // if (statTracker != null)
    // {
    // String lowerName = toLowerCase(statTracker.getMonitorInstanceName());
    // DirectoryServer.deregisterMonitorProvider(lowerName);
    // }
    if (connMonitor != null)
    {
      String lowerName = toLowerCase(connMonitor.getMonitorInstanceName());
      DirectoryServer.deregisterMonitorProvider(lowerName);
    }
    if (statTracker != null)
    {
      String lowerName = toLowerCase(statTracker.getMonitorInstanceName());
      DirectoryServer.deregisterMonitorProvider(lowerName);
    }
  }
  /** {@inheritDoc} */
@@ -451,6 +481,16 @@
    return handlerName;
  }
  /**
   * Retrieves the set of statistics maintained by this connection handler.
   *
   * @return The set of statistics maintained by this connection handler.
   */
  public HTTPStatistics getStatTracker()
  {
    return statTracker;
  }
  /** {@inheritDoc} */
  @Override
  public void initializeConnectionHandler(HTTPConnectionHandlerCfg config)
@@ -484,14 +524,14 @@
    }
    // TODO JNR
    // handle ds-cfg-keep-stats
    // handle ds-cfg-num-request-handlers??
    // // Create and register monitors.
    // statTracker = new LDAPStatistics(handlerName + " Statistics");
    // DirectoryServer.registerMonitorProvider(statTracker);
    //
    // connMonitor = new ClientConnectionMonitorProvider(this);
    // DirectoryServer.registerMonitorProvider(connMonitor);
    // Create and register monitors.
    statTracker = new HTTPStatistics(handlerName + " Statistics");
    DirectoryServer.registerMonitorProvider(statTracker);
    connMonitor = new ClientConnectionMonitorProvider(this);
    DirectoryServer.registerMonitorProvider(connMonitor);
    // Register this as a change listener.
    config.addHTTPChangeListener(this);
@@ -612,6 +652,17 @@
    return isConfigurationAcceptable(configuration, unacceptableReasons);
  }
  /**
   * Indicates whether this connection handler should maintain usage statistics.
   *
   * @return <CODE>true</CODE> if this connection handler should maintain usage
   *         statistics, or <CODE>false</CODE> if not.
   */
  public boolean keepStats()
  {
    return currentConfig.isKeepStats();
  }
  /** {@inheritDoc} */
  @Override
  public void processServerShutdown(Message reason)
@@ -715,6 +766,10 @@
        this.httpServer.getServerConfiguration();
    serverConfig.setMaxBufferedPostSize(requestSize);
    serverConfig.setMaxFormPostSize(requestSize);
    if (keepStats())
    {
      setHttpStatsProbe();
    }
    try
    {
@@ -799,6 +854,19 @@
    }
  }
  private void setHttpStatsProbe()
  {
    httpProbe = new HTTPStatsProbe(this.statTracker);
    getHttpConfig().addProbes(httpProbe);
  }
  private MonitoringConfig<HttpProbe> getHttpConfig()
  {
    final HttpServerMonitoringConfig monitoringCfg =
        this.httpServer.getServerConfiguration().getMonitoringConfig();
    return monitoringCfg.getHttpConfig();
  }
  private HTTPAuthenticationConfig getAuthenticationConfig(
      final JsonValue configuration)
  {
@@ -867,6 +935,7 @@
    TRACER.debugInfo("Stopping HTTP server...");
    this.httpServer.stop();
    this.httpServer = null;
    this.httpProbe = null;
    TRACER.debugInfo("HTTP server stopped");
    logError(NOTE_CONNHANDLER_STOPPED_LISTENING.get(handlerName));
  }
opends/src/server/org/opends/server/protocols/http/HTTPStatistics.java
New file
@@ -0,0 +1,144 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2013 ForgeRock AS
 */
package org.opends.server.protocols.http;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
import org.opends.server.protocols.ldap.LDAPStatistics;
import org.opends.server.types.Attribute;
/**
 * Collects statistics for HTTP. This class inherits from {@link LDAPStatistics}
 * to show the administrator how the underlying LDAP internal operations are
 * performing.
 */
public class HTTPStatistics extends LDAPStatistics
{
  /**
   * Map containing the total number of requests.
   * <p>
   * key: HTTP verb => value: number of requests for that verb.
   * </p>
   * Not using a ConcurrentMap implementation because the keys are static. The
   * keys are static because they need to be listed in the schema which is
   * static.
   */
  private Map<String, AtomicInteger> nbRequests =
      new HashMap<String, AtomicInteger>();
  /**
   * Total number of requests. The total number may be different than the sum of
   * the supported HTTP methods above because clients could use unsupported HTTP
   * verbs.
   */
  private AtomicInteger nbRequestsTotalCount = new AtomicInteger(0);
  /**
   * Constructor for this class.
   *
   * @param instanceName
   *          The name for this monitor provider instance.
   */
  public HTTPStatistics(String instanceName)
  {
    super(instanceName);
    // List the HTTP methods supported by Rest2LDAP
    final List<String> supportedHttpMethods =
        Arrays.asList("delete", "get", "patch", "post", "put");
    for (String method : supportedHttpMethods)
    {
      nbRequests.put(method, new AtomicInteger(0));
    }
  }
  /** {@inheritDoc} */
  @Override
  public void clearStatistics()
  {
    this.nbRequests.clear();
    super.clearStatistics();
  }
  /** {@inheritDoc} */
  @Override
  public List<Attribute> getMonitorData()
  {
    // first take a snapshot of all the data as fast as possible
    final Map<String, Integer> snapshot = new HashMap<String, Integer>();
    for (Entry<String, AtomicInteger> entry : this.nbRequests.entrySet())
    {
      snapshot.put(entry.getKey(), entry.getValue().get());
    }
    // do the same with the underlying data
    final List<Attribute> results = super.getMonitorData();
    // then add the snapshot data to the monitoring data
    int total = 0;
    for (Entry<String, Integer> entry : snapshot.entrySet())
    {
      final String httpMethod = entry.getKey();
      final Integer nb = entry.getValue();
      final String number = nb.toString();
      // nb should never be null since we only allow supported HTTP methods
      total += nb;
      results.add(createAttribute("ds-mon-http-" + httpMethod
          + "-requests-total-count", number));
    }
    results.add(createAttribute("ds-mon-http-requests-total-count", Integer
        .toString(total)));
    return results;
  }
  /**
   * Adds a request to the stats using the provided HTTP method.
   *
   * @param httpMethod
   *          the method of the HTTP request to add to the stats
   * @throws NullPointerException
   *           if the httpMethod is null
   */
  public void addRequest(String httpMethod) throws NullPointerException
  {
    AtomicInteger nb = this.nbRequests.get(httpMethod.toLowerCase());
    if (nb != null)
    {
      nb.incrementAndGet();
    } // else this is an unsupported HTTP method
    // always count any requests regardless of whether the method is supported
    this.nbRequestsTotalCount.incrementAndGet();
  }
}
opends/src/server/org/opends/server/protocols/http/HTTPStatsProbe.java
New file
@@ -0,0 +1,76 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2013 ForgeRock AS
 */
package org.opends.server.protocols.http;
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.Connection;
import org.glassfish.grizzly.http.HttpProbe;
/**
 * Probe that collect some statistics on the HTTP server: bytes read and bytes
 * written to the HTTP connection. We are using
 * {@link #onDataReceivedEvent(Connection, Buffer)} and
 * {@link #onDataSentEvent(Connection, Buffer)} because they are the only ones
 * that really output the number of bytes sent on the wire, including data
 * formatting for the HTTP protocol (chunk size, etc.).
 * <p>
 * Use
 * <code>curl "http://localhost:8080/users?_queryFilter=true&_prettyPrint=true"
 * --trace-ascii output.txt</code> to trace the client-server communication.
 * </p>
 */
@SuppressWarnings("rawtypes")
final class HTTPStatsProbe extends HttpProbe.Adapter
{
  private final HTTPStatistics statTracker;
  /**
   * Constructs and object from this class.
   *
   * @param statTracker
   *          the statistic tracker
   */
  public HTTPStatsProbe(HTTPStatistics statTracker)
  {
    this.statTracker = statTracker;
  }
  /** {@inheritDoc} */
  @Override
  public void onDataSentEvent(Connection connection, Buffer buffer)
  {
    this.statTracker.updateBytesWritten(buffer.limit());
  }
  /** {@inheritDoc} */
  @Override
  public void onDataReceivedEvent(Connection connection, Buffer buffer)
  {
    this.statTracker.updateBytesRead(buffer.limit());
  }
}
opends/src/server/org/opends/server/protocols/http/SdkConnectionAdapter.java
@@ -58,20 +58,42 @@
import org.forgerock.opendj.ldap.responses.CompareResult;
import org.forgerock.opendj.ldap.responses.ExtendedResult;
import org.forgerock.opendj.ldap.responses.Result;
import org.opends.server.core.AbandonOperation;
import org.opends.server.core.AbandonOperationBasis;
import org.opends.server.core.AddOperation;
import org.opends.server.core.AddOperationBasis;
import org.opends.server.core.BindOperation;
import org.opends.server.core.BindOperationBasis;
import org.opends.server.core.BoundedWorkQueueStrategy;
import org.opends.server.core.CompareOperation;
import org.opends.server.core.CompareOperationBasis;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DeleteOperationBasis;
import org.opends.server.core.ExtendedOperation;
import org.opends.server.core.ExtendedOperationBasis;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.ModifyDNOperationBasis;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.ModifyOperationBasis;
import org.opends.server.core.QueueingStrategy;
import org.opends.server.core.SearchOperation;
import org.opends.server.core.SearchOperationBasis;
import org.opends.server.core.UnbindOperation;
import org.opends.server.core.UnbindOperationBasis;
import org.opends.server.loggers.HTTPRequestInfo;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.ldap.AbandonRequestProtocolOp;
import org.opends.server.protocols.ldap.AddRequestProtocolOp;
import org.opends.server.protocols.ldap.BindRequestProtocolOp;
import org.opends.server.protocols.ldap.CompareRequestProtocolOp;
import org.opends.server.protocols.ldap.DeleteRequestProtocolOp;
import org.opends.server.protocols.ldap.ExtendedRequestProtocolOp;
import org.opends.server.protocols.ldap.LDAPMessage;
import org.opends.server.protocols.ldap.ModifyDNRequestProtocolOp;
import org.opends.server.protocols.ldap.ModifyRequestProtocolOp;
import org.opends.server.protocols.ldap.ProtocolOp;
import org.opends.server.protocols.ldap.SearchRequestProtocolOp;
import org.opends.server.protocols.ldap.UnbindRequestProtocolOp;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.ByteString;
import org.opends.server.types.DebugLogLevel;
@@ -142,6 +164,15 @@
    {
      operation.setInnerOperation(this.clientConnection.isInnerConnection());
      HTTPConnectionHandler connHandler =
          this.clientConnection.getConnectionHandler();
      if (connHandler.keepStats())
      {
        connHandler.getStatTracker().updateMessageRead(
            new LDAPMessage(operation.getMessageID(),
                toRequestProtocolOp(operation)));
      }
      // need this raw cast here to fool the compiler's generic type safety
      // Problem here is due to the generic type R on enqueueOperation()
      clientConnection.addOperationInProgress(operation,
@@ -163,6 +194,69 @@
    return futureResult;
  }
  private ProtocolOp toRequestProtocolOp(Operation operation)
  {
    operation.getResultCode().getIntValue();
    if (operation instanceof AbandonOperation)
    {
      final AbandonOperation op = (AbandonOperation) operation;
      return new AbandonRequestProtocolOp(op.getIDToAbandon());
    }
    else if (operation instanceof AddOperation)
    {
      final AddOperation op = (AddOperation) operation;
      return new AddRequestProtocolOp(op.getRawEntryDN(),
          op.getRawAttributes());
    }
    else if (operation instanceof BindOperation)
    {
      final BindOperation op = (BindOperation) operation;
      return new BindRequestProtocolOp(op.getRawBindDN(),
          op.getSASLMechanism(), op.getSASLCredentials());
    }
    else if (operation instanceof CompareOperation)
    {
      final CompareOperation op = (CompareOperation) operation;
      return new CompareRequestProtocolOp(op.getRawEntryDN(), op
          .getRawAttributeType(), op.getAssertionValue());
    }
    else if (operation instanceof DeleteOperation)
    {
      final DeleteOperation op = (DeleteOperation) operation;
      return new DeleteRequestProtocolOp(op.getRawEntryDN());
    }
    else if (operation instanceof ExtendedOperation)
    {
      final ExtendedOperation op = (ExtendedOperation) operation;
      return new ExtendedRequestProtocolOp(op.getRequestOID(), op
          .getRequestValue());
    }
    else if (operation instanceof ModifyDNOperation)
    {
      final ModifyDNOperation op = (ModifyDNOperation) operation;
      return new ModifyDNRequestProtocolOp(op.getRawEntryDN(), op
          .getRawNewRDN(), op.deleteOldRDN(), op.getRawNewSuperior());
    }
    else if (operation instanceof ModifyOperation)
    {
      final ModifyOperation op = (ModifyOperation) operation;
      return new ModifyRequestProtocolOp(op.getRawEntryDN(), op
          .getRawModifications());
    }
    else if (operation instanceof SearchOperation)
    {
      final SearchOperation op = (SearchOperation) operation;
      return new SearchRequestProtocolOp(op.getRawBaseDN(), op.getScope(), op
          .getDerefPolicy(), op.getSizeLimit(), op.getTimeLimit(), op
          .getTypesOnly(), op.getRawFilter(), op.getAttributes());
    }
    else if (operation instanceof UnbindOperation)
    {
      return new UnbindRequestProtocolOp();
    }
    throw new RuntimeException("Not implemented for operation " + operation);
  }
  /** {@inheritDoc} */
  @Override
  public FutureResult<Void> abandonAsync(AbandonRequest request)
opends/src/server/org/opends/server/protocols/ldap/LDAPStatistics.java
@@ -35,8 +35,9 @@
import static org.opends.server.util.ServerConstants.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.opends.messages.Message;
import org.opends.server.admin.std.server.MonitorProviderCfg;
import org.opends.server.api.MonitorProvider;
@@ -198,10 +199,9 @@
   *         is requested.
   */
  @Override
  public ArrayList<Attribute> getMonitorData()
  public List<Attribute> getMonitorData()
  {
      ArrayList<Attribute> attrs = new ArrayList<Attribute>();
      List<Attribute> attrs = new ArrayList<Attribute>();
      long tmpAbandonRequests = abandonRequests.get();
      long tmpAddRequests = addRequests.get();
@@ -662,7 +662,7 @@
   *          The value to use for the attribute.
   * @return the constructed attribute.
   */
  private Attribute createAttribute(String name, String value)
  protected Attribute createAttribute(String name, String value)
  {
    AttributeType attrType =
      DirectoryServer.getAttributeType(name.toLowerCase());