From 9460e3eb5f025af1dd4f5642046ac6ba5b92e39c Mon Sep 17 00:00:00 2001
From: Jean-Noel Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Mon, 13 May 2013 12:35:26 +0000
Subject: [PATCH] OPENDJ-858 (CR-1651) Add stats tracking to HTTP client connections 

---
 opendj-sdk/opends/src/server/org/opends/server/protocols/http/HTTPClientConnection.java             |  151 ++++++++++++
 opendj-sdk/opends/src/server/org/opends/server/protocols/http/SdkConnectionAdapter.java             |   94 +++++++
 opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/HTTPConnectionHandlerConfiguration.xml |   26 ++
 opendj-sdk/opends/resource/config/config.ldif                                                       |    2 
 opendj-sdk/opends/src/admin/messages/HTTPConnectionHandlerCfgDefn.properties                        |    2 
 opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/LDAPStatistics.java                   |   10 
 opendj-sdk/opends/src/server/org/opends/server/protocols/http/HTTPStatistics.java                   |  144 ++++++++++++
 opendj-sdk/opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java   |    5 
 opendj-sdk/opends/src/server/org/opends/server/protocols/http/HTTPConnectionHandler.java            |  107 +++++++-
 opendj-sdk/opends/src/server/org/opends/server/protocols/http/HTTPStatsProbe.java                   |   76 ++++++
 opendj-sdk/opends/resource/schema/02-config.ldif                                                    |   46 +++
 11 files changed, 630 insertions(+), 33 deletions(-)

diff --git a/opendj-sdk/opends/resource/config/config.ldif b/opendj-sdk/opends/resource/config/config.ldif
index 8c659e0..4a4e40d 100644
--- a/opendj-sdk/opends/resource/config/config.ldif
+++ b/opendj-sdk/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
diff --git a/opendj-sdk/opends/resource/schema/02-config.ldif b/opendj-sdk/opends/resource/schema/02-config.ldif
index 76a3a62..7967342 100644
--- a/opendj-sdk/opends/resource/schema/02-config.ldif
+++ b/opendj-sdk/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'
diff --git a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/HTTPConnectionHandlerConfiguration.xml b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/HTTPConnectionHandlerConfiguration.xml
index ae4d7fe..d8aff43 100644
--- a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/HTTPConnectionHandlerConfiguration.xml
+++ b/opendj-sdk/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
diff --git a/opendj-sdk/opends/src/admin/messages/HTTPConnectionHandlerCfgDefn.properties b/opendj-sdk/opends/src/admin/messages/HTTPConnectionHandlerCfgDefn.properties
index 65f6e25..577968f 100644
--- a/opendj-sdk/opends/src/admin/messages/HTTPConnectionHandlerCfgDefn.properties
+++ b/opendj-sdk/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.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java
index 7d0acaa..a7738ea 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java
+++ b/opendj-sdk/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))
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/http/HTTPClientConnection.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/http/HTTPClientConnection.java
index 1b0244c..97d1854 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/http/HTTPClientConnection.java
+++ b/opendj-sdk/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()
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/http/HTTPConnectionHandler.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/http/HTTPConnectionHandler.java
index e4e4399..c44f70d 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/http/HTTPConnectionHandler.java
+++ b/opendj-sdk/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));
   }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/http/HTTPStatistics.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/http/HTTPStatistics.java
new file mode 100644
index 0000000..be4a8f0
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/protocols/http/HTTPStatistics.java
@@ -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();
+  }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/http/HTTPStatsProbe.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/http/HTTPStatsProbe.java
new file mode 100644
index 0000000..91a6a91
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/protocols/http/HTTPStatsProbe.java
@@ -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());
+  }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/http/SdkConnectionAdapter.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/http/SdkConnectionAdapter.java
index ff2a1d5..737b542 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/http/SdkConnectionAdapter.java
+++ b/opendj-sdk/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)
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/LDAPStatistics.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/LDAPStatistics.java
index b8174f9..cac937a 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/LDAPStatistics.java
+++ b/opendj-sdk/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());

--
Gitblit v1.10.0